π 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]:
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]:
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]: