PyWAsP: Working with a WAsP workspace (WWH file)#

This tutorial covers how to read a WAsP workspace hierarchy (.wwh) file with windkit, and how to use its contents in pywasp calculations. We will use a workspace of the Serra Santa Luzia site in Portugal, the same site as in the first and fourth tutorials, saved by WAsP 12.10 with default settings. It holds everything needed for a classic WAsP wind farm calculation:

  • a met station with its observed wind climate,

  • a vector map with elevation contours and roughness-change lines,

  • a wind turbine generator and a group of 15 turbine sites,

  • a reference site,

and also every result WAsP calculated from them: the generalised wind climate, the predicted wind climates and site effects at each site, and the annual energy production (AEP) of the turbines.

We will first import the inputs and the WAsP results as xarray datasets, then repeat the calculation with pywasp and compare the two. Finally, we will use the same inputs to calculate a resource grid over the wind farm.

After importing the necessary libraries, let’s read the wwh file and list its contents:

[1]:
import warnings
warnings.filterwarnings('ignore')  # We will ignore warnings to avoid cluttering the notebook

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import windkit as wk
import pywasp as pw
[2]:
wwh = wk.Workspace.read_wwh('./data/import/ssl_default_12_10_0_30.wwh')
wwh
[2]:
WAsP workspace ssl_default_12_10_0_30.wwh (format version 4.01.0006, CRS EPSG:32629)
ID  Object                         Name
 0  WAsP workspace                 ssl_default_12_10_0_30
 1    WAsP project                 Project 1
 2      Turbine site group         Turbine cluster
 3        Turbine site             T1
 4        Turbine site             T2
 5        Turbine site             T3
 6        Turbine site             T4
 7        Turbine site             T5
 8        Turbine site             T6
 9        Turbine site             T7
10        Turbine site             T8
11        Turbine site             T9
12        Turbine site             T10
13        Turbine site             T11
14        Turbine site             T12
15        Turbine site             T13
16        Turbine site             T14
17        Turbine site             T15
18        Reference site           Reference site for the turbine cluster
19      Terrain analysis (IBZ)     Terrain analysis (IBZ) 1
20        Vector map               Serra Santa Luzia
21          Vector map data layer  Serra Santa Luzia
22          Vector map data layer  Serra Santa Luzia
23      Generalised wind climate   GWC
24        Met. station             SerraSantaluzia
25          Observed wind climate  SerraSantaLuzia
26      Wind turbine generator     Bonus 1 MW
27      Reference site             Reference site

The listing shows the WAsP hierarchy as a tree: every object has an ID, its type (the Object column) and the name given to it in WAsP. The first line also tells us the coordinate reference system (CRS) of the workspace, which windkit reads from the header of the vector map. Every dataset we import below is positioned in this CRS, so we don’t need to look up EPSG codes ourselves:

[3]:
print("CRS of the workspace:", wwh.crs)
print("Met. station:", wwh.mast_coords)
CRS of the workspace: 32629
Met. station: {'height': 25.3, 'west_east': 514679.7, 'south_north': 4620970.0, 'description': 'SerraSantaluzia', 'crs': 32629}

Finding objects in the workspace#

Objects are imported by their ID. Instead of reading them off the listing, we can look them up by their type with wwh.get_ids(), which returns a list with the IDs of all objects of that type. This workspace has two reference sites: one in the project and one in the turbine site group. We pick the project’s by its parent in wwh.catalogue:

[4]:
mast_id = wwh.get_ids('Met. station')[0]
group_id = wwh.get_ids('Turbine site group')[0]
turbine_ids = wwh.get_ids('Turbine site')
(ref_id,) = [ID for ID in wwh.get_ids('Reference site') if wwh.catalogue[ID]['ParentID'] != group_id]
print(f"Met. station: {mast_id}, reference site: {ref_id}, turbine site group: {group_id}, turbine sites: {turbine_ids}")
Met. station: 24, reference site: 27, turbine site group: 2, turbine sites: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]

ID note: the ID is optional in all the wwh.get_ methods we use below. Without it, the method imports the only object of its kind in the workspace, and raises an error listing the candidates if there is more than one. This workspace has a single observed wind climate, map, turbine generator and turbine site group, so most of the calls below could be written without an ID. We pass it anyway where it makes the example clearer.

Importing the inputs#

The observed wind climate#

We import the Observed wind climate with wwh.get_owc(). It returns a binned wind climate, positioned at the met station that holds it in the workspace tree. As an exercise, we’ll also export it as a NetCDF file, a format commonly used in meteorological applications and large wind atlases:

[5]:
bwc = wwh.get_owc()
print(bwc)

bwc.to_netcdf('./data/export/bwc.nc')
<xarray.Dataset> Size: 4kB
Dimensions:       (point: 1, sector: 12, wsbin: 32)
Coordinates:
    height        (point) float64 8B 25.3
    south_north   (point) float64 8B 4.621e+06
    west_east     (point) float64 8B 5.147e+05
  * sector        (sector) float64 96B 0.0 30.0 60.0 90.0 ... 270.0 300.0 330.0
    sector_ceil   (sector) float64 96B 15.0 45.0 75.0 ... 285.0 315.0 345.0
    sector_floor  (sector) float64 96B 345.0 15.0 45.0 ... 255.0 285.0 315.0
  * wsbin         (wsbin) float64 256B 0.5 1.5 2.5 3.5 ... 28.5 29.5 30.5 31.5
    wsceil        (wsbin) float64 256B 1.0 2.0 3.0 4.0 ... 29.0 30.0 31.0 32.0
    wsfloor       (wsbin) float64 256B 0.0 1.0 2.0 3.0 ... 28.0 29.0 30.0 31.0
    crs           int8 1B 0
Dimensions without coordinates: point
Data variables:
    wdfreq        (sector, point) float64 96B 0.05314 0.03321 ... 0.1148 0.0707
    wsfreq        (wsbin, sector, point) float64 3kB 0.02601 0.04219 ... 0.0 0.0
Attributes:
    Conventions:      CF-1.8
    history:          2026-10-01T12:51:21+00:00:\twindkit==0.1.0.dev1+g565f93...
    description:      SerraSantaluzia
    Package name:     windkit
    Package version:  0.1.0.dev1+g565f937b5
    Creation date:    2026-10-01T12:51:21+00:00
    Object type:      Binned Wind Climate
    author:           Default User
    author_email:     default_email@example.com
    institution:      Default Institution

The vector map#

The workspace has one Vector map object with two Vector map data layer children: one with the elevation contours and one with the roughness-change lines. In WAsP both are used through the single map object. In pywasp we read them as two separate maps, with wwh.get_elevation_map() and wwh.get_roughness_map(), and bundle them together in a pw.wasp.TopographyMap, which is needed for the flow-perturbation calculations.

wwh.get_roughness_map() can return the roughness map either as polygons (the default) or as roughness-change lines, like WAsP uses. To use the default Polygons is recommended.

[6]:
elev_map = wwh.get_elevation_map()
rough_map = wwh.get_roughness_map()
print(elev_map.head())
print(rough_map.head())

topo_map = pw.wasp.TopographyMap(elev_map, rough_map)
   elev                                           geometry
0   0.1  LINESTRING (512460.3 4637000, 512464.4 4636988...
1   0.1  LINESTRING (515280.7 4608000, 515277.1 4608009...
2   0.1  LINESTRING (517893.5 4616000, 517895.9 4615991...
3   0.1  LINESTRING (516457.4 4616000, 516454.6 4615989...
4   0.1  LINESTRING (515413.3 4616000, 515412.8 4615993...
    id                                           geometry    z0    d desc
0  0.0  POLYGON ((520129.5 4608000, 520157.1 4608018.5...  0.03  0.0
1  0.0  POLYGON ((520157.1 4608018.5, 520129.5 4608000...  0.03  0.0
2  0.0  POLYGON ((522602.4 4608000, 522613.5 4608058, ...  0.03  0.0
3  1.0  POLYGON ((522613.5 4608058, 522602.4 4608000, ...  0.60  0.0
4  1.0  POLYGON ((518024.6 4608000, 518062.3 4608074.5...  0.60  0.0

The wind turbine generator and the turbine sites#

The Wind turbine generator is imported with wwh.get_wtg(), which gives the same dataset as wk.read_wtg does for a .wtg file. The turbine positions and hub heights come from the Turbine site group, with wwh.get_turbines():

[7]:
wtg = wwh.get_wtg()
print(wtg.name.item())

turbines = wwh.get_turbines(group_id)
print(turbines)
Bonus 1 MW
<xarray.Dataset> Size: 481B
Dimensions:      (point: 15)
Coordinates:
    height       (point) float64 120B 50.0 50.0 50.0 50.0 ... 50.0 50.0 50.0
    south_north  (point) float64 120B 4.622e+06 4.622e+06 ... 4.624e+06
    west_east    (point) float64 120B 5.139e+05 5.142e+05 ... 5.163e+05
    crs          int8 1B 0
Dimensions without coordinates: point
Data variables:
    output       (point) float64 120B 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0
Attributes:
    Conventions:  CF-1.8
    history:      2026-10-01T12:51:23+00:00:\twindkit==0.1.0.dev1+g565f937b5\...

The mesoscale climate and the air density#

To generalise a wind climate, WAsP needs the atmospheric stability and baroclinicity of the site, which it looks up in its global mesoscale climate database. Since WAsP 12.8, the workspace stores the values it used. wwh.get_mesoclimate() returns them with the pywasp variable names, so we can pass them straight to pw.wasp.generalize:

[8]:
mesoclimate = wwh.get_mesoclimate()
print(mesoclimate)
<xarray.Dataset> Size: 1kB
Dimensions:               (point: 1, sector: 12)
Coordinates:
    height                (point) float64 8B 25.3
    south_north           (point) float64 8B 4.621e+06
    west_east             (point) float64 8B 5.147e+05
  * sector                (sector) float64 96B 0.0 30.0 60.0 ... 300.0 330.0
    sector_ceil           (sector) float64 96B 15.0 45.0 75.0 ... 315.0 345.0
    sector_floor          (sector) float64 96B 345.0 15.0 45.0 ... 285.0 315.0
    crs                   int8 1B 0
Dimensions without coordinates: point
Data variables:
    mean_temp_scale_land  (sector, point) float64 96B 0.0002808 ... 0.03341
    rms_temp_scale_land   (sector, point) float64 96B 0.04385 0.0403 ... 0.04724
    mean_temp_scale_sea   (sector, point) float64 96B -0.01386 ... -0.001693
    rms_temp_scale_sea    (sector, point) float64 96B 0.04861 ... 0.03138
    mean_pblh_scale_land  (sector, point) float64 96B 900.7 639.9 ... 1.037e+03
    mean_pblh_scale_sea   (sector, point) float64 96B 1.603e+03 ... 1.802e+03
    mean_dgdz             (sector, point) float64 96B 0.007907 ... 0.00851
    mean_dgdz_dir         (sector, point) float64 96B -36.37 15.28 ... -76.17
Attributes:
    Conventions:      CF-1.8
    history:          2026-10-01T12:51:23+00:00:\twindkit==0.1.0.dev1+g565f93...
    Package name:     windkit
    Package version:  0.1.0.dev1+g565f937b5
    Creation date:    2026-10-01T12:51:23+00:00
    author:           Default User
    author_email:     default_email@example.com
    institution:      Default Institution

Likewise, wwh.get_air_density_reference() gives the pressure, temperature and humidity from which WAsP derived the air density at the site:

[9]:
print(wwh.get_air_density_reference())
<xarray.Dataset> Size: 73B
Dimensions:                       (point: 1)
Coordinates:
    height                        (point) float64 8B 25.3
    south_north                   (point) float64 8B 4.621e+06
    west_east                     (point) float64 8B 5.147e+05
    crs                           int8 1B 0
Dimensions without coordinates: point
Data variables:
    source_elevation              (point) float64 8B 127.5
    source_elevation_temperature  (point) float64 8B 129.5
    surface_pressure              (point) float64 8B 1.003e+05
    temperature_celcius           (point) float64 8B 14.55
    relative_humidity             (point) float64 8B 77.42
    lapse_rate                    (point) float64 8B -0.005174
Attributes:
    Conventions:      CF-1.8
    history:          2026-10-01T12:51:23+00:00:\twindkit==0.1.0.dev1+g565f93...
    Package name:     windkit
    Package version:  0.1.0.dev1+g565f937b5
    Creation date:    2026-10-01T12:51:23+00:00
    author:           Default User
    author_email:     default_email@example.com
    institution:      Default Institution

The WAsP parameters#

Finally, wwh.get_parameters() returns the WAsP parameters of every object as a pandas DataFrame, and whether each one still has WAsP’s default value. This workspace was made with default settings, so no parameter differs from its default:

[10]:
parameters = wwh.get_parameters()
print(parameters.head())
print("Parameters that differ from WAsP's default:", len(parameters.query('not `Is default`')))
   Object ID Object description    Source Parameter  Value  Is default
0          1       WAsP project  settings  DSECTORS   12.0        True
1          1       WAsP project  settings   NUMSTDH    5.0        True
2          1       WAsP project  settings   NUMSTDR    5.0        True
3          1       WAsP project  settings     STDH1   10.0        True
4          1       WAsP project  settings     STDH2   25.0        True
Parameters that differ from WAsP's default: 0

Importing the WAsP results#

Besides the inputs, the workspace stores what WAsP calculated from them. These can be imported as well, so they can be analysed in Python or compared with other models.

The generalised wind climate#

We import the Generalised wind climate with wwh.get_gwc(), and export it to NetCDF too:

[11]:
gwc_wasp = wwh.get_gwc()
print(gwc_wasp)

gwc_wasp.to_netcdf('./data/export/gwc.nc')
<xarray.Dataset> Size: 8kB
Dimensions:        (point: 1, gen_height: 5, gen_roughness: 5, sector: 12)
Coordinates:
    height         (point) float64 8B 25.3
    south_north    (point) float64 8B 4.621e+06
    west_east      (point) float64 8B 5.147e+05
  * gen_height     (gen_height) float64 40B 10.0 25.0 50.0 100.0 200.0
  * gen_roughness  (gen_roughness) float64 40B 0.0 0.03 0.1 0.4 1.5
  * sector         (sector) float64 96B 0.0 30.0 60.0 90.0 ... 270.0 300.0 330.0
    sector_ceil    (sector) float64 96B 15.0 45.0 75.0 ... 285.0 315.0 345.0
    sector_floor   (sector) float64 96B 345.0 15.0 45.0 ... 255.0 285.0 315.0
    crs            int8 1B 0
Dimensions without coordinates: point
Data variables:
    A              (gen_height, gen_roughness, sector, point) float64 2kB 5.6...
    k              (gen_height, gen_roughness, sector, point) float64 2kB 1.7...
    wdfreq         (gen_height, gen_roughness, sector, point) float64 2kB 0.0...
Attributes:
    description:      SerraSantaluzia
    Conventions:      CF-1.8
    Package name:     windkit
    Package version:  0.1.0.dev1+g565f937b5
    Creation date:    2026-10-01T12:51:23+00:00
    Object type:      Weibull Wind Climate
    author:           Default User
    author_email:     default_email@example.com
    institution:      Default Institution
    history:          2026-10-01T12:51:23+00:00:\twindkit==0.1.0.dev1+g565f93...

Predicted wind climates and site effects#

wwh.get_wwc() returns the predicted wind climate (the sector-wise Weibull A and k, the sector frequencies and the air density) and wwh.get_site_effects() the site effects (speed-ups, turnings, RIX, mesoscale roughness, …). Both accept the ID of a single site, or of a turbine site group to get one point per turbine site:

[12]:
wwc_ref_wasp = wwh.get_wwc(ref_id)
site_effects_ref_wasp = wwh.get_site_effects(ref_id)
print(wwc_ref_wasp)
<xarray.Dataset> Size: 617B
Dimensions:       (point: 1, sector: 12)
Coordinates:
    height        (point) float64 8B 100.0
    south_north   (point) float64 8B 4.621e+06
    west_east     (point) float64 8B 5.137e+05
    name          (point) object 8B 'Reference site'
  * sector        (sector) float64 96B 0.0 30.0 60.0 90.0 ... 270.0 300.0 330.0
    sector_ceil   (sector) float64 96B 15.0 45.0 75.0 ... 285.0 315.0 345.0
    sector_floor  (sector) float64 96B 345.0 15.0 45.0 ... 255.0 285.0 315.0
    crs           int8 1B 0
Dimensions without coordinates: point
Data variables:
    A             (sector, point) float64 96B 6.809 6.414 5.823 ... 7.166 6.797
    k             (sector, point) float64 96B 2.084 2.346 2.568 ... 2.115 2.002
    wdfreq        (sector, point) float64 96B 0.06475 0.04059 ... 0.08201
    air_density   (point) float64 8B 1.178
Attributes:
    Conventions:      CF-1.8
    history:          2026-10-01T12:51:23+00:00:\twindkit==0.1.0.dev1+g565f93...
    Package name:     windkit
    Package version:  0.1.0.dev1+g565f937b5
    Creation date:    2026-10-01T12:51:23+00:00
    Object type:      Weibull Wind Climate
    author:           Default User
    author_email:     default_email@example.com
    institution:      Default Institution
    description:      WAsP predicted wind climate at reference site 'Referenc...

Several objects in this workspace hold a predicted wind climate (the met station, the reference site and each turbine site), so here the ID is not optional. Calling wwh.get_wwc() without it raises an error that lists the candidates:

[13]:
try:
    wwh.get_wwc()
except ValueError as error:
    print(error)
The workspace holds 19 'Met. station', 'Reference site', 'Turbine site' or 'Turbine site group' objects, so ID must be given. Candidates:
ID  Object              Name
 2  Turbine site group  Turbine cluster
 3  Turbine site        T1
 4  Turbine site        T2
 5  Turbine site        T3
 6  Turbine site        T4
 7  Turbine site        T5
 8  Turbine site        T6
 9  Turbine site        T7
10  Turbine site        T8
11  Turbine site        T9
12  Turbine site        T10
13  Turbine site        T11
14  Turbine site        T12
15  Turbine site        T13
16  Turbine site        T14
17  Turbine site        T15
18  Reference site      Reference site for the turbine cluster
24  Met. station        SerraSantaluzia
27  Reference site      Reference site

Annual energy production#

wwh.get_aep() returns the gross and the potential AEP of the turbine sites in GWh/y. WAsP calls the AEP after wake losses its “net” AEP; in pywasp this is the potential_aep, since further losses are still to be subtracted (see tutorial 6):

[14]:
aep_wasp = wwh.get_aep(group_id)
print(aep_wasp)
print(f"Wind farm gross AEP: {aep_wasp.gross_aep.sum().item():.2f} GWh/y, potential AEP: {aep_wasp.potential_aep.sum().item():.2f} GWh/y")
<xarray.Dataset> Size: 4kB
Dimensions:               (point: 15, sector: 12)
Coordinates:
    height                (point) float64 120B 50.0 50.0 50.0 ... 50.0 50.0 50.0
    south_north           (point) float64 120B 4.622e+06 4.622e+06 ... 4.624e+06
    west_east             (point) float64 120B 5.139e+05 5.142e+05 ... 5.163e+05
    name                  (point) object 120B 'T1' 'T2' 'T3' ... 'T14' 'T15'
  * sector                (sector) float64 96B 0.0 30.0 60.0 ... 300.0 330.0
    sector_ceil           (sector) float64 96B 15.0 45.0 75.0 ... 315.0 345.0
    sector_floor          (sector) float64 96B 345.0 15.0 45.0 ... 285.0 315.0
    crs                   int8 1B 0
Dimensions without coordinates: point
Data variables:
    gross_aep_sector      (sector, point) float64 1kB 0.1428 0.1423 ... 0.2183
    gross_aep             (point) float64 120B 2.796 2.66 2.448 ... 3.063 3.277
    potential_aep_sector  (sector, point) float64 1kB 0.1428 0.1423 ... 0.2183
    potential_aep         (point) float64 120B 2.695 2.568 2.341 ... 2.895 3.157
Attributes:
    Conventions:      CF-1.8
    history:          2026-10-01T12:51:23+00:00:\twindkit==0.1.0.dev1+g565f93...
    Package name:     windkit
    Package version:  0.1.0.dev1+g565f937b5
    Creation date:    2026-10-01T12:51:23+00:00
    Object type:      Anual Energy Production
    author:           Default User
    author_email:     default_email@example.com
    institution:      Default Institution
    description:      WAsP annual energy production at turbine site group 'Tu...
Wind farm gross AEP: 41.64 GWh/y, potential AEP: 40.28 GWh/y

warnings note: we switched off warnings at the start of this notebook, but the result getters warn when WAsP marked results as not up to date (e.g., the inputs were changed after the calculation). Recalculate in WAsP and save the workspace before reading its results. All results in this workspace are up to date.

Anything else: the low-level member data#

The wwh.get_ methods cover what pywasp needs as datasets. Everything else stored for an object can be read from wwh.get_member(), which returns a plain dictionary. For example, the wake model settings of the turbine site group:

[15]:
wake_modelling = wwh.get_member(group_id)['wake_modelling']
print(wake_modelling)
{'is_active': True, 'model': 'Park2', 'n_sub_sectors': 5, 'speed_resolution': np.float64(1.0), 'default_decay_coefficient': np.float64(0.09), 'decay_coefficient': array([0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09, 0.09,
       0.09])}

Repeating the WAsP calculation with pywasp#

We now have all the inputs WAsP used, so let’s repeat its calculation with pywasp and see how the results compare.

The workspace was saved by WAsP 12.10. pywasp’s default configuration, pw.wasp.Config(), uses its WAsP_12.10 parameter set, which reproduces WAsP 12.10. pw.wasp.generalize also defaults to WAsP’s standard generalisation heights, the same heights as the generalised wind climate in the workspace:

[16]:
conf = pw.wasp.Config()

print("Generalisation heights:", gwc_wasp.gen_height.values)
Generalisation heights: [ 10.  25.  50. 100. 200.]

The wind directions of the observed wind climate are measured relative to the grid north of the map, as in WAsP, so we tell pywasp by setting the wind_dir_crs attribute to the CRS of the map. We pass the mesoscale climate from the workspace, so pywasp uses exactly the same stability and baroclinicity as WAsP:

[17]:
bwc.attrs['wind_dir_crs'] = wk.spatial.get_crs(bwc).to_wkt()

gwc = pw.wasp.generalize(bwc, topo_map, conf=conf, mesoclimate=mesoclimate)
At line 555 of file ../../modules/waspcore/Rvea0288/Internal/generalization.f90
Fortran runtime warning: An array temporary was created for argument 'gd' of procedure 'gmom_ntb'
At line 555 of file ../../modules/waspcore/Rvea0288/Internal/generalization.f90
Fortran runtime warning: An array temporary was created for argument 'gf' of procedure 'gmom_ntb'
At line 586 of file ../../modules/waspcore/Rvea0288/Internal/generalization.f90
Fortran runtime warning: An array temporary was created for argument 'gd' of procedure 'gmom_ntb'
At line 586 of file ../../modules/waspcore/Rvea0288/Internal/generalization.f90
Fortran runtime warning: An array temporary was created for argument 'gf' of procedure 'gmom_ntb'

Next we downscale the generalised wind climate to the turbine sites, and calculate their gross AEP and, with WAsP’s wake model settings, their potential AEP. WAsP’s wake model is the PARK2_onshore wind farm model in pywasp:

[18]:
pwc_turbines = pw.wasp.downscale(gwc, topo_map, turbines, conf=conf, return_site_effects=True)

gross_aep = pw.gross_aep(pwc_turbines, wtg, air_density_correction='infer')
potential_aep = pw.potential_aep(
    pwc_turbines,
    wtg,
    air_density_correction='infer',
    wind_farm_model='PARK2_onshore',
    n_subsector=wake_modelling['n_sub_sectors'],
    ws_stepsize=wake_modelling['speed_resolution'],
)

Let’s put the AEP of each turbine from both models side by side:

[19]:
aep_comparison = pd.DataFrame(
    {
        'Gross WAsP': aep_wasp.gross_aep.values,
        'Gross PyWAsP': gross_aep.gross_aep.values,
        'Potential WAsP': aep_wasp.potential_aep.values,
        'Potential PyWAsP': potential_aep.potential_aep.values,
    },
    index=aep_wasp.name.values,
)
aep_comparison.loc['Wind farm'] = aep_comparison.sum()
aep_comparison.round(4)
[19]:
Gross WAsP Gross PyWAsP Potential WAsP Potential PyWAsP
T1 2.7958 2.7957 2.6951 2.6950
T2 2.6601 2.6600 2.5682 2.5682
T3 2.4484 2.4483 2.3408 2.3407
T4 2.5325 2.5325 2.4229 2.4229
T5 2.6970 2.6970 2.5877 2.5877
T6 2.7743 2.7743 2.7290 2.7289
T7 2.6528 2.6528 2.6219 2.6218
T8 2.9498 2.9497 2.8987 2.8987
T9 3.0417 3.0417 2.9793 2.9792
T10 2.2840 2.2840 2.2102 2.2102
T11 2.6803 2.6802 2.6201 2.6201
T12 2.9138 2.9138 2.8259 2.8258
T13 2.8710 2.8709 2.7241 2.7241
T14 3.0630 3.0629 2.8948 2.8948
T15 3.2769 3.2768 3.1568 3.1568
Wind farm 41.6413 41.6407 40.2755 40.2749

The two models agree to within a few thousandths of a percent, both for the individual turbines and for the whole wind farm.

Calculating a resource grid over the wind farm#

With the inputs from the workspace we are not limited to the sites defined in WAsP. Let’s create a uniform grid with a resolution of 100 m around the turbines, at their hub height of 50 m, and downscale the generalised wind climate to it:

[20]:
height = 50
res = 100

output_locs = wk.spatial.create_dataset(
    np.arange(512500, 517000 + res, res),
    np.arange(4620500, 4625000 + res, res),
    np.array([height]),
    wwh.crs,
)

pwc_grid = pw.wasp.downscale(gwc, topo_map, output_locs, conf=conf)
print(pwc_grid)

pwc_grid.to_netcdf(f'./data/export/results_{height}_m.nc')
<xarray.Dataset> Size: 340kB
Dimensions:        (sector: 12, height: 1, south_north: 46, west_east: 46)
Coordinates:
  * sector         (sector) float64 96B 0.0 30.0 60.0 90.0 ... 270.0 300.0 330.0
    sector_ceil    (sector) float64 96B 15.0 45.0 75.0 ... 285.0 315.0 345.0
    sector_floor   (sector) float64 96B 345.0 15.0 45.0 ... 255.0 285.0 315.0
  * height         (height) int64 8B 50
  * south_north    (south_north) int64 368B 4620500 4620600 ... 4624900 4625000
  * west_east      (west_east) int64 368B 512500 512600 512700 ... 516900 517000
    crs            int8 1B 0
Data variables:
    A              (sector, height, south_north, west_east) float32 102kB 4.6...
    k              (sector, height, south_north, west_east) float32 102kB 2.0...
    wdfreq         (sector, height, south_north, west_east) float32 102kB 0.0...
    site_elev      (south_north, west_east) float32 8kB 199.8 194.9 ... 252.1
    air_density    (height, south_north, west_east) float32 8kB 1.194 ... 1.187
    wspd           (height, south_north, west_east) float32 8kB 6.238 ... 5.179
    power_density  (height, south_north, west_east) float32 8kB 262.7 ... 160.1
Attributes:
    Conventions:      CF-1.8
    history:          2026-10-01T12:51:25+00:00:\twindkit==0.1.0.dev1+g565f93...
    title:            WAsP site effects
    Package name:     windkit
    Package version:  0.1.0.dev1+g565f937b5
    Creation date:    2026-10-01T12:51:45+00:00
    Object type:      Met fields
    author:           Default User
    author_email:     default_email@example.com
    institution:      Default Institution

And plot the mean wind speed and power density, with the turbine sites (black) and the met station (red) on top:

[21]:
fig, axes = plt.subplots(1, 2, figsize=(17, 6))
ax1, ax2 = axes.flat
variables = ['wspd', 'power_density']
for var, ax in zip(variables, axes.flat):
    pwc_grid[var].isel(height=0).plot(ax=ax, cmap='coolwarm')
    ax.scatter(turbines.west_east, turbines.south_north, c='k', s=15)
    ax.scatter(bwc.west_east, bwc.south_north, c='r', marker='^', s=40)
    ax.set_aspect('equal')
    plt.setp(ax.get_xticklabels(), rotation=30, horizontalalignment='right')
ax1.set_title("Wind Speed")
ax2.set_title("Power Density")
fig.tight_layout()
../_images/tutorials_tutorial5_nb_42_0.png