optiwindnet.loads

Cable-load computation over solution topologies and routesets.

Module Contents

optiwindnet.loads.quantize_for_capacity(powers_set: collections.abc.Iterable[float | fractions.Fraction], capacity_nominal: float | fractions.Fraction, power_rtol: float = DEFAULT_POWER_RTOL) → tuple[dict[fractions.Fraction, int], fractions.Fraction, int][source]

Quantize turbine power while preserving cable-packing feasibility.

The capacity constrains power_per_inflow beyond accuracy: among the mappings that keep every power within power_rtol, this takes the first in order from coarse to fine that preserves packing. It selects the least-denominator rational nearest the least-squares fit within that mapping’s feasible interval. A cable then admits the same sets of turbines in inflow as in nominal power, and the routed network is the one the nominal powers define.

The exact power_per_inflow preserves packing because every achievable load is a whole multiple of it. It guarantees a candidate when its inflows are within the solver limit and the integer capacity is positive.

Only the distinct powers matter, not how many turbines share each. Results are memoized.

Parameters:
  • powers_set – distinct turbine powers, in nominal units.

  • capacity_nominal – cable capacity, in the same nominal units.

  • power_rtol – most per-turbine deviation accepted from a larger power_per_inflow.

Returns:

Inflow of each distinct power, nominal power per inflow, and the capacity in whole inflow.

Raises:

ValueError – the inputs are not positive finite numbers, power_rtol is not in [0, 1), or no power_per_inflow preserves the packing at an inflow a MILP can carry.

optiwindnet.loads.quantized(A: networkx.Graph, *, capacity: int | None = None, capacity_nominal: float | fractions.Fraction | None = None, power_rtol: float = DEFAULT_POWER_RTOL) → tuple[networkx.Graph, int, dict[str, Any]][source]

Validate the power of A and quantize it for a solve.

Exactly one of capacity and capacity_nominal is given. capacity is in inflow and requires A to declare no unequal power. capacity_nominal is in the unit of the power A declares; unequal powers are quantized for it (see quantize_for_capacity()) on a copy of A whose terminals get 'inflow'.

Parameters:
  • A – location or available-links graph.

  • capacity – cable capacity in inflow.

  • capacity_nominal – cable capacity in nominal power.

  • power_rtol – quantization tolerance, used with capacity_nominal.

Returns:

The graph to solve over, the capacity in inflow, and the graph attributes that record the quantization on the solution.

Raises:

ValueError – both or neither capacity is given, the capacity does not suit the power A declares, or the quantization fails.

optiwindnet.loads.total_inflow(G: networkx.Graph) → int[source]

Return total terminal power injection in inflow integer units.

optiwindnet.loads.nonunit_inflow(G: networkx.Graph) → dict[int, int][source]

Return terminal injections differing from one inflow, keyed by node.

optiwindnet.loads.validate_terminal_power(G: networkx.Graph) → None[source]

Validate terminal power, normalizing declared values to fractions.

'power_per_inflow' is a positive rational and terminal 'inflow' a positive integer, one by default. With 'powers_set' (at least two distinct powers), every terminal has a 'power' listed there. A graph with 'powers_set' but no 'power_per_inflow' declares unequal power, and its terminals have no 'inflow'. Otherwise, a terminal’s 'power' must quantize to its inflow (their ratio to 'power_per_inflow' rounds half up to it), and the graph attribute 'power_quantization_inexact' is refreshed.

Raises:

ValueError – any of these conventions is violated.

optiwindnet.loads.set_terminal_power(L: networkx.Graph, powers: collections.abc.Sequence[float | fractions.Fraction] | numpy.ndarray, power_unit: str | None = None) → None[source]

Set the nominal power of each terminal, replacing any power attributes.

Equal powers become the graph attribute 'power_per_inflow'; unequal ones become terminal 'power' and the graph attribute 'powers_set'.

Parameters:
  • L – location or available-links graph, changed in place.

  • powers – nominal power of each terminal; floats are rationalized.

  • power_unit – physical unit of the powers, e.g. 'MW'.

Raises:

ValueError – powers are not T positive finite numbers.

optiwindnet.loads.bfs_subtree_loads(G, parent, children, subtree, visited=None)[source]

Descend the subtree, updating edge and node attributes.

Meant to be called by calcload(), but can be used independently (e.g. from PathFinder). Nodes must not have a 'load' attribute.

Parameters:
  • G – graph to traverse.

  • parent – node the traversal descends from.

  • children – nodes of G to descend into.

  • subtree – subtree id to assign to every node visited.

  • visited – nodes already claimed by this traversal; pass one set across several calls to keep them from claiming a node twice. A fresh set is used when omitted.

Returns:

Load of parent after accumulating its descendants’ loads, including its own inflow if it is a terminal.

Raises:

ValueError – a node is reached twice, so the traversal is not descending a tree – G holds a cycle, or two roots reach the same node.

optiwindnet.loads.split_rings_and_calc_loads(S: networkx.Graph, A: networkx.Graph) → None[source]

Close path-form ring arms into canonical rings and compute their loads.

Only the ringed builders (HGS, LKH and the method='ringed' constructor) call this, on a solution S that is still a set of simple root → … → root paths missing their zero-load links. Each path is walked and closed into a canonical ring (see _add_ring_to_S()), using A to pick the longer zero-load link on odd-length rings; a tail already touching a root bridges two roots (r1, r2). Every ring receives exactly one zero-load link (load=0, no current flows through it), and each node’s subtree id and load, the edges’ loads, and the graph’s max_load / has_loads / root loads are set.

All ringed solvers must call this before returning a solution, so that every ringed S carries exactly one load=0 link per ring.

optiwindnet.loads.calcload(G: networkx.Graph, *, nominal: bool = False) → None[source]

Calculate link loads and update edge and node attributes of G.

G must already be in final form (a forest, or a ring-form graph whose load=0 zero-load links are present). A breadth-first traversal of each root’s subtree propagates the loads, treating load=0 links (ring zero-load links) as breaks. Each node’s subtree id and outgoing load land on its 'subtree' / 'load' attributes. Edge loads, root loads, and the graph’s 'max_load' and 'has_loads' attributes are set.

With nominal, only 'load_nominal' is updated on nodes and edges; integer loads, subtree ids, edge directions, and graph metadata are retained. Ring breaks are always identified by 'load' == 0.

The traversal must reach all T terminals, regardless of their contribution. Ring construction — closing path-form arms into rings — lives in split_rings_and_calc_loads(), which the ringed builders call instead.

Parameters:
  • G – Forest or ring-form graph to update.

  • nominal – Accumulate declared terminal 'power' into 'load_nominal' instead of integer inflow into 'load'. Terminals without a declaration contribute their inflow times 'power_per_inflow'. Only worth the traversal when quantization is inexact: exact quantization makes nominal loads 'load' times 'power_per_inflow'.