Modifiers#

Non-mutating transformations for boundaries, strings, and layouts.

ExclusionZones subtracts a separate named area from a boundary, while Setback takes a margin off every edge the boundary already has, the outer ring and the rim of any hole alike. The clearance a setback delivers is a minimum, met and exceeded slightly rather than ever falling short.

BoundaryModifier()

Abstract transform applied to a boundary geometry.

StringModifier()

Transform ordered logical strings without mutating the source guide.

LayoutModifier()

Transform a completed layout after its base algorithm has run.

ExclusionZones(holes)

Subtract exclusion zones, such as protected areas or cables, from a boundary.

Setback(distance)

Hold turbines a fixed distance inside the boundary.

Breaks([n_breaks, size, seed])

Remove random internal sections from logical strings.

Meander([roughness, seed])

Add smooth correlated lateral variation while fixing endpoints.

TurbineRemoval([n_remove, ...])

Remove turbines randomly or in correlated grid rows.

PositionNoise(relative_standard_deviation, *)

Add boundary-constrained Gaussian turbine-position noise.

Relaxation(*[, method, length_scale, ...])

Relax an existing layout inside its boundary.

Boundary partitions#

Partition is a namespace rather than a modifier itself. Its concrete partition modifiers retain their cells, which a Sampler given n_farms turns into separate farms. Voronoi clips one cell around each supplied or sampled site. PowerDiagram adjusts weighted-distance cells to requested relative areas. Channels recursively cuts the boundary using a string guide.

class oowl.modifiers.Partition.Voronoi(n_farms, *, sites=None, gap=0.0, seed=None)[source]#

Partition a boundary into Voronoi cells clipped to it.

Each cell holds the points nearer its site than any other.

Parameters:
  • n_farms (int) – Exact number of partition cells.

  • sites (Sequence[Sequence[float]] | np.ndarray | None) – Optional finite (x, y) site per farm, fixed in the plane, so a resized boundary must still contain them. Sites are sampled uniformly inside the boundary when omitted.

  • gap (float) – Distance in meters kept between cells, half taken from each.

  • seed (int | np.random.Generator | None) – Random seed used when sampling sites.

partition(geometry)[source]#

Return Voronoi cells clipped to geometry, one per site.

Sites in a MultiPolygon parent are allocated across components by area, at least one each. With more components than farms the smallest joins its nearest until each farm has one. Holes are preserved. Each cell loses the points within gap / 2 of another. Sampled sites are redrawn a few times when the gap removes a whole cell, and given sites or a last draw that still loses one raise ValueError. Sampled sites replay from seed and the cell order is deterministic. Sites and the draws taken are recorded in details.

Parameters:

geometry (BaseGeometry)

Return type:

tuple[Polygon | MultiPolygon, …]

class oowl.modifiers.Partition.PowerDiagram(n_farms, *, area_weights=None, sites=None, gap=0.0, seed=None)[source]#

Partition one boundary into cells with controlled relative areas.

Cell i contains points where its weighted squared distance

\[d_i(x) = \lVert x-p_i\rVert^2-w_i\]

is no larger than for any other site, the power diagram of Aurenhammer (1987, doi:10.1137/0216006). Additive weights w_i are fitted so cell areas follow area_weights before any gap is removed.

Parameters:
  • n_farms (int) – Exact number of partition cells.

  • area_weights (Sequence[float] | None) – Positive relative target areas, one per farm. Equal by default.

  • sites (Sequence[Sequence[float]] | np.ndarray | None) – Optional finite (x, y) site per farm. Sites are sampled inside the boundary when omitted.

  • gap (float) – Distance in meters kept between cells, half taken from each.

  • seed (int | np.random.Generator | None) – Random seed used when sampling sites.

partition(geometry)[source]#

Return power cells of geometry, one per site.

Holes are honored. A parent in several disconnected polygons, as a setback or exclusion zone can leave, is fitted as one region, so the areas stay exact and a cell may take parts of several polygons. Unseeded sites are redrawn a few times before a failed area fit propagates. Each cell loses the points within gap / 2 of another, and a gap that consumes a whole cell raises ValueError. Cell order is deterministic. details records the sites, target, achieved and retained area fractions, and the fitted weights in details["power_weights_m2"].

Parameters:

geometry (BaseGeometry)

Return type:

tuple[Polygon | MultiPolygon, …]

class oowl.modifiers.Partition.Channels(n_farms, *, guide=None, gap=0.0, seed=None)[source]#

Partition a boundary by recursive cuts along a string guide.

Each cut halves the number of farms left in a cell. Its orientation is drawn at random and its offset gives that share of the area exactly. The parts of a cell on one side of the cut form one new cell, so a cut across a bay of the boundary leaves a cell in several parts. For a guide that is a graph over its chord the cut moves continuously with the cell. The cuts are chosen before any gap is removed, so a resized boundary is cut alike and the area left changes smoothly with its size. Each cell then loses a band gap wide along every cut that split one of its parents.

Parameters:
  • n_farms (int) – Exact number of partition cells.

  • guide (StringGuide | None) – StringGuide with one continuous string and distinct endpoints that does not cross itself, stretched across the cell to shape each cut. Straight() by default. A random guide given no seed is redrawn from seed, so every reseeded partition bends its channels anew. A seeded guide keeps its shape.

  • gap (float) – Width in meters of the channel each cut removes.

  • seed (int | np.random.Generator | None) – Random seed for the cut orientations and an unseeded guide.

Raises:

ValueError – If guide is not one continuous string with distinct endpoints, if it crosses itself, if no orientation gives a cut with two valid sides, or if the gap is too wide for a cell.

partition(geometry)[source]#

Return the cells cut from geometry by recursive channel splits.

Cells in a MultiPolygon parent are allocated across components by area, at least one each. With more components than farms the smallest joins its nearest until each farm has one. Holes survive the cuts. Cells and their order replay from seed. The guide and the channel lines are recorded in details.

Parameters:

geometry (BaseGeometry)

Return type:

tuple[Polygon | MultiPolygon, …]

Modifiers of boundaries, string guides and generated layouts.

Layout modifiers change a finished farm and attach to a layout with modify(). Boundary and string modifiers are re-exported so every modifier is available from one module.

class oowl.modifiers.BoundaryModifier[source]#

Abstract transform applied to a boundary geometry.

abstractmethod apply(geometry)[source]#

Return a new geometry, leaving geometry unmodified.

Modifiers must be re-appliable to the same input without side effects.

Parameters:

geometry (BaseGeometry)

Return type:

BaseGeometry

class oowl.modifiers.Breaks(n_breaks=1, *, size=0.05, seed=None)[source]#

Remove random internal sections from logical strings.

Adds n_breaks gaps, each size times the original arc length of its string, with size in (0, 1). Without seed, a String layout draws new breaks for every farm.

Parameters:
  • n_breaks (int)

  • size (float)

  • seed (int | np.random.Generator | None)

apply(strings)[source]#

Return new logical strings with n_breaks internal gaps removed.

Break locations are weighted by available length. Raises ValueError when no segment can hold another break.

Parameters:

strings (tuple[_LogicalString, ...])

Return type:

tuple[_LogicalString, …]

class oowl.modifiers.ExclusionZones(holes)[source]#

Subtract exclusion zones, such as protected areas or cables, from a boundary.

Collect the exclusion holes to subtract, in meter coordinates.

Each hole is a Shapely Polygon or MultiPolygon or a Boundary, whose geometry is read once here. A random boundary hole without a seed is redrawn for each farm a sampler draws. Holes are fixed in the plane, so resizing the boundary moves its edge over them rather than scaling them. Buffer a line, such as a cable route, to its corridor width first. Raises TypeError for another kind of hole and ValueError for an invalid polygon.

Parameters:

holes (Sequence[shapely.Polygon | MultiPolygon | Boundary])

apply(geometry)[source]#

Return geometry with the union of all holes subtracted.

The result may be a multipolygon or empty.

Parameters:

geometry (BaseGeometry)

Return type:

BaseGeometry

class oowl.modifiers.LayoutModifier[source]#

Transform a completed layout after its base algorithm has run.

abstractmethod apply(layout, rng, seed)[source]#

Return a new Farm, leaving layout untouched.

rng is seeded from seed, this modifier’s effective seed. Implementations append a record to metadata["modifiers"].

Parameters:
Return type:

Farm

class oowl.modifiers.Meander(roughness=0.03, *, seed=None)[source]#

Add smooth correlated lateral variation while fixing endpoints.

roughness is the peak lateral offset as a fraction of segment arc length, and 0 leaves the guide unchanged. Without seed, a String layout draws a new meander for every farm.

Parameters:
  • roughness (float)

  • seed (int | np.random.Generator | None)

apply(strings)[source]#

Return new logical strings displaced laterally, endpoints unchanged.

Each segment is offset along its normal by a smoothed random walk. Candidates are retried a bounded number of times until the guide is simple, else ValueError is raised.

Parameters:

strings (tuple[_LogicalString, ...])

Return type:

tuple[_LogicalString, …]

class oowl.modifiers.PositionNoise(relative_standard_deviation, *, minimum_spacing=None, seed=None, max_attempts=100)[source]#

Add boundary-constrained Gaussian turbine-position noise.

relative_standard_deviation is a fraction of the layout’s median nearest-neighbor spacing, not a distance. When the layout carries a StringGuide, a turbine on it moves only along it, so it stays on the string. Turbines off the guide, such as those a Mixture takes from another source, move freely. minimum_spacing in meters rejects moves that come closer to another turbine, and each turbine gets max_attempts draws.

Parameters:
  • relative_standard_deviation (float)

  • minimum_spacing (float | None)

  • seed (int | np.random.Generator | None)

  • max_attempts (int)

apply(layout, rng, seed)[source]#

Return a new farm with Gaussian noise added to every position.

The noise has relative_standard_deviation times the median nearest-neighbor spacing. Turbines move in order, each trying up to max_attempts draws against the current positions of the others. A turbine with no boundary- and spacing-feasible candidate stays put. A turbine never leaves the piece of the boundary it starts in. Raises ValueError when the input already violates minimum_spacing.

Parameters:
Return type:

Farm

class oowl.modifiers.Relaxation(*, method='repulsion', length_scale=None, max_iterations=100, step=0.1, tolerance=0.001, max_displacement=None, minimum_spacing=None, seed=None)[source]#

Relax an existing layout inside its boundary.

method="repulsion" uses the all-pairs potential

\[E_{ij}=\frac{h^2}{d_{ij}^2+\epsilon^2}.\]

Every pair pushes apart, so the layout expands into the available geometry. method="gaussian" uses the shorter-range potential

\[E_{ij}=\exp(-d_{ij}^2/h^2),\]

plus a penalty for pairs closer than length_scale and the centroid energy

\[E_c=\frac{\|\bar p-c_B\|^2}{2h^2},\]

where c_B is the boundary centroid. Steps follow the per-turbine normalized negative gradient and accept only energy decreases. A step that would leave the boundary slides to the nearest point of its edge, unless that crosses a hole or a gap between parts, where it stops at the crossing instead. Turbine count and ordering are unchanged. The repulsion energy visits every turbine pair in cache-sized blocks, so its time and memory grow quadratically with the turbine count. The Gaussian energy only visits pairs within four length scales, as farther pairs hold less than exp(-16) of it, and gradients are only computed for accepted steps.

Parameters:
  • method (Literal['repulsion', 'gaussian']) – "repulsion" for inverse-square spreading, "gaussian" for local pair repulsion.

  • length_scale (float | None) – The length h of the potentials and the compact-penalty threshold, in meters. Defaults to the square root of boundary area per turbine.

  • max_iterations (int) – Maximum number of energy-descent steps.

  • step (float) – Maximum movement per iteration as a fraction of length_scale.

  • tolerance (float) – Stop once the energy falls by at most this fraction over five accepted steps.

  • max_displacement (float | None) – Optional maximum total movement from each initial position, in meters. None leaves displacement unconstrained.

  • minimum_spacing (float | None) – Optional distance in meters no step brings a pair closer than. The moves of a pair that would come closer are halved until it does not. A layout filled at a minimum spacing holds its relaxation to it.

  • seed (int | np.random.Generator | None) – Seed used only to separate exactly coincident positions.

apply(layout, rng, seed)[source]#

Return a new farm with positions relaxed inside the boundary.

rng only separates exactly coincident positions. Records the energies, largest displacement and convergence in metadata["modifiers"]. The string guide is dropped when any turbine moves. Raises ValueError without a boundary, when a position starts outside it, or when a pair starts closer than minimum_spacing.

Parameters:
Return type:

Farm

class oowl.modifiers.Setback(distance)[source]#

Hold turbines a fixed distance inside the boundary.

The distance is in meters and stays so when the boundary is resized.

Set the standoff distance from every boundary edge, in meters.

The margin applies to the outer ring and to the rim of any hole.

Parameters:

distance (float)

apply(geometry)[source]#

Return geometry eroded by at least distance on every edge.

The result may be a multipolygon. Raises ValueError when the setback consumes the whole site. The buffer radius is divided by the half-angle cosine so GEOS chords lie outside the true arc. The margin is then met and exceeded by at most about 0.12 percent.

Parameters:

geometry (BaseGeometry)

Return type:

BaseGeometry

class oowl.modifiers.StringModifier[source]#

Transform ordered logical strings without mutating the source guide.

abstractmethod apply(strings)[source]#

Return new logical strings, leaving strings untouched.

Parameters:

strings (tuple[_LogicalString, ...])

Return type:

tuple[_LogicalString, …]

class oowl.modifiers.TurbineRemoval(n_remove=None, *, relative_n_remove=None, method='random', transition_penalty=None, row_axis=0, seed=None)[source]#

Remove turbines randomly or in correlated grid rows.

n_remove removes that many turbines, and a layout given n_turbines places them on top, so exactly n_turbines remain. relative_n_remove in [0, 1) removes that share of the turbines the layout places, rounded down, so at least one always remains however small a sampled farm turns out. A layout given n_turbines then places the fewest turbines that leave exactly that many. Give one of the two. method="random" removes a uniform random subset. method="markov" needs a Grid layout and removes runs of neighbors along its rows, which follow the first grid vector for row_axis=0 and the second for row_axis=1. A larger transition_penalty gives longer runs.

Parameters:
  • n_remove (int | None)

  • relative_n_remove (float | None)

  • method (Literal['random', 'markov'])

  • transition_penalty (float | None)

  • row_axis (int)

  • seed (int | np.random.Generator | None)

apply(layout, rng, seed)[source]#

Return a new farm with the turbines this removes dropped.

Removal is uniform for method="random" and row-correlated for method="markov". Records the seed, method and turbine counts in metadata["modifiers"] and updates metadata["achieved_n"]. Raises ValueError if n_remove would leave no turbine.

Parameters:
Return type:

Farm