# Quickstart

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

## Draw a farm

Describe the scenario as a {class}`~oowl.Sampler` from a boundary, a layout and
optionally a wind resource, then draw from it. The layout places exactly the
requested number of turbines.

```python
boundary = B.Rectangle(9, 5, area=45_000_000).modify(
    M.Partition.Channels(n_farms=3, gap=500, seed=3)
)
resource = R.WindResource(direction=R.VonMises(n_modes=2, seed=7), n_sectors=12)

spec = oowl.Sampler(
    boundary,
    L.Grid(n_turbines=92, lattice_angle=60, seed=4),
    resource,
)

farm = spec.sample()[0]
farm.plot()
print(farm.n_turbines)  # 92
print(farm.positions.shape)  # (92, 2)
```

{meth}`~oowl.Sampler.sample` returns a tuple of scenarios, so one farm is the
first element. A {class}`~oowl.Farm` holds `positions` in meters, its
`boundary`, and `metadata` recording how it was drawn. Drawing again gives the
same farm.

Shape arguments set proportions and `area` sets scale. Without `area`, a
generated boundary is a unit-size form, which the sampler sizes from its
`area`, a density, or `n_turbines` with a spacing. A farm on a shape without
any size raises a `ValueError`.

## Try another method

Only the layout changes.

```python
contours = oowl.Sampler(
    boundary,
    L.Contour(n_turbines=80, include_center=True, seed=5),
).sample()[0]

sunflower = oowl.Sampler(
    B.Circle(area=28_274_333.882),
    L.Sunflower(n_turbines=80, rotation=15, seed=6),
).sample()[0]
```

## Measure a farm

```python
print(farm.summary())
print(farm.summary(["clark_evans", "aspect_ratio"]))
```

## Draw a population

Give a value a `(low, high)` range, or pass a class instead of an instance, and
the description becomes a population.

```python
population = oowl.Sampler(
    boundary=B.OffshorePolygon,
    layout=L.Grid,
    n_turbines=(40, 120),
    density={"minimum_spacing": 800.0},
    seed=7,
)

farms = population.sample(100)
print(round(farms[0].metadata["sample"]["closest_pair"], 1))  # 800.0

for index, drawn in enumerate(farms):
    drawn.save(f"dataset/farm_{index:04d}.yaml")
```

`density` names how tightly turbines are packed, here the minimum turbine
distance in meters, and it may also be a `mean_spacing`, a `maximum_spacing`,
an `area_per_turbine` or a `power_density`. A sampler takes any two of count,
area and density. A count and a
spacing scale each boundary without an area of its own to fit, which keeps
every farm buildable. A boundary given an area keeps it, so a count and a
spacing there over-constrain the farm. To hold a farm of fixed count and area
to a spacing it must clear, check it with
`requirements={"spacing": (minimum, None)}`. See {doc}`Concepts <concepts>`.

Seeds are derived per scenario, so `population.sample(10, start=90)` gives
farms 90 to 99 without drawing the others, and
{meth}`~oowl.Sampler.stream` yields scenarios one at a time.

## Reproduce a farm

An unseeded sampler records its root seed on every farm.

```python
farm = oowl.Sampler(boundary, L.Grid(92)).sample()[0]
record = farm.metadata["sample"]

replay = oowl.Sampler(boundary, L.Grid(92), seed=record["root"]).sample(
    start=record["index"]
)[0]
```

The same seed gives the same farm with the same code, so the record also keeps
`versions`, those of OOWL, NumPy, SciPy and Shapely that drew it. With pip,
install those versions. In a checkout of the release,
`pixi install --frozen --environment dev` installs exactly the packages
`pixi.lock` pins.

Next, read [Concepts](concepts.md) or start the guides with
{doc}`Boundary shapes <boundaries/shapes>`.
