{ "cells": [ { "cell_type": "markdown", "id": "525f818c-2d78-49b8-9d59-2697d39b324c", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "# Simple TOPFARM Example" ] }, { "cell_type": "markdown", "id": "98940300", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "This notebook walks through a minimal wind-farm design optimization that couples **OptiWindNet** (for wind farm electrical network optimization) with **[TopFarm](https://gitlab.windenergy.dtu.dk/TOPFARM)** (for wind farm layout optimization). For this purpose we will:\n", "\n", "\n", "* **Build three cost components**:\n", "\n", " * Energy (AEP): we use PyWake to evaluate farm energy for a set of wind directions and turbine coordinates.\n", " * Electrical network (cabling): based on the data (coordiantes and cables) we build a cost model for cabling via a `WFNComponent` wrapper.\n", " * Economics (IRR / NPV): we create an `economic_evaluation` function and wrap it in a `CostModelComponent` that takes `AEP` and `cabling_cost` and outputs `IRR` (set as the optimization objective with `objective=True, maximize=True`).\n", "\n", "**How it plugs into TopFarm**\n", "\n", "1. We instantiate each component: `aep_comp`, `network_cost_comp`, and `npv_comp`.\n", "2. We combine them in a `TopFarmGroup([aep_comp, network_cost_comp, npv_comp])`. With promotion, outputs/inputs line up automatically:\n", "\n", " * `aep_comp` produces **AEP** → used by the economic component.\n", " * `network_cost_comp` produces **cabling_cost** → also used by the economic component.\n", " * The economic component outputs **IRR**, which TopFarm treats as the **objective**.\n", "3. We create a `TopFarmProblem` based on design variables (turbine positions), the defined cost component using `TopFarmGroup` (as mentiond above), constraints and driver.\n", "4. Then we call `optimize()` in topfarm problem to run optimization\n", "\n", "This setup yields a clean dataflow: `turbine positions → AEP & cabling_cost → IRR (objective)`. The constraints keep turbine layouts and the optimized electrical network feasible. The chosen router governs the electrical network quality/speed trade-off." ] }, { "cell_type": "markdown", "id": "2277463d", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "> **Note on boundaries and buffering**\n", ">\n", "> TopFarm’s gradient-based drivers occasionally move turbines a little outside the user-defined site polygon while searching the design space.\n", "> OptiWindNet can accommodate these small excursions with\n", "> `wfn.add_buffer(buff_dist=dist)`, which expands the outer border and shrinks any obstacle polygons by `dist` before it optimizes the electrical network.\n", ">\n", "> In this demo notebook we omit the border when we instantiate `WindFarmNetwork` because the rectangular site has no concavities or internal obstacles, and we don’t know in advance how far the topfarm driver might overshoot. In real projects (especially when the site has concavity or exclusion zones), the user should\n", ">\n", "> 1. **Estimate the maximum overshoot** the chosen TopFarm driver may produce (e.g. by running a few test iterations or inspecting previous layouts).\n", "> 2. Call `wfn.add_buffer(buff_dist=max_overshoot)` *before* optimisation, or pass the buffered border/obstacles directly when creating the `WindFarmNetwork`.\n", ">\n", "> This ensures OptiWindNet always receives a feasible geometry, while keeping the electrical-network solution as much as possible consistent with the true site limits.\n" ] }, { "cell_type": "markdown", "id": "7869bd02", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Import required packages" ] }, { "cell_type": "code", "execution_count": 1, "id": "bc20badf-5d4e-42ca-a606-b619c5adc978", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:34:09.806661Z", "iopub.status.busy": "2025-09-17T16:34:09.806661Z", "iopub.status.idle": "2025-09-17T16:34:11.929325Z", "shell.execute_reply": "2025-09-17T16:34:11.929325Z", "shell.execute_reply.started": "2025-09-17T16:34:09.806661Z" }, "frozen": false }, "outputs": [], "source": [ "import math\n", "import numpy as np\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": 2, "id": "550d25a8-4347-4659-8802-14b19e74277d", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:34:11.929325Z", "iopub.status.busy": "2025-09-17T16:34:11.929325Z", "iopub.status.idle": "2025-09-17T16:34:24.175731Z", "shell.execute_reply": "2025-09-17T16:34:24.175731Z", "shell.execute_reply.started": "2025-09-17T16:34:11.929325Z" }, "frozen": false }, "outputs": [], "source": [ "from py_wake.examples.data.dtu10mw import DTU10MW\n", "from py_wake.examples.hornsrev1_example import Hornsrev1Site\n", "from py_wake import Nygaard_2022\n", "\n", "from topfarm import TopFarmProblem, TopFarmGroup\n", "from topfarm.cost_models.py_wake_wrapper import PyWakeAEPCostModelComponent\n", "from topfarm.plotting import XYPlotComp\n", "from topfarm.constraint_components.spacing import SpacingConstraint\n", "from topfarm.constraint_components.boundary import XYBoundaryConstraint\n", "from topfarm.cost_models.cost_model_wrappers import CostModelComponent\n", "from topfarm.cost_models.economic_models.dtu_wind_cm_main import economic_evaluation\n", "from topfarm.easy_drivers import EasySGDDriver\n", "from topfarm.cost_models.cost_model_wrappers import CostModelComponent\n", "from topfarm.constraint_components.constraint_aggregation import DistanceConstraintAggregation\n", "\n", "from optiwindnet.api import WindFarmNetwork\n", "from optiwindnet.augmentation import poisson_disc_filler" ] }, { "cell_type": "code", "execution_count": 3, "id": "fa470f55-6d31-4c67-b725-dfd632556219", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:34:24.182256Z", "iopub.status.busy": "2025-09-17T16:34:24.182256Z", "iopub.status.idle": "2025-09-17T16:34:24.190315Z", "shell.execute_reply": "2025-09-17T16:34:24.189306Z", "shell.execute_reply.started": "2025-09-17T16:34:24.182256Z" }, "frozen": false }, "outputs": [], "source": [ "# Display figures as SVG in Jupyter notebooks\n", "%config InlineBackend.figure_formats = ['svg']" ] }, { "cell_type": "markdown", "id": "40f00349-4801-4215-bda4-f9ad55fd3586", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "## Wind farm design parameters" ] }, { "cell_type": "markdown", "id": "f7994e9b-04bf-468e-86ec-8ef81df95a45", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Define turbine type and count:" ] }, { "cell_type": "code", "execution_count": 4, "id": "1b53fd53-70de-4e27-86d1-06e3addc1d50", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:34:24.191321Z", "iopub.status.busy": "2025-09-17T16:34:24.191321Z", "iopub.status.idle": "2025-09-17T16:34:24.919891Z", "shell.execute_reply": "2025-09-17T16:34:24.919891Z", "shell.execute_reply.started": "2025-09-17T16:34:24.191321Z" }, "frozen": false }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2025-09-17T18:34:24.862616\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.10.6, 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", " 0\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 5\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 10\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 15\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 20\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 25\n", " \n", " \n", " \n", " Wind speed [m/s]\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.0\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.2\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.4\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.6\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.8\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 1.0\n", " \n", " \n", " \n", " Power [W]\n", " \n", " \n", " 1e7\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " DTU10MW\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " Power\n", " \n", " \n", " \n", " \n", " \n", " CT\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.2\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.4\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.6\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.8\n", " \n", " \n", " \n", " Thrust coefficient\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" } ], "source": [ "wind_turbines = DTU10MW()\n", "n_wt = 20\n", "wind_turbines.plot_power_ct();" ] }, { "cell_type": "markdown", "id": "62dfdedf-1934-401e-aba5-1329087a6115", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Define the wind resource:" ] }, { "cell_type": "code", "execution_count": 5, "id": "0b8814df-ddc2-42ea-b4b9-9b214a7c7607", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:34:24.919891Z", "iopub.status.busy": "2025-09-17T16:34:24.919891Z", "iopub.status.idle": "2025-09-17T16:34:25.107111Z", "shell.execute_reply": "2025-09-17T16:34:25.107111Z", "shell.execute_reply.started": "2025-09-17T16:34:24.919891Z" }, "frozen": false }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2025-09-17T18:34:25.059972\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.10.6, 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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 45°\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 90°\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 135°\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 180°\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 225°\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 270°\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 315°\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.02\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.04\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.06\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.08\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.10\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.12\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.14\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "site = Hornsrev1Site()\n", "site.plot_wd_distribution();" ] }, { "cell_type": "markdown", "id": "15c8c4ec-8e73-4188-b3db-27a74bd3628f", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Define the area:" ] }, { "cell_type": "code", "execution_count": 6, "id": "de8b31ce-dd27-4a6c-80c5-d3d6392b068f", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:34:25.107111Z", "iopub.status.busy": "2025-09-17T16:34:25.107111Z", "iopub.status.idle": "2025-09-17T16:34:25.112575Z", "shell.execute_reply": "2025-09-17T16:34:25.112575Z", "shell.execute_reply.started": "2025-09-17T16:34:25.107111Z" }, "frozen": false }, "outputs": [], "source": [ "d_west_east = 4000 # [m] width\n", "d_south_north = 2000 # [m] height\n", "min_wt_spacing = 3.5*wind_turbines.diameter() # [m] inter-turbine minimum distance\n", "boundary = np.array([\n", " (0, 0),\n", " (d_west_east, 0),\n", " (d_west_east, d_south_north),\n", " (0, d_south_north)\n", "])" ] }, { "cell_type": "markdown", "id": "da392d01-3044-4ec6-ba6c-ae6a51759609", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Define the substation position:" ] }, { "cell_type": "code", "execution_count": 7, "id": "eecc97ef-04a7-4150-a34b-319121f3a29a", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:34:25.112575Z", "iopub.status.busy": "2025-09-17T16:34:25.112575Z", "iopub.status.idle": "2025-09-17T16:34:25.119049Z", "shell.execute_reply": "2025-09-17T16:34:25.119049Z", "shell.execute_reply.started": "2025-09-17T16:34:25.112575Z" }, "frozen": false }, "outputs": [], "source": [ "x_ss, y_ss = (d_west_east/5, d_south_north/2)" ] }, { "cell_type": "markdown", "id": "609472f6-dc0d-4d16-a512-ac0badd4f5a4", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "## Initial wind farm layout (turbine positions)" ] }, { "cell_type": "code", "execution_count": 8, "id": "2c746c0f-dbb2-4b83-a2b4-a2ca80e4ffd3", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:34:25.119049Z", "iopub.status.busy": "2025-09-17T16:34:25.119049Z", "iopub.status.idle": "2025-09-17T16:34:31.613560Z", "shell.execute_reply": "2025-09-17T16:34:31.612112Z", "shell.execute_reply.started": "2025-09-17T16:34:25.119049Z" }, "frozen": false }, "outputs": [], "source": [ "# Generate initial random turbine layout\n", "x_init, y_init = poisson_disc_filler(\n", " n_wt,\n", " min_dist=min_wt_spacing,\n", " BorderC=boundary,\n", " seed=42,\n", " rounds=10,\n", ").T" ] }, { "cell_type": "markdown", "id": "4c91cd0f-4241-4f45-a0c9-65856e6df619", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "## Build components" ] }, { "cell_type": "markdown", "id": "de69a34f", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "### AEP component" ] }, { "cell_type": "code", "execution_count": 9, "id": "bcb47b76-aa77-4bf4-92f6-906363a0c9b3", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:34:31.615250Z", "iopub.status.busy": "2025-09-17T16:34:31.615250Z", "iopub.status.idle": "2025-09-17T16:34:31.622523Z", "shell.execute_reply": "2025-09-17T16:34:31.621509Z", "shell.execute_reply.started": "2025-09-17T16:34:31.615250Z" }, "frozen": false }, "outputs": [], "source": [ "# number of wind directions to consider\n", "n_wd = 12\n", "\n", "aep_comp = PyWakeAEPCostModelComponent(\n", " windFarmModel=Nygaard_2022(\n", " site=site,\n", " windTurbines=wind_turbines,\n", " ),\n", " n_wt=n_wt,\n", " wd=np.linspace(0.0, 360.0, n_wd, endpoint=False),\n", " objective=False,\n", ")" ] }, { "cell_type": "markdown", "id": "e6ed1e6f", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "### Electrical network component" ] }, { "cell_type": "markdown", "id": "fcf85bb2", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "#### Choose the router\n", "\n", "Pick one of the available routers in OptiWindNet:\n", "\n", "**EWRouter**\n", "\n", "```python\n", "from optiwindnet.api import EWRouter\n", "router = EWRouter()\n", "```\n", "\n", "**HGSRouter**\n", "\n", "```python\n", "from optiwindnet.api import HGSRouter\n", "router = HGSRouter(time_limit=0.1) # seconds\n", "```\n", "\n", "**MILPRouter**\n", "\n", "```python\n", "from optiwindnet.api import MILPRouter\n", "router = MILPRouter(\n", " solver_name='gurobi', # or 'cplex', 'ortools'\n", " time_limit=1, # seconds\n", " mip_gap=0.005, # relative optimality gap (0.5%)\n", " verbose=True\n", ")\n", "```\n", "\n", "> After choosing, pass `router` to `WindFarmNetwork(..., router=router)` through `WFNComponent`. If you use `MILPRouter`, make sure a supported solver is installed (and licensed if necessary)." ] }, { "cell_type": "markdown", "id": "286c79ea", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Let's proceed with `MILPRouter` for this example. You can easily switch to other routers using the sample codes above." ] }, { "cell_type": "code", "execution_count": 10, "id": "78c34067-18ff-4b35-9c53-caabaaebf5f7", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:34:31.623525Z", "iopub.status.busy": "2025-09-17T16:34:31.622523Z", "iopub.status.idle": "2025-09-17T16:34:32.561435Z", "shell.execute_reply": "2025-09-17T16:34:32.561435Z", "shell.execute_reply.started": "2025-09-17T16:34:31.623525Z" }, "frozen": false }, "outputs": [], "source": [ "from optiwindnet.api import MILPRouter\n", "router = MILPRouter(solver_name='gurobi', time_limit=0.1, mip_gap=0.005)" ] }, { "cell_type": "markdown", "id": "6e00f256", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Assemble initial turbine/substation positions in OptiWindNet format." ] }, { "cell_type": "code", "execution_count": 11, "id": "93d38d9c-6517-4a82-81d2-d3b737a4a5e1", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:34:32.564310Z", "iopub.status.busy": "2025-09-17T16:34:32.561435Z", "iopub.status.idle": "2025-09-17T16:34:32.568690Z", "shell.execute_reply": "2025-09-17T16:34:32.568690Z", "shell.execute_reply.started": "2025-09-17T16:34:32.564310Z" }, "frozen": false }, "outputs": [], "source": [ "turbines_pos = np.column_stack((x_init, y_init))\n", "substations_pos = np.column_stack((x_ss, y_ss))" ] }, { "cell_type": "markdown", "id": "e5186b52", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Define cables." ] }, { "cell_type": "code", "execution_count": 12, "id": "ce481bec", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:34:32.568690Z", "iopub.status.busy": "2025-09-17T16:34:32.568690Z", "iopub.status.idle": "2025-09-17T16:34:32.575136Z", "shell.execute_reply": "2025-09-17T16:34:32.575136Z", "shell.execute_reply.started": "2025-09-17T16:34:32.568690Z" }, "frozen": false }, "outputs": [], "source": [ "cables = np.array([(2, 2000), (5, 2200)])" ] }, { "cell_type": "markdown", "id": "e9983983", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Build a Cost model for electrical network (cabling) optimizaiton" ] }, { "cell_type": "code", "execution_count": 13, "id": "cf8e43f1", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:36:22.450240Z", "iopub.status.busy": "2025-09-17T16:36:22.450240Z", "iopub.status.idle": "2025-09-17T16:36:22.461847Z", "shell.execute_reply": "2025-09-17T16:36:22.461242Z", "shell.execute_reply.started": "2025-09-17T16:36:22.450240Z" }, "frozen": false }, "outputs": [], "source": [ "class WFNComponent(CostModelComponent):\n", " def __init__(self, turbines_pos, substations_pos, cables, router, borderC=boundary, **kwargs):\n", " self.wfn = WindFarmNetwork(\n", " turbinesC=turbines_pos,\n", " substationsC=substations_pos,\n", " cables=cables,\n", " router=router,)\n", " #borderC=boundary)\n", "\n", " def compute(x, y, xs, ys):\n", " self.wfn.optimize(turbinesC=np.column_stack((x, y)),\n", " substationsC=np.column_stack((xs, ys)),\n", " )\n", " return self.wfn.cost(), {\n", " 'network_length': self.wfn.length(),\n", " 'terse_links': self.wfn.terse_links(),\n", " }\n", "\n", " def compute_partials(x, y, xs, ys):\n", " grad_wt, grad_ss = self.wfn.gradient(\n", " turbinesC=np.column_stack((x, y)),\n", " substationsC=np.column_stack((xs, ys)),\n", " gradient_type='cost'\n", " )\n", " dc_dx, dc_dy = grad_wt[:, 0], grad_wt[:, 1]\n", " dc_dxss, dc_dyss = grad_ss[:, 0], grad_ss[:, 1]\n", " return [dc_dx, dc_dy, dc_dxss, dc_dyss]\n", "\n", " x_init, y_init = turbines_pos.T\n", " x_ss_init, y_ss_init = substations_pos.T\n", " super().__init__(\n", " input_keys=[('x', x_init), ('y', y_init),\n", " ('xs', x_ss_init), ('ys', y_ss_init)],\n", " n_wt=turbines_pos.shape[0],\n", " cost_function=compute,\n", " cost_gradient_function=compute_partials,\n", " objective=False,\n", " output_keys=[('cabling_cost', 0.0)],\n", " additional_output=[\n", " ('network_length', 0.0),\n", " ('terse_links', np.zeros(turbines_pos.shape[0])),\n", " ],\n", " **kwargs,\n", " )" ] }, { "cell_type": "markdown", "id": "a8689c00", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Initialize the `WFNComponent` with input data." ] }, { "cell_type": "code", "execution_count": 14, "id": "4957e085", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:36:23.803559Z", "iopub.status.busy": "2025-09-17T16:36:23.803559Z", "iopub.status.idle": "2025-09-17T16:36:23.808310Z", "shell.execute_reply": "2025-09-17T16:36:23.808310Z", "shell.execute_reply.started": "2025-09-17T16:36:23.803559Z" }, "frozen": false }, "outputs": [], "source": [ "network_cost_comp = WFNComponent(\n", " turbines_pos=turbines_pos,\n", " substations_pos=substations_pos,\n", " cables=cables,\n", " router=router,\n", ")" ] }, { "cell_type": "markdown", "id": "568faae0", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "### IRR - Internal Rate of Return component" ] }, { "cell_type": "code", "execution_count": 15, "id": "2c3854e5", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:36:24.484001Z", "iopub.status.busy": "2025-09-17T16:36:24.484001Z", "iopub.status.idle": "2025-09-17T16:36:24.490032Z", "shell.execute_reply": "2025-09-17T16:36:24.490032Z", "shell.execute_reply.started": "2025-09-17T16:36:24.484001Z" }, "frozen": false }, "outputs": [], "source": [ "fixed_economic_parameters = dict(\n", " rated_rpm_array=np.full((n_wt,), 12.0),\n", " D_rotor_array=np.full((n_wt,), wind_turbines.diameter()),\n", " Power_rated_array=np.full((n_wt,), wind_turbines.power(20.0)*1e-6),\n", " hub_height_array=np.full((n_wt,), wind_turbines.hub_height()),\n", " water_depth_array=np.full((n_wt,), 33.0),\n", ")" ] }, { "cell_type": "code", "execution_count": 16, "id": "bd403a6c", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:36:24.995360Z", "iopub.status.busy": "2025-09-17T16:36:24.995360Z", "iopub.status.idle": "2025-09-17T16:36:24.999389Z", "shell.execute_reply": "2025-09-17T16:36:24.998970Z", "shell.execute_reply.started": "2025-09-17T16:36:24.995360Z" }, "frozen": false }, "outputs": [], "source": [ "eco_eval = economic_evaluation(\n", " distance_from_shore=30, # [km]\n", " energy_price=0.06, # [€/kWh] revenue\n", " project_duration=25, # [years]\n", ")" ] }, { "cell_type": "code", "execution_count": 17, "id": "0935a04f", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:36:25.689519Z", "iopub.status.busy": "2025-09-17T16:36:25.689519Z", "iopub.status.idle": "2025-09-17T16:36:25.700028Z", "shell.execute_reply": "2025-09-17T16:36:25.700028Z", "shell.execute_reply.started": "2025-09-17T16:36:25.689519Z" }, "frozen": false }, "outputs": [], "source": [ "# Internal Rate of Return\n", "def calc_irr(AEP, cabling_cost, **kwargs):\n", " return eco_eval.calculate_irr(\n", " **fixed_economic_parameters,\n", " aep_array=np.full((n_wt,), AEP/n_wt*10**6),\n", " cabling_cost=cabling_cost,\n", " )" ] }, { "cell_type": "markdown", "id": "959d6073-0748-4aa1-b487-3b852a53b6f0", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "```python\n", "# Net Present Value\n", "def calc_npv(AEP, cabling_cost, **kwargs):\n", " return eco_eval.calculate_npv(\n", " **fixed_economic_parameters,\n", " aep_array=np.full((n_wt,), AEP/n_wt*10**6),\n", " cabling_cost=cabling_cost,\n", " )\n", "```" ] }, { "cell_type": "code", "execution_count": 18, "id": "687fbafc", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:36:26.959306Z", "iopub.status.busy": "2025-09-17T16:36:26.959306Z", "iopub.status.idle": "2025-09-17T16:36:26.965401Z", "shell.execute_reply": "2025-09-17T16:36:26.964381Z", "shell.execute_reply.started": "2025-09-17T16:36:26.959306Z" }, "frozen": false }, "outputs": [], "source": [ "# Economy\n", "npv_comp = CostModelComponent(\n", " input_keys=[\n", " ('AEP', 0),\n", " ('cabling_cost', 0)\n", " ],\n", " n_wt=n_wt,\n", " # cost_function=calc_npv,\n", " cost_function=calc_irr,\n", " objective=True,\n", " maximize=True,\n", " # output_keys=[('NPV', 0)],\n", " output_keys=[('IRR', 0)],\n", ")" ] }, { "cell_type": "markdown", "id": "b814e38f", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "## Build Topfarm problem" ] }, { "cell_type": "markdown", "id": "8215582c", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Defined a cost component using `TopFarmGroup` based on three cost components defined above." ] }, { "cell_type": "code", "execution_count": 19, "id": "a927f2dd-0cbc-429a-9bc4-8d94104cd94a", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:36:28.430991Z", "iopub.status.busy": "2025-09-17T16:36:28.430991Z", "iopub.status.idle": "2025-09-17T16:36:28.437678Z", "shell.execute_reply": "2025-09-17T16:36:28.436583Z", "shell.execute_reply.started": "2025-09-17T16:36:28.430991Z" }, "frozen": false }, "outputs": [], "source": [ "cost_comp = TopFarmGroup([\n", " aep_comp,\n", " network_cost_comp,\n", " npv_comp,\n", "])" ] }, { "cell_type": "markdown", "id": "d99a13bf", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Create a `TopFarmProblem` based on design variables (turbine positions), the defined cost component, constraints and driver." ] }, { "cell_type": "code", "execution_count": 20, "id": "16f137a5-ee3e-4e5d-8ad1-d9383e570c95", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:36:29.291948Z", "iopub.status.busy": "2025-09-17T16:36:29.291948Z", "iopub.status.idle": "2025-09-17T16:36:29.507863Z", "shell.execute_reply": "2025-09-17T16:36:29.506851Z", "shell.execute_reply.started": "2025-09-17T16:36:29.291948Z" }, "frozen": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO: checking out_of_order...\n", "INFO: out_of_order check complete (0.000944 sec).\n", "INFO: checking system...\n", "INFO: system check complete (0.000061 sec).\n", "INFO: checking solvers...\n", "INFO: solvers check complete (0.000336 sec).\n", "INFO: checking dup_inputs...\n", "INFO: dup_inputs check complete (0.000202 sec).\n", "INFO: checking missing_recorders...\n", "INFO: missing_recorders check complete (0.000006 sec).\n", "INFO: checking unserializable_options...\n", "INFO: unserializable_options check complete (0.000502 sec).\n", "INFO: checking comp_has_no_outputs...\n", "INFO: comp_has_no_outputs check complete (0.000175 sec).\n", "INFO: checking auto_ivc_warnings...\n", "INFO: auto_ivc_warnings check complete (0.000007 sec).\n" ] } ], "source": [ "tf_problem = TopFarmProblem(\n", " design_vars=dict(\n", " x=x_init,\n", " y=y_init,\n", " ),\n", " cost_comp=cost_comp,\n", " constraints=DistanceConstraintAggregation(\n", " XYBoundaryConstraint(boundary, 'polygon'),\n", " n_wt,\n", " min_wt_spacing,\n", " wind_turbines,\n", " ),\n", " driver=EasySGDDriver(maxiter=200),\n", " plot_comp=XYPlotComp(),\n", ")" ] }, { "cell_type": "markdown", "id": "ae40824d", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "## Run optimization." ] }, { "cell_type": "code", "execution_count": 21, "id": "6b9ec150-4ff0-4cd2-b92b-6a90e1d63472", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:36:37.558405Z", "iopub.status.busy": "2025-09-17T16:36:37.558405Z", "iopub.status.idle": "2025-09-17T16:38:18.808939Z", "shell.execute_reply": "2025-09-17T16:38:18.808939Z", "shell.execute_reply.started": "2025-09-17T16:36:37.558405Z" }, "frozen": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "INFO: checking out_of_order...\n", "INFO: out_of_order check complete (0.000328 sec).\n", "INFO: checking system...\n", "INFO: system check complete (0.000023 sec).\n", "INFO: checking solvers...\n", "INFO: solvers check complete (0.000211 sec).\n", "INFO: checking dup_inputs...\n", "INFO: dup_inputs check complete (0.000083 sec).\n", "INFO: checking missing_recorders...\n", "INFO: missing_recorders check complete (0.000008 sec).\n", "INFO: checking unserializable_options...\n", "INFO: unserializable_options check complete (0.000312 sec).\n", "INFO: checking comp_has_no_outputs...\n", "INFO: comp_has_no_outputs check complete (0.000072 sec).\n", "INFO: checking auto_ivc_warnings...\n", "INFO: auto_ivc_warnings check complete (0.000003 sec).\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "Ignoring fixed y limits to fulfill fixed data aspect with adjustable data limits.\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2025-09-17T18:36:41.763747\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.10.6, 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", " \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", " 0\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 1000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 2000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 3000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 4000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " −500\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 500\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 1000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 1500\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 2000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 2500\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", " \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", " \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", " \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", " \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", " \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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0) IRR 9.636092 (+0.00%)\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " Current position\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stderr", "output_type": "stream", "text": [ "Ignoring fixed y limits to fulfill fixed data aspect with adjustable data limits.\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Optimized in\t100.948s\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2025-09-17T18:38:18.733412\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.10.6, 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", " \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", " 0\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 1000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 2000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 3000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 4000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " −500\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 500\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 1000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 1500\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 2000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 2500\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", " \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", " \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", " \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", " \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", " \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", " \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", " \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", " \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", " \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", " \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", " \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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 199) IRR 10.458162 (+8.53%)\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " Initial position\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " Current position\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "cost, state, recorder = tf_problem.optimize(disp=True)" ] }, { "cell_type": "markdown", "id": "b8ff9e64", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "## Plotting\n", "In this section we monitor AEP, cabling_cost, IRR, and constraint_violation over time." ] }, { "cell_type": "code", "execution_count": 22, "id": "f66e5aa2-0efe-423b-8d0b-f80d9282d068", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:38:18.811671Z", "iopub.status.busy": "2025-09-17T16:38:18.811671Z", "iopub.status.idle": "2025-09-17T16:38:19.536805Z", "shell.execute_reply": "2025-09-17T16:38:19.536805Z", "shell.execute_reply.started": "2025-09-17T16:38:18.811671Z" }, "frozen": false }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2025-09-17T18:38:19.414138\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.10.6, 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", " \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", " 820\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 830\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 840\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 850\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 860\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", " 859\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " AEP\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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 3.08\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 3.10\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 3.12\n", " \n", " \n", " \n", " 1e7\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", " 3.09e+07\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " cabling_cost\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 20\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 40\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 60\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 80\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 100\n", " \n", " \n", " \n", " Time (seconds)\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 9.6\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 9.8\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 10.0\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 10.2\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 10.4\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", " 10.5\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " IRR\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 20\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 40\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 60\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 80\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 100\n", " \n", " \n", " \n", " Time (seconds)\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " −0.04\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " −0.02\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.00\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.02\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.04\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", " constraint_violation\n", " \n", " \n", " \n", " \n", " OptiWindNet's router: MILPRouter\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" } ], "source": [ "records_to_plot = ['AEP', 'cabling_cost', 'IRR', 'constraint_violation']\n", "time = recorder['timestamp'] - recorder['timestamp'][0]\n", "fig, axs = plt.subplots(math.ceil(len(records_to_plot)/2), 2, sharex=True, layout='constrained')\n", "\n", "\n", "pos_final_values = [\n", " dict(offset=(-10, -10), ha='right', va='top'),\n", " dict(offset=(-10, 10), ha='right', va='bottom'),\n", " dict(offset=(-10, -10), ha='right', va='top'),\n", " None\n", "]\n", "\n", "for i, (ax, key) in enumerate(zip(axs.ravel(), records_to_plot)):\n", " line, = ax.plot(time, recorder[key], label=key)\n", " ax.legend()\n", "\n", " pos = pos_final_values[i] if i < len(pos_final_values) else None\n", " if pos is None:\n", " break\n", "\n", " # last point\n", " x_last = line.get_xdata()[-1]\n", " y_last = line.get_ydata()[-1]\n", " ax.plot(x_last, y_last, 'o', ms=4)\n", "\n", " ax.annotate(\n", " f\"{y_last:.3g}\",\n", " xy=(x_last, y_last),\n", " xytext=pos['offset'], textcoords='offset points',\n", " ha=pos['ha'], va=pos['va'],\n", " arrowprops=dict(arrowstyle='->', lw=1),\n", " bbox=dict(boxstyle='round,pad=0.2', fc='white', ec='0.5', alpha=0.9),\n", " )\n", "\n", "# label bottom subplots\n", "for ax in axs[-1, :]:\n", " ax.set_xlabel(\"Time (seconds)\")\n", "\n", "router_name = type(router).__name__ \n", "sub_title = fig.suptitle(f\"OptiWindNet's router: {router_name}\")" ] }, { "cell_type": "markdown", "id": "d5c1c742", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Plot the optimized network for final wind farm layout." ] }, { "cell_type": "code", "execution_count": 23, "id": "4600e4a5-38e6-448b-85b8-9fb39872b9ea", "metadata": { "deletable": true, "editable": true, "execution": { "iopub.execute_input": "2025-09-17T16:43:16.229830Z", "iopub.status.busy": "2025-09-17T16:43:16.229433Z", "iopub.status.idle": "2025-09-17T16:43:16.334719Z", "shell.execute_reply": "2025-09-17T16:43:16.334719Z", "shell.execute_reply.started": "2025-09-17T16:43:16.229830Z" }, "frozen": false }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2025-09-17T18:43:16.318366\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.10.6, 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", " \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 = 20\n", " (+1) [-1]: 5\n", " Σλ = 14727.0 m\n", " 30934594 €\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "network_cost_comp.wfn.plot()" ] } ], "metadata": { "kernelspec": { "display_name": "«work312»", "language": "python", "name": "work312" }, "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.11" } }, "nbformat": 4, "nbformat_minor": 5 }