Core#

The two realized containers and the windIO persistence functions. Realization itself is two steps and is documented on the Sampling page. Describe the scenario you want as an Sampler, then draw scenarios from that description with sample() or stream().

A scenario is a Farm, whose boundary may be a multipolygon when a single project holds several lease pieces. A description that sets n_farms= or domain= makes a scenario a Cluster of farms placed next to one another instead, which carries what belongs to the set rather than to any member, namely the root seed that replays the arrangement, the placement record, and one wind resource and surface for the whole set. Cluster.plot draws every member on one shared map, because they were placed in one coordinate frame. Which of the two a scenario is follows from the description rather than from what a particular draw rolls, so a population never mixes them.

save() and load() round-trip one farm, turbine or wind resource, and the windIO document kind fixes which one load returns. A cluster has its own directory format through Cluster.save and Cluster.load. A population is an ordinary tuple, so persisting or plotting one is an ordinary loop.

import oowl
from oowl import boundaries as B
from oowl import layouts as L

farms = oowl.Sampler(
    B.OffshorePolygon, L.Grid, n_turbines=(40, 120), area=(30e6, 90e6), seed=7
).sample(5)
for index, farm in enumerate(farms):
    farm.save(f"dataset/farm_{index:04d}.yaml")

oowl.save(obj, path, *[, name, crs, ...])

Write obj to path as a windIO document and return the path.

oowl.load(path)

Read a windIO document into the OOWL object it describes.

oowl.Farm(positions, boundary, *[, ...])

One wind farm with turbine positions and their associated context.

oowl.Cluster(farms, *[, metadata])

A sequence of Farm members with metadata for the whole set.

oowl.save(obj, path, *, name=None, crs=None, energy_resource=None, document=None, surface_format='yaml')[source]#

Write obj to path as a windIO document and return the path.

Parameters:
  • obj (Farm | Turbine | WindResource | Dimitrov) – A Farm, a Turbine, a WindResource or a Dimitrov design.

  • path (str | Path) – Destination .yaml file. Missing parent directories are created.

  • name (str | None) – Name recorded in the document. Defaults to the farm’s metadata["name"], as kept by load(), then to "oowl farm", to the turbine’s name for a turbine and to "oowl wind resource" for a resource.

  • crs (str | None) – Optional PROJ/EPSG string stored with every coordinate set. Defaults to a CRS retained by load(). A turbine or resource document has no coordinates, so passing crs for one raises ValueError.

  • energy_resource (dict | None) – Optional windIO energy_resource mapping. Defaults to the farm’s attached resource. Only a site or system document carries one, so passing it for a turbine, a resource or document="wind_farm" raises ValueError.

  • document (Literal['wind_energy_system', 'wind_farm', 'site', 'turbine', 'energy_resource'] | None) – Document kind to write. A turbine is always "turbine" and a resource "energy_resource". A farm defaults to "wind_energy_system", and to "wind_farm" when it has no boundary, since windIO sites need one. "wind_farm" writes the layout alone and "site" the boundary, resource and surface alone. A site or system for a farm without a resource gets a labeled uniform placeholder.

  • surface_format (Literal['yaml', 'netcdf']) – "yaml" writes a surface into the document in windIO point form, which validates against the windIO schema. "netcdf" writes it beside the document as <name>_surface.nc, a grid of x, y and depth or elevation, and includes it with !include. That file is about five times smaller and reads and writes in a fraction of the time, and both load() and windIO load it, but windIO’s schema does not accept the included grid, so the site no longer validates. It needs PyYAML, and a document without a site raises ValueError for it.

Return type:

Path

oowl.load(path)[source]#

Read a windIO document into the OOWL object it describes.

A wind_energy_system becomes a Farm with boundary, resource, surface and turbines. A wind_farm document gives a farm with only positions and turbines, and a site document a farm with only boundary, resource and surface. A turbine document becomes a Turbine and an energy_resource document a WindResource.

The boundary is the site boundaries minus exclusions, and positions come from the first layout. A circle boundary becomes a circumscribed 64-gon, so turbines on its rim stay inside. YAML !include files are read relative to the including file, and a file included twice is read and built once, so the objects that include it share it. A NetCDF include is read as a surface grid, as save(..., surface_format="netcdf") writes it, and any other include is refused. A CRS found in the coordinates is kept in the farm metadata. Layout z coordinates are not read, because a farm takes turbine heights from its terrain, so a wind_farm document saved from a terrain farm reloads without them.

A resource loads from per-sector Weibull parameters, from a probability table over direction and speed, from a rose with one wind speed for every direction, which every case then blows at, or from a time series, counted into 12 sectors and 1 m/s speed bins. A resource mapped over the site, given per turbine or varying with height raises ValueError. windIO has no field for the stability climate, so a loaded resource uses the default KellyGryning and ignores any stored LMO. windIO also cannot store Farm.parts, so a multipart farm reloads as one farm. Cluster.save keeps farms apart.

Parameters:

path (str | Path)

Return type:

Farm | Turbine | WindResource

class oowl.Farm(positions, boundary, *, metadata=None, guide=None, resource=None, turbines=None, metrics=None, surface=None)[source]#

One wind farm with turbine positions and their associated context.

positions has shape (N, 2) in meters. turbines is one shared Turbine or one per position. metadata records how the farm was generated, and metrics caches summary().

Parameters:
property bounds: tuple[float, float, float, float]#

Turbine-position bounding box (min_x, min_y, max_x, max_y) in meters.

capacity()[source]#

Installed capacity in watts, the rated powers of the turbines summed.

nan for a farm without turbines.

Return type:

float

property centroid: tuple[float, float]#

Mean turbine position (x, y) in meters.

property coordinates: ndarray#

Return turbine coordinates as (x, y, z) rows in meters.

z is the terrain elevation when the surface is terrain, else zero.

coverage()[source]#

Share of the boundary’s area inside the convex hull of the turbines.

Near 1 when the turbines span their whole site and small when they bunch in one part of it. Turbines that enclose no area, fewer than three or in one line, cover none. nan for a farm without a boundary.

Return type:

float

property extent: tuple[float, float]#

Turbine-position extent (width, height) in meters.

maximum_spacing()[source]#

Nearest-neighbor spacing of the loneliest turbine in meters.

The largest distance from a turbine to its nearest neighbor, nan below two turbines, which the top end of a sampler’s requirements={"spacing": (None, maximum)} bounds.

Return type:

float

mean_spacing()[source]#

Mean nearest-neighbor spacing in meters, nan below two turbines.

Return type:

float

minimum_spacing()[source]#

Closest pair in meters, nan below two turbines.

What a layout’s minimum_spacing holds, so a farm sized by one reaches at least it. A sampled farm records it in metadata["sample"]["closest_pair"].

Return type:

float

property n_turbines: int#

Number of turbine positions in this farm.

nearest_neighbor_distances()[source]#

Distance in meters from each turbine to its nearest neighbor.

Shape (N,), filled with nan when the farm has fewer than two turbines, as in oowl.metrics.nearest_neighbor_distances().

Return type:

ndarray

property parts: tuple[Farm, ...]#

This farm’s recorded pieces, each as a farm of its own.

Pieces come from a disjoint boundary, a partition or a region. On a partitioned boundary farm.parts[i] is the farm in farm.boundary.parts[i]. A farm of one piece returns (self,). A boundary whose part count differs from the recorded components raises ValueError.

plot(ax=None, **kwargs)[source]#

Plot the farm and return its Matplotlib axes.

Extra keyword arguments go to Matplotlib’s scatter for the turbines, such as s and color. Turbines default to the first color of the style’s property cycle. facecolor, edgecolor, alpha and linewidth style the boundary. regions shades exclusion zones, and regions=False punches them out.

Parameters:
Return type:

matplotlib.axes.Axes

save(path, **kwargs)[source]#

Write this farm to path as a windIO document.

Wraps oowl.save() and takes the same keyword arguments (name, crs, energy_resource, document). document is "wind_energy_system" (default), "wind_farm" (the default for a farm without a boundary) or "site". Returns the written path.

Parameters:
Return type:

Path

summary(metrics=None, refresh=False)[source]#

Overview of this farm as an ordered name -> value dict.

metrics=None gives the turbine count, the mean nearest-neighbor distance and its coefficient of variation, the density and the area. Otherwise pass names from oowl.metrics.SCALAR_METRICS. The result is cached on self.metrics until the positions or the boundary change. Pass refresh=True to recompute.

Parameters:
  • metrics (Iterable[str] | None)

  • refresh (bool)

Return type:

dict

property x: ndarray#

X coordinates of all turbines in meters.

property y: ndarray#

Y coordinates of all turbines in meters.

class oowl.Cluster(farms, *, metadata=None)[source]#

A sequence of Farm members with metadata for the whole set.

Cluster metadata holds the root seed, the placement record and any shared wind resource and surface. A slice is a cluster of the kept members whose per-member records are cut to match.

Parameters:
  • farms (Sequence[Farm])

  • metadata (Mapping | None)

property boundaries: tuple[Boundary | None, ...]#

Boundary of each farm, in order.

property bounds: tuple[float, float, float, float]#

Turbine-position bounding box (min_x, min_y, max_x, max_y) in meters.

property centroid: tuple[float, float]#

Mean turbine position (x, y) across every farm, in meters.

property extent: tuple[float, float]#

Turbine-position extent (width, height) in meters.

classmethod load(directory)[source]#

Rebuild a cluster from a directory written by Cluster.save().

Loads each farm through windIO, then restores its metadata and partition cells from the manifest. A farm keeps the name and CRS its file records, and farms that include one file share the object it holds. Raises FileNotFoundError for a missing manifest and ValueError for an unsupported schema or a malformed entry.

Parameters:

directory (str | Path)

Return type:

Cluster

property n_farms: int#

Number of member farms.

property n_turbines: int#

Total number of turbines across every farm.

plot(ax=None, **kwargs)[source]#

Plot every member farm on one map and return its Matplotlib axes.

Each member is drawn in its own color of the Matplotlib cycle, in member order. For a cluster scattered across a domain, regions outlines the domain and shades its exclusion zones, and regions=False omits them. Other keywords go to plot(), so turbine size is s. Raises ValueError when the cluster holds no farms.

Parameters:
Return type:

matplotlib.axes.Axes

property positions: ndarray#

Every farm’s positions concatenated, in order.

save(directory, **kwargs)[source]#

Write the cluster into directory and return that directory.

Writes manifest.json holding schema_version, the cluster metadata and a {file, summary, metadata} entry per farm. A farm on partition cells adds their hex WKB as cells, so its parts reload in the same order. Each farm is saved as windIO YAML (farm_%04d.yaml), a system with its site where it has a boundary and a wind_farm document of its positions and turbines where it has none, as Farm.save() writes them. Extra keyword arguments go to Farm.save, only name and crs to a farm without a boundary. A resource or surface that several farms share is written once, as resource_%04d.yaml or surface_%04d.yaml, which their files !include. windIO records no guide, and a farm without a boundary has no site to hold a resource or surface, so Cluster.load() does not recover those.

Parameters:
Return type:

Path

summary(metrics=None)[source]#

Return one summary row per farm, with its sampling record prefixed.

Each row holds index, algorithm and seed from the farm’s sample metadata, which are None for a farm not drawn by sample(), plus Farm.summary(metrics).

Parameters:

metrics (Sequence[str] | None)

Return type:

list[dict]