Open and run in Colab (interactive) Edit on Gitlab

Design variables

Design variables are the principal element within an optimization problem. During the optimization, the design variables are changed while influencing the objective function.

These variables can be either continuous or discrete depending on the type of optimization problem that is being studied. The turbine positions are the most common continuous design variable used in wind farm layout optimization. In essence, they are continuous because they can take an infinite amount of values for a given range. Discrete variables, on the other hand, can only take finite amount of values for a specific interval. A common discrete variable used in wind farm optimization is turbine type. The type of design variable will influence the decision of the optimization driver to use.

Continuous design variables can be treated with both gradient-free and gradient-based algorithms, where the calculation of the derivatives (in gradient-based cases) can be performed. On the other hand, discrete variables are usually analyzed with gradient-free methods, as the calculation of the gradients of these variables is not possible to perform. In some cases, it is possible to convert the discrete variable into a continuous one and then find its derivative.

Besides the turbine positions, the development of TOPFARM has allowed the inclusion of several other design variables such as:

  • Turbine types,

  • Turbine hub heights,

  • Yaw angles,

  • and even the inter-turbine spacing and rotation of a wind farm seen as a grid.

This example goes through two of the most common design variables available in TOPFARM and describes how to set them up within the TopFarmProblem object in the optimization.

Before working on the notebook exercises, make sure that you import or install topfarm on your newly created environment.

[ ]:
# Install TopFarm if needed
import importlib
if not importlib.util.find_spec("topfarm"):
    !pip install git+https://gitlab.windenergy.dtu.dk/TOPFARM/TopFarm2.git

First we import some supporting Python libraries

[1]:
import numpy as np
import matplotlib.pyplot as plt

We start by importing the turbine and site to use as well as the wake model from PyWake.

From TOPFARM we import:

  • TopFarmProblem. Overall topfarm problem class to which the objectives, design variables, and constraints are added

  • EasyScipyOptimizeDriver. A subclass of ScipyOptimizeDriver which is configured for the given workflow

  • get_iea37_initial, get_iea37_constraints, get_iea37_cost. Functions to get the initial layout, the constraints and the cost function for the IEA task 37 benchmark example

  • NoPlot, XYPlotComp. Plotting components to visualize the results

[2]:
from py_wake.deficit_models.gaussian import IEA37SimpleBastankhahGaussian     #wake model
from py_wake.examples.data.iea37 import IEA37_WindTurbines, IEA37Site         #wind turbines and site used
from topfarm.cost_models.py_wake_wrapper import PyWakeAEPCostModelComponent   #cost model

from topfarm import TopFarmProblem
from topfarm.easy_drivers import EasyScipyOptimizeDriver
from topfarm.examples.iea37 import get_iea37_initial, get_iea37_constraints, get_iea37_cost
from topfarm.plotting import NoPlot, XYPlotComp

1) Turbine position (x,y) layout optimization

First we set up the parameters for the wind farm model simulation, that is the site and wind turbine objects as well as the wind directions to study.

[3]:
n_wt = 9
n_wd = 16

site = IEA37Site(9)
wind_turbines = IEA37_WindTurbines()
wd = np.linspace(0.,360.,n_wd, endpoint=False)
wfmodel = IEA37SimpleBastankhahGaussian(site, wind_turbines)   #PyWake's wind farm model

Now we set up the CostModelComponent that will calculate the AEP as the objective function of the optimization.

[4]:
cost_comp = PyWakeAEPCostModelComponent(wfmodel, n_wt, wd=wd)

Then we can set up the optimization problem, where the initial conditions of the optimization, design variables and constraints are specified.

[5]:
initial = get_iea37_initial(n_wt)
driver = EasyScipyOptimizeDriver()

When using the turbine positions as design variables, you can specify them in TOPFARM in 4 different ways:

- {'x': [1, 2, 3], 'y':([3, 2, 1], 0, 1), 'z':([4, 5, 6],[4, 5, 4], [6, 7, 6])}
- [('x', [1,2,3]), ('y',([3,2,1], 0, 1)), ('z',([4, 5, 6],[4, 5, 4], [6, 7, 6]))]
- [('x', ([1,2,3], 0, 3, 'm')), ('y', ([3,2,1], 'm')), ('z', ([4,5,6], [4,5,4], [6,7,6]))]
- zip('xy', pos.T)

The values are either:

  • the initial value or

  • on of the following tuples:

    • initial value, unit

    • initial value, lower bound, upper bound

    • initial value, lower bound, upper bound, unit

[6]:
design_vars = dict(zip('xy', (initial[:, :2]).T))

tf_problem = TopFarmProblem(
            design_vars,
            cost_comp,
            constraints=get_iea37_constraints(n_wt),
            driver=driver,
            plot_comp=XYPlotComp())
INFO: checking out_of_order
INFO: checking system
INFO: checking solvers
INFO: checking dup_inputs
INFO: checking missing_recorders
INFO: checking unserializable_options
INFO: checking comp_has_no_outputs
INFO: checking auto_ivc_warnings
[7]:
_, state, _ = tf_problem.optimize()
INFO: checking out_of_order
INFO: checking system
INFO: checking solvers
INFO: checking dup_inputs
INFO: checking missing_recorders
INFO: checking unserializable_options
INFO: checking comp_has_no_outputs
INFO: checking auto_ivc_warnings
../_images/notebooks_design_variables_17_1.png
Optimization terminated successfully    (Exit mode 0)
            Current function value: -239.6945378502371
            Iterations: 171
            Function evaluations: 173
            Gradient evaluations: 171
Optimization Complete
-----------------------------------
../_images/notebooks_design_variables_17_3.png

2) Turbine type optimization

In this example we use the turbine types as design variables, which represent discrete points in the optimization. Because of this, gradient-based methods are not suitable and gradient-free options are needed instead. For this case, we use the FullFactorialGenerator as our driver (see the Drivers tutorial for more information.) We also use the TurbineTypePlotComponent available in TOPFARM to visualize the final turbine types.

[8]:
from topfarm.cost_models.dummy import DummyCost
from topfarm._topfarm import TopFarmProblem
from openmdao.drivers.doe_generators import FullFactorialGenerator
from topfarm.plotting import TurbineTypePlotComponent, NoPlot
[9]:
# define the conditions for the wind farm
positions = np.array([[0, 0], [6, 6]])  # initial turbine position
optimal_types = np.array([[2], [6]])    # optimal layout

#set up plotting component for turbine types
plot_comp = TurbineTypePlotComponent(
                turbine_type_names=["Turbine %d" % i for i in range(5)],
                plot_initial=False,
                delay=0.1, legendloc=0)
[10]:
tf = TopFarmProblem(
            design_vars={'type': ([0, 0], 0, 4)},
            cost_comp=DummyCost(optimal_types, ['type']),
            plot_comp=plot_comp,
            driver=FullFactorialGenerator(5),
            ext_vars={'x': positions[:, 0], 'y': positions[:, 1]},
        )
INFO: checking out_of_order
INFO: checking system
INFO: checking solvers
INFO: checking dup_inputs
INFO: checking missing_recorders
INFO: checking unserializable_options
INFO: checking comp_has_no_outputs
INFO: checking auto_ivc_warnings
[11]:
state = {}
cost, state, recorder = tf.optimize(state)
INFO: checking out_of_order
INFO: checking system
INFO: checking solvers
INFO: checking dup_inputs
INFO: checking missing_recorders
INFO: checking unserializable_options
INFO: checking comp_has_no_outputs
INFO: checking auto_ivc_warnings
../_images/notebooks_design_variables_23_1.png
INFO: checking out_of_order
INFO: checking system
INFO: checking solvers
INFO: checking dup_inputs
INFO: checking missing_recorders
INFO: checking unserializable_options
INFO: checking comp_has_no_outputs
INFO: checking auto_ivc_warnings
../_images/notebooks_design_variables_23_3.png
[12]:
print(state['type'])
[2. 4.]