{ "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** (electrical-network routing) with **[TopFarm](https://gitlab.windenergy.dtu.dk/TOPFARM)** (turbine-layout optimization), and uses the standalone **`costmodels`** package for offshore CAPEX/OPEX and project finance.\n", "\n", "* **Three cost components**:\n", "\n", " * **Energy (AEP)**: PyWake evaluates farm energy for a set of wind directions and turbine coordinates.\n", " * **Electrical network (cabling)**: based on turbine/substation coordinates and the cable catalogue, `WFNComponent` (a TopFarm wrapper around OptiWindNet's `WindFarmNetwork`) returns cabling cost (and gradients).\n", " * **Economics (NPV)**: a `costmodels.project.Project` with a `Technology` backed by `DTUOffshoreCostModel` returns project NPV given AEP and cabling cost. NPV is the optimization **objective** (`maximize=True`); IRR, LCOE, CAPEX and OPEX are tracked as auxiliary outputs.\n", "\n", "* **Design variables** — the optimizer is free to move:\n", "\n", " * `x`, `y`: turbine coordinates (one entry per turbine)\n", " * `xs`, `ys`: **substation coordinates** — letting TopFarm co-locate the substation alongside the turbines reduces total cabling cost. The boundary constraint applies to turbines only; the substation is unconstrained, so its trajectory may exit the site polygon. The final substation position is shown in a dedicated trajectory plot at the end.\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 **NPV**, which TopFarm treats as the **objective**.\n", "3. We create a `TopFarmProblem` with the turbine **and substation** coordinates as design variables, the `TopFarmGroup` cost component, the layout constraints, and a driver.\n", "4. We call `optimize()` to run the optimization.\n", "\n", "Dataflow: `(turbine & substation positions) → AEP & cabling_cost → NPV (objective)`. Constraints keep the turbine layout feasible; the chosen OptiWindNet router controls the 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(buffer_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(buffer_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." ] }, { "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, "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, "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.cost_models.cost_model_wrappers import CostModelComponent\n", "from topfarm.cost_models.electrical.optiwindnet_wrapper import WFNComponent\n", "from topfarm.constraint_components.spacing import SpacingConstraint\n", "from topfarm.constraint_components.boundary import XYBoundaryConstraint\n", "from topfarm.constraint_components.constraint_aggregation import DistanceConstraintAggregation\n", "from topfarm.easy_drivers import EasySGDDriver\n", "from topfarm.plotting import XYPlotComp\n", "\n", "from optiwindnet.api import MILPRouter\n", "from optiwindnet.augmentation import poisson_disc_filler\n", "\n", "from costmodels.finance import Depreciation, Technology, Product\n", "from costmodels.project import Project\n", "from costmodels.models import DTUOffshoreCostModel" ] }, { "cell_type": "code", "execution_count": 3, "id": "fa470f55-6d31-4c67-b725-dfd632556219", "metadata": { "deletable": true, "editable": true, "frozen": false }, "outputs": [], "source": [ "%config InlineBackend.figure_formats = ['svg']\n", "plt.rcParams['svg.fonttype'] = 'none'" ] }, { "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, "frozen": false }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " image/svg+xml\n", " \n", " \n", " OptiWindNet\n", " \n", " \n", " \n", " \n", " \n", " \n", " \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, "frozen": false }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " image/svg+xml\n", " \n", " \n", " OptiWindNet\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \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, "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 (will also be a design variable in the optimization):" ] }, { "cell_type": "code", "execution_count": 7, "id": "eecc97ef-04a7-4150-a34b-319121f3a29a", "metadata": { "deletable": true, "editable": true, "frozen": false }, "outputs": [], "source": [ "x_ss_init, y_ss_init = (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, "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, "frozen": false }, "outputs": [], "source": [ "# number of wind directions to consider\n", "n_wd = 12\n", "\n", "wfm = Nygaard_2022(site=site, windTurbines=wind_turbines)\n", "\n", "aep_comp = PyWakeAEPCostModelComponent(\n", " windFarmModel=wfm,\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 'highs', 'cplex'\n", " time_limit=1, # seconds\n", " mip_gap=0.005, # relative optimality gap (0.5%)\n", " verbose=True\n", ")\n", "```\n", "\n", "> The selected `router` is passed to `WFNComponent`, which forwards it to OptiWindNet's `WindFarmNetwork`. 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 code above." ] }, { "cell_type": "code", "execution_count": 10, "id": "78c34067-18ff-4b35-9c53-caabaaebf5f7", "metadata": { "deletable": true, "editable": true, "frozen": false }, "outputs": [], "source": [ "router = MILPRouter(solver_name='ortools.cp_sat', 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, "frozen": false }, "outputs": [], "source": [ "turbines_pos = np.column_stack((x_init, y_init))\n", "substations_pos = np.column_stack(([x_ss_init], [y_ss_init]))" ] }, { "cell_type": "markdown", "id": "e5186b52", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Define cables. Each row is `[, ]`." ] }, { "cell_type": "code", "execution_count": 12, "id": "ce481bec", "metadata": { "deletable": true, "editable": true, "frozen": false }, "outputs": [], "source": [ "cables = np.array([(2, 2000), (5, 2200)])" ] }, { "cell_type": "markdown", "id": "a8689c00", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Build the cabling cost component using TopFarm's `WFNComponent` (a thin wrapper around OptiWindNet's `WindFarmNetwork` exposing cost and gradient hooks). The component takes turbine and substation coordinates as inputs and outputs `cabling_cost` (plus the network length and the topology in `terse_links`)." ] }, { "cell_type": "code", "execution_count": 13, "id": "4957e085", "metadata": { "deletable": true, "editable": true, "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": [ "### Economic component (NPV objective)\n", "\n", "We use the `costmodels` package to assemble a single-technology offshore wind project. `DTUOffshoreCostModel` provides the technology-level CAPEX/OPEX cost model; `Technology` wraps it with finance parameters (lifetime, WACC, OPEX, product); `Project` aggregates technologies into a project that can compute NPV (and IRR, LCOE, CAPEX, OPEX as auxiliaries).\n", "\n", "We expose an `economic_func` (and its analytical gradient `economic_func_grad`) that takes `AEP` (in GWh) and `cabling_cost` (in €) and returns the project NPV. The cabling cost is fed in as `shared_capex` so it adds to the project CAPEX. Water depth is held fixed across the site for this simple example." ] }, { "cell_type": "code", "execution_count": 14, "id": "2c3854e5", "metadata": { "deletable": true, "editable": true, "frozen": false }, "outputs": [], "source": [ "LIFETIME = 25 # [years]\n", "EL_PRICE = 60 # [€/MWh] flat PPA price (matches the original notebook's 0.06 €/kWh)\n", "WATER_DEPTH = 33.0 # [m] uniform site water depth\n", "DISTANCE_FROM_SHORE = 30 # [km] (informational — not used by DTUOffshoreCostModel directly)\n", "\n", "RP_MW = float(wind_turbines.power(20.0)) * 1e-6 # rated power [MW]\n", "\n", "# Reference AEP (used only to seed the model with a plausible capacity factor)\n", "_simres_ref = wfm(x_init, y_init)\n", "_aep_ref_gwh = float(_simres_ref.aep().values.sum())\n", "CF_ref = _aep_ref_gwh * 1e3 / (RP_MW * 24 * 365 * n_wt)\n", "\n", "cost_model = DTUOffshoreCostModel(\n", " rated_power=RP_MW,\n", " rotor_speed=12.0,\n", " rotor_diameter=wind_turbines.diameter(),\n", " hub_height=wind_turbines.hub_height(),\n", " lifetime=LIFETIME,\n", " capacity_factor=CF_ref,\n", " nwt=n_wt,\n", " profit=0,\n", ")\n", "\n", "wind_plant = Technology(\n", " name='wind',\n", " lifetime=LIFETIME,\n", " product=Product.SPOT_ELECTRICITY,\n", " opex=12600 * n_wt * RP_MW + 1.35 * _aep_ref_gwh * 1000, # [€/yr]\n", " wacc=0.06,\n", " cost_model=cost_model,\n", ")\n", "\n", "project = Project(\n", " technologies=[wind_plant],\n", " product_prices={Product.SPOT_ELECTRICITY: EL_PRICE},\n", " depreciation=Depreciation(rate=(0, 1), year=(0, LIFETIME)),\n", ")\n", "\n", "water_depth_array = np.full((n_wt,), WATER_DEPTH)" ] }, { "cell_type": "code", "execution_count": 15, "id": "0935a04f", "metadata": { "deletable": true, "editable": true, "frozen": false }, "outputs": [], "source": [ "def economic_func(AEP, cabling_cost, **kwargs):\n", " aep_mwh = AEP * 1e3 # GWh → MWh\n", " npv, aux = project.npv(\n", " productions={wind_plant.name: aep_mwh},\n", " cost_model_args={\n", " wind_plant.name: {'water_depth': water_depth_array, 'aep': aep_mwh},\n", " },\n", " finance_args={'shared_capex': cabling_cost},\n", " return_aux=True,\n", " )\n", " return npv, {\n", " 'IRR': aux['IRR'],\n", " 'LCOE': aux['LCOE'][0],\n", " 'CAPEX': aux['CAPEX'],\n", " 'OPEX': float(np.mean(aux['OPEX'])),\n", " }\n", "\n", "\n", "def economic_func_grad(AEP, cabling_cost, **kwargs):\n", " aep_mwh = AEP * 1e3\n", " grad = project.npv_grad(\n", " productions={wind_plant.name: aep_mwh},\n", " cost_model_args={\n", " wind_plant.name: {'water_depth': water_depth_array, 'aep': aep_mwh},\n", " },\n", " finance_args={'shared_capex': cabling_cost},\n", " )\n", " return (\n", " grad[0][wind_plant.name] * 1e3, # dNPV/dAEP (chain rule for GWh→MWh)\n", " grad[2]['shared_capex'], # dNPV/dCablingCost\n", " )" ] }, { "cell_type": "code", "execution_count": 16, "id": "687fbafc", "metadata": { "deletable": true, "editable": true, "frozen": false }, "outputs": [], "source": [ "npv_comp = CostModelComponent(\n", " input_keys=[('AEP', 0.0), ('cabling_cost', 0.0)],\n", " n_wt=n_wt,\n", " cost_function=economic_func,\n", " cost_gradient_function=economic_func_grad,\n", " output_keys=[('NPV', 0.0)],\n", " additional_output=[\n", " ('IRR', 0.0),\n", " ('LCOE', 0.0),\n", " ('CAPEX', 0.0),\n", " ('OPEX', 0.0),\n", " ],\n", " objective=True,\n", " maximize=True,\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": [ "Combine the three cost components in a `TopFarmGroup`." ] }, { "cell_type": "code", "execution_count": 17, "id": "a927f2dd-0cbc-429a-9bc4-8d94104cd94a", "metadata": { "deletable": true, "editable": true, "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 the `TopFarmProblem`. Design variables are the turbine coordinates `x`, `y` **and** the substation coordinates `xs`, `ys` — letting the optimizer co-locate the substation reduces total cabling cost." ] }, { "cell_type": "code", "execution_count": 18, "id": "16f137a5-ee3e-4e5d-8ad1-d9383e570c95", "metadata": { "deletable": true, "editable": true, "frozen": false }, "outputs": [], "source": [ "tf_problem = TopFarmProblem(\n", " design_vars=dict(\n", " x=x_init,\n", " y=y_init,\n", " xs=x_ss_init,\n", " ys=y_ss_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=500),\n", " plot_comp=XYPlotComp(),\n", ")" ] }, { "cell_type": "markdown", "id": "ae40824d", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "## Run optimization\n", "\n", "We use `maxiter=10` here so the example runs quickly end-to-end. Increase it for a meaningful optimization." ] }, { "cell_type": "code", "execution_count": 19, "id": "6b9ec150-4ff0-4cd2-b92b-6a90e1d63472", "metadata": { "deletable": true, "editable": true, "frozen": false }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " image/svg+xml\n", " \n", " \n", " OptiWindNet\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \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", " 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", " 3000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 3500\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) NPV 186422590.535065 (+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": "stdout", "output_type": "stream", "text": [ "Optimized in\t141.805s\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " image/svg+xml\n", " \n", " \n", " OptiWindNet\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \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", " 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", " 3000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 3500\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", " 499) NPV 223826279.055338 (+20.06%)\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", "Track AEP, cabling cost, NPV, IRR and constraint violation over time." ] }, { "cell_type": "code", "execution_count": 20, "id": "f66e5aa2-0efe-423b-8d0b-f80d9282d068", "metadata": { "deletable": true, "editable": true, "frozen": false }, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " image/svg+xml\n", " \n", " \n", " OptiWindNet\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \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", " 840\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", " 866\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", " 2.9\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 3.0\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 3.1\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", " 2.9e+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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 2.0\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 2.2\n", " \n", " \n", " \n", " 1e8\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 2.24e+08\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " NPV\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \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.1075\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.1100\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.1125\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.1150\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \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.115\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", " 50\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.05\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.00\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 0.05\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \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", " 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", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "records_to_plot = ['AEP', 'cabling_cost', 'NPV', 'IRR', 'constraint_violation']\n", "time = recorder['timestamp'] - recorder['timestamp'][0]\n", "n_rows = math.ceil(len(records_to_plot) / 2)\n", "fig, axs = plt.subplots(n_rows, 2, sharex=True, layout='constrained')\n", "\n", "for ax, key in zip(axs.ravel(), records_to_plot):\n", " line, = ax.plot(time, recorder[key], label=key)\n", " ax.legend()\n", " x_last, y_last = line.get_xdata()[-1], line.get_ydata()[-1]\n", " ax.plot(x_last, y_last, 'o', ms=4)\n", " ax.annotate(\n", " f'{y_last:.3g}',\n", " xy=(x_last, y_last),\n", " xytext=(-10, -10), textcoords='offset points',\n", " ha='right', va='top',\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", "# hide any unused axes (when records_to_plot is odd)\n", "for ax in axs.ravel()[len(records_to_plot):]:\n", " ax.set_visible(False)\n", "\n", "for ax in axs[-1, :]:\n", " ax.set_xlabel('Time (seconds)')\n", "\n", "fig.suptitle(f\"OptiWindNet's router: {type(router).__name__}\");" ] }, { "cell_type": "markdown", "id": "4b15adee", "metadata": {}, "source": [ "### Substation trajectory\n", "\n", "Because `xs`, `ys` are design variables (and unconstrained by the site polygon), the substation drifts during optimization. The plot below overlays the site boundary, the **initial** substation position (open marker), the **final** position (filled marker), and the **path** the optimizer took, plus the final turbine layout for context." ] }, { "cell_type": "code", "execution_count": 21, "id": "6ea3c579", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " image/svg+xml\n", " \n", " \n", " OptiWindNet\n", " \n", " \n", " \n", " \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", " 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", " 3000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 3500\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 4000\n", " \n", " \n", " \n", " x [m]\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", " 250\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 500\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 750\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 1000\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 1250\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 1500\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 1750\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " 2000\n", " \n", " \n", " \n", " y [m]\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " Substation moved 837 m over 500 iterations\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " site boundary\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " turbines (initial)\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " turbines (final)\n", " \n", " \n", " \n", " \n", " \n", " SS path\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " SS initial\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " SS final\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "xs_path = np.asarray(recorder['xs']).reshape(-1)\n", "ys_path = np.asarray(recorder['ys']).reshape(-1)\n", "x_final = np.asarray(recorder['x'])[-1].reshape(-1)\n", "y_final = np.asarray(recorder['y'])[-1].reshape(-1)\n", "\n", "fig, ax = plt.subplots(layout='constrained')\n", "ax.set_aspect('equal')\n", "\n", "# site boundary\n", "ax.plot(\n", " np.r_[boundary[:, 0], boundary[0, 0]],\n", " np.r_[boundary[:, 1], boundary[0, 1]],\n", " color='0.5', lw=1, label='site boundary',\n", ")\n", "\n", "# turbines (initial vs final)\n", "ax.plot(x_init, y_init, '.', color='C0', alpha=0.4, label='turbines (initial)')\n", "ax.plot(x_final, y_final, '.', color='C0', label='turbines (final)')\n", "\n", "# substation trajectory\n", "ax.plot(xs_path, ys_path, '-', color='C3', lw=1, alpha=0.6, label='SS path')\n", "ax.plot(xs_path[0], ys_path[0], 'o', mfc='none', mec='C3', ms=8, label='SS initial')\n", "ax.plot(xs_path[-1], ys_path[-1], 'o', mfc='C3', mec='C3', ms=8, label='SS final')\n", "\n", "ax.set_xlabel('x [m]')\n", "ax.set_ylabel('y [m]')\n", "ax.legend(loc='upper right', fontsize='small', framealpha=0.9)\n", "ax.set_title(\n", " f'Substation moved {np.hypot(xs_path[-1] - xs_path[0], ys_path[-1] - ys_path[0]):.0f} m '\n", " f'over {len(xs_path)} iterations'\n", ");" ] }, { "cell_type": "markdown", "id": "d5c1c742", "metadata": { "deletable": true, "editable": true, "frozen": false }, "source": [ "Plot the optimized network for the final wind farm layout." ] }, { "cell_type": "code", "execution_count": 22, "id": "4600e4a5-38e6-448b-85b8-9fb39872b9ea", "metadata": { "deletable": true, "editable": true, "frozen": false }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " image/svg+xml\n", " \n", " \n", " OptiWindNet\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \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", " Σλ = 13 876 m\n", " 29 000 953 €\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": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }