Resources

Contents

Resources#

Wind resources, the models of each case they sample, and the cases.

A resource gives the direction, speed, surface, stability and turbulence of a site, and WindResource.sample() draws SiteConditions.

from oowl import resources as R

resource = R.WindResource(
    direction=R.VonMises(seed=2),
    mean_wind_speed=9.0,
    roughness_length=R.Charnock(),
    stability=R.KellyGryning(unstable_fraction=0.4),
    turbulence=R.SurfaceLayer(coefficient=2.3),
    mann=R.Mann(),
)
cases = resource.sample(1024, seed=1)

Every model argument is optional, and the defaults describe open water. GWA takes the directions and speeds of a site from the Global Wind Atlas, and Dimitrov is the coverage design for training data. R.profile holds the Monin-Obukhov profile functions the models use.

Resources#

class oowl.resources.WindResource(*, direction=<factory>, n_sectors=12, mean_wind_speed=None, weibull_k=2.0, weibull_a=None, stability=<factory>, turbulence=<factory>, reference_height=100.0, roughness_length=0.0002, mann=None)[source]#

Wind resource of a site, its directions, speeds, surface and turbulence.

direction shares the wind between n_sectors direction sectors, UniformDirection by default, or SingleDirection, VonMises or Elliptical. Each sector’s speed is Weibull, and roses follow analytically. mean_wind_speed is the exact probability-weighted mean in m/s, 9 by default. weibull_a instead gives the Weibull scale in m/s, one value or one per sector, and the mean follows from it. weibull_k is one shape for all sectors or one per sector.

Each case is drawn along a chain of links. stability draws 1/L from a KellyGryning climate, turbulence draws sigma_u from SurfaceLayer after it and also gives the TI rose, and mann adds the Mann parameters. reference_height and roughness_length in meters define the surface, and with the stability the log-law shear of each case. The defaults describe open water. A Charnock roughness grows with the wind instead, and a sequence gives each sector its own upwind surface, a length or a Charnock model, so a coast has offshore and onshore sectors whose turbulence, shear and stability differ.

Parameters:
metadata()[source]#

Return a JSON-compatible record of the resource configuration.

The sector probabilities and Weibull parameters give the joint distribution of direction and speed, and the models the rest of each case.

Return type:

dict

plot(ax=None, *, variable='wind_speed', **kwargs)#

Plot the analytical wind resource and return its Matplotlib axes.

variable="wind_speed" draws the joint direction-speed rose. variable="turbulence_intensity" draws the analytical TI rose. Bars are percent of all time, or of a Dimitrov design’s cases, as wind_rose() and ti_rose() give.

Parameters:
Return type:

matplotlib.axes.Axes

sample(n_cases, *, seed=None, u_min=4.0, u_max=25.0)#

Return n_cases inflow cases drawn with a scrambled Sobol sequence.

Each case draws direction, wind speed, roughness, stability, sigma_u and the optional Mann and inflow variables in turn, each given the ones before it. The cases are conditional on a wind speed in [u_min, u_max] in m/s, and metadata["in_range_fraction"] is the share of time they represent, None for a Dimitrov design. A given seed replays the cases, and None draws fresh entropy that the metadata records.

Parameters:
Return type:

SiteConditions

save(path, *, name=None)#

Write this resource to path as a windIO energy_resource document.

As oowl.save(), it returns the written path, and oowl.load() reads it back.

Parameters:
Return type:

Path

property sector_mean_wind_speed: ndarray#

Return the conditional mean wind speed in every sector, in m/s.

property sector_turbulence_intensity: ndarray#

Return each sector’s mean turbulence intensity over 4 to 25 m/s.

The mean weighs each speed by its probability in the sector, so a sector whose wind lies far outside the range takes the turbulence intensity at the nearer end.

property ti_bins: ndarray#

Default turbulence-intensity bin edges used by ti_rose().

ti_rose(ti_bins=None, *, u_min=4.0, u_max=25.0, n_quadrature=2000, require_full_range=True)#

Return the share of time in each direction sector and TI bin.

Entry (i, b) is P(sector i, TI in bin b, speed in [u_min, u_max]), the share of all time, integrated over n_quadrature speeds. A rose whose bins cover the reachable TI sums to the share of time in the speed range, as wind_rose() does, and its rows sum to the rows of wind_rose(), a Dimitrov design’s to one. Turbulence intensity is undefined in calm air, so u_min must be above 0. The default bins split the reachable TI into eight, and custom bins must cover it unless require_full_range=False.

Parameters:
  • ti_bins (ArrayLike | None)

  • u_min (float)

  • u_max (float)

  • n_quadrature (int)

  • require_full_range (bool)

Return type:

np.ndarray

wind_rose(speed_bins=None, *, u_min=4.0, u_max=25.0, require_full_range=True)#

Return the share of time in each direction sector and speed bin.

The table has shape (n_sectors, n_speed_bins), and entry (i, b) is P(sector i, speed in bin b), the share of all time, calm included. It sums to the share of time in [u_min, u_max] in m/s, so annual energy is 8760 h times the sum of the table times the power in each bin. u_min=0 covers calm air, and 1 - sum is then the share above u_max. Divide by the sum for the distribution given a speed in the range. A Dimitrov design has no share of time, so its rose is its distribution over the range and sums to one. The default bins split the range into six, and custom bins must cover it unless require_full_range=False.

Parameters:
  • speed_bins (ArrayLike | None)

  • u_min (float)

  • u_max (float)

  • require_full_range (bool)

Return type:

np.ndarray

property wind_speed_standard_deviation: float#

Return the standard deviation in m/s of the speed over all time.

class oowl.resources.GWA(latitude, longitude, roughness_length, reference_height, *, stability=KellyGryning(unstable_fraction=0.6, stable_scale=0.015, unstable_scale=0.03, reference_friction_velocity=0.3, stable_exponent=2, unstable_exponent=3, fraction_friction_velocity=None), turbulence=SurfaceLayer(coefficient=2.3, intensity=None), mann=None, timeout=30.0, libfile_path=None)[source]#

Wind resource from the Global Wind Atlas (GWA) Global Wind Climate (GWC).

Downloads the GWC libfile for a latitude/longitude point from the Global Wind Atlas v4 API and interpolates its per-sector Weibull A and k linearly in ln z0 to roughness_length and linearly in height to reference_height. The sector frequencies, which the libfile gives per roughness class only, are interpolated in ln z0. The same surface values define the stability model. Adapted from PyWake’s py_wake.site.xrsite.GlobalWindAtlasSite.

One roughness length per atlas sector gives each sector the atlas climate at its own roughness, for a site whose upwind surface changes with direction, such as a coast. The frequencies are then renormalized. The atlas climates assume that roughness holds far upwind in each sector.

Parameters:
  • latitude (float) – Latitude in [-90, 90] and longitude in [-180, 180] degrees.

  • longitude (float) – Latitude in [-90, 90] and longitude in [-180, 180] degrees.

  • roughness_length (float or sequence of float) – Roughness length in meters, or one per atlas sector.

  • reference_height (float) – Height above ground in meters.

  • stability (KellyGryning) – Stability climate of the site.

  • turbulence (SurfaceLayer) – Turbulence model.

  • mann (Mann, optional) – Mann turbulence parameters, None to omit them.

  • timeout (float) – Maximum seconds to wait for the Global Wind Atlas API. Ignored when libfile_path is supplied.

  • libfile_path (str or pathlib.Path, optional) – Previously downloaded GWC libfile to read instead of the network.

GWA_URL = 'https://globalwindatlas.info/api/gwa/custom/Lib'#

Global Wind Atlas v4 GWC libfile endpoint.

MAX_RESPONSE_BYTES = 10485760#

Maximum response body size accepted before parsing.

READ_CHUNK_BYTES = 65536#

Bytes read from the response at a time.

REQUEST_HEADERS = {'Referer': 'https://globalwindatlas.info/', 'User-Agent': 'oowl.GWA'}#

Browser Referer required by the v4 API.

metadata()[source]#

Return the base record plus the GWC location and source.

Return type:

dict

class oowl.resources.Dimitrov(*, minimum_intensity=0.025, reference_intensity=0.18, maximum_intensity=None, roughness_range=(1e-05, 1.0), heat_flux=0.3, temperature_scale=0.3, rotor_diameter=None, latitude=50.0, n_sectors=12, reference_height=100.0)[source]#

The coverage design of Dimitrov et al. (2018), for training data.

Cases spread evenly over bounds that hold nearly all measured site conditions, so a surrogate trained on them seldom extrapolates (https://doi.org/10.5194/wes-3-767-2018). Check that the conditions it is applied to lie inside them. The cases are not weighted by how often they occur, so do not use the design for the energy, loads or statistics of a real site.

Every case draws each variable within bounds that depend on the variables before it, uniformly unless stated below. The wind speed at reference_height in meters spreads over the sampled range, u_min to u_max of sample(), and the direction over n_sectors sectors. sigma_u lies between minimum_intensity U and reference_intensity (6.8 + 0.75 U + 3 (10 / U)^2), capped at maximum_intensity U when given. The Mann length scale lies between max(7.5, 15 |alpha|^-2/3) and 275 m and the anisotropy between 1 and 5. Yaw and tilt lie within 10 degrees and air density between 1.1 and 1.35 kg/m^3. Veer across the rotor scales with its size, so it is drawn only with rotor_diameter D in meters, between -0.1 D (5 / U) and min(60 sin|latitude|, D (5 / U)^2) degrees. These bounds are those of Dimitrov et al.

Their power-law shear bounds are replaced by the log law, which OOWL bounds instead. The roughness length is log-uniform over roughness_range in meters. 1/L lies between -kappa g heat_flux / (T0 u*^3) and kappa g temperature_scale / (T0 u*^2), with u* over the smoothest surface, so heat_flux in K m/s bounds unstable air and temperature_scale in K stable air. The shear of each case follows from its roughness and stability.

Parameters:
metadata()[source]#

Return a JSON-compatible record of the design and its bounds.

Return type:

dict

plot(ax=None, *, variable='wind_speed', **kwargs)#

Plot the analytical wind resource and return its Matplotlib axes.

variable="wind_speed" draws the joint direction-speed rose. variable="turbulence_intensity" draws the analytical TI rose. Bars are percent of all time, or of a Dimitrov design’s cases, as wind_rose() and ti_rose() give.

Parameters:
Return type:

matplotlib.axes.Axes

sample(n_cases, *, seed=None, u_min=4.0, u_max=25.0)#

Return n_cases inflow cases drawn with a scrambled Sobol sequence.

Each case draws direction, wind speed, roughness, stability, sigma_u and the optional Mann and inflow variables in turn, each given the ones before it. The cases are conditional on a wind speed in [u_min, u_max] in m/s, and metadata["in_range_fraction"] is the share of time they represent, None for a Dimitrov design. A given seed replays the cases, and None draws fresh entropy that the metadata records.

Parameters:
Return type:

SiteConditions

save(path, *, name=None)#

Write this resource to path as a windIO energy_resource document.

As oowl.save(), it returns the written path, and oowl.load() reads it back.

Parameters:
Return type:

Path

property sector_turbulence_intensity: ndarray#

Return each sector’s mean turbulence intensity over 4 to 25 m/s.

The mean weighs each speed by its probability in the sector, so a sector whose wind lies far outside the range takes the turbulence intensity at the nearer end.

property ti_bins: ndarray#

Default turbulence-intensity bin edges used by ti_rose().

ti_rose(ti_bins=None, *, u_min=4.0, u_max=25.0, n_quadrature=2000, require_full_range=True)#

Return the share of time in each direction sector and TI bin.

Entry (i, b) is P(sector i, TI in bin b, speed in [u_min, u_max]), the share of all time, integrated over n_quadrature speeds. A rose whose bins cover the reachable TI sums to the share of time in the speed range, as wind_rose() does, and its rows sum to the rows of wind_rose(), a Dimitrov design’s to one. Turbulence intensity is undefined in calm air, so u_min must be above 0. The default bins split the reachable TI into eight, and custom bins must cover it unless require_full_range=False.

Parameters:
  • ti_bins (ArrayLike | None)

  • u_min (float)

  • u_max (float)

  • n_quadrature (int)

  • require_full_range (bool)

Return type:

np.ndarray

wind_rose(speed_bins=None, *, u_min=4.0, u_max=25.0, require_full_range=True)#

Return the share of time in each direction sector and speed bin.

The table has shape (n_sectors, n_speed_bins), and entry (i, b) is P(sector i, speed in bin b), the share of all time, calm included. It sums to the share of time in [u_min, u_max] in m/s, so annual energy is 8760 h times the sum of the table times the power in each bin. u_min=0 covers calm air, and 1 - sum is then the share above u_max. Divide by the sum for the distribution given a speed in the range. A Dimitrov design has no share of time, so its rose is its distribution over the range and sums to one. The default bins split the range into six, and custom bins must cover it unless require_full_range=False.

Parameters:
  • speed_bins (ArrayLike | None)

  • u_min (float)

  • u_max (float)

  • require_full_range (bool)

Return type:

np.ndarray

Direction models#

class oowl.resources.UniformDirection[source]#

Equal probability in every sector.

Named apart from the layout oowl.layouts.Uniform.

probabilities(n_sectors)[source]#

Return each sector’s share of time, summing to one.

Parameters:

n_sectors (int)

Return type:

ndarray

class oowl.resources.SingleDirection(direction)[source]#

All probability in one sector, centered on direction.

direction is in degrees, 0 north and 90 east. The sectors turn so one is centered on it, and every case blows from exactly that direction.

Parameters:

direction (float)

direction: float#

Direction the wind blows from, in degrees.

probabilities(n_sectors)[source]#

Return each sector’s share of time, summing to one.

Parameters:

n_sectors (int)

Return type:

ndarray

sector_angles(n_sectors)[source]#

Return the sector centers in degrees, 360 i / n by default.

Parameters:

n_sectors (int)

Return type:

ndarray

class oowl.resources.VonMises(n_modes=2, *, modes=None, background_fraction=0.2, concentration_range=(3.0, 12.0), seed=None)[source]#

A mixture of von Mises distributions on a uniform background.

The wind direction model of Carta et al. (2008), https://doi.org/10.1016/j.enconman.2007.10.017. Each mode has a mean direction in degrees, a concentration, larger for a narrower mode, and a weight. n_modes modes are drawn from seed unless modes gives them as (direction, concentration, weight) tuples. Without a seed, a sampler redraws the modes for each scenario from its own seed. A drawn mode takes its direction uniformly, its concentration from concentration_range and its weight from 0.5 to 1.5. The weights are scaled to share 1 - background_fraction, and the background spreads the rest evenly, a mode of zero concentration.

Parameters:
background_fraction: float = 0.2#

Share of probability spread evenly over the compass.

concentration_range: tuple[float, float] = (3.0, 12.0)#

Range of the drawn concentrations.

metadata()[source]#

Return the record, with the modes used, drawn or given.

Return type:

dict

modes: Sequence[tuple[float, float, float]] | None = None#

Modes as (direction, concentration, weight), replacing drawn ones.

n_modes: int = 2#

Number of modes drawn from seed.

probabilities(n_sectors)[source]#

Return each sector’s share of time, summing to one.

Parameters:

n_sectors (int)

Return type:

ndarray

seed: int | None = None#

Seed of the drawn modes.

class oowl.resources.Elliptical(*, semi_axis=0.5641895835477563, folding=0.0, direction=0.0)[source]#

Hart’s three-parameter elliptical wind rose.

Described in https://doi.org/10.5194/wes-10-1821-2025. Directions follow an ellipse of unit area whose semi_axis lies along the prevailing axis. The default 1 / sqrt(pi) is a circle, uniform when folding is 0, a longer one concentrates the rose along the axis and a shorter one across it. folding moves probability from the opposite half towards the axis. direction is the prevailing direction in degrees, 0 north and 90 east.

Parameters:
direction: float = 0.0#

Prevailing direction in degrees.

folding: float = 0.0#

Share moved from the opposite half, in [0, 1].

probabilities(n_sectors)[source]#

Return each sector’s share of time, summing to one.

Parameters:

n_sectors (int)

Return type:

ndarray

semi_axis: float = 0.5641895835477563#

Semi-axis of the unit-area ellipse along the prevailing axis.

Case models#

class oowl.resources.KellyGryning(*, unstable_fraction=0.6, stable_scale=0.015, unstable_scale=0.03, reference_friction_velocity=0.3, stable_exponent=2.0, unstable_exponent=3.0, fraction_friction_velocity=None)[source]#

Stability climate of Kelly and Gryning (2010), conditioned on the wind.

Each side of the distribution of 1/L is a stretched exponential,

\[P(L^{-1}) = n_\pm \frac{C_\pm}{\sigma_\pm \Gamma(5/2)} \exp\left[-\left(\frac{C_\pm |L^{-1}|}{\sigma_\pm}\right)^{2/3}\right],\]

with the universal C+ = 1 and C- = 3. n+ and n- are the stable and unstable fractions and sigma+ and sigma- the scales of 1/L on each side. Kelly and Gryning derive the scales from heat-flux statistics as sigma = (g / T0) std(H) / <u_star>^3. fit() estimates them from a record of 1/L by maximum likelihood, which at Høvsøre gives an unstable scale a third of the heat-flux estimate.

Conditioning on the wind speed is an OOWL extension. The scales hold at the site’s mean friction velocity reference_friction_velocity. A case whose neutral friction velocity is u_star uses sigma+ (u_ref / u_star)^p+ and sigma- (u_ref / u_star)^p-. A stable surface layer keeps its temperature scale, so 1/L falls as u_star^-2, while convection keeps its heat flux, so 1/L falls as u_star^-3. These are the default exponents, and fit() can estimate them instead.

unstable_fraction is one number, or one per friction velocity in fraction_friction_velocity, interpolated linearly in the neutral u_star and held beyond the ends. Sites often turn unstable in strong wind, when cold air crosses warmer water, and at low wind by day over land.

The defaults are round offshore magnitudes, not a site calibration.

Parameters:
bounds(wind_speed, *, height, roughness_length, coverage=0.98)[source]#

Return the central coverage interval of 1/L, exactly.

Parameters:
  • wind_speed (ArrayLike)

  • height (float)

  • roughness_length (ArrayLike)

  • coverage (float)

Return type:

tuple[np.ndarray, np.ndarray]

classmethod fit(inverse_obukhov_length, wind_speed, *, height, roughness_length, fit_exponents=False, fraction_bins=1)[source]#

Return the climate that best explains a record of 1/L.

inverse_obukhov_length in m^-1 and wind_speed at height are paired records, for example ten-minute means, over a surface of roughness_length. The unstable fraction is the share of negative 1/L and reference_friction_velocity the mean neutral friction velocity. Given the exponents, each scale maximizes the likelihood in closed form,

\[\sigma_\pm = \left[\frac{2}{3N_\pm}\sum_i \left(\frac{C_\pm |L_i^{-1}|}{r_i^{p_\pm}}\right)^{2/3} \right]^{3/2}, \qquad r_i = \frac{u_{\mathrm{ref}}}{u_{*,i}}.\]

With fit_exponents the exponents also maximize the likelihood, searched between 0 and 6. Both sides need records.

fraction_bins above one splits the records into that many equal-count bins of friction velocity and tabulates the unstable share of each at its mean u_star. Records of one friction velocity share the bin of its first record in sorted order, so a record of few distinct wind speeds, such as speeds stored at a fixed resolution, gives fewer bins than requested, and a single bin gives one fraction. The scales describe each side given its sign, so they do not change.

Parameters:
  • inverse_obukhov_length (ArrayLike)

  • wind_speed (ArrayLike)

  • height (float)

  • roughness_length (float)

  • fit_exponents (bool)

  • fraction_bins (int)

Return type:

KellyGryning

fraction_friction_velocity: tuple[float, ...] | None = None#

Neutral friction velocities in m/s of a table of unstable fractions.

fractions(wind_speed, *, height, roughness_length)[source]#

Return the unstable fraction n- at each wind speed.

Parameters:
  • wind_speed (ArrayLike)

  • height (float)

  • roughness_length (ArrayLike)

Return type:

np.ndarray

metadata()[source]#

Return a JSON-compatible record of the model configuration.

Return type:

dict

reference_friction_velocity: float = 0.3#

Mean friction velocity in m/s at which the scales hold.

sample(wind_speed, quantiles, *, height, roughness_length)[source]#

Return 1/L in m^-1 at each wind speed and quantile, exactly.

Quantiles below the unstable fraction are unstable and the rest stable. On each side (C |1/L| / sigma)^(2/3) follows a gamma law of shape 3/2, whose inverse gives the tail.

Parameters:
  • wind_speed (ArrayLike)

  • quantiles (ArrayLike)

  • height (float)

  • roughness_length (ArrayLike)

Return type:

np.ndarray

scales(wind_speed, *, height, roughness_length)[source]#

Return (sigma+, sigma-) in m^-1 at each wind speed.

Parameters:
  • wind_speed (ArrayLike)

  • height (float)

  • roughness_length (ArrayLike)

Return type:

tuple[np.ndarray, np.ndarray]

stable_exponent: float = 2.0#

Exponent p+ of u_ref / u_star scaling the stable side.

stable_scale: float = 0.015#

Stable scale sigma+ in m^-1 at reference_friction_velocity.

unstable_exponent: float = 3.0#

Exponent p- of u_ref / u_star scaling the unstable side.

unstable_fraction: float | tuple[float, ...] = 0.6#

Fraction n- of unstable cases, one or one per friction velocity.

unstable_scale: float = 0.03#

Unstable scale sigma- in m^-1 at reference_friction_velocity.

class oowl.resources.Charnock(constant=0.0144)[source]#

Offshore roughness that grows with the wind, z0 = A_c u_star^2 / g.

Waves roughen the water surface as the wind rises (Charnock, 1955). With the neutral log law this is solved exactly for the wind speed U at height z through the lower branch of the Lambert W function (Kelly, 2021),

\[z_0 = \frac{\kappa^2 A_c}{4} \frac{U^2 / g} {\left[W_{-1}\!\left(-\frac{\kappa U}{2} \sqrt{\frac{A_c}{g z}}\right)\right]^2}.\]

constant is the Charnock constant A_c. The default 0.0144 is the ocean fit of Garratt (1977), and ESDU International (1982) recommends 0.0162. Pass an instance as a resource’s roughness_length.

Parameters:

constant (float)

constant: float = 0.0144#

Dimensionless Charnock constant A_c.

metadata()[source]#

Return a JSON-compatible record of the roughness model.

Return type:

dict

roughness_length(wind_speed, height)[source]#

Return z0 in meters for each positive wind speed at height.

Parameters:
  • wind_speed (ArrayLike)

  • height (float)

Return type:

np.ndarray

class oowl.resources.SurfaceLayer(coefficient=2.3, intensity=None)[source]#

Surface-layer turbulence, sigma_u = C u_star with the diabatic u_star.

The friction velocity comes from the Monin-Obukhov profile at the case’s own stability, u_star = kappa U / [ln(z / z0) - Psi(z / L) + Psi(z0 / L)], so unstable air is more turbulent and stable air less. Beyond STABLE_LIMIT the stable gradient is held at the critical Richardson number, so Psi grows logarithmically and turbulence keeps falling slowly into very stable air.

coefficient is C, one number or a frozen scipy.stats distribution with bounded non-negative support, drawn independently for every case. Kelly (2018) found 2.3 on average over flat land at Høvsøre from 4 to 25 m/s, the default. C = 2.5 gives the neutral rule of thumb TI = 1 / ln(z / z0).

intensity optionally sets the level, one value or one per sector. A resource then scales sigma_u in each sector so its mean turbulence intensity over 4 to 25 m/s is that value, keeping the spread over speed and stability. The level depends on the site’s winds, so the resource applies it and the methods here give the unscaled model.

Parameters:
  • coefficient (Any)

  • intensity (float | Sequence[float] | np.ndarray | None)

coefficient: Any = 2.3#

sigma_u / u_star, a number or a bounded scipy.stats distribution.

friction_velocity(wind_speed, *, height, roughness_length, inverse_obukhov_length)[source]#

Return the diabatic u_star in m/s at each case.

Parameters:
  • wind_speed (ArrayLike)

  • height (float)

  • roughness_length (ArrayLike)

  • inverse_obukhov_length (ArrayLike)

Return type:

np.ndarray

intensity: float | Sequence[float] | np.ndarray | None = None#

Mean turbulence intensity over 4 to 25 m/s, one or one per sector.

intensity_cdf(wind_speed, values, *, height, roughness_length, stability)[source]#

Return P(TI < value | U), one row per speed and column per value.

Given the stability, TI lies below a value when C lies below it divided by the scale, so the coefficient CDF is averaged over the climate, drawn once for every value. A fixed C counts exactly. A distributed C interpolates its CDF over a grid in the log of the scale, so it is evaluated a few hundred times per value rather than at every speed and stability. The kinks of the CDF at the ends of the support are summed exactly, so a piecewise-linear CDF such as the uniform is exact to rounding. A truncated normal is within about 2e-9 and a density with a kink inside its support within about 1e-7, while a density unbounded at an end, such as a beta with a shape below one, errs by up to about 1e-4 near it. The result is clipped to [0, 1] and made non-decreasing in the value.

Parameters:
  • wind_speed (ArrayLike)

  • values (ArrayLike)

  • height (float)

  • roughness_length (ArrayLike)

  • stability (KellyGryning)

Return type:

np.ndarray

intensity_range(wind_speed, *, height, roughness_length, stability)[source]#

Return the lowest and highest turbulence intensity at each speed.

The extremes pair the coefficient support with the most stable and the most unstable 1/L the climate can draw, its quantiles 0 and 1.

Parameters:
  • wind_speed (ArrayLike)

  • height (float)

  • roughness_length (ArrayLike)

  • stability (KellyGryning)

Return type:

tuple[np.ndarray, np.ndarray]

mean_intensity(wind_speed, *, height, roughness_length, stability)[source]#

Return the mean turbulence intensity at each wind speed.

Parameters:
  • wind_speed (ArrayLike)

  • height (float)

  • roughness_length (ArrayLike)

  • stability (KellyGryning)

Return type:

np.ndarray

metadata()[source]#

Return a JSON-compatible record of the model configuration.

Return type:

dict

sample(quantiles, *, wind_speed, height, roughness_length, inverse_obukhov_length)[source]#

Return sigma_u in m/s, C at each quantile times u_star.

Parameters:
  • quantiles (ArrayLike)

  • wind_speed (ArrayLike)

  • height (float)

  • roughness_length (ArrayLike)

  • inverse_obukhov_length (ArrayLike)

Return type:

np.ndarray

class oowl.resources.Mann(anisotropy=3.0)[source]#

Mann turbulence parameters from each case’s turbulence and shear.

The length scale follows Kelly (2018), l_M = sigma_u / (dU/dz), with dU/dz = U alpha / z from the case’s own profile at the reference height, so it grows in unstable air and shrinks in stable air. With SurfaceLayer it is C kappa z / phi_m(z/L). The anisotropy is anisotropy, about 3 in measurements (de Maré and Mann, 2016, and Kelly, 2018) against the 3.9 of IEC 61400-1. Pass it as a resource’s mann.

Parameters:

anisotropy (float)

anisotropy: float = 3.0#

Eddy-lifetime parameter Gamma, from 0 isotropic to about 5.

dimensions = 0#

Sobol dimensions the link draws.

metadata()[source]#

Return a JSON-compatible record of the model configuration.

Return type:

dict

sample(quantiles, *, wind_speed, sigma_u, shear_exponent, height)[source]#

Return mann_length_scale in meters and mann_anisotropy.

quantiles holds no rows, since the link is deterministic.

Parameters:
  • quantiles (ArrayLike)

  • wind_speed (ArrayLike)

  • sigma_u (ArrayLike)

  • shear_exponent (ArrayLike)

  • height (float)

Return type:

dict[str, np.ndarray]

Sampled cases#

class oowl.resources.SiteConditions(wind_direction, wind_speed, sigma_u, turbulence_intensity, inverse_obukhov_length, shear_exponent, reference_height, roughness_length, weight, *, metadata=<factory>, mann_length_scale=None, mann_anisotropy=None, wind_veer=None, yaw_misalignment=None, inflow_tilt=None, air_density=None)[source]#

Sampled inflow cases and their sampling metadata.

Each case holds a wind-direction sector center, wind speed, sigma_u, TI = sigma_u / U, inverse Obukhov length, the site’s reference_height repeated per case, and the roughness_length each case sees, which varies with wind speed for a Charnock roughness and is drawn per case by Dimitrov. The wind profile is the log law of that roughness and stability, so wind_speed_at() gives the speed at any height and shear_exponent is its local power-law exponent at the reference height. weight is each case’s probability mass given a speed in the sampled range and sums to one. metadata["in_range_fraction"] is that range’s share of time.

A mann link fills mann_length_scale in meters and mann_anisotropy. A Dimitrov design fills yaw_misalignment and inflow_tilt in degrees and air_density in kg/m^3, and with a rotor diameter wind_veer across the rotor in degrees. Otherwise they are None.

Parameters:
classmethod load(path)[source]#

Read the conditions save() wrote to path.

Raises ValueError for a file of another version or one missing a case array, and the checks of the constructor apply to what it holds.

Parameters:

path (str | Path)

Return type:

SiteConditions

property n_cases: int#

Number of sampled inflow cases.

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

Plot the wind rose, turbulence, stability and wind-speed panels.

Draws four panels, so ax is a sequence of four Axes, the first polar, and a new figure is created without it. Returns the array of the four Axes.

Parameters:
Return type:

np.ndarray

save(path)[source]#

Write the cases to path, a NumPy .npz file, and return the path.

Each array is stored under its field name, the optional ones only when set, and the metadata as JSON text under metadata, so load() rebuilds equal conditions. Raises ValueError for another suffix.

Parameters:

path (str | Path)

Return type:

Path

stability_parameter(height)[source]#

Return z/L at height meters for every case, positive when stable.

Parameters:

height (float)

Return type:

ndarray

wind_speed_at(height)[source]#

Return every case’s wind speed in m/s at height meters.

The Monin-Obukhov log law of the case, scaled to its speed at the reference height,

\[U(z) = U(z_r) \frac{\ln(z/z_0) - \Psi(z/L) + \Psi(z_0/L)} {\ln(z_r/z_0) - \Psi(z_r/L) + \Psi(z_0/L)},\]

with the stable gradient held beyond STABLE_LIMIT. height must lie above every case’s roughness length.

Parameters:

height (float)

Return type:

ndarray

Profile functions#

R.profile holds the Monin-Obukhov profile the models integrate.

oowl.resources.profile.stability_correction(zeta)[source]#

Return the diabatic correction Psi(z / L) to the log wind profile.

zeta is z / L, positive stable. Psi is zero at neutral, positive when unstable and negative when stable. It is the integral Psi = int (1 - phi_m) d ln(z/L) of stability_gradient(), so Psi = -4.8 z/L up to STABLE_LIMIT and falls logarithmically beyond it, where the gradient is held.

Parameters:

zeta (ArrayLike)

Return type:

np.ndarray

oowl.resources.profile.stability_gradient(zeta)[source]#

Return the dimensionless wind gradient phi_m(z / L).

Högström’s (1 - 19.3 z/L)^(-1/4) when unstable and 1 + 4.8 z/L when stable, held beyond STABLE_LIMIT. It is one at neutral, below one when unstable and above one when stable.

Parameters:

zeta (ArrayLike)

Return type:

np.ndarray

oowl.resources.profile.friction_velocity(wind_speed, height, roughness_length)[source]#

Return the neutral friction velocity u_star in m/s.

u_star = kappa u(z) / ln(z / z0) with no stability correction. roughness_length is one value or one per wind speed.

Parameters:
  • wind_speed (ArrayLike)

  • height (float)

  • roughness_length (ArrayLike)

Return type:

np.ndarray

oowl.resources.profile.STABLE_LIMIT#

z/L beyond which the stable wind gradient phi_m is held, 0.854. Here the gradient Richardson number with Högström’s gradients reaches the critical 0.25, where turbulence is fully suppressed, so the local shear stops growing. The profile correction keeps integrating the held gradient.