Workspace Format (.wwh)#

Reading WAsP Workspaces#

A .wwh file is a zip archive whose Inventory.xml holds the whole WAsP session: the projects, met stations, generalized wind climates, vector maps, turbine sites and turbines, nested in a hierarchy. Besides those inputs it holds what WAsP calculated from them, which no other WAsP file carries together: the predicted wind climate and site effects at every met station, reference site and turbine site, the annual energy production of every turbine site, and WAsP’s Weibull fit of the observed wind climate.

read_wwh() returns the hierarchy as plain dicts:

from windkit.io.wasp import read_wwh

workspace = read_wwh("site.wwh")
print(workspace["format_version"])   # e.g. '4.01.0006'
print(len(workspace["members"]))     # one dict per object in the workspace

for member in workspace["members"]:
    print(member["class_description"], member["description"])

members is in document order, so a member comes before its children, and each one records its place in the hierarchy as indices into that list:

root = workspace["members"][0]
print(root["parent"])                # None: the workspace itself
print(root["children"])              # indices of its child members

The same integers are the object IDs of the high-level windkit.Workspace, so workspace["members"][24] is the object Workspace.get_wwc(24) reads.

What a Member Holds#

Every member dict has the same keys, and a part the member does not hold is None. Which parts are filled follows the kind of object:

Met. station         coords, site_assessment, predicted_wind_climate,
                     observed_wind_climate_weibull_fit
Reference site       coords, site_assessment, predicted_wind_climate
Turbine site         coords, site_assessment, predicted_wind_climate, production
Turbine site group   wake_modelling
Observed wind climate        observed_wind_climate
Generalised wind climate     generalized_wind_climate
Wind turbine generator       wind_turbine_generator, air_density_correction
WAsP project, Terrain analysis (IBZ), Resource grid    parameters
Vector map data layer        data_layer

Reading one object’s results:

turbine = workspace["members"][3]
production = turbine["production"]
print(production["gross"])                    # per sector, Wh/y as stored
print(production["sum_of_sectors_gross"])      # WAsP's total, Wh/y
print(production["wake_loss"])                 # fraction, 0-1

station = workspace["members"][24]
print(station["predicted_wind_climate"]["A"])   # per sector
print(station["site_assessment"]["dirrix"])     # per sector
print(station["results_dirty"])                 # {'Calculation1': False, ...}

results_dirty is WAsP’s own flag: it marks a calculation whose inputs changed after it ran, so results read from that object may not match the rest of the workspace.

The observed and generalized wind climates and the wind turbine generator are parsed by parse_owc_element(), parse_gwc_element() and parse_wtg_element(), so they follow those functions’ own data contracts.

Parsing an Element Directly#

parse_wwh_element() reads a root you parsed yourself, and parse_wwh_member_element() reads a single WaspHierarchyMember element and only that member’s own data, not the members below it. Both accept lxml.etree and xml.etree.ElementTree elements:

import zipfile
from lxml import etree
from windkit.io.wasp import parse_wwh_element, parse_wwh_member_element

with zipfile.ZipFile("site.wwh") as archive:
    root = etree.parse(archive.open("Inventory.xml")).getroot()

workspace = parse_wwh_element(root, context="site.wwh")

element = next(
    el
    for el in root.iter("WaspHierarchyMember")
    if el.get("ClassDescription") == "Met. station"
)
station = parse_wwh_member_element(element, context="site.wwh")

Units and Precision#

The dicts mirror the file. WAsP writes its numbers as text with up to seven significant digits, and they are parsed straight into float64, so reading adds no rounding of its own. Energies are in Wh/y, frequencies, Rix and wake losses are fractions (0-1), and turnings are in degrees, as stored. Nothing is renormalized: the sector frequencies of a wind rose sum to 1 only to within the rounding of the stored values.

WAsP Versions#

A part an older WAsP version did not write reads as None, so the same code works across workspace generations. The differences worth knowing:

  • DisplacementHeight and SiteRoughness are absent before format 4.01.0006, so site_assessment["displ"] and ["site_roughness"] are None.

  • The mesoscale climate a generalized wind climate was made with — stability_inputs and geostrophic_shear — is written by WAsP 12.8 and newer only. barometric_reference is written by older versions too, but its lapse_rate only from WAsP 12.6.

  • Older workspaces instead record the profile model parameters of their generalization, as profile_model_parameters.

  • Format 4.01.0002 and older store combined TerrainSpeedMultiplier and TerrainDeflection instead of the separate roughness and orographic speed-ups and turnings, and no Rix, TotalRix or RoughnessChanges. Those keys of site_assessment are None, and the combined terrain effects are not read. Format 3.03 also stores no MeanAirDensity, so predicted_wind_climate["air_density"] is None.

Which WAsP versions a workspace may come from is not checked when reading; the high-level windkit.Workspace rejects formats below 4.01.0002.

Errors#

Within a part that is present, required elements and attributes are validated at the file boundary: a missing or unparseable one raises WindkitFileFormatError, a subclass of ValueError. The message names the member by its class and description, then locates the element by a zero-based index into the document order of the elements the reader reads:

site.wwh: Turbine site 'T1': SiteAssessment: SectorAssessment[3]: missing required attribute 'Rix'

See Also#