Design philosophy#

OOWL generates varied, interpretable wind-farm descriptions for use in research and engineering workflows. It provides boundaries, turbine layouts, wind resources, turbine models, and reference farms.

OOWL is not a wake model or a wind-farm layout optimizer. It also does not try to reconstruct a particular real wind farm. Those are downstream applications that can use OOWL’s output.

The principles below describe how OOWL’s concepts fit together. They should remain recognizable as the package grows.

Keep concepts separate#

The public API separates the main parts of a wind farm.

  • boundaries define the allowed area

  • strings define optional line guides

  • layouts decide where turbines are placed

  • modifiers make explicit changes to a configuration

  • resources describe wind conditions

  • turbines describe turbine performance and assignment

  • references load source-backed examples of real farms

Each concept should remain useful on its own.

A boundary knows its geometry, but not how many turbines it will contain. A layout owns the turbine count and placement method. A wind resource does not decide where turbines are placed. A turbine model does not modify the farm boundary.

This separation allows the same components to be combined in different ways.

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

boundary = B.OffshorePolygon(area=40_000_000, seed=8)
layout = L.PoissonDisc(80, seed=8)

farm = oowl.Sampler(boundary, layout).sample()[0]

New behavior should normally come from combining existing concepts rather than adding special cases to unrelated ones.

Describe first, then draw#

Boundaries, layouts, resources, turbines, and similar objects describe how a farm should be generated. They are configuration objects.

Sampler collects that configuration into one description of a scenario, and drawing from it realizes whatever it describes, whether farms or clusters of them.

There is a useful distinction here.

Configuration objects describe what may be generated. A Farm describes what was generated.

Realization is two steps because a description is worth having as a value. It is validated where it is written, can be held and passed around, and gives the same scenarios however often it is drawn from. The draw decides only how many (n) and from where (start). A fixed description and one built from hundreds of lines of nested choices are drawn from the same way.

There is one description type rather than one per shape. Fix every value for one scenario, or give values ranges or choices for a population, without rewriting the description.

Random choices should normally happen during a draw rather than being hidden inside configuration.

Modifiers follow the same idea. They return a new configuration instead of unexpectedly changing an object that may be reused elsewhere.

A farm is a farm, regardless of where it came from#

A realized wind farm should use the same main representation whether it was generated synthetically or loaded from a reference source.

The generation history belongs in metadata rather than requiring separate and incompatible farm types.

Reference farms therefore provide real examples, validation data, and evidence about wind-farm populations. They are not hidden templates that synthetic generators copy or reshape.

Geometry has explicit meaning#

Geometric structure alone does not always tell us what a wind farm represents.

For example, a disconnected MultiPolygon may still describe one logical farm if an exclusion channel divides a single lease area.

A Cluster, by contrast, contains several complete Farm objects that remain separate and can have different boundaries, layouts, turbines, and resources.

There is one multi-farm container, Cluster, with save and summary as a farm has. A farm loads with oowl.load and a cluster with Cluster.load.

What a scenario is follows from the arguments given, not from the values drawn. Any n_farms, a sequence of boundaries, or a domain gives a Cluster, so n_farms=1 is a cluster of one and one sampler never returns mixed types. A cluster’s farms share one coordinate frame, so Cluster.plot draws one map, while a population is a tuple plotted in an ordinary loop.

The requested turbine count is also part of the meaning of a layout. A layout should either produce that count or report that it cannot.

A removal modifier on a layout given a count places the turbines it removes on top, so the count stays exact. On a layout filled to a spacing it is an explicit reduction, and the farm keeps fewer turbines.

Prefer composition to specialized variants#

OOWL should gain new behavior by composing a small number of clear concepts where possible.

For example, if a feature changes placement, it probably belongs in a layout or modifier rather than in a boundary. If it describes atmospheric conditions, it belongs in a resource rather than in the farm generator.

Avoid adding the same idea independently to several classes.

Composition should also remain visible to the user. A generated farm should be understandable from the boundary, layout, modifiers, resource, turbine model, and other components that produced it.

Scientific methods should remain understandable#

OOWL favors methods whose behavior can be understood from the geometry, physics, or statistical assumptions they encode.

Public parameters should describe meaningful properties of the generated farm. They should not expose an arbitrary collection of internal tuning values simply because those values existed during model development.

A fitted model may represent patterns learned from a population of real farms. Its training data, preprocessing, assumptions, fitting procedure, and validation should be documented with that model.

Real reference farms provide evidence about the population. Synthetic farms should remain new examples drawn from the model rather than disguised copies of individual projects.

Reproducibility is part of the result#

Randomness is an input to generation, not hidden program state.

Giving the same explicit seed should reproduce the same result on the supported implementation. An unseeded operation may use fresh entropy, but the resolved seed should be recorded.

Independent parts of a calculation should use deterministic child seeds. Adding an unrelated random operation should not unexpectedly change an earlier part of the generated farm.

One farm therefore has one root seed, and every process derives its own stream from it by a stable name, such as the boundary, layout selection, placement, each modifier by position, each cluster member and the surface. Naming rather than numbering keeps streams independent, so adding a process leaves existing seeds untouched. A sampler derives each farm’s root from its own root and the farm’s index, which record = farm.metadata["sample"] holds, and Sampler(..., seed=record["root"]).sample(start=record["index"]) replays the whole farm.

A component with its own explicit seed keeps it, and an unseeded one is rebuilt from the farm’s root. A seeded boundary therefore stays fixed across several layouts, while the recorded root and index reproduce an unseeded farm end to end.

A seed alone is not always enough to explain a result. OOWL therefore records metadata alongside generated objects.

Metadata should describe important decisions such as the following.

  • the method used

  • resolved seeds

  • fitted parameters

  • applied modifiers

  • component allocation

  • other information needed to understand or replay the result

Metadata is data, not a debug dump. It should remain compact, meaningful, and serializable using ordinary JSON types.

Keep runtime separate from research#

Ordinary OOWL use should work offline with a compact scientific Python stack.

Generation code belongs in the installed package. Large studies, fitting pipelines, scraped data, exploratory notebooks, and research-only dependencies do not.

Network access should be explicit. A feature such as an opt-in Global Wind Atlas resource may use an external service, but importing or using unrelated parts of OOWL should not require a network connection.

Small fitted or reference artifacts may be shipped when they are needed at runtime. The processes used to create those artifacts should remain reproducible without becoming part of the runtime package.

Add structure when there is a reason#

OOWL is still evolving. New abstractions should solve a problem that exists now rather than anticipate every possible future use.

A new class, hierarchy, configuration layer, registry, or general-purpose framework should make the current package easier to understand or extend. It should not exist mainly because it might become useful later.

Before version 1.0, a clean and documented API change is generally better than preserving an awkward interface through aliases and compatibility layers.

The goal is a small set of concepts that combine predictably, not a large collection of overlapping conveniences.