# Concepts

A scenario is built from six composable layers and drawn from a
{class}`~oowl.Sampler`.

## The six layers

### Boundaries

{mod}`oowl.boundaries` define the farm area. Shape arguments set proportions and
`area` sets scale, so `Rectangle(8, 5, area=40)` is an `8:5` rectangle of area
40. Without `area` a generated shape is a unit-size form, which a sampler sizes
from its `area`, a density, or a count and a spacing, and a farm on a shape
without any size raises a `ValueError`. A shape without a `center` sits at
`(0, 0)` and may be moved when a cluster is arranged, while
{class}`~oowl.boundaries.Polygon` keeps the coordinates it is given.

Modifiers compose through `.modify(...)` without changing the original.
`ExclusionZones` removes named areas, such as a wreck or a cable corridor.
`Setback` keeps turbines a minimum distance inside every edge. `Partition`
cuts a boundary into cells for multipart farms and clusters. A multipart
boundary, such as a {class}`~oowl.boundaries.Union` of separate pieces, is still
one farm.

### Strings

{mod}`oowl.strings` are optional line guides, such as
{class}`~oowl.strings.Straight`, {class}`~oowl.strings.Arc` and
{class}`~oowl.strings.Parallel`, that {class}`~oowl.layouts.String` places
turbines along. `Breaks` and `Meander` modify them.

### Layouts

{mod}`oowl.layouts` place an **exact turbine count**, or fill a boundary to a
`density`, one mapping that names `area_per_turbine`, `minimum_spacing`, no
pair closer than it, `mean_spacing`, the mean nearest-neighbor distance, or
`maximum_spacing`, no turbine farther than it from its nearest neighbor, as
in `L.Grid(density={"minimum_spacing": 800.0})`. `Uniform` and `Sobol`
place turbines at random, so a minimum spacing sizes them by their single
closest pair and a mean spacing gives steadier farms.

- {class}`~oowl.layouts.Grid`, {class}`~oowl.layouts.Circular`,
  {class}`~oowl.layouts.Contour` and {class}`~oowl.layouts.Sunflower` select
  positions from a lattice, rings, inward offsets of the boundary or a Fermat
  spiral, scaled to fill the boundary.
- {class}`~oowl.layouts.BoundaryGrid` combines edge turbines with an inner grid
  after [Stanley and Ning (2019)](https://doi.org/10.5194/wes-4-663-2019), and
  {class}`~oowl.layouts.Perimeter` places turbines along the edge only.
- {class}`~oowl.layouts.Delaunay` relaxes an unstructured triangular mesh.
- {class}`~oowl.layouts.PoissonDisc` draws blue noise,
  {class}`~oowl.layouts.Sobol` a low-discrepancy sequence, and
  {class}`~oowl.layouts.Uniform` independent points, the null model the spatial
  metrics compare against.
- {class}`~oowl.layouts.String` follows string guides, and
  {class}`~oowl.layouts.Mixture` lays several families out in cells of one farm.

Layout modifiers act after fitting, for example removing row-correlated
vacancies from a grid. A layout given `n_turbines` places the turbines a
removal drops on top, so exactly that many remain.

```python
import oowl
from oowl import boundaries as B
from oowl import layouts as L
from oowl import modifiers as M

boundary = B.Rectangle(9, 5, area=45_000_000)

layout = L.Grid(100, seed=8).modify(
    M.TurbineRemoval(70, method="markov", transition_penalty=0.75, seed=9)
)
farm = oowl.Sampler(boundary, layout).sample()[0]  # 100 of 170 grid nodes kept
```

### Resources

A {class}`~oowl.resources.WindResource` is an analytical wind climate. Its
`direction` shares the wind between sectors, `UniformDirection`, `SingleDirection`,
`VonMises` or `Elliptical`, and each sector has Weibull speeds, a surface,
stability and turbulence. `GWA` builds one from the Global Wind Atlas. `resource.sample(n)` draws
inflow cases over 4 to 25 m/s by default, or over another range given by `u_min`
and `u_max`. Every case includes atmospheric stability, coupled to turbulence, as
[Stability and turbulence](wind-resources/stability-and-turbulence.ipynb) describes.

### Turbines

{mod}`oowl.turbines` provides {class}`~oowl.turbines.Idealized`,
{class}`~oowl.turbines.Controlled` and {class}`~oowl.turbines.Tabulated`
performance models, assigned to a farm as one type or one per position.

### Surfaces

{mod}`oowl.surfaces` give terrain elevation onshore or positive water depth
offshore, from Gaussian or Perlin fields. A farm carries at most one, and a
cluster shares one across its farms.

## Scenarios

The description decides what a scenario is. A boundary and a layout give one
{class}`~oowl.Farm`. `n_farms=N`, or a list of boundaries, each built or a
recipe drawn for its member, gives a
{class}`~oowl.Cluster` of farms placed together, `gap` meters apart. `domain=`
gives a region of development sites scattered across it.

In a cluster, `turbines` are drawn per farm while one `resource` and `surface`
cover the whole cluster. `cluster.plot()` draws its farms on one map.

```python
from oowl import resources as R

cluster = oowl.Sampler(
    [B.Circle(area=28_274_333.882), B.OffshorePolygon(area=20_000_000, seed=8)],
    [L.Grid(60), L.PoissonDisc(40)],
    R.WindResource(direction=R.VonMises(n_modes=2, seed=7), n_sectors=12),
    gap=(500, 3000),
    seed=8,
).sample()[0]
```

## Populations

Fix every value and each scenario is the same. Give a value a `(low, high)`
range, an `f(rng)` callable, an `oowl.sampling.Choice` or a
`(constructor, kwargs)` recipe, and the description becomes a population.
`configuration` varies several values together, and a component with its own
`seed` stays fixed across the population.

Count, area and spacing constrain one geometry, so a sampler takes at most two.
Ranging count and area independently lets density wander, while count and
spacing scale each boundary to fit.

```python
loose = oowl.Sampler(
    B.OffshorePolygon, L.Grid(), n_turbines=(20, 200), area=(5e6, 40e6), seed=3
).sample(8)
tight = oowl.Sampler(
    B.OffshorePolygon,
    L.Grid(),
    n_turbines=(20, 200),
    density={"minimum_spacing": 800.0},
    seed=3,
).sample(8)

print([round(farm.metadata["sample"]["closest_pair"]) for farm in loose])
print([round(farm.metadata["sample"]["closest_pair"]) for farm in tight])
```

The `loose` farms come out 322 m to 1037 m apart, and at 322 m a 240 m rotor
stands 1.3 diameters from its neighbor. Every `tight` farm is at 800 m, on a
boundary scaled to between 23.7 and 100.5 km². [Sampling](api/sampling.md)
gives the full rule.

## Seeds

Seeds are derived per scenario by index, so scenario *i* is the same however
many are drawn, and `start=` shards a population. An unseeded sampler records
its root seed on every scenario, which replays it.

```python
record = farm.metadata["sample"]
replay = oowl.Sampler(boundary, layout, seed=record["root"]).sample(
    start=record["index"]
)[0]
```

A random boundary or guide given a `seed` keeps its geometry in every scenario,
and one without is redrawn from each scenario's seed.

## Units and windIO

Coordinates are in meters. String guide lengths are relative when fitted to a
boundary. `Farm.positions` is an `(N, 2)` array matching windIO's
`coordinates.x` and `coordinates.y`. `oowl.save` and `oowl.load` handle one farm
and `Cluster.save` a cluster, as
[windIO interchange](interoperability/windio.ipynb) describes.
