{
"cells": [
{
"cell_type": "markdown",
"id": "6ef7c0e8",
"metadata": {},
"source": [
"# Borders, Obstacles & Buffering"
]
},
{
"cell_type": "markdown",
"id": "ed13a6b8-1d5b-4ce0-9cd4-b81bac7b07dd",
"metadata": {},
"source": [
"**OptiWindNet** models site bounds with an exterior **border** (allowed area) and interior **obstacles** (exclusion zones), and keeps electrical network design compliant with the bounds. This notebook walks from initialization and optimization under constraints, through cleaning and buffering the geometry, to validating turbine/substation placement. We will explore following features (methods):\n",
"\n",
"* **Optimize under bounds**: `.optimize()`: optimize electrical network while enforcing border/obstacle constraints.\n",
"* **Merge & clean boundary**: `.merge_obstacles_into_border()`: resolve intersections/touching constraints, absorb irrelevant obstacles, and simplify the exterior boundary.\n",
"* **Buffer geometry**: `.add_buffer(buffer_dist=...)`: expand the border and shrink obstacles to add safety margins; might smooth concavities or remove obstacles depending on `buffer_dist`.\n",
"* **Compare originals vs buffered**: `.plot_original_vs_buffered()`: overlay plot of border/obstacles before and after buffering to inspect how buffering changed shapes.\n",
"* **Validate placement**: `.optimize()` raises a `ValueError` if any turbine or substation lies outside the border or inside an obstacle."
]
},
{
"cell_type": "markdown",
"id": "1998fe8b",
"metadata": {},
"source": [
"Import required packages"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "b0f61785-6be5-4a0f-8282-5c17f4cc28ad",
"metadata": {},
"outputs": [],
"source": [
"from optiwindnet.api import WindFarmNetwork\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "fc07dcc1",
"metadata": {},
"outputs": [],
"source": [
"# Display figures as SVG in Jupyter notebooks\n",
"%config InlineBackend.figure_formats = ['svg']"
]
},
{
"cell_type": "markdown",
"id": "f624996b",
"metadata": {},
"source": [
"## Borders and obstacles"
]
},
{
"cell_type": "markdown",
"id": "9779f2b9",
"metadata": {},
"source": [
"### 1. Initialize\n",
"Create a `WindFarmNetwork` instances from an `osm.pbf´ file and optimize it."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "4e702047",
"metadata": {},
"outputs": [],
"source": [
"wfn1 = WindFarmNetwork.from_pbf(filepath='data/DTU_letters.osm.pbf', cables=7)\n"
]
},
{
"cell_type": "markdown",
"id": "cf12b38e",
"metadata": {},
"source": [
"Plot the location geometry and the optimized network to confirm that the borders and obstacles are properly initialized and considered for optimization."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "142de4c6",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
""
],
"text/plain": [
""
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"wfn1"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "00e76735",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"40"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"wfn1.L.graph['T']"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "07bf9dea",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
""
],
"text/plain": [
""
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"res_optimize = wfn1.optimize()\n",
"wfn1"
]
},
{
"cell_type": "markdown",
"id": "669f3a6a",
"metadata": {},
"source": [
"> Note: OptiWindNet works properly without borders/obstacles also."
]
},
{
"cell_type": "markdown",
"id": "af7ea1dd",
"metadata": {},
"source": [
"### 2. Obstacles intersecting with the exterior borders"
]
},
{
"cell_type": "markdown",
"id": "2a3bf5dd",
"metadata": {},
"source": [
"Let's create an instance of WindFarmNetwork directly via coordinates to have full control over border/obstacle definition."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "bfeef076",
"metadata": {},
"outputs": [],
"source": [
"turbinesC = np.array([\n",
" [ 316976, 6175410],\n",
" [ 316998, 6175380],\n",
" [ 316965, 6175368],\n",
" [ 316997, 6175347],\n",
" [ 316967 , 6175314],\n",
" [ 317009 , 6175267],\n",
" [ 317044, 6175282],\n",
" [ 316994 , 6175296],\n",
" [ 317065, 6175335],\n",
" [ 317030, 6175318],\n",
" [ 317029 , 6175366],\n",
" [ 317026, 6175401],\n",
" [ 317057, 6175379],\n",
" [ 317085, 6175354],\n",
" [ 317068, 6175406],\n",
" [ 317144, 6175403],\n",
" [ 317115, 6175342],\n",
" [ 317091, 6175320],\n",
" [ 317240, 6175259],\n",
" [ 317111 , 6175293],\n",
" [ 317126, 6175262],\n",
" [ 317152, 6175320],\n",
" [ 317102, 6175385],\n",
" [ 317179, 6175383],\n",
" [ 317143, 6175367],\n",
" [ 317223, 6175399],\n",
" [ 317222, 6175358],\n",
" [ 317185, 6175347],\n",
" [ 317208, 6175323],\n",
" [ 317188, 6175299],\n",
" [ 317205, 6175270],\n",
" [ 317231, 6175296],\n",
" [ 317272, 6175287],\n",
" [ 317244, 6175340],\n",
" [ 317283, 6175321],\n",
" [ 317285, 6175360],\n",
" [ 316960, 6175270],\n",
" [ 317253, 6175380],\n",
" [ 317287, 6175397],\n",
" [ 317152, 6175286],\n",
" ])\n",
"\n",
"borderC = np.array([\n",
" [316956, 6175267],\n",
" [317022, 6175265],\n",
" [317043, 6175275],\n",
" [317059 , 6175295],\n",
" [317068, 6175322],\n",
" [317069, 6175349],\n",
" [317090, 6175348],\n",
" [317086, 6175262],\n",
" [317153, 6175259],\n",
" [317157, 6175346],\n",
" [317178 , 6175345],\n",
" [317177, 6175316],\n",
" [317184, 6175289],\n",
" [317199, 6175268],\n",
" [317218, 6175256],\n",
" [317240, 6175256],\n",
" [317260, 6175266],\n",
" [317276, 6175286],\n",
" [317285, 6175312],\n",
" [317289, 6175399],\n",
" [317242, 6175401],\n",
" [317239, 6175319],\n",
" [317238, 6175312],\n",
" [317235, 6175308],\n",
" [317232, 6175305],\n",
" [317229, 6175305],\n",
" [317226, 6175308],\n",
" [317224, 6175313],\n",
" [317223, 6175319],\n",
" [317227, 6175402],\n",
" [316963, 6175413],\n",
" ])\n",
"\n",
"obstaclesC_ = [\n",
" np.array([\n",
" [316998, 6175301],\n",
" [317001 , 6175376],\n",
" [317013, 6175375],\n",
" [317021, 6175369],\n",
" [317026, 6175359],\n",
" [317029, 6175345],\n",
" [317029, 6175330],\n",
" [317025, 6175316],\n",
" [317019, 6175306],\n",
" [317011, 6175301],\n",
" ]),\n",
" np.array([\n",
" [316920, 6175300],\n",
" [316920, 6175400],\n",
" [316950, 6175400],\n",
" [316950, 6175300],\n",
" ]),\n",
" np.array([\n",
" [317100, 6175450],\n",
" [317100, 6175400],\n",
" [317080, 6175400],\n",
" [317080, 6175450],\n",
" ]),\n",
" ]\n",
"\n",
"substationsC = np.array([\n",
" [317167 , 6175351]])"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "e74498a4",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
""
],
"text/plain": [
""
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"wfn2 = WindFarmNetwork(turbinesC=turbinesC, substationsC=substationsC, borderC=borderC, obstacleC_=obstaclesC_, cables=7)\n",
"wfn2"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "955c4d56",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
""
],
"text/plain": [
""
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"res_optimize = wfn2.optimize()\n",
"wfn2"
]
},
{
"cell_type": "markdown",
"id": "ca61cc80",
"metadata": {},
"source": [
"We can use `.merge_obstacles_into_border()` to refine the borders, simplify the boundary constraints and remove irrelevant obstacles."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "19171ff2",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Obstacle at index 1 is completely outside the border and is neglected.\n",
"Obstacle at index 2 intersects with the exteriour border and is merged into the exterior border.\n"
]
},
{
"data": {
"image/svg+xml": [
""
],
"text/plain": [
""
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"wfn2.merge_obstacles_into_border()\n",
"res_optimize = wfn2.optimize()\n",
"wfn2"
]
},
{
"cell_type": "markdown",
"id": "94cee1a3",
"metadata": {},
"source": [
"## Buffering\n",
"After initializing the WindFarmNetwork, a buffer can be applied to borders and obstacles via the `.add_buffer()` method. The exterior border will be expanded, while interior obstacles will be shrunk accordingly."
]
},
{
"cell_type": "markdown",
"id": "720bd420",
"metadata": {},
"source": [
"Initialize `WindFarmNetwork` instance"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "8b234771",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
""
],
"text/plain": [
""
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"wfn3 = WindFarmNetwork.from_pbf(filepath='data/DTU_letters.osm.pbf', cables=7)\n",
"res_optimize = wfn3.optimize()\n",
"wfn3"
]
},
{
"cell_type": "markdown",
"id": "b4f7bcce",
"metadata": {},
"source": [
"apply buffering to `wfn3`."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "308c5173",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"The defined border is non-convex and buffering may introduce unexpected changes. For visual comparison use plot_original_vs_buffered().\n"
]
},
{
"data": {
"image/svg+xml": [
""
],
"text/plain": [
""
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"wfn3.add_buffer(buffer_dist=5)\n",
"res_optimize = wfn3.optimize()\n",
"wfn3"
]
},
{
"cell_type": "markdown",
"id": "edb3d90d-5939-437f-b286-a06ed0bd945d",
"metadata": {},
"source": [
"Original vs buffered border/obstacles can be visualized using plot_original_vs_buffered()"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "52dac514",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"wfn3.plot_original_vs_buffered();"
]
},
{
"cell_type": "markdown",
"id": "2afef74e",
"metadata": {},
"source": [
"### Removal of a concavity\n",
"In this case a message is printed out, providing information about the potential changes in the border."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "2bd324eb",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"The defined border is non-convex and buffering may introduce unexpected changes. For visual comparison use plot_original_vs_buffered().\n"
]
}
],
"source": [
"wfn4 = WindFarmNetwork.from_pbf(filepath='data/DTU_letters.osm.pbf', cables=7)\n",
"wfn4.add_buffer(buffer_dist=10)"
]
},
{
"cell_type": "markdown",
"id": "5d481a21",
"metadata": {},
"source": [
"Plotting original vs buffered borders confirms that one of the concavities is removed after buffering."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "c583e0d2",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"wfn4.plot_original_vs_buffered();"
]
},
{
"cell_type": "markdown",
"id": "fbbe3176",
"metadata": {},
"source": [
"**Optimize**\n",
"\n",
"The optimized network might change and use new routes after buffering."
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "1806e2b5",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
""
],
"text/plain": [
""
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"res_optimize = wfn4.optimize()\n",
"wfn4"
]
},
{
"cell_type": "markdown",
"id": "f3f5d1ac",
"metadata": {},
"source": [
"### Removal of an obstacle"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "319644b3",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"The defined border is non-convex and buffering may introduce unexpected changes. For visual comparison use plot_original_vs_buffered().\n",
"Buffering by 15.00 completely removed the obstacle at index 0. For visual comparison use plot_original_vs_buffered().\n"
]
}
],
"source": [
"wfn5 = WindFarmNetwork.from_pbf(filepath='data/DTU_letters.osm.pbf', cables=7)\n",
"wfn5.add_buffer(buffer_dist=15)"
]
},
{
"cell_type": "markdown",
"id": "1882cf0a",
"metadata": {},
"source": [
"Plotting original vs buffered borders confirms that obstacle (inside D letter) is removed after buffering."
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "05465845",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"wfn5.plot_original_vs_buffered();"
]
},
{
"cell_type": "markdown",
"id": "82ab5351",
"metadata": {},
"source": [
"**Optimize**\n",
"\n",
"The optimized network might change and use new routes after buffering."
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "12b7adf6",
"metadata": {},
"outputs": [
{
"data": {
"image/svg+xml": [
""
],
"text/plain": [
""
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"wfn5.optimize()\n",
"wfn5"
]
},
{
"cell_type": "markdown",
"id": "1c43d62a",
"metadata": {},
"source": [
"### Turbines outside the border or inside the obstacles\n",
"\n",
"When `wfn.optimize()` is called, it first checks if the all turbines are within the allowed area and not inside any exclusion zone. A **ValueError** exception is raised if this is not the case."
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "3fdb9fbc",
"metadata": {},
"outputs": [],
"source": [
"turbinesC = np.array([\n",
" [0, 0],\n",
" [ 316976, 6175410],\n",
" [ 316998, 6175380],\n",
" ])\n",
"\n",
"borderC = np.array([\n",
" [316956, 6175267],\n",
" [317022, 6175265],\n",
" [317043, 6175275],\n",
" [317059 , 6175295],\n",
" [317068, 6175322],\n",
" [317069, 6175349],\n",
" [317090, 6175348],\n",
" [317086, 6175262],\n",
" [317153, 6175259],\n",
" [317157, 6175346],\n",
" [317178 , 6175345],\n",
" [317177, 6175316],\n",
" [317184, 6175289],\n",
" [317199, 6175268],\n",
" [317218, 6175256],\n",
" [317240, 6175256],\n",
" [317260, 6175266],\n",
" [317276, 6175286],\n",
" [317285, 6175312],\n",
" [317289, 6175399],\n",
" [317242, 6175401],\n",
" [317239, 6175319],\n",
" [317238, 6175312],\n",
" [317235, 6175308],\n",
" [317232, 6175305],\n",
" [317229, 6175305],\n",
" [317226, 6175308],\n",
" [317224, 6175313],\n",
" [317223, 6175319],\n",
" [317227, 6175402],\n",
" [316963, 6175413],\n",
" ])\n",
"\n",
"substationsC = np.array([\n",
" [317167 , 6175351]])"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "cc1b2043-8b01-46fa-b0b4-387bd110a959",
"metadata": {},
"outputs": [],
"source": [
"wfn6 = WindFarmNetwork(\n",
" turbinesC=turbinesC,\n",
" substationsC=substationsC,\n",
" borderC=borderC,\n",
" obstacleC_=obstaclesC_,\n",
" cables=7,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "a4757a44",
"metadata": {
"tags": [
"raises-exception"
]
},
"outputs": [
{
"ename": "ValueError",
"evalue": "Turbine out of bounds!",
"output_type": "error",
"traceback": [
"\u001b[31m---------------------------------------------------------------------------\u001b[39m",
"\u001b[31mValueError\u001b[39m Traceback (most recent call last)",
"\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[23]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m wfn6.optimize()\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/docs/git/OptiWindNet.git/build/__editable__.optiwindnet-0.2.2.dev82+g4d9174546-py3-none-any/optiwindnet/api.py:602\u001b[39m, in \u001b[36mWindFarmNetwork.optimize\u001b[39m\u001b[34m(self, turbinesC, substationsC, router, verbose)\u001b[39m\n\u001b[32m 598\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 599\u001b[39m warmstart = {}\n\u001b[32m 601\u001b[39m \u001b[38;5;28mself\u001b[39m._S, \u001b[38;5;28mself\u001b[39m._G = router.route(\n\u001b[32m--> \u001b[39m\u001b[32m602\u001b[39m P=\u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mP\u001b[39;49m,\n\u001b[32m 603\u001b[39m A=\u001b[38;5;28mself\u001b[39m.A,\n\u001b[32m 604\u001b[39m cables=\u001b[38;5;28mself\u001b[39m.cables,\n\u001b[32m 605\u001b[39m cables_capacity=\u001b[38;5;28mself\u001b[39m.cables_capacity,\n\u001b[32m 606\u001b[39m verbose=verbose,\n\u001b[32m 607\u001b[39m **warmstart,\n\u001b[32m 608\u001b[39m )\n\u001b[32m 609\u001b[39m \u001b[38;5;28mself\u001b[39m._is_stale_SG = \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[32m 611\u001b[39m terse_links = \u001b[38;5;28mself\u001b[39m.terse_links()\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/docs/git/OptiWindNet.git/build/__editable__.optiwindnet-0.2.2.dev82+g4d9174546-py3-none-any/optiwindnet/api.py:267\u001b[39m, in \u001b[36mWindFarmNetwork.P\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 265\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mTriangular mesh over `L` (navigation mesh).\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m 266\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._is_stale_PA:\n\u001b[32m--> \u001b[39m\u001b[32m267\u001b[39m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_refresh_planar\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 268\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._P\n",
"\u001b[36mFile \u001b[39m\u001b[32m~/docs/git/OptiWindNet.git/build/__editable__.optiwindnet-0.2.2.dev82+g4d9174546-py3-none-any/optiwindnet/api.py:226\u001b[39m, in \u001b[36mWindFarmNetwork._refresh_planar\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 222\u001b[39m out_of_bounds -= obstacle.exterior\n\u001b[32m 223\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m out_of_bounds.is_empty:\n\u001b[32m 224\u001b[39m \u001b[38;5;66;03m# TODO: if relevant, get coordinates of turbines from out_of_bounds\u001b[39;00m\n\u001b[32m 225\u001b[39m \u001b[38;5;66;03m# print(list(out_of_bounds.geoms))\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m226\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[33m'\u001b[39m\u001b[33mTurbine out of bounds!\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m 227\u001b[39m \u001b[38;5;28mself\u001b[39m._P, \u001b[38;5;28mself\u001b[39m._A = make_planar_embedding(\u001b[38;5;28mself\u001b[39m._L)\n\u001b[32m 228\u001b[39m \u001b[38;5;28mself\u001b[39m._is_stale_PA = \u001b[38;5;28;01mFalse\u001b[39;00m\n",
"\u001b[31mValueError\u001b[39m: Turbine out of bounds!"
]
}
],
"source": [
"wfn6.optimize()"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}