{ "cells": [ { "cell_type": "markdown", "id": "f7b072be-a181-48f3-9a7c-5d8f16012d49", "metadata": {}, "source": [ "# WindFarmNetwork/Router" ] }, { "cell_type": "markdown", "id": "2ce7a38d", "metadata": {}, "source": [ "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:\n", "\n", "* Explore the `WindFarmNetwork` class\n", "* Compare available routers and their use cases\n", "* Run a complete optimization example" ] }, { "cell_type": "markdown", "id": "80c8c206-b99d-4ec4-a593-a10e8fbd57ed", "metadata": {}, "source": [ "## ✅ WindFarmNetwork" ] }, { "cell_type": "markdown", "id": "2196d3c5", "metadata": {}, "source": [ "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." ] }, { "cell_type": "markdown", "id": "a8acf159", "metadata": {}, "source": [ "### To create a `WindFarmNetwork` instance:\n", "\n", "**Required:**\n", "\n", "* Turbine and substation coordinates *(see [Data Input](a01_data_input.ipynb) for formats)*\n", "* Cable data (capacities and costs, or at least maximum capacity as a single number)\n", "\n", "**Optional:**\n", "\n", "* Borders and obstacles: to add spatial constraints\n", "* Router: optimization strategy (defaults to `EWRouter` if not specified)\n", "* verbose: log/hide logging messages (default to `False`)." ] }, { "cell_type": "markdown", "id": "4c944a01", "metadata": {}, "source": [ "### Key Responsibilities" ] }, { "cell_type": "markdown", "id": "d128ab24", "metadata": {}, "source": [ "| Feature | Description |\n", "| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |\n", "| **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. |\n", "| **Optimization** | Interfaces smoothly with different routers (`EWRouter`, `HGSRouter`, `MILPRouter`) to optimize the network (cable routing) for cost and cable length. |\n", "| **Visualization** | Provides plotting functions for location geometry, links, mesh, and the optimized network. |\n", "| **Gradient & Update Graph** | Computes gradients for optimization and allows updating the electrical network using a compact \"terse link\" format. |\n" ] }, { "cell_type": "markdown", "id": "a41b0f1b", "metadata": {}, "source": [ "### Example Workflow" ] }, { "cell_type": "markdown", "id": "1d7ab495", "metadata": {}, "source": [ "```python\n", "# Initialize with coordinates and cable types\n", "wfn = WindFarmNetwork(\n", " cables=[(2, 1500.0), (5, 1800.0)],\n", " turbinesC=...,\n", " substationsC=...,\n", ")\n", "\n", "# Optimize electrical network using the default router (EWRouter)\n", "wfn.optimize()\n", "\n", "# Access total cost or cable length\n", "total_cost = wfn.cost()\n", "total_length = wfn.length()\n", "\n", "# Visualize network\n", "wfn.plot() # or simply wfn if you are running on notebooks\n", "```" ] }, { "cell_type": "markdown", "id": "dcf9eec1", "metadata": {}, "source": [ "## 🧭 **Router**" ] }, { "cell_type": "markdown", "id": "ffd963f6", "metadata": {}, "source": [ "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)*." ] }, { "cell_type": "markdown", "id": "d9f536d0", "metadata": {}, "source": [ "### Currently Available Routers" ] }, { "cell_type": "markdown", "id": "dbf55f1a", "metadata": {}, "source": [ "The routers currently included in the `optiwindnet.api` module are:\n", "\n", "* **EWRouter**\n", "\n", " * *Fastest option* — completes in a fraction of a second\n", " * Great for quick network generation\n", " * Produces only **branched-topology** solutions\n", " * Heuristic method (EW = modified Esau-Williams) — solutions may be far from the optimum\n", "\n", "* **HGSRouter**\n", "\n", " * *Still fast* — can provide high-quality solutions in **0.5–2 seconds**\n", " * Produces only **radial-topology** solutions (no branching)\n", " * A maximum number of feeders can be enforced\n", " * Meta-heuristic method (HGS = Hybrid Genetic Search)\n", "\n", "* **MILPRouter**\n", "\n", " * Delivers solutions with **quality guarantees** (a bound on the distance from the optimum)\n", " * May take a **few to several minutes** depending on the problem size and quality required\n", " * Full set of model options to choose from\n", " * Should be used when optimality is more important than speed\n", " * MILP = Mixed Integer Linear Programming" ] }, { "cell_type": "markdown", "id": "961f9090", "metadata": {}, "source": [ "## Run an example" ] }, { "cell_type": "markdown", "id": "39bcea21", "metadata": {}, "source": [ "In this section we will:\n", "- Create a simple wind farm network\n", "- Use heuristic and MILP optimization\n", "- Explore key methods such as:\n", " - `.optimize()`\n", " - `.plot()`\n", " - `.cost()`, `.length()`\n", " - `.terse_links()`, `.update_from_terse_links()`\n", " - `.gradient()`\n", " - `.get_network()`\n", " - `.add_buffer()`" ] }, { "cell_type": "markdown", "id": "de0f0c52", "metadata": {}, "source": [ "### Create a `WindFarmNetwork` instance" ] }, { "cell_type": "markdown", "id": "c36112a8", "metadata": {}, "source": [ "Import required modules" ] }, { "cell_type": "code", "execution_count": 1, "id": "afeaaf92", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "from optiwindnet.api import WindFarmNetwork, EWRouter, MILPRouter, ModelOptions" ] }, { "cell_type": "code", "execution_count": 2, "id": "d8fb593b", "metadata": {}, "outputs": [], "source": [ "# Display figures as SVG in Jupyter notebooks\n", "%config InlineBackend.figure_formats = ['svg']" ] }, { "cell_type": "markdown", "id": "d5373461", "metadata": {}, "source": [ "#### Load location data" ] }, { "cell_type": "markdown", "id": "4ff13e41", "metadata": {}, "source": [ "OptiWindNet operates on location geometries, which define turbine and substation positions (plus optional borders/obstacles).\n", "\n", "You can load a location from:\n", "- `.yaml` or `.osm.pbf` files;\n", "- The included locations repository;\n", "- Your own coordinate arrays.\n", "\n", "For more details see [Data Input](a01_data_input.ipynb).\n", "\n", "We start with a simple geometry (5 turbines and 1 substation), and define a simple set of cables." ] }, { "cell_type": "code", "execution_count": 3, "id": "910dc391-6bf5-4fda-9a0b-a4faab58248a", "metadata": {}, "outputs": [], "source": [ "wfn = WindFarmNetwork(\n", " cables=[(2, 1500.0), (5, 1800.0)],\n", " turbinesC=np.array([[0, 0], [1, 1], [2, 0], [3, 1], [4, 0]]),\n", " substationsC=np.array([[2, -2]]),\n", " borderC=np.array([[0, -3], [0, 2], [4, 2.1], [5, 1], [4, -3]]),\n", " obstacleC_=[np.array([[0.2, -2.5], [1.5, -2.5], [0.2, -1]])]\n", ")" ] }, { "cell_type": "markdown", "id": "fcafb617", "metadata": {}, "source": [ "##### Plot location\n", "\n", "> **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**.\n", "> *In JupyterLab, you can do this by pressing* `Ctrl + Shift + C` *and selecting* **Trust Notebook**." ] }, { "cell_type": "code", "execution_count": 4, "id": "c2b946a4", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "" ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wfn" ] }, { "cell_type": "markdown", "id": "9cd882d6", "metadata": {}, "source": [ "### Method: `optimize(router=...)`" ] }, { "cell_type": "markdown", "id": "03cf721a", "metadata": {}, "source": [ "This is the main method that optimizes the electrical network using the given router (heuristic, metaheuristic or MILP).\n", " > Note that the router could be passed directly to `WindFarmNetwork(router=...)`." ] }, { "cell_type": "markdown", "id": "2865ca33-b8d1-42c9-bb49-5d088e866a9d", "metadata": {}, "source": [ "#### Use a heuristic router (Esau-Williams)" ] }, { "cell_type": "code", "execution_count": 5, "id": "43d027d8-1b4c-4b78-96ba-2207a6a6056a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([ 1, 2, -1, 2, 3])" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wfn.optimize(router=EWRouter())" ] }, { "cell_type": "markdown", "id": "b2bb6e6d", "metadata": {}, "source": [ "Plot the result" ] }, { "cell_type": "code", "execution_count": 6, "id": "1388e2d8", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "12 085 €Σλ = 7.6569 m(+0) [-1]: 1κ = 5, T = 5" ], "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wfn" ] }, { "cell_type": "markdown", "id": "775d7118", "metadata": {}, "source": [ "#### Use a MILP router with full control over topology and feeders\n", "\n", "The solution from the previous call to `wfn.optimize()` is stored within the `wfn` object.\n", "If this solution is *feasible under the current `MILPRouter` settings*, it will be *automatically used as a warm start*.\n", "Otherwise, if it does not meet the current `ModelOptions` constraints, it will be *ignored*, and the MILP solver will start from scratch.\n" ] }, { "cell_type": "code", "execution_count": 7, "id": "a5621cbd", "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "IntegerBoundsPreprocessor 85 rows, 46 columns, 267 entries with magnitude in [1.000000e+00, 5.000000e+00]\n", "BoundPropagationPreprocessor 85 rows, 46 columns, 267 entries with magnitude in [1.000000e+00, 5.000000e+00]\n", "ImpliedIntegerPreprocessor 85 rows, 46 columns, 267 entries with magnitude in [1.000000e+00, 5.000000e+00]\n", "IntegerBoundsPreprocessor 85 rows, 46 columns, 267 entries with magnitude in [1.000000e+00, 5.000000e+00]\n", "ReduceCostOverExclusiveOrConstraintPreprocessor 85 rows, 46 columns, 267 entries with magnitude in [1.000000e+00, 5.000000e+00]\n", "\n", "Scaling to pure integer problem.\n", "Num integers: 46/46 (implied: 0 in_inequalities: 0 max_scaling: 0) [IP] \n", "Maximum constraint coefficient relative error: 0\n", "Maximum constraint worst-case activity error: 0\n", "Constraint scaling factor range: [1, 1]\n", "\n", "Starting CP-SAT solver v9.15.6755\n", "Parameters: max_time_in_seconds: 2 log_search_progress: true catch_sigint_signal: false relative_gap_limit: 0.01\n", "Setting number of workers to 16\n", "\n", "Initial optimization model 'optiwindnet': (model_fingerprint: 0x625dc01df5e87447)\n", "#Variables: 46 (#bools: 18 in floating point objective) (35 primary variables)\n", " - 23 Booleans in [0,1]\n", " - 18 in [0,4]\n", " - 5 in [0,5]\n", "#kLinear2: 55\n", "#kLinear3: 8\n", "#kLinearN: 22 (#terms: 133)\n", "\n", "Starting presolve at 0.00s\n", "The solution hint is complete, but it is infeasible! we will try to repair it.\n", "[Scaling] Floating point objective has 18 terms with magnitude in [0.251936, 2.33385] average = 1.20522\n", "[Scaling] Objective coefficient relative error: 5.27705e-05\n", "[Scaling] Objective worst-case absolute error: 9.19728e-05\n", "[Scaling] Objective scaling factor: 32768\n", " 2.25e-05s 0.00e+00d [DetectDominanceRelations] \n", " 3.18e-04s 0.00e+00d [PresolveToFixPoint] #num_loops=2 #num_dual_strengthening=1 \n", " 5.25e-06s 0.00e+00d [ExtractEncodingFromLinear] #potential_supersets=18 \n", " 8.41e-06s 0.00e+00d [DetectDuplicateColumns] \n", " 1.24e-05s 0.00e+00d [DetectDuplicateConstraints] \n", "[Symmetry] Graph for symmetry has 186 nodes and 331 arcs.\n", "[Symmetry] Symmetry computation done. time: 4.2877e-05 dtime: 5.163e-05\n", "[SAT presolve] num removable Booleans: 0 / 23\n", "[SAT presolve] num trivial clauses: 0\n", "[SAT presolve] [0s] clauses:9 literals:18 vars:18 one_side_vars:18 simple_definition:0 singleton_clauses:0\n", "[SAT presolve] [4.88e-06s] clauses:9 literals:18 vars:18 one_side_vars:18 simple_definition:0 singleton_clauses:0\n", "[SAT presolve] [7.559e-06s] clauses:9 literals:18 vars:18 one_side_vars:18 simple_definition:0 singleton_clauses:0\n", " 2.66e-05s 0.00e+00d [DetectDuplicateConstraintsWithDifferentEnforcements] \n", " 6.29e-04s 1.48e-04d [Probe] #probed=86 #fixed_bools=2 #new_bounds=9 #new_binary_clauses=27 \n", " 3.27e-05s 1.54e-05d [MaxClique] Merged 21 constraints with 60 literals into 15 constraints with 48 literals\n", " 2.47e-05s 0.00e+00d [DetectDominanceRelations] \n", " 2.91e-04s 0.00e+00d [PresolveToFixPoint] #num_loops=2 #num_dual_strengthening=1 \n", " 5.47e-05s 0.00e+00d [ProcessAtMostOneAndLinear] #num_changes=14 \n", " 2.03e-05s 0.00e+00d [DetectDuplicateConstraints] \n", " 1.33e-05s 0.00e+00d [DetectDuplicateConstraintsWithDifferentEnforcements] \n", " 2.00e-05s 3.23e-07d [DetectDominatedLinearConstraints] #relevant_constraints=11 #num_inclusions=5 \n", " 3.02e-06s 0.00e+00d [DetectDifferentVariables] \n", " 3.35e-05s 7.62e-07d [ProcessSetPPC] #relevant_constraints=23 #num_inclusions=18 \n", " 2.88e-05s 0.00e+00d [TransformClausesToExactlyOne] #num_amos=27 \n", " 2.96e-05s 0.00e+00d [DetectEncodedComplexDomains] \n", " 4.58e-06s 0.00e+00d [FindAlmostIdenticalLinearConstraints] \n", " 2.63e-05s 3.93e-06d [FindBigAtMostOneAndLinearOverlap] \n", " 6.48e-06s 3.13e-06d [FindBigVerticalLinearOverlap] \n", " 3.08e-06s 1.95e-07d [FindBigHorizontalLinearOverlap] #linears=5 \n", " 1.76e-06s 0.00e+00d [MergeClauses] \n", " 2.12e-05s 0.00e+00d [DetectDominanceRelations] \n", " 2.16e-04s 0.00e+00d [PresolveToFixPoint] #num_loops=2 #num_dual_strengthening=1 \n", " 1.67e-05s 0.00e+00d [DetectDominanceRelations] \n", " 1.10e-04s 0.00e+00d [PresolveToFixPoint] #num_loops=1 #num_dual_strengthening=1 \n", " 5.61e-06s 0.00e+00d [DetectDuplicateColumns] \n", " 1.39e-05s 0.00e+00d [DetectDuplicateConstraints] #duplicates=1 \n", "[Symmetry] Graph for symmetry has 156 nodes and 236 arcs.\n", "[Symmetry] Symmetry computation done. time: 6.3732e-05 dtime: 6.161e-05\n", "[Symmetry] #generators: 1, average support size: 38\n", "[Symmetry] 19 orbits on 38 variables with sizes: 2,2,2,2,2,2,2,2,2,2,...\n", "[Symmetry] Num fixable by intersecting at_most_one with orbits: 2 largest_orbit: 2\n", "[SAT presolve] num removable Booleans: 0 / 21\n", "[SAT presolve] num trivial clauses: 0\n", "[SAT presolve] [0s] clauses:4 literals:8 vars:8 one_side_vars:8 simple_definition:0 singleton_clauses:0\n", "[SAT presolve] [3.952e-06s] clauses:4 literals:8 vars:8 one_side_vars:8 simple_definition:0 singleton_clauses:0\n", "[SAT presolve] [6.582e-06s] clauses:4 literals:8 vars:8 one_side_vars:8 simple_definition:0 singleton_clauses:0\n", " 2.52e-05s 0.00e+00d [DetectDuplicateConstraintsWithDifferentEnforcements] \n", " 2.93e-04s 3.72e-05d [Probe] #probed=51 #fixed_bools=1 #new_bounds=6 #new_binary_clauses=13 \n", " 1.95e-05s 6.31e-06d [MaxClique] \n", " 1.99e-05s 0.00e+00d [DetectDominanceRelations] \n", " 1.54e-04s 0.00e+00d [PresolveToFixPoint] #num_loops=2 #num_dual_strengthening=1 \n", " 1.79e-05s 0.00e+00d [ProcessAtMostOneAndLinear] \n", " 1.41e-05s 0.00e+00d [DetectDuplicateConstraints] \n", " 1.04e-05s 0.00e+00d [DetectDuplicateConstraintsWithDifferentEnforcements] \n", " 1.65e-05s 2.17e-07d [DetectDominatedLinearConstraints] #relevant_constraints=10 #num_inclusions=5 \n", " 2.20e-06s 0.00e+00d [DetectDifferentVariables] \n", " 1.11e-05s 1.85e-07d [ProcessSetPPC] #relevant_constraints=18 \n", " 1.66e-05s 0.00e+00d [TransformClausesToExactlyOne] #num_amos=12 \n", " 1.63e-05s 0.00e+00d [DetectEncodedComplexDomains] \n", " 2.79e-06s 0.00e+00d [FindAlmostIdenticalLinearConstraints] \n", " 8.55e-06s 2.98e-06d [FindBigAtMostOneAndLinearOverlap] \n", " 4.94e-06s 2.17e-06d [FindBigVerticalLinearOverlap] \n", " 2.35e-06s 1.65e-07d [FindBigHorizontalLinearOverlap] #linears=5 \n", " 9.61e-07s 0.00e+00d [MergeClauses] \n", " 1.98e-05s 0.00e+00d [DetectDominanceRelations] \n", " 1.11e-04s 0.00e+00d [PresolveToFixPoint] #num_loops=1 #num_dual_strengthening=1 \n", " 1.51e-05s 0.00e+00d [DetectDominanceRelations] \n", " 9.47e-05s 0.00e+00d [PresolveToFixPoint] #num_loops=1 #num_dual_strengthening=1 \n", " 4.66e-06s 0.00e+00d [DetectDuplicateColumns] \n", " 1.14e-05s 0.00e+00d [DetectDuplicateConstraints] \n", "[Symmetry] Graph for symmetry has 137 nodes and 189 arcs.\n", "[Symmetry] Symmetry computation done. time: 2.4193e-05 dtime: 2.144e-05\n", "[SAT presolve] num removable Booleans: 0 / 18\n", "[SAT presolve] num trivial clauses: 0\n", "[SAT presolve] [0s] clauses:6 literals:12 vars:9 one_side_vars:9 simple_definition:0 singleton_clauses:0\n", "[SAT presolve] [3.668e-06s] clauses:6 literals:12 vars:9 one_side_vars:9 simple_definition:0 singleton_clauses:0\n", "[SAT presolve] [2.5755e-05s] clauses:6 literals:12 vars:9 one_side_vars:9 simple_definition:0 singleton_clauses:0\n", " 1.63e-05s 0.00e+00d [DetectDuplicateConstraintsWithDifferentEnforcements] \n", " 5.20e-04s 3.27e-05d [Probe] #probed=50 #new_binary_clauses=14 \n", " 2.07e-05s 7.83e-06d [MaxClique] Merged 12 constraints with 32 literals into 11 constraints with 30 literals\n", " 1.85e-05s 0.00e+00d [DetectDominanceRelations] \n", " 1.13e-04s 0.00e+00d [PresolveToFixPoint] #num_loops=1 #num_dual_strengthening=1 \n", " 1.84e-05s 0.00e+00d [ProcessAtMostOneAndLinear] \n", " 1.19e-05s 0.00e+00d [DetectDuplicateConstraints] \n", " 9.43e-06s 0.00e+00d [DetectDuplicateConstraintsWithDifferentEnforcements] \n", " 1.50e-05s 2.17e-07d [DetectDominatedLinearConstraints] #relevant_constraints=10 #num_inclusions=5 \n", " 2.23e-06s 0.00e+00d [DetectDifferentVariables] \n", " 1.03e-05s 1.76e-07d [ProcessSetPPC] #relevant_constraints=17 \n", " 1.53e-05s 0.00e+00d [TransformClausesToExactlyOne] #num_amos=11 \n", " 1.58e-05s 0.00e+00d [DetectEncodedComplexDomains] \n", " 2.64e-06s 0.00e+00d [FindAlmostIdenticalLinearConstraints] \n", " 8.65e-06s 3.00e-06d [FindBigAtMostOneAndLinearOverlap] \n", " 3.95e-06s 2.16e-06d [FindBigVerticalLinearOverlap] \n", " 1.99e-06s 1.65e-07d [FindBigHorizontalLinearOverlap] #linears=5 \n", " 8.15e-07s 0.00e+00d [MergeClauses] \n", " 1.52e-05s 0.00e+00d [DetectDominanceRelations] \n", " 9.35e-05s 0.00e+00d [PresolveToFixPoint] #num_loops=1 #num_dual_strengthening=1 \n", " 8.38e-07s 0.00e+00d [MergeNoOverlap] \n", " 5.27e-07s 0.00e+00d [MergeNoOverlap2D] \n", " 1.39e-05s 0.00e+00d [ExpandObjective] #entries=54 #tight_variables=21 #tight_constraints=6 \n", "\n", "Presolve summary:\n", " - 12 affine relations were detected.\n", " - rule 'TODO linear inclusion: superset is equality' was applied 15 times.\n", " - rule 'TODO linear2: convert ax + by != cte to clauses for large domains' was applied 120 times.\n", " - rule 'affine: new relation' was applied 12 times.\n", " - rule 'at_most_one: removed literals' was applied 10 times.\n", " - rule 'at_most_one: transformed into max clique' was applied 2 times.\n", " - rule 'bool_and: x => x' was applied 14 times.\n", " - rule 'bool_or: implications' was applied 16 times.\n", " - rule 'deductions: 52 stored' was applied 1 time.\n", " - rule 'duplicate: removed constraint' was applied 1 time.\n", " - rule 'enforcement: false literal' was applied 3 times.\n", " - rule 'enforcement: true literal' was applied 5 times.\n", " - rule 'exactly_one: removed literals' was applied 5 times.\n", " - rule 'exactly_one: simplified objective' was applied 2 times.\n", " - rule 'linear + amo: extracted enforcement literal' was applied 14 times.\n", " - rule 'linear1: transformed to implication' was applied 4 times.\n", " - rule 'linear2: contains a boolean' was applied 23 times.\n", " - rule 'linear2: convert ax + by != cte to clauses' was applied 16 times.\n", " - rule 'linear: divide by GCD' was applied 4 times.\n", " - rule 'linear: empty' was applied 11 times.\n", " - rule 'linear: enforcement literal in expression' was applied 4 times.\n", " - rule 'linear: fixed or dup variables' was applied 22 times.\n", " - rule 'linear: positive at most one' was applied 12 times.\n", " - rule 'linear: positive equal one' was applied 7 times.\n", " - rule 'linear: remapped using affine relations' was applied 19 times.\n", " - rule 'linear: simplified rhs' was applied 5 times.\n", " - rule 'new_bool: integer encoding' was applied 7 times.\n", " - rule 'objective: shifted cost with exactly ones' was applied 4 times.\n", " - rule 'presolve: 10 unused variables removed.' was applied 1 time.\n", " - rule 'presolve: iteration' was applied 3 times.\n", " - rule 'setppc: exactly_one included in linear' was applied 5 times.\n", " - rule 'setppc: reduced linear coefficients' was applied 4 times.\n", " - rule 'setppc: removed dominated constraints' was applied 2 times.\n", " - rule 'setppc: removed trivial linear constraint' was applied 1 time.\n", " - rule 'symmetry: fixed to false in general orbit' was applied 2 times.\n", " - rule 'variables with 2 values: new affine relation' was applied 3 times.\n", " - rule 'variables with 2 values: register other encoding' was applied 3 times.\n", " - rule 'variables: add encoding constraint' was applied 4 times.\n", " - rule 'variables: both boolean and its negation fix the same variable' was applied 2 times.\n", " - rule 'variables: detect fully reified value encoding' was applied 14 times.\n", " - rule 'variables: detect half reified value encoding' was applied 39 times.\n", "\n", "Presolved optimization model 'optiwindnet': (model_fingerprint: 0x3bd35ec8d182bcae)\n", "#Variables: 31 (#bools: 9 in objective) (21 primary variables)\n", " - 18 Booleans in [0,1]\n", " - 2 in [0][2,4]\n", " - 1 in [0,3]\n", " - 10 in [0,4]\n", "#kAtMostOne: 5 (#literals: 18)\n", "#kBoolAnd: 5 (#enforced: 5) (#literals: 11)\n", "#kExactlyOne: 6 (#literals: 21)\n", "#kLinear1: 26 (#enforced: 26)\n", "#kLinear2: 2\n", "#kLinear3: 1\n", "#kLinearN: 7 (#terms: 41)\n", "[Symmetry] Graph for symmetry has 114 nodes and 187 arcs.\n", "[Symmetry] Symmetry computation done. time: 2.1219e-05 dtime: 2.148e-05\n", "\n", "Preloading model.\n", "#Bound 0.00s best:inf next:[7.65682461,16.6584115] initial_domain\n", "The solution hint is complete, but it is infeasible! we will try to repair it.\n", "#Model 0.00s var:31/31 constraints:52/52\n", "\n", "Starting search at 0.00s with 16 workers.\n", "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]\n", "5 first solution subsolvers: [fj(2), fs_random, fs_random_no_lp, fs_random_quick_restart_no_lp]\n", "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]\n", "3 helper subsolvers: [neighborhood_helper, synchronization_agent, update_gap_integral]\n", "\n", "#1 0.01s best:8.82839443 next:[7.65682461,8.82836392] core (fixed_bools=0/18)\n", "#2 0.01s best:8.48525478 next:[7.65682461,8.48522427] no_lp [hint] (fixed_bools=3/18)\n", "#Done 0.01s no_lp\n", "#Done 0.01s no_lp\n", "#Done 0.01s core\n", "\n", "Task timing n [ min, max] avg dev time n [ min, max] avg dev dtime\n", " 'core': 1 [ 4.08ms, 4.08ms] 4.08ms 0.00ns 4.08ms 2 [ 37.12us, 78.36us] 57.74us 20.62us 115.48us\n", " 'default_lp': 1 [ 4.00ms, 4.00ms] 4.00ms 0.00ns 4.00ms 1 [ 39.19us, 39.19us] 39.19us 0.00ns 39.19us\n", " 'feasibility_pump': 1 [ 1.22ms, 1.22ms] 1.22ms 0.00ns 1.22ms 0 [ 0.00ns, 0.00ns] 0.00ns 0.00ns 0.00ns\n", " 'fj': 0 [ 0.00ns, 0.00ns] 0.00ns 0.00ns 0.00ns 0 [ 0.00ns, 0.00ns] 0.00ns 0.00ns 0.00ns\n", " 'fj': 1 [ 1.20ms, 1.20ms] 1.20ms 0.00ns 1.20ms 0 [ 0.00ns, 0.00ns] 0.00ns 0.00ns 0.00ns\n", " 'fs_random': 1 [ 2.61ms, 2.61ms] 2.61ms 0.00ns 2.61ms 0 [ 0.00ns, 0.00ns] 0.00ns 0.00ns 0.00ns\n", " '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\n", " '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\n", " '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\n", " '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\n", " '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\n", " '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\n", " '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\n", " '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\n", " 'ls': 0 [ 0.00ns, 0.00ns] 0.00ns 0.00ns 0.00ns 0 [ 0.00ns, 0.00ns] 0.00ns 0.00ns 0.00ns\n", " 'ls_lin': 0 [ 0.00ns, 0.00ns] 0.00ns 0.00ns 0.00ns 0 [ 0.00ns, 0.00ns] 0.00ns 0.00ns 0.00ns\n", " 'max_lp': 1 [ 3.61ms, 3.61ms] 3.61ms 0.00ns 3.61ms 1 [115.07us, 115.07us] 115.07us 0.00ns 115.07us\n", " 'no_lp': 1 [ 2.66ms, 2.66ms] 2.66ms 0.00ns 2.66ms 1 [ 40.97us, 40.97us] 40.97us 0.00ns 40.97us\n", " '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\n", " 'probing': 1 [ 2.98ms, 2.98ms] 2.98ms 0.00ns 2.98ms 1 [ 36.95us, 36.95us] 36.95us 0.00ns 36.95us\n", " 'pseudo_costs': 1 [ 2.95ms, 2.95ms] 2.95ms 0.00ns 2.95ms 1 [ 37.57us, 37.57us] 37.57us 0.00ns 37.57us\n", " 'quick_restart': 1 [ 3.44ms, 3.44ms] 3.44ms 0.00ns 3.44ms 1 [ 37.42us, 37.42us] 37.42us 0.00ns 37.42us\n", " '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\n", " 'reduced_costs': 1 [ 3.24ms, 3.24ms] 3.24ms 0.00ns 3.24ms 1 [ 37.57us, 37.57us] 37.57us 0.00ns 37.57us\n", " 'rins/rens': 0 [ 0.00ns, 0.00ns] 0.00ns 0.00ns 0.00ns 0 [ 0.00ns, 0.00ns] 0.00ns 0.00ns 0.00ns\n", " '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\n", " '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\n", "\n", "Search stats Bools Conflicts Branches Restarts BacktrackToRoot Backtrack BoolPropag IntegerPropag\n", " 'core': 18 1 106 0 66 91 469 904\n", " 'default_lp': 18 0 36 0 36 36 149 288\n", " 'fs_random': 18 0 0 0 0 0 0 0\n", " 'fs_random_no_lp': 18 0 0 0 0 0 0 0\n", " 'fs_random_quick_restart_no_lp': 18 0 0 0 0 0 0 0\n", " 'lb_tree_search': 18 0 36 0 36 36 149 350\n", " 'max_lp': 18 0 36 0 36 36 149 350\n", " 'no_lp': 18 0 39 0 37 37 179 357\n", " 'objective_lb_search': 18 0 36 0 36 36 149 288\n", " 'probing': 18 0 36 0 36 36 149 287\n", " 'pseudo_costs': 18 0 36 0 36 36 149 349\n", " 'quick_restart': 18 0 36 0 36 36 149 288\n", " 'quick_restart_no_lp': 18 0 36 0 36 36 149 288\n", " 'reduced_costs': 18 0 36 0 36 36 149 349\n", "\n", "SAT formula Fixed Equiv Total VarLeft BinaryClauses PermanentClauses TemporaryClauses\n", " 'core': 18 0 18 0 66 8 0\n", " 'default_lp': 0 0 18 18 48 6 0\n", " 'fs_random': 0 0 18 18 0 0 0\n", " 'fs_random_no_lp': 0 0 18 18 0 0 0\n", " 'fs_random_quick_restart_no_lp': 0 0 18 18 0 0 0\n", " 'lb_tree_search': 0 0 18 18 48 6 0\n", " 'max_lp': 0 0 18 18 48 6 0\n", " 'no_lp': 18 0 18 0 60 6 0\n", " 'objective_lb_search': 0 0 18 18 48 6 0\n", " 'probing': 0 0 18 18 32 6 0\n", " 'pseudo_costs': 0 0 18 18 48 6 0\n", " 'quick_restart': 0 0 18 18 48 6 0\n", " 'quick_restart_no_lp': 0 0 18 18 48 6 0\n", " 'reduced_costs': 0 0 18 18 48 6 0\n", "\n", "SAT stats ClassicMinim LitRemoved LitRemovedBinary LitLearned LitForgotten Subsumed\n", " 'core': 0 0 1 3 0 0\n", " 'default_lp': 0 0 0 0 0 0\n", " 'fs_random': 0 0 0 0 0 0\n", " 'fs_random_no_lp': 0 0 0 0 0 0\n", " 'fs_random_quick_restart_no_lp': 0 0 0 0 0 0\n", " 'lb_tree_search': 0 0 0 0 0 0\n", " 'max_lp': 0 0 0 0 0 0\n", " 'no_lp': 0 0 0 0 0 0\n", " 'objective_lb_search': 0 0 0 0 0 0\n", " 'probing': 0 0 0 0 0 0\n", " 'pseudo_costs': 0 0 0 0 0 0\n", " 'quick_restart': 0 0 0 0 0 0\n", " 'quick_restart_no_lp': 0 0 0 0 0 0\n", " 'reduced_costs': 0 0 0 0 0 0\n", "\n", "Vivification Clauses Decisions LitTrue Subsumed LitRemoved DecisionReused Conflicts\n", " 'core': 13 26 0 0 0 0 0\n", " 'default_lp': 0 0 0 0 0 0 0\n", " 'fs_random': 0 0 0 0 0 0 0\n", " 'fs_random_no_lp': 0 0 0 0 0 0 0\n", " 'fs_random_quick_restart_no_lp': 0 0 0 0 0 0 0\n", " 'lb_tree_search': 0 0 0 0 0 0 0\n", " 'max_lp': 0 0 0 0 0 0 0\n", " 'no_lp': 0 0 0 0 0 0 0\n", " 'objective_lb_search': 0 0 0 0 0 0 0\n", " 'probing': 0 0 0 0 0 0 0\n", " 'pseudo_costs': 0 0 0 0 0 0 0\n", " 'quick_restart': 0 0 0 0 0 0 0\n", " 'quick_restart_no_lp': 0 0 0 0 0 0 0\n", " 'reduced_costs': 0 0 0 0 0 0 0\n", "\n", "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\n", " 'core': 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1\n", " 'default_lp': 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 'fs_random': 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 'fs_random_no_lp': 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 'fs_random_quick_restart_no_lp': 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 'lb_tree_search': 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 'max_lp': 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 'no_lp': 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 'objective_lb_search': 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 'probing': 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 'pseudo_costs': 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 'quick_restart': 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 'quick_restart_no_lp': 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", " 'reduced_costs': 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", "\n", "Lp stats Component Iterations AddedCuts OPTIMAL DUAL_F. DUAL_U.\n", " 'default_lp': 1 4 0 2 0 0\n", " 'lb_tree_search': 1 19 0 1 0 0\n", " 'max_lp': 1 19 0 1 1 0\n", " 'objective_lb_search': 1 0 0 1 0 0\n", " 'probing': 1 0 0 1 0 0\n", " 'pseudo_costs': 1 0 0 1 0 0\n", " 'quick_restart': 1 0 0 1 0 0\n", " 'reduced_costs': 1 0 0 1 0 0\n", "\n", "Lp dimension Final dimension of first component\n", " 'default_lp': 5 rows, 25 columns, 33 entries\n", " 'lb_tree_search': 66 rows, 31 columns, 186 entries\n", " 'max_lp': 66 rows, 31 columns, 186 entries\n", " 'objective_lb_search': 5 rows, 25 columns, 33 entries\n", " 'probing': 5 rows, 25 columns, 33 entries\n", " 'pseudo_costs': 9 rows, 31 columns, 48 entries\n", " 'quick_restart': 5 rows, 25 columns, 33 entries\n", " 'reduced_costs': 9 rows, 31 columns, 48 entries\n", "\n", "Lp debug CutPropag CutEqPropag Adjust Overflow Bad BadScaling\n", " 'default_lp': 0 0 0 0 0 0\n", " 'lb_tree_search': 0 0 1 0 0 0\n", " 'max_lp': 0 0 2 0 0 0\n", " 'objective_lb_search': 0 0 0 0 0 0\n", " 'probing': 0 0 0 0 0 0\n", " 'pseudo_costs': 0 0 0 0 0 0\n", " 'quick_restart': 0 0 0 0 0 0\n", " 'reduced_costs': 0 0 0 0 0 0\n", "\n", "Lp pool Constraints Updates Simplif Merged Shortened Split Strengthened Cuts/Call\n", " 'default_lp': 42 0 0 0 0 0 0 0/0\n", " 'lb_tree_search': 66 0 0 0 0 0 0 0/0\n", " 'max_lp': 66 0 0 0 0 0 0 0/0\n", " 'objective_lb_search': 42 0 0 0 0 0 0 0/0\n", " 'probing': 42 0 0 0 0 0 0 0/0\n", " 'pseudo_costs': 66 0 0 0 0 0 0 0/0\n", " 'quick_restart': 42 0 0 0 0 0 0 0/0\n", " 'reduced_costs': 66 0 0 0 0 0 0 0/0\n", "\n", "LNS stats Improv/Calls Closed Difficulty TimeLimit\n", " 'graph_arc_lns': 0/0 0% 5.00e-01 0.10\n", " 'graph_cst_lns': 0/0 0% 5.00e-01 0.10\n", " 'graph_dec_lns': 0/0 0% 5.00e-01 0.10\n", " 'graph_var_lns': 0/0 0% 5.00e-01 0.10\n", " 'lb_relax_lns': 0/0 0% 5.00e-01 0.50\n", " 'rins/rens': 0/0 0% 5.00e-01 0.10\n", " 'rnd_cst_lns': 0/0 0% 5.00e-01 0.10\n", " 'rnd_var_lns': 0/0 0% 5.00e-01 0.10\n", "\n", "Solutions (2) Num Rank\n", " 'core': 2 [0,1]\n", " 'no_lp': 2 [1,2]\n", "\n", "Objective bounds Num\n", " 'initial_domain': 1\n", "\n", "Solution repositories Added Queried Synchro\n", " 'alternative_path': 1 0 1\n", " 'best_solutions': 3 0 3\n", " 'fj solution hints': 0 0 0\n", " 'lp solutions': 0 0 0\n", " 'pump': 0 0\n", "\n", "Improving bounds shared Num Sym\n", " 'core': 31 0\n", "\n", "Clauses shared #Exported #Imported #BinaryRead #BinaryTotal\n", " 'core': 6 0 6 6\n", " 'default_lp': 0 0 0 6\n", " 'fs_random': 0 0 0 6\n", " 'fs_random_no_lp': 0 0 0 6\n", " 'fs_random_quick_restart_no_lp': 0 0 0 6\n", " 'lb_tree_search': 0 0 0 6\n", " 'max_lp': 0 0 0 6\n", " 'no_lp': 0 0 6 6\n", " 'objective_lb_search': 0 0 0 6\n", " 'probing': 0 0 0 6\n", " 'pseudo_costs': 0 0 0 6\n", " 'quick_restart': 0 0 0 6\n", " 'quick_restart_no_lp': 0 0 0 6\n", " 'reduced_costs': 0 0 0 6\n", "\n", "LRAT_status: NA\n", "[Scaling] scaled_objective_bound: 8.48525 corrected_bound: 8.48526 delta: -8.1213e-07\n", "CpSolverResponse summary:\n", "status: OPTIMAL\n", "objective: 8.485281374238571\n", "best_bound: 8.48525559680267\n", "integers: 26\n", "booleans: 18\n", "conflicts: 0\n", "branches: 39\n", "propagations: 179\n", "integer_propagations: 357\n", "restarts: 0\n", "lp_iterations: 0\n", "walltime: 0.011203\n", "usertime: 0.011203\n", "deterministic_time: 0.000916404\n", "gap_integral: 0.000265343\n", "solution_fingerprint: 0x32d6c99bc4018a9b\n", "\n" ] }, { "data": { "image/svg+xml": [ "14 425 €Σλ = 8.4853 m(+0) [-1]: 1κ = 5, T = 5" ], "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "model_opts = ModelOptions(\n", " topology='radial',\n", " feeder_limit='minimum',\n", " feeder_route='straight',\n", ")\n", "wfn.optimize(\n", " router=MILPRouter(\n", " solver_name='ortools.cp_sat',\n", " time_limit=2,\n", " mip_gap=0.01,\n", " model_options=model_opts\n", " ),\n", " verbose=True,\n", ")\n", "wfn" ] }, { "cell_type": "markdown", "id": "2511d0d4-06d9-4baf-8de9-7939373fa5e8", "metadata": {}, "source": [ "### Method: `plot()` and Variants" ] }, { "cell_type": "markdown", "id": "3dbd5d88", "metadata": {}, "source": [ "These methods help you visualize different stages of the optimization:\n", "\n", "- `plot_location()`: plot turbine/substation coordinates and borders\n", "- `plot()`: plot optimized electrical network\n", "\n", "> For more details, see the [Plotting](a04_Plotting.ipynb) tutorial." ] }, { "cell_type": "code", "execution_count": 8, "id": "89161332", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2026-05-26T22:02:49.265754\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.10.9, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2026-05-26T22:02:49.277568\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.10.9, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " κ = 5, T = 5\n", " (+0) [-1]: 1\n", " Σλ = 8.4853 m\n", " 14 425 €\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "wfn.plot_location()\n", "wfn.plot() # wfn will do the same on notebooks (and if no G is available it will display L)" ] }, { "cell_type": "markdown", "id": "b93855a6-3175-4c75-8358-0bc56e8e787b", "metadata": {}, "source": [ "### Method: `cost()` and `length()`" ] }, { "cell_type": "markdown", "id": "711e2851-3c45-4e25-b6d2-30db7f0d56da", "metadata": {}, "source": [ "Returns the total cost and cable length of the optimized network." ] }, { "cell_type": "code", "execution_count": 9, "id": "72ef1d9a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Network cost: 14424.97833620557\n", "Network length: 8.485281374238571\n" ] } ], "source": [ "print(\"Network cost:\", wfn.cost())\n", "print(\"Network length:\", wfn.length())" ] }, { "cell_type": "markdown", "id": "405e5e24-76d9-481f-bf5b-3e2b84feeb46", "metadata": {}, "source": [ "### Method: `terse_links()`" ] }, { "cell_type": "markdown", "id": "5a5a48f5-c2b7-449b-8a10-0b2f62d19ca6", "metadata": {}, "source": [ "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:\n", "\n", "* Each position `i` represents turbine `i`\n", "\n", "* The value at position `i` is the node that turbine `i` connects to\n", "(this could be another turbine or a substation)" ] }, { "cell_type": "code", "execution_count": 10, "id": "deb7d87f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Terse link array: [ 1 2 3 4 -1]\n" ] } ], "source": [ "terse = wfn.terse_links()\n", "print(\"Terse link array:\", terse)" ] }, { "cell_type": "markdown", "id": "831e287d-0cf1-44cb-9b0c-11433a5006ea", "metadata": {}, "source": [ "### Method: `update_from_terse_links()`" ] }, { "cell_type": "markdown", "id": "3e95d580-3031-43bd-b277-f020b96731b6", "metadata": {}, "source": [ "`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:\n", "* The connections form a proper **feeder tree**\n", "* Every turbine is (indirectly or directly) connected to a **substation**\n", "* Capacity constraints are not violated\n", "\n", "Suppose we have a wind farm with:\n", "* **5 turbines** → nodes `0, 1, 2, 3, 4`\n", "* **1 substation** → node `-1` (*Note: In OptiWindNet, substations are assigned negative indices*)\n", "\n", "We want the following connections:\n", "* Turbine 0 → Substation (-1)\n", "* Turbine 1 → Turbine 0\n", "* Turbine 2 → Substation (-1)\n", "* Turbine 3 → Turbine 2\n", "* Turbine 4 → Turbine 3\n", "\n", "This gives us the `terse_links` array:\n", "\n", "```python\n", "terse_links = [-1, 0, -1, 2, 3]" ] }, { "cell_type": "code", "execution_count": 11, "id": "af4c603a", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "14 207 €Σλ = 9.0711 m(+1) [-1]: 2κ = 5, T = 5" ], "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "new_terse_links = np.array([-1, 0, -1, 2, 3])\n", "\n", "# Apply the new configuration\n", "wfn.update_from_terse_links(new_terse_links)\n", "\n", "# Visualize the updated network (cable routing)\n", "wfn\n" ] }, { "cell_type": "markdown", "id": "3f277b56", "metadata": {}, "source": [ "### Method: `gradient()`" ] }, { "cell_type": "markdown", "id": "96a70239", "metadata": {}, "source": [ "This method computes the gradient of the cost or length with respect to turbine/substation positions.\n", "Useful for hybrid optimization or sensitivity analysis.\n", " > Note: default of `gradient_type` is `length`." ] }, { "cell_type": "code", "execution_count": 12, "id": "3fe297b4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--- gradient_type=length ---\n", "\n", "Gradient (w.r.t. turbines):\n", " [[-1.41421356 0. ]\n", " [ 0.70710678 0.70710678]\n", " [-0.70710678 0.29289322]\n", " [ 0. 1.41421356]\n", " [ 0.70710678 -0.70710678]] \n", "\n", "Gradient (w.r.t. substations):\n", " [[ 0.70710678 -1.70710678]]\n", "\n", "\n", "--- gradient_type=cost ---\n", "\n", "Gradient (w.r.t. turbines):\n", " [[-2121.32034356 0. ]\n", " [ 1060.66017178 1060.66017178]\n", " [-1060.66017178 739.33982822]\n", " [ 0. 2121.32034356]\n", " [ 1060.66017178 -1060.66017178]] \n", "\n", "Gradient (w.r.t. substations):\n", " [[ 1060.66017178 -2860.66017178]]\n" ] } ], "source": [ "print('--- gradient_type=length ---\\n')\n", "grad_turb, grad_subs = wfn.gradient()\n", "print(\"Gradient (w.r.t. turbines):\\n\", grad_turb, \"\\n\")\n", "print(\"Gradient (w.r.t. substations):\\n\", grad_subs)\n", "print('\\n')\n", "print('--- gradient_type=cost ---\\n')\n", "grad_turb, grad_subs = wfn.gradient(gradient_type='cost')\n", "print(\"Gradient (w.r.t. turbines):\\n\", grad_turb, \"\\n\")\n", "print(\"Gradient (w.r.t. substations):\\n\", grad_subs)" ] }, { "cell_type": "markdown", "id": "95831235", "metadata": {}, "source": [ "### Method: `get_network()`" ] }, { "cell_type": "markdown", "id": "4eea9d03", "metadata": {}, "source": [ "This method returns the **final optimized network** as a **structured NumPy array**, where each row represents an edge in the network.\n", "\n", "Each edge includes detailed attributes such as:\n", "\n", "* **`src`**: index of the source node\n", "* **`tgt`**: index of the target (destination node)\n", "* **`length`**: physical cable length\n", "* **`load`**: electrical load carried through the cable (number of turbines)\n", "* **`cable`**: index of the cable type used (e.g., 0 for first type in cable list)\n", "* **`cost`**: cost associated with the used cable (if cost is provided by the user)" ] }, { "cell_type": "code", "execution_count": 13, "id": "9a0b2118", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([(1, 0, 1.41421356, 1., 0), (0, -1, 2.82842712, 2., 0)],\n", " dtype=[('src', '" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2026-05-26T22:02:49.383838\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.10.9, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " Original and Buffered Shapes\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " Original Border\n", " \n", " \n", " \n", " \n", " \n", " Original Obstacle\n", " \n", " \n", " \n", " \n", " \n", " Buffered Border\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "wfn.plot_original_vs_buffered()" ] }, { "cell_type": "code", "execution_count": 16, "id": "77dab41a", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "14 425 €Σλ = 8.4853 m(+0) [-1]: 1κ = 5, T = 5" ], "text/plain": [ "" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "wfn.optimize()\n", "wfn" ] }, { "cell_type": "markdown", "id": "9276fb5d", "metadata": {}, "source": [ "### In this section:\n", "\n", "we explored the most useful methods of the `WindFarmNetwork` class:\n", "\n", "| Method | Purpose |\n", "|--------|---------|\n", "| `optimize()` | Run optimization with a router |\n", "| `plot()` | Visualize the network |\n", "| `cost()`, `length()` | Get total cost and length |\n", "| `terse_links()` | Get compact link encoding |\n", "| `update_from_terse_links()` | Apply terse links manually |\n", "| `gradient()` | Compute network's gradient |\n", "| `get_network()` | Export the optimized network data |\n", "| `add_buffer()`| Expand border - Shrink obstacles|\n", "\n", "For deeper insights into individual methods, refer to the dedicated notebooks provided.\n" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }