{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Layout optimization" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "This example demonstrates some basics about running wind farm optimization tasks with `foxes`. All optimizations use the [iwopy](https://github.com/FraunhoferIWES/iwopy) interface in the background (also by Fraunhofer IWES, see link for details). \n", "\n", "In the following we invoke the optimization library [pymoo](https://pymoo.org/) which contains a number of very nice genetic algorithm implementations. Within `foxes` we do that implicitely via the `iwopy` interface.\n", "\n", "These are the required imports for this example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from iwopy.interfaces.pymoo import Optimizer_pymoo\n", "\n", "import foxes\n", "import foxes.variables as FV\n", "import foxes.utils.geom2d as gm\n", "from foxes.opt.problems.layout import FarmLayoutOptProblem\n", "from foxes.opt.constraints import FarmBoundaryConstraint, MinDistConstraint\n", "from foxes.opt.objectives import MaxFarmPower" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "In the following we are tackling the problem of optimizing a wind farm layout for a site near Bremen, Germany. The data of a (coarse) wind rose with 216 states is provided as static data file with name `\"wind_rose_bremen.csv\"`:\n", "```\n", "state,wd,ws,weight\n", "0,0.0,3.5,0.00158\n", "1,0.0,6.0,0.00244\n", "2,0.0,8.5,0.00319\n", "3,0.0,12.5,0.0036700002\n", "4,0.0,17.5,0.00042\n", "...\n", "```\n", "First, let's create the states object and have a look at the wind rose:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "states = foxes.input.states.StatesTable(\n", " data_source=\"wind_rose_bremen.csv\",\n", " output_vars=[FV.WS, FV.WD, FV.TI, FV.RHO],\n", " var2col={FV.WS: \"ws\", FV.WD: \"wd\", FV.WEIGHT: \"weight\"},\n", " fixed_vars={FV.RHO: 1.225, FV.TI: 0.05},\n", ")\n", "\n", "o = foxes.output.StatesRosePlotOutput(states, point=[0., 0., 100.])\n", "fig = o.get_figure(16, FV.AMB_WS, [0, 3.5, 6, 10, 15, 20], figsize=(6, 6))\n", "plt.show()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Next, we need to specify the area within which the turbines are allowed to move during optimization. We use the `foxes.utils.geom2d` sub-package for that purpose (imported as `gm`, see above) which allows us to add and subtract polygons, circles, etc." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "boundary = \\\n", " gm.ClosedPolygon(np.array(\n", " [[0, 0], [0, 1200], [1000, 800], [900, -200]], dtype=np.float64)) \\\n", " + gm.ClosedPolygon(np.array(\n", " [[500, 0], [500, 1500], [1000, 1500], [1000, 0]], dtype=np.float64)) \\\n", " - gm.Circle([-100., -100.], 700)\n", "\n", "fig, ax = plt.subplots()\n", "boundary.add_to_figure(ax)\n", "plt.show()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Later on we wish to apply boundary constraints that make sure all turbines are placed within this area geometry. These conditions make use of the minimal distance calculation from each point in question to the boundary. We can check the results by plotting again, now using the `fill_mode` option:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, axs = plt.subplots(1, 2, figsize=(14, 8))\n", "boundary.add_to_figure(axs[0], fill_mode=\"dist_inside\")\n", "boundary.add_to_figure(axs[1], fill_mode=\"dist_outside\")\n", "plt.show()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "We now setup the model book and a wind farm with 10 turbines in some initial layout, including the boundary:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mbook = foxes.models.ModelBook()\n", "\n", "farm = foxes.WindFarm(boundary=boundary)\n", "foxes.input.farm_layout.add_row(\n", " farm=farm,\n", " xy_base=np.array([500.0, 500.0]),\n", " xy_step=np.array([50.0, 50.0]),\n", " n_turbines=10,\n", " turbine_models=[\"layout_opt\", \"NREL5MW\"],\n", ")\n", "\n", "ax = foxes.output.FarmLayoutOutput(farm).get_figure()\n", "plt.show()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Notice the appearing turbine model `layout_opt`. This is not part of the model book but will be defined shortly by the optimization problem. In the context of the turbine models it defines where in the model order the optimization variables application should be applied. In our case we are optimizing the (X, Y)-coordinates of the turbines, and they should be updated at the very beginning.\n", "\n", "Let's new define the algorithm and the layout optimization problem. The latter should include boundary constraints and a minimal distance of 2 rotor diameters between turbines. Our objective is the maximization of the total wind farm power:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "algo = foxes.algorithms.Downwind(\n", " mbook,\n", " farm,\n", " states=states,\n", " rotor_model=\"centre\",\n", " wake_models=[\"Bastankhah025_linear_k002\"],\n", " wake_frame=\"rotor_wd\",\n", " partial_wakes_model=\"auto\",\n", " verbosity=0,\n", ")\n", "\n", "problem = FarmLayoutOptProblem(\"layout_opt\", algo)\n", "problem.add_objective(MaxFarmPower(problem))\n", "problem.add_constraint(FarmBoundaryConstraint(problem))\n", "problem.add_constraint(\n", " MinDistConstraint(problem, min_dist=2., min_dist_unit=\"D\")\n", ")\n", "problem.initialize()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Notice that the two added constraint models imply a total of 55 individual constraint component functions. The wake model choice `Bastankhah025` corresponds to the `Bastankhah2014` deficit model with parameter `sbeta_factor=0.25`. This choice switches off the near wake modelling, rendering the model a bit smoother. This is for demonstrational purposes only and not required for running this example.\n", "\n", "Next, we setup the optimizer. In our case we use the genetic algorithm [GA from pymoo](https://pymoo.org/algorithms/soo/ga.html) via the [iwopy](https://github.com/FraunhoferIWES/iwopy) interface, here in vectorized form (flag `vectorize=True`), with 100 generations (`n_max_gen=100`) with population size 50 (`pop_size=50`):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "solver = Optimizer_pymoo(\n", " problem,\n", " problem_pars=dict(vectorize=True),\n", " algo_pars=dict(\n", " type=\"GA\", \n", " pop_size=50, \n", " seed=42,\n", " ),\n", " setup_pars=dict(),\n", " term_pars=dict(\n", " type=\"default\",\n", " n_max_gen=100,\n", " ftol=1e-6,\n", " xtol=1e-3,\n", " ),\n", ")\n", "solver.initialize()\n", "solver.print_info()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "After all the setup we can now solve the problem:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "results = solver.solve()\n", "solver.finalize(results)\n", "\n", "print()\n", "print(results)\n", "print(results.problem_results)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "This visualizes the results, once the layout and once the mean wind speed over all wind rose states:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, axs = plt.subplots(1, 2, figsize=(12, 8))\n", "\n", "foxes.output.FarmLayoutOutput(farm).get_figure(fig=fig, ax=axs[0])\n", "\n", "o = foxes.output.FlowPlots2D(algo, results.problem_results)\n", "p_min = np.array([-100.0, -350.0])\n", "p_max = np.array([1100.0, 1600.0])\n", "fig = o.get_mean_fig_xy(\"WS\", resolution=20, fig=fig, ax=axs[1],\n", " xmin=p_min[0], xmax=p_max[0], ymin=p_min[1], ymax=p_max[1])\n", "dpars = dict(alpha=0.6, zorder=10, p_min=p_min, p_max=p_max)\n", "farm.boundary.add_to_figure(\n", " axs[1], fill_mode=\"outside_white\", pars_distance=dpars\n", ")\n", "\n", "plt.show()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Note that the optimization can also be run using a `DaskRunner`, i.e., optionally in parallel on a (local) cluster. For that invoke the `runner` parameter when creating the problem within a`with` block and solve the problem as before:\n", "\n", "```python\n", "with foxes.utils.runners.DaskRunner(scheduler=\"distributed\") as runner:\n", "\n", " problem = FarmLayoutOptProblem(\"layout_opt\", algo, runner=runner)\n", " ...\n", " solver = ...\n", " ...\n", " results = solver.solve()\n", " \n", "```\n", "\n", "Anything within the `with` block will then be calculated using the `runner`. This includes the output figures, if they appear there as well." ] } ], "metadata": { "kernelspec": { "display_name": "foxescf", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" }, "orig_nbformat": 4, "vscode": { "interpreter": { "hash": "49e77450ac3b792529b8ec53c4156102ea20ff425b704a19d2a6ff376cdbc220" } } }, "nbformat": 4, "nbformat_minor": 2 }