{
"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 `Fault`)."
]
},
{
"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": [
""
],
"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": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Warning: No warmstarting (even though a solution is available) due to the following reason(s):\n",
" - branched network incompatible with model option: topology=\"radial\"\n",
"\n"
]
},
{
"data": {
"image/svg+xml": [
""
],
"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',\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"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/svg+xml": [
"\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": [
""
],
"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=length ---\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=length ---\\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"
],
"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": [
""
],
"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": {
"kernelspec": {
"display_name": "OptiWindNet",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}