WindFarmNetwork/Router

This notebook is a practical guide to OptiWindNet’s Network/Router API (i.e. high-level API). It covers creating a wind farm model, using different routers (EWRouter, HGSRouter, MILPRouter), and applying key WindFarmNetwork functionalities. Specifically, we will:

  • Explore the WindFarmNetwork class

  • Compare available routers and their use cases

  • Run a complete optimization example

✅ WindFarmNetwork

The WindFarmNetwork class is the central user-facing component in the OptiWindNet Network/Router-API. It is flexible and extensible, supporting multiple input formats and routers (electrical network optimizers), and is used to model and optimize the electrical network of a wind farm.

To create a WindFarmNetwork instance:

Required:

  • Turbine and substation coordinates (see Data Input for formats)

  • Cable data (capacities and costs, or at least maximum capacity as a single number)

Optional:

  • Borders and obstacles: to add spatial constraints

  • Router: optimization strategy (defaults to EWRouter if not specified)

  • verbose: log/hide logging messages (default to False).

Key Responsibilities

Feature

Description

Initialization & Parsing

Accepts turbine/substation coordinates or the Location geometry L (Can be constructed from YAML, PBF, or WindIO formats for integration with real-world datasets), validates cable data, and constructs the internal graph structure.

Optimization

Interfaces smoothly with different routers (EWRouter, HGSRouter, MILPRouter) to optimize the network (cable routing) for cost and cable length.

Visualization

Provides plotting functions for location geometry, links, mesh, and the optimized network.

Gradient & Update Graph

Computes gradients for optimization and allows updating the electrical network using a compact “terse link” format.

Example Workflow

# Initialize with coordinates and cable types
wfn = WindFarmNetwork(
    cables=[(2, 1500.0), (5, 1800.0)],
    turbinesC=...,
    substationsC=...,
)

# Optimize electrical network using the default router (EWRouter)
wfn.optimize()

# Access total cost or cable length
total_cost = wfn.cost()
total_length = wfn.length()

# Visualize network
wfn.plot() # or simply wfn if you are running on notebooks

🧭 Router

In OptiWindNet, a Router is used to compute the optimal network (cable routing) of the wind farm’s electrical network. Given turbine and substation positions (layout), available cable options, and routing constraints, a router determines which turbines connect to which substations and how cables should be laid to minimize length (and consequently cost).

Currently Available Routers

The routers currently included in the optiwindnet.api module are:

  • EWRouter

    • Fastest option — completes in a fraction of a second

    • Great for quick network generation

    • Produces only branched-topology solutions

    • Heuristic method (EW = modified Esau-Williams) — solutions may be far from the optimum

  • HGSRouter

    • Still fast — can provide high-quality solutions in 0.5–2 seconds

    • Produces only radial-topology solutions (no branching)

    • A maximum number of feeders can be enforced

    • Meta-heuristic method (HGS = Hybrid Genetic Search)

  • MILPRouter

    • Delivers solutions with quality guarantees (a bound on the distance from the optimum)

    • May take a few to several minutes depending on the problem size and quality required

    • Full set of model options to choose from

    • Should be used when optimality is more important than speed

    • MILP = Mixed Integer Linear Programming

Run an example

In this section we will:

  • Create a simple wind farm network

  • Use heuristic and MILP optimization

  • Explore key methods such as:

    • .optimize()

    • .plot()

    • .cost(), .length()

    • .terse_links(), .update_from_terse_links()

    • .gradient()

    • .get_network()

    • .add_buffer()

Create a WindFarmNetwork instance

Import required modules

[1]:
import numpy as np
from optiwindnet.api import WindFarmNetwork, EWRouter, MILPRouter, ModelOptions
[2]:
# Display figures as SVG in Jupyter notebooks
%config InlineBackend.figure_formats = ['svg']

Load location data

OptiWindNet operates on location geometries, which define turbine and substation positions (plus optional borders/obstacles).

You can load a location from:

  • .yaml or .osm.pbf files;

  • The included locations repository;

  • Your own coordinate arrays.

For more details see Data Input.

We start with a simple geometry (5 turbines and 1 substation), and define a simple set of cables.

[3]:
wfn = WindFarmNetwork(
    cables=[(2, 1500.0), (5, 1800.0)],
    turbinesC=np.array([[0, 0], [1, 1], [2, 0], [3, 1], [4, 0]]),
    substationsC=np.array([[2, -2]]),
    borderC=np.array([[0, -3], [0, 2], [4, 2.1], [5, 1], [4, -3]]),
    obstacleC_=[np.array([[0.2, -2.5], [1.5, -2.5], [0.2, -1]])]
)
Plot location

Note: Many of the Jupyter notebooks provided include SVG figures as output. To ensure these visuals are displayed correctly in JupyterLab or Jupyter Notebook, make sure the notebook is marked as trusted. In JupyterLab, you can do this by pressing Ctrl + Shift + C and selecting Trust Notebook.

[4]:
wfn
[4]:
../_images/notebooks_a02_WindFarmNetwork_23_0.svg

Method: optimize(router=...)

This is the main method that optimizes the electrical network using the given router (heuristic, metaheuristic or MILP). > Note that the router could be passed directly to WindFarmNetwork(router=...).

Use a heuristic router (Esau-Williams)

[5]:
wfn.optimize(router=EWRouter())
[5]:
array([ 1,  2, -1,  2,  3])

Plot the result

[6]:
wfn
[6]:
../_images/notebooks_a02_WindFarmNetwork_29_0.svg

Use a MILP router with full control over topology and feeders

The solution from the previous call to wfn.optimize() is stored within the wfn object. If this solution is feasible under the current ``MILPRouter`` settings, it will be automatically used as a warm start. Otherwise, if it does not meet the current ModelOptions constraints, it will be ignored, and the MILP solver will start from scratch.

[7]:
model_opts = ModelOptions(
    topology='radial',
    feeder_limit='minimum',
    feeder_route='straight',
)
wfn.optimize(
    router=MILPRouter(
        solver_name='ortools.cp_sat',
        time_limit=2,
        mip_gap=0.01,
        model_options=model_opts
    ),
    verbose=True,
)
wfn
IntegerBoundsPreprocessor                              85 rows, 46 columns, 267 entries with magnitude in [1.000000e+00, 5.000000e+00]
BoundPropagationPreprocessor                           85 rows, 46 columns, 267 entries with magnitude in [1.000000e+00, 5.000000e+00]
ImpliedIntegerPreprocessor                             85 rows, 46 columns, 267 entries with magnitude in [1.000000e+00, 5.000000e+00]
IntegerBoundsPreprocessor                              85 rows, 46 columns, 267 entries with magnitude in [1.000000e+00, 5.000000e+00]
ReduceCostOverExclusiveOrConstraintPreprocessor        85 rows, 46 columns, 267 entries with magnitude in [1.000000e+00, 5.000000e+00]

Scaling to pure integer problem.
Num integers: 46/46 (implied: 0 in_inequalities: 0 max_scaling: 0) [IP]
Maximum constraint coefficient relative error: 0
Maximum constraint worst-case activity error: 0
Constraint scaling factor range: [1, 1]

Starting CP-SAT solver v9.15.6755
Parameters: max_time_in_seconds: 2 log_search_progress: true catch_sigint_signal: false relative_gap_limit: 0.01
Setting number of workers to 16

Initial optimization model 'optiwindnet': (model_fingerprint: 0x625dc01df5e87447)
#Variables: 46 (#bools: 18 in floating point objective) (35 primary variables)
  - 23 Booleans in [0,1]
  - 18 in [0,4]
  - 5 in [0,5]
#kLinear2: 55
#kLinear3: 8
#kLinearN: 22 (#terms: 133)

Starting presolve at 0.00s
The solution hint is complete, but it is infeasible! we will try to repair it.
[Scaling] Floating point objective has 18 terms with magnitude in [0.251936, 2.33385] average = 1.20522
[Scaling] Objective coefficient relative error: 5.27705e-05
[Scaling] Objective worst-case absolute error: 9.19728e-05
[Scaling] Objective scaling factor: 32768
  2.25e-05s  0.00e+00d  [DetectDominanceRelations]
  3.18e-04s  0.00e+00d  [PresolveToFixPoint] #num_loops=2 #num_dual_strengthening=1
  5.25e-06s  0.00e+00d  [ExtractEncodingFromLinear] #potential_supersets=18
  8.41e-06s  0.00e+00d  [DetectDuplicateColumns]
  1.24e-05s  0.00e+00d  [DetectDuplicateConstraints]
[Symmetry] Graph for symmetry has 186 nodes and 331 arcs.
[Symmetry] Symmetry computation done. time: 4.2877e-05 dtime: 5.163e-05
[SAT presolve] num removable Booleans: 0 / 23
[SAT presolve] num trivial clauses: 0
[SAT presolve] [0s] clauses:9 literals:18 vars:18 one_side_vars:18 simple_definition:0 singleton_clauses:0
[SAT presolve] [4.88e-06s] clauses:9 literals:18 vars:18 one_side_vars:18 simple_definition:0 singleton_clauses:0
[SAT presolve] [7.559e-06s] clauses:9 literals:18 vars:18 one_side_vars:18 simple_definition:0 singleton_clauses:0
  2.66e-05s  0.00e+00d  [DetectDuplicateConstraintsWithDifferentEnforcements]
  6.29e-04s  1.48e-04d  [Probe] #probed=86 #fixed_bools=2 #new_bounds=9 #new_binary_clauses=27
  3.27e-05s  1.54e-05d  [MaxClique] Merged 21 constraints with 60 literals into 15 constraints with 48 literals
  2.47e-05s  0.00e+00d  [DetectDominanceRelations]
  2.91e-04s  0.00e+00d  [PresolveToFixPoint] #num_loops=2 #num_dual_strengthening=1
  5.47e-05s  0.00e+00d  [ProcessAtMostOneAndLinear] #num_changes=14
  2.03e-05s  0.00e+00d  [DetectDuplicateConstraints]
  1.33e-05s  0.00e+00d  [DetectDuplicateConstraintsWithDifferentEnforcements]
  2.00e-05s  3.23e-07d  [DetectDominatedLinearConstraints] #relevant_constraints=11 #num_inclusions=5
  3.02e-06s  0.00e+00d  [DetectDifferentVariables]
  3.35e-05s  7.62e-07d  [ProcessSetPPC] #relevant_constraints=23 #num_inclusions=18
  2.88e-05s  0.00e+00d  [TransformClausesToExactlyOne] #num_amos=27
  2.96e-05s  0.00e+00d  [DetectEncodedComplexDomains]
  4.58e-06s  0.00e+00d  [FindAlmostIdenticalLinearConstraints]
  2.63e-05s  3.93e-06d  [FindBigAtMostOneAndLinearOverlap]
  6.48e-06s  3.13e-06d  [FindBigVerticalLinearOverlap]
  3.08e-06s  1.95e-07d  [FindBigHorizontalLinearOverlap] #linears=5
  1.76e-06s  0.00e+00d  [MergeClauses]
  2.12e-05s  0.00e+00d  [DetectDominanceRelations]
  2.16e-04s  0.00e+00d  [PresolveToFixPoint] #num_loops=2 #num_dual_strengthening=1
  1.67e-05s  0.00e+00d  [DetectDominanceRelations]
  1.10e-04s  0.00e+00d  [PresolveToFixPoint] #num_loops=1 #num_dual_strengthening=1
  5.61e-06s  0.00e+00d  [DetectDuplicateColumns]
  1.39e-05s  0.00e+00d  [DetectDuplicateConstraints] #duplicates=1
[Symmetry] Graph for symmetry has 156 nodes and 236 arcs.
[Symmetry] Symmetry computation done. time: 6.3732e-05 dtime: 6.161e-05
[Symmetry] #generators: 1, average support size: 38
[Symmetry] 19 orbits on 38 variables with sizes: 2,2,2,2,2,2,2,2,2,2,...
[Symmetry] Num fixable by intersecting at_most_one with orbits: 2 largest_orbit: 2
[SAT presolve] num removable Booleans: 0 / 21
[SAT presolve] num trivial clauses: 0
[SAT presolve] [0s] clauses:4 literals:8 vars:8 one_side_vars:8 simple_definition:0 singleton_clauses:0
[SAT presolve] [3.952e-06s] clauses:4 literals:8 vars:8 one_side_vars:8 simple_definition:0 singleton_clauses:0
[SAT presolve] [6.582e-06s] clauses:4 literals:8 vars:8 one_side_vars:8 simple_definition:0 singleton_clauses:0
  2.52e-05s  0.00e+00d  [DetectDuplicateConstraintsWithDifferentEnforcements]
  2.93e-04s  3.72e-05d  [Probe] #probed=51 #fixed_bools=1 #new_bounds=6 #new_binary_clauses=13
  1.95e-05s  6.31e-06d  [MaxClique]
  1.99e-05s  0.00e+00d  [DetectDominanceRelations]
  1.54e-04s  0.00e+00d  [PresolveToFixPoint] #num_loops=2 #num_dual_strengthening=1
  1.79e-05s  0.00e+00d  [ProcessAtMostOneAndLinear]
  1.41e-05s  0.00e+00d  [DetectDuplicateConstraints]
  1.04e-05s  0.00e+00d  [DetectDuplicateConstraintsWithDifferentEnforcements]
  1.65e-05s  2.17e-07d  [DetectDominatedLinearConstraints] #relevant_constraints=10 #num_inclusions=5
  2.20e-06s  0.00e+00d  [DetectDifferentVariables]
  1.11e-05s  1.85e-07d  [ProcessSetPPC] #relevant_constraints=18
  1.66e-05s  0.00e+00d  [TransformClausesToExactlyOne] #num_amos=12
  1.63e-05s  0.00e+00d  [DetectEncodedComplexDomains]
  2.79e-06s  0.00e+00d  [FindAlmostIdenticalLinearConstraints]
  8.55e-06s  2.98e-06d  [FindBigAtMostOneAndLinearOverlap]
  4.94e-06s  2.17e-06d  [FindBigVerticalLinearOverlap]
  2.35e-06s  1.65e-07d  [FindBigHorizontalLinearOverlap] #linears=5
  9.61e-07s  0.00e+00d  [MergeClauses]
  1.98e-05s  0.00e+00d  [DetectDominanceRelations]
  1.11e-04s  0.00e+00d  [PresolveToFixPoint] #num_loops=1 #num_dual_strengthening=1
  1.51e-05s  0.00e+00d  [DetectDominanceRelations]
  9.47e-05s  0.00e+00d  [PresolveToFixPoint] #num_loops=1 #num_dual_strengthening=1
  4.66e-06s  0.00e+00d  [DetectDuplicateColumns]
  1.14e-05s  0.00e+00d  [DetectDuplicateConstraints]
[Symmetry] Graph for symmetry has 137 nodes and 189 arcs.
[Symmetry] Symmetry computation done. time: 2.4193e-05 dtime: 2.144e-05
[SAT presolve] num removable Booleans: 0 / 18
[SAT presolve] num trivial clauses: 0
[SAT presolve] [0s] clauses:6 literals:12 vars:9 one_side_vars:9 simple_definition:0 singleton_clauses:0
[SAT presolve] [3.668e-06s] clauses:6 literals:12 vars:9 one_side_vars:9 simple_definition:0 singleton_clauses:0
[SAT presolve] [2.5755e-05s] clauses:6 literals:12 vars:9 one_side_vars:9 simple_definition:0 singleton_clauses:0
  1.63e-05s  0.00e+00d  [DetectDuplicateConstraintsWithDifferentEnforcements]
  5.20e-04s  3.27e-05d  [Probe] #probed=50 #new_binary_clauses=14
  2.07e-05s  7.83e-06d  [MaxClique] Merged 12 constraints with 32 literals into 11 constraints with 30 literals
  1.85e-05s  0.00e+00d  [DetectDominanceRelations]
  1.13e-04s  0.00e+00d  [PresolveToFixPoint] #num_loops=1 #num_dual_strengthening=1
  1.84e-05s  0.00e+00d  [ProcessAtMostOneAndLinear]
  1.19e-05s  0.00e+00d  [DetectDuplicateConstraints]
  9.43e-06s  0.00e+00d  [DetectDuplicateConstraintsWithDifferentEnforcements]
  1.50e-05s  2.17e-07d  [DetectDominatedLinearConstraints] #relevant_constraints=10 #num_inclusions=5
  2.23e-06s  0.00e+00d  [DetectDifferentVariables]
  1.03e-05s  1.76e-07d  [ProcessSetPPC] #relevant_constraints=17
  1.53e-05s  0.00e+00d  [TransformClausesToExactlyOne] #num_amos=11
  1.58e-05s  0.00e+00d  [DetectEncodedComplexDomains]
  2.64e-06s  0.00e+00d  [FindAlmostIdenticalLinearConstraints]
  8.65e-06s  3.00e-06d  [FindBigAtMostOneAndLinearOverlap]
  3.95e-06s  2.16e-06d  [FindBigVerticalLinearOverlap]
  1.99e-06s  1.65e-07d  [FindBigHorizontalLinearOverlap] #linears=5
  8.15e-07s  0.00e+00d  [MergeClauses]
  1.52e-05s  0.00e+00d  [DetectDominanceRelations]
  9.35e-05s  0.00e+00d  [PresolveToFixPoint] #num_loops=1 #num_dual_strengthening=1
  8.38e-07s  0.00e+00d  [MergeNoOverlap]
  5.27e-07s  0.00e+00d  [MergeNoOverlap2D]
  1.39e-05s  0.00e+00d  [ExpandObjective] #entries=54 #tight_variables=21 #tight_constraints=6

Presolve summary:
  - 12 affine relations were detected.
  - rule 'TODO linear inclusion: superset is equality' was applied 15 times.
  - rule 'TODO linear2: convert ax + by != cte to clauses for large domains' was applied 120 times.
  - rule 'affine: new relation' was applied 12 times.
  - rule 'at_most_one: removed literals' was applied 10 times.
  - rule 'at_most_one: transformed into max clique' was applied 2 times.
  - rule 'bool_and: x => x' was applied 14 times.
  - rule 'bool_or: implications' was applied 16 times.
  - rule 'deductions: 52 stored' was applied 1 time.
  - rule 'duplicate: removed constraint' was applied 1 time.
  - rule 'enforcement: false literal' was applied 3 times.
  - rule 'enforcement: true literal' was applied 5 times.
  - rule 'exactly_one: removed literals' was applied 5 times.
  - rule 'exactly_one: simplified objective' was applied 2 times.
  - rule 'linear + amo: extracted enforcement literal' was applied 14 times.
  - rule 'linear1: transformed to implication' was applied 4 times.
  - rule 'linear2: contains a boolean' was applied 23 times.
  - rule 'linear2: convert ax + by != cte to clauses' was applied 16 times.
  - rule 'linear: divide by GCD' was applied 4 times.
  - rule 'linear: empty' was applied 11 times.
  - rule 'linear: enforcement literal in expression' was applied 4 times.
  - rule 'linear: fixed or dup variables' was applied 22 times.
  - rule 'linear: positive at most one' was applied 12 times.
  - rule 'linear: positive equal one' was applied 7 times.
  - rule 'linear: remapped using affine relations' was applied 19 times.
  - rule 'linear: simplified rhs' was applied 5 times.
  - rule 'new_bool: integer encoding' was applied 7 times.
  - rule 'objective: shifted cost with exactly ones' was applied 4 times.
  - rule 'presolve: 10 unused variables removed.' was applied 1 time.
  - rule 'presolve: iteration' was applied 3 times.
  - rule 'setppc: exactly_one included in linear' was applied 5 times.
  - rule 'setppc: reduced linear coefficients' was applied 4 times.
  - rule 'setppc: removed dominated constraints' was applied 2 times.
  - rule 'setppc: removed trivial linear constraint' was applied 1 time.
  - rule 'symmetry: fixed to false in general orbit' was applied 2 times.
  - rule 'variables with 2 values: new affine relation' was applied 3 times.
  - rule 'variables with 2 values: register other encoding' was applied 3 times.
  - rule 'variables: add encoding constraint' was applied 4 times.
  - rule 'variables: both boolean and its negation fix the same variable' was applied 2 times.
  - rule 'variables: detect fully reified value encoding' was applied 14 times.
  - rule 'variables: detect half reified value encoding' was applied 39 times.

Presolved optimization model 'optiwindnet': (model_fingerprint: 0x3bd35ec8d182bcae)
#Variables: 31 (#bools: 9 in objective) (21 primary variables)
  - 18 Booleans in [0,1]
  - 2 in [0][2,4]
  - 1 in [0,3]
  - 10 in [0,4]
#kAtMostOne: 5 (#literals: 18)
#kBoolAnd: 5 (#enforced: 5) (#literals: 11)
#kExactlyOne: 6 (#literals: 21)
#kLinear1: 26 (#enforced: 26)
#kLinear2: 2
#kLinear3: 1
#kLinearN: 7 (#terms: 41)
[Symmetry] Graph for symmetry has 114 nodes and 187 arcs.
[Symmetry] Symmetry computation done. time: 2.1219e-05 dtime: 2.148e-05

Preloading model.
#Bound   0.00s best:inf   next:[7.65682461,16.6584115] initial_domain
The solution hint is complete, but it is infeasible! we will try to repair it.
#Model   0.00s var:31/31 constraints:52/52

Starting search at 0.00s with 16 workers.
11 full problem subsolvers: [core, default_lp, lb_tree_search, max_lp, no_lp, objective_lb_search, probing, pseudo_costs, quick_restart, quick_restart_no_lp, reduced_costs]
5 first solution subsolvers: [fj(2), fs_random, fs_random_no_lp, fs_random_quick_restart_no_lp]
11 interleaved subsolvers: [feasibility_pump, graph_arc_lns, graph_cst_lns, graph_dec_lns, graph_var_lns, lb_relax_lns, ls, ls_lin, rins/rens, rnd_cst_lns, rnd_var_lns]
3 helper subsolvers: [neighborhood_helper, synchronization_agent, update_gap_integral]

#1       0.01s best:8.82839443 next:[7.65682461,8.82836392] core (fixed_bools=0/18)
#2       0.01s best:8.48525478 next:[7.65682461,8.48522427] no_lp [hint] (fixed_bools=3/18)
#Done    0.01s no_lp
#Done    0.01s no_lp
#Done    0.01s core

Task timing                                n [     min,      max]      avg      dev     time         n [     min,      max]      avg      dev    dtime
                           'core':         1 [  4.08ms,   4.08ms]   4.08ms   0.00ns   4.08ms         2 [ 37.12us,  78.36us]  57.74us  20.62us 115.48us
                     'default_lp':         1 [  4.00ms,   4.00ms]   4.00ms   0.00ns   4.00ms         1 [ 39.19us,  39.19us]  39.19us   0.00ns  39.19us
               'feasibility_pump':         1 [  1.22ms,   1.22ms]   1.22ms   0.00ns   1.22ms         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
                             'fj':         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
                             'fj':         1 [  1.20ms,   1.20ms]   1.20ms   0.00ns   1.20ms         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
                      'fs_random':         1 [  2.61ms,   2.61ms]   2.61ms   0.00ns   2.61ms         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
                'fs_random_no_lp':         1 [  2.76ms,   2.76ms]   2.76ms   0.00ns   2.76ms         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
  'fs_random_quick_restart_no_lp':         1 [  1.34ms,   1.34ms]   1.34ms   0.00ns   1.34ms         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
                  'graph_arc_lns':         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
                  'graph_cst_lns':         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
                  'graph_dec_lns':         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
                  'graph_var_lns':         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
                   'lb_relax_lns':         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
                 'lb_tree_search':         1 [  3.01ms,   3.01ms]   3.01ms   0.00ns   3.01ms         1 [114.07us, 114.07us] 114.07us   0.00ns 114.07us
                             'ls':         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
                         'ls_lin':         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
                         'max_lp':         1 [  3.61ms,   3.61ms]   3.61ms   0.00ns   3.61ms         1 [115.07us, 115.07us] 115.07us   0.00ns 115.07us
                          'no_lp':         1 [  2.66ms,   2.66ms]   2.66ms   0.00ns   2.66ms         1 [ 40.97us,  40.97us]  40.97us   0.00ns  40.97us
            'objective_lb_search':         1 [  2.76ms,   2.76ms]   2.76ms   0.00ns   2.76ms         1 [ 37.42us,  37.42us]  37.42us   0.00ns  37.42us
                        'probing':         1 [  2.98ms,   2.98ms]   2.98ms   0.00ns   2.98ms         1 [ 36.95us,  36.95us]  36.95us   0.00ns  36.95us
                   'pseudo_costs':         1 [  2.95ms,   2.95ms]   2.95ms   0.00ns   2.95ms         1 [ 37.57us,  37.57us]  37.57us   0.00ns  37.57us
                  'quick_restart':         1 [  3.44ms,   3.44ms]   3.44ms   0.00ns   3.44ms         1 [ 37.42us,  37.42us]  37.42us   0.00ns  37.42us
            'quick_restart_no_lp':         1 [  1.92ms,   1.92ms]   1.92ms   0.00ns   1.92ms         1 [ 37.13us,  37.13us]  37.13us   0.00ns  37.13us
                  'reduced_costs':         1 [  3.24ms,   3.24ms]   3.24ms   0.00ns   3.24ms         1 [ 37.57us,  37.57us]  37.57us   0.00ns  37.57us
                      'rins/rens':         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
                    'rnd_cst_lns':         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns
                    'rnd_var_lns':         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns         0 [  0.00ns,   0.00ns]   0.00ns   0.00ns   0.00ns

Search stats                        Bools  Conflicts  Branches  Restarts  BacktrackToRoot  Backtrack  BoolPropag  IntegerPropag
                           'core':     18          1       106         0               66         91         469            904
                     'default_lp':     18          0        36         0               36         36         149            288
                      'fs_random':     18          0         0         0                0          0           0              0
                'fs_random_no_lp':     18          0         0         0                0          0           0              0
  'fs_random_quick_restart_no_lp':     18          0         0         0                0          0           0              0
                 'lb_tree_search':     18          0        36         0               36         36         149            350
                         'max_lp':     18          0        36         0               36         36         149            350
                          'no_lp':     18          0        39         0               37         37         179            357
            'objective_lb_search':     18          0        36         0               36         36         149            288
                        'probing':     18          0        36         0               36         36         149            287
                   'pseudo_costs':     18          0        36         0               36         36         149            349
                  'quick_restart':     18          0        36         0               36         36         149            288
            'quick_restart_no_lp':     18          0        36         0               36         36         149            288
                  'reduced_costs':     18          0        36         0               36         36         149            349

SAT formula                         Fixed  Equiv  Total  VarLeft  BinaryClauses  PermanentClauses  TemporaryClauses
                           'core':     18      0     18        0             66                 8                 0
                     'default_lp':      0      0     18       18             48                 6                 0
                      'fs_random':      0      0     18       18              0                 0                 0
                'fs_random_no_lp':      0      0     18       18              0                 0                 0
  'fs_random_quick_restart_no_lp':      0      0     18       18              0                 0                 0
                 'lb_tree_search':      0      0     18       18             48                 6                 0
                         'max_lp':      0      0     18       18             48                 6                 0
                          'no_lp':     18      0     18        0             60                 6                 0
            'objective_lb_search':      0      0     18       18             48                 6                 0
                        'probing':      0      0     18       18             32                 6                 0
                   'pseudo_costs':      0      0     18       18             48                 6                 0
                  'quick_restart':      0      0     18       18             48                 6                 0
            'quick_restart_no_lp':      0      0     18       18             48                 6                 0
                  'reduced_costs':      0      0     18       18             48                 6                 0

SAT stats                           ClassicMinim  LitRemoved  LitRemovedBinary  LitLearned  LitForgotten  Subsumed
                           'core':             0           0                 1           3             0         0
                     'default_lp':             0           0                 0           0             0         0
                      'fs_random':             0           0                 0           0             0         0
                'fs_random_no_lp':             0           0                 0           0             0         0
  'fs_random_quick_restart_no_lp':             0           0                 0           0             0         0
                 'lb_tree_search':             0           0                 0           0             0         0
                         'max_lp':             0           0                 0           0             0         0
                          'no_lp':             0           0                 0           0             0         0
            'objective_lb_search':             0           0                 0           0             0         0
                        'probing':             0           0                 0           0             0         0
                   'pseudo_costs':             0           0                 0           0             0         0
                  'quick_restart':             0           0                 0           0             0         0
            'quick_restart_no_lp':             0           0                 0           0             0         0
                  'reduced_costs':             0           0                 0           0             0         0

Vivification                        Clauses  Decisions  LitTrue  Subsumed  LitRemoved  DecisionReused  Conflicts
                           'core':       13         26        0         0           0               0          0
                     'default_lp':        0          0        0         0           0               0          0
                      'fs_random':        0          0        0         0           0               0          0
                'fs_random_no_lp':        0          0        0         0           0               0          0
  'fs_random_quick_restart_no_lp':        0          0        0         0           0               0          0
                 'lb_tree_search':        0          0        0         0           0               0          0
                         'max_lp':        0          0        0         0           0               0          0
                          'no_lp':        0          0        0         0           0               0          0
            'objective_lb_search':        0          0        0         0           0               0          0
                        'probing':        0          0        0         0           0               0          0
                   'pseudo_costs':        0          0        0         0           0               0          0
                  'quick_restart':        0          0        0         0           0               0          0
            'quick_restart_no_lp':        0          0        0         0           0               0          0
                  'reduced_costs':        0          0        0         0           0               0          0

Clause deletion                     at_true  l_and_not(l)  to_binary  sub_conflict  sub_extra  sub_decisions  sub_eager  sub_vivify  sub_probing  sub_inpro  blocked  eliminated  forgotten  promoted  conflicts
                           'core':        0             0          0             0          0              0          0           0            0          0        0           0          0         0          1
                     'default_lp':        0             0          0             0          0              0          0           0            0          0        0           0          0         0          0
                      'fs_random':        0             0          0             0          0              0          0           0            0          0        0           0          0         0          0
                'fs_random_no_lp':        0             0          0             0          0              0          0           0            0          0        0           0          0         0          0
  'fs_random_quick_restart_no_lp':        0             0          0             0          0              0          0           0            0          0        0           0          0         0          0
                 'lb_tree_search':        0             0          0             0          0              0          0           0            0          0        0           0          0         0          0
                         'max_lp':        0             0          0             0          0              0          0           0            0          0        0           0          0         0          0
                          'no_lp':        0             0          0             0          0              0          0           0            0          0        0           0          0         0          0
            'objective_lb_search':        0             0          0             0          0              0          0           0            0          0        0           0          0         0          0
                        'probing':        0             0          0             0          0              0          0           0            0          0        0           0          0         0          0
                   'pseudo_costs':        0             0          0             0          0              0          0           0            0          0        0           0          0         0          0
                  'quick_restart':        0             0          0             0          0              0          0           0            0          0        0           0          0         0          0
            'quick_restart_no_lp':        0             0          0             0          0              0          0           0            0          0        0           0          0         0          0
                  'reduced_costs':        0             0          0             0          0              0          0           0            0          0        0           0          0         0          0

Lp stats                  Component  Iterations  AddedCuts  OPTIMAL  DUAL_F.  DUAL_U.
           'default_lp':          1           4          0        2        0        0
       'lb_tree_search':          1          19          0        1        0        0
               'max_lp':          1          19          0        1        1        0
  'objective_lb_search':          1           0          0        1        0        0
              'probing':          1           0          0        1        0        0
         'pseudo_costs':          1           0          0        1        0        0
        'quick_restart':          1           0          0        1        0        0
        'reduced_costs':          1           0          0        1        0        0

Lp dimension              Final dimension of first component
           'default_lp':      5 rows, 25 columns, 33 entries
       'lb_tree_search':    66 rows, 31 columns, 186 entries
               'max_lp':    66 rows, 31 columns, 186 entries
  'objective_lb_search':      5 rows, 25 columns, 33 entries
              'probing':      5 rows, 25 columns, 33 entries
         'pseudo_costs':      9 rows, 31 columns, 48 entries
        'quick_restart':      5 rows, 25 columns, 33 entries
        'reduced_costs':      9 rows, 31 columns, 48 entries

Lp debug                  CutPropag  CutEqPropag  Adjust  Overflow  Bad  BadScaling
           'default_lp':          0            0       0         0    0           0
       'lb_tree_search':          0            0       1         0    0           0
               'max_lp':          0            0       2         0    0           0
  'objective_lb_search':          0            0       0         0    0           0
              'probing':          0            0       0         0    0           0
         'pseudo_costs':          0            0       0         0    0           0
        'quick_restart':          0            0       0         0    0           0
        'reduced_costs':          0            0       0         0    0           0

Lp pool                   Constraints  Updates  Simplif  Merged  Shortened  Split  Strengthened  Cuts/Call
           'default_lp':           42        0        0       0          0      0             0        0/0
       'lb_tree_search':           66        0        0       0          0      0             0        0/0
               'max_lp':           66        0        0       0          0      0             0        0/0
  'objective_lb_search':           42        0        0       0          0      0             0        0/0
              'probing':           42        0        0       0          0      0             0        0/0
         'pseudo_costs':           66        0        0       0          0      0             0        0/0
        'quick_restart':           42        0        0       0          0      0             0        0/0
        'reduced_costs':           66        0        0       0          0      0             0        0/0

LNS stats           Improv/Calls  Closed  Difficulty  TimeLimit
  'graph_arc_lns':           0/0      0%    5.00e-01       0.10
  'graph_cst_lns':           0/0      0%    5.00e-01       0.10
  'graph_dec_lns':           0/0      0%    5.00e-01       0.10
  'graph_var_lns':           0/0      0%    5.00e-01       0.10
   'lb_relax_lns':           0/0      0%    5.00e-01       0.50
      'rins/rens':           0/0      0%    5.00e-01       0.10
    'rnd_cst_lns':           0/0      0%    5.00e-01       0.10
    'rnd_var_lns':           0/0      0%    5.00e-01       0.10

Solutions (2)    Num   Rank
        'core':    2  [0,1]
       'no_lp':    2  [1,2]

Objective bounds     Num
  'initial_domain':    1

Solution repositories    Added  Queried  Synchro
    'alternative_path':      1        0        1
      'best_solutions':      3        0        3
   'fj solution hints':      0        0        0
        'lp solutions':      0        0        0
                'pump':      0        0

Improving bounds shared    Num  Sym
                  'core':   31    0

Clauses shared                      #Exported  #Imported  #BinaryRead  #BinaryTotal
                           'core':          6          0            6             6
                     'default_lp':          0          0            0             6
                      'fs_random':          0          0            0             6
                'fs_random_no_lp':          0          0            0             6
  'fs_random_quick_restart_no_lp':          0          0            0             6
                 'lb_tree_search':          0          0            0             6
                         'max_lp':          0          0            0             6
                          'no_lp':          0          0            6             6
            'objective_lb_search':          0          0            0             6
                        'probing':          0          0            0             6
                   'pseudo_costs':          0          0            0             6
                  'quick_restart':          0          0            0             6
            'quick_restart_no_lp':          0          0            0             6
                  'reduced_costs':          0          0            0             6

LRAT_status: NA
[Scaling] scaled_objective_bound: 8.48525 corrected_bound: 8.48526 delta: -8.1213e-07
CpSolverResponse summary:
status: OPTIMAL
objective: 8.485281374238571
best_bound: 8.48525559680267
integers: 26
booleans: 18
conflicts: 0
branches: 39
propagations: 179
integer_propagations: 357
restarts: 0
lp_iterations: 0
walltime: 0.011203
usertime: 0.011203
deterministic_time: 0.000916404
gap_integral: 0.000265343
solution_fingerprint: 0x32d6c99bc4018a9b

[7]:
../_images/notebooks_a02_WindFarmNetwork_31_1.svg

Method: plot() and Variants

These methods help you visualize different stages of the optimization:

  • plot_location(): plot turbine/substation coordinates and borders

  • plot(): plot optimized electrical network

For more details, see the Plotting tutorial.

[8]:
wfn.plot_location()
wfn.plot() # wfn will do the same on notebooks (and if no G is available it will display L)
[8]:
<Axes: >
../_images/notebooks_a02_WindFarmNetwork_34_1.svg
../_images/notebooks_a02_WindFarmNetwork_34_2.svg

Method: cost() and length()

Returns the total cost and cable length of the optimized network.

[9]:
print("Network cost:", wfn.cost())
print("Network length:", wfn.length())
Network cost: 14424.97833620557
Network length: 8.485281374238571

Method: terse_links()

A terse link is a compact way to describe how each turbine is connected in the electrical network. It’s just a list (or array) where:

  • Each position i represents turbine i

  • The value at position i is the node that turbine i connects to (this could be another turbine or a substation)

[10]:
terse = wfn.terse_links()
print("Terse link array:", terse)
Terse link array: [ 1  2  3  4 -1]

Method: update_from_terse_links()

update_from_terse_links() allows you to reconstruct the network from a known terse_link, optionally updating coordinates. This method assumes a valid and feasible network, so it’s your responsibility to ensure that:

  • The connections form a proper feeder tree

  • Every turbine is (indirectly or directly) connected to a substation

  • Capacity constraints are not violated

Suppose we have a wind farm with:

  • 5 turbines → nodes 0, 1, 2, 3, 4

  • 1 substation → node -1 (Note: In OptiWindNet, substations are assigned negative indices)

We want the following connections:

  • Turbine 0 → Substation (-1)

  • Turbine 1 → Turbine 0

  • Turbine 2 → Substation (-1)

  • Turbine 3 → Turbine 2

  • Turbine 4 → Turbine 3

This gives us the terse_links array:

```python terse_links = [-1, 0, -1, 2, 3]

[11]:
new_terse_links = np.array([-1, 0, -1, 2, 3])

# Apply the new configuration
wfn.update_from_terse_links(new_terse_links)

# Visualize the updated network (cable routing)
wfn

[11]:
../_images/notebooks_a02_WindFarmNetwork_43_0.svg

Method: gradient()

This method computes the gradient of the cost or length with respect to turbine/substation positions. Useful for hybrid optimization or sensitivity analysis. > Note: default of gradient_type is length.

[12]:
print('--- gradient_type=length ---\n')
grad_turb, grad_subs = wfn.gradient()
print("Gradient (w.r.t. turbines):\n", grad_turb, "\n")
print("Gradient (w.r.t. substations):\n", grad_subs)
print('\n')
print('--- gradient_type=cost ---\n')
grad_turb, grad_subs = wfn.gradient(gradient_type='cost')
print("Gradient (w.r.t. turbines):\n", grad_turb, "\n")
print("Gradient (w.r.t. substations):\n", grad_subs)
--- gradient_type=length ---

Gradient (w.r.t. turbines):
 [[-1.41421356  0.        ]
 [ 0.70710678  0.70710678]
 [-0.70710678  0.29289322]
 [ 0.          1.41421356]
 [ 0.70710678 -0.70710678]]

Gradient (w.r.t. substations):
 [[ 0.70710678 -1.70710678]]


--- gradient_type=cost ---

Gradient (w.r.t. turbines):
 [[-2121.32034356     0.        ]
 [ 1060.66017178  1060.66017178]
 [-1060.66017178   739.33982822]
 [    0.          2121.32034356]
 [ 1060.66017178 -1060.66017178]]

Gradient (w.r.t. substations):
 [[ 1060.66017178 -2860.66017178]]

Method: get_network()

This method returns the final optimized network as a structured NumPy array, where each row represents an edge in the network.

Each edge includes detailed attributes such as:

  • ``src``: index of the source node

  • ``tgt``: index of the target (destination node)

  • ``length``: physical cable length

  • ``load``: electrical load carried through the cable (number of turbines)

  • ``cable``: index of the cable type used (e.g., 0 for first type in cable list)

  • ``cost``: cost associated with the used cable (if cost is provided by the user)

[13]:
network_data = wfn.get_network()
network_data[:2]  # preview only first few edges
[13]:
array([(1,  0, 1.41421356, 1., 0), (0, -1, 2.82842712, 2., 0)],
      dtype=[('src', '<i8'), ('tgt', '<i8'), ('length', '<f8'), ('load', '<f8'), ('cable', '<i8')])

Method: add_buffer()

This method redefines the border and obstacles by expanding the borders and shrinking the obstacles; useful when turbines are near edges or when integrating with tools like TopFarm|

[14]:
wfn.add_buffer(buffer_dist=0.5)
Buffering by 0.50 completely removed the obstacle at index 0. For visual comparison use plot_original_vs_buffered().
[15]:
wfn.plot_original_vs_buffered()
[15]:
<Axes: title={'center': 'Original and Buffered Shapes'}>
../_images/notebooks_a02_WindFarmNetwork_53_1.svg
[16]:
wfn.optimize()
wfn
[16]:
../_images/notebooks_a02_WindFarmNetwork_54_0.svg

In this section:

we explored the most useful methods of the WindFarmNetwork class:

Method

Purpose

optimize()

Run optimization with a router

plot()

Visualize the network

cost(), length()

Get total cost and length

terse_links()

Get compact link encoding

update_from_terse_links()

Apply terse links manually

gradient()

Compute network’s gradient

get_network()

Export the optimized network data

add_buffer()

Expand border - Shrink obstacles

For deeper insights into individual methods, refer to the dedicated notebooks provided.