πŸš€ Quick StartΒΆ

Concepts: Graph representations Β· Optimization approaches Network/Router API: πŸš€ Quick Start

Three ways to obtain a network, in increasing order of runtime and quality. The routers are described in Routers; the graphs passed between the calls (L, P, A, S, G) are described in Graph representations.

[1]:
from optiwindnet.converting import G_from_S
from optiwindnet.importer import load_repository
from optiwindnet.mesh import make_planar_embedding
from optiwindnet.pathfinding import PathFinder
from optiwindnet.svg import svgplot
[2]:
locations = load_repository()

Quickest (sub-second)ΒΆ

[3]:
from optiwindnet.heuristics import constructor
[4]:
L = locations.doggerA
[5]:
%%timeit
P, A = make_planar_embedding(L)
5.28 ms Β± 30.8 ΞΌs per loop (mean Β± std. dev. of 7 runs, 100 loops each)
[6]:
P, A = make_planar_embedding(L)
[7]:
%%timeit
S_pre = constructor(A, capacity=8)
6.85 ms Β± 171 ΞΌs per loop (mean Β± std. dev. of 7 runs, 100 loops each)
[8]:
S_pre = constructor(A, capacity=8)
[9]:
%%timeit
G_tentative = G_from_S(S_pre, A)
G_pre = PathFinder(G_tentative, planar=P, A=A).create_detours()
11.3 ms Β± 451 ΞΌs per loop (mean Β± std. dev. of 7 runs, 100 loops each)
[10]:
G_tentative = G_from_S(S_pre, A)
G_pre = PathFinder(G_tentative, planar=P, A=A).create_detours()
[11]:
svgplot(G_pre)
[11]:
../_images/notebooks_lo00_quickstart_14_0.svg

Quick (radial only, a second or two)ΒΆ

[12]:
from optiwindnet.baselines.hgs import hgs_cvrp
from optiwindnet.transforming import as_normalized
[13]:
L = locations.doggerA
P, A = make_planar_embedding(L)
S_hgs = hgs_cvrp(as_normalized(A), capacity=8, time_limit=2, seed=1234567)
G_tentative = G_from_S(S_hgs, A)
G_hgs = PathFinder(G_tentative, planar=P, A=A).create_detours()
svgplot(G_hgs)
[13]:
../_images/notebooks_lo00_quickstart_17_0.svg

With quality assurance (a few minutes)ΒΆ

[14]:
from optiwindnet.constraints import NetworkConstraints
from optiwindnet.MILP import solver_factory
[15]:
solver = solver_factory('ortools.cp_sat')
[16]:
help(NetworkConstraints)
Help on class NetworkConstraints in module optiwindnet.constraints:

class NetworkConstraints(builtins.object)
 |  NetworkConstraints(
 |      topology: Topology | str = <Topology.BRANCHED: 'branched'>,
 |      feeder_route: FeederRoute | str = <FeederRoute.SEGMENTED: 'segmented'>,
 |      feeders: int | str | None = None,
 |      max_feeders: int | str | None = None,
 |      substation_feeders: int | Sequence[int | None] | None = None,
 |      max_substation_feeders: int | Sequence[int | None] | None = None,
 |      max_substation_loads: float | Fraction | Sequence[float | Fraction | None] | None = None,
 |      balanced: bool = False,
 |      max_link_length: float | None = None
 |  ) -> None
 |
 |  Constraints on the electrical network of a wind farm.
 |
 |  ``topology`` and ``feeder_route`` limit what a solution may be: a radial
 |  network satisfies ``'branched'``, and straight feeders satisfy
 |  ``'segmented'``.
 |
 |  Feeder counts are in feeders, i.e. substation connections (a ring has two).
 |  The whole-farm count is either exact (``feeders``) or an upper bound
 |  (``max_feeders``), given as an integer or relative to the minimum:
 |  ``'min'`` or ``'min+k'`` (``k`` extra feeders). The minimum is the capacity
 |  bound ``ceil(total load / capacity)`` (in subtrees or rings), which
 |  indivisible turbine power or substation limits may make unattainable;
 |  relative counts are rejected if turbine power is unequal.
 |
 |  Per-substation counts are also exact (``substation_feeders``) or upper
 |  bounds (``max_substation_feeders``). Each is one integer for every
 |  substation or a sequence with one entry per substation, from ``-R`` to ``-1``
 |  (``None`` entries leave a substation free). A substation may have one or the
 |  other, not both.
 |
 |  ``max_substation_loads`` limits the power each substation collects, in the
 |  unit of the turbine power (or in turbines, if no power is given).
 |
 |  ``balanced`` makes the loads of all subtrees (or rings) of the network
 |  differ at most by one unit of the turbine power quantization. It requires
 |  the feeder count to be fixed, e.g. ``feeders='min'``.
 |
 |  ``max_link_length`` caps the length of each individual cable section
 |  (feeders included), in the unit of the coordinates and measured as the
 |  routers measure: a link that follows a contour around an obstacle is capped
 |  along that contour. It is enforced through the search space: the
 |  available-links graph ``A`` must have been limited by
 |  :func:`~optiwindnet.interarraylib.limit_link_length` (which
 |  :class:`~optiwindnet.api.WindFarmNetwork` does), and :meth:`resolve` checks
 |  that it was. Detours added after the solve may still stretch a feeder past
 |  the limit (see :func:`~optiwindnet.interarraylib.links_over_length`).
 |
 |  Args:
 |    topology: ``'radial'``, ``'branched'`` or ``'ringed'``.
 |    feeder_route: ``'segmented'`` or ``'straight'``.
 |    feeders: exact number of feeders of the wind farm.
 |    max_feeders: maximum number of feeders of the wind farm.
 |    substation_feeders: exact number of feeders of each substation.
 |    max_substation_feeders: maximum number of feeders of each substation.
 |    max_substation_loads: maximum load of each substation.
 |    balanced: whether the subtree (or ring) loads must be balanced.
 |    max_link_length: maximum length of a single cable section.
 |
 |  Raises:
 |    ValueError: a value has the wrong form, or two constraints exclude each
 |      other regardless of the problem instance.
 |
 |  Example::
 |
 |    NetworkConstraints(max_substation_feeders=6, max_feeders='min+1')
 |
 |  Methods defined here:
 |
 |  __delattr__(self, name)
 |      Implement delattr(self, name).
 |
 |  __eq__(self, other)
 |      Return self==value.
 |
 |  __hash__(self)
 |      Return hash(self).
 |
 |  __init__(
 |      self,
 |      topology: Topology | str = <Topology.BRANCHED: 'branched'>,
 |      feeder_route: FeederRoute | str = <FeederRoute.SEGMENTED: 'segmented'>,
 |      feeders: int | str | None = None,
 |      max_feeders: int | str | None = None,
 |      substation_feeders: int | Sequence[int | None] | None = None,
 |      max_substation_feeders: int | Sequence[int | None] | None = None,
 |      max_substation_loads: float | Fraction | Sequence[float | Fraction | None] | None = None,
 |      balanced: bool = False,
 |      max_link_length: float | None = None
 |  ) -> None
 |      Initialize self.  See help(type(self)) for accurate signature.
 |
 |  __replace__ = _replace(self, /, **changes) from dataclasses
 |
 |  __repr__(self)
 |      Return repr(self).
 |
 |  __setattr__(self, name, value)
 |      Implement setattr(self, name, value).
 |
 |  resolve(
 |      self,
 |      A: nx.Graph,
 |      *,
 |      capacity: int | None = None,
 |      capacity_nominal: float | Fraction | None = None,
 |      power_rtol: float = 0.01
 |  ) -> NetworkBounds
 |      Check the constraints against a problem instance and bound it.
 |
 |      The instance is ``A`` (a location or available-links graph) at a cable
 |      capacity, given as for the routers' producers: ``capacity`` in inflow,
 |      or ``capacity_nominal`` in the unit of the turbine power, which is then
 |      quantized (see :func:`~optiwindnet.loads.quantized`). The bounds are
 |      those that :func:`~optiwindnet.baselines.hgs.hgs_cvrp` and
 |      :meth:`Solver.set_problem() <optiwindnet.MILP.Solver.set_problem>` derive
 |      for the same arguments.
 |
 |      The per-substation counts must be compatible with the whole-farm count:
 |      their sums must leave a count within the whole-farm bounds, which are
 |      narrowed to it. The substations must also have room for the total load,
 |      given their feeders (each carrying up to the capacity) and their load
 |      limits.
 |
 |      Args:
 |        A: location or available-links graph.
 |        capacity: cable capacity, in inflow (per arm, for rings).
 |        capacity_nominal: cable capacity, in the unit of the turbine power,
 |          instead of ``capacity``.
 |        power_rtol: quantization tolerance of unequal turbine power.
 |
 |      Returns:
 |        The bounds the routers enforce.
 |
 |      Raises:
 |        ValueError: the constraints do not suit the instance, or cannot all
 |          be met; or the capacity does not suit the power ``A`` declares; or
 |          ``A`` is not limited to ``max_link_length``.
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables
 |
 |  __weakref__
 |      list of weak references to the object
 |
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |
 |  __dataclass_fields__ = {'balanced': Field(name='balanced',type=<class ...
 |
 |  __dataclass_params__ = _DataclassParams(init=False,repr=True,eq=True,o...
 |
 |  __match_args__ = ('topology', 'feeder_route', 'feeders', 'max_feeders'...

[17]:
solver.set_problem(
    P,
    A,
    capacity=S_hgs.graph['capacity'],
    constraints=NetworkConstraints(topology='branched', feeder_route='segmented'),
    warmstart=S_hgs,
)
[18]:
solver.solve(
    time_limit=40,
    mip_gap=0.005,
    options={'random_seed': 1234567},
)
[18]:
SolutionInfo(
    termination = 'FEASIBLE'
    objective   = 240715.16
    bound       = 230016.76
    relgap      = 4.44%
    runtime     = 40.05s
    topology_id = '3746edba793ed82e09d72c12f3c2954e'
)
[19]:
S, G = solver.get_solution()
svgplot(G)
[19]:
../_images/notebooks_lo00_quickstart_24_0.svg