Open and run in Colab (interactive) Edit on Gitlab

Constraints

Try this yourself (requires google account)

Constraints are the second key element of an optimization problem formulation and they ensure that the optimization results represent feasible/realistic solutions. There are three types of constraints in optimization:

  • Variable bounds: upper and lower boundary values for design variables.

  • Inequality constraints: constraint function values must be less or more than a given threshold.

  • Equality constraints: constraint function must be exactly equal to a value (not as commonly used).

In TOPFARM, the two most common type of constraints are boundary (XYBoundaryConstraint) and spacing constraints (SpacingConstraint). The boundaries constraints restrict the wind farm within a defined region, where the turbines inside are not allowed to cross this boundary and any exclusion zone must be avoided. In addition, spacing constraints tell TOPFARM the minimum allowable inter-turbine spacing in the farm.

There is also the possibility of specifying additional constraints, such as fatigue loading or AEP. To do this, the post_constraints option in the TOPFARM problem is specified. More on how to set this up can be seen in the load constrained layout optimization and wake steering with load constraints examples.

This notebook walks through a process to set up typical constraints in TOPFARM for wind farm design problems. Before working through the two types of boundaries, you must first import some basic libraries and functions.

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

Import supporting libraries in Python

[1]:
# Import numpy and matplotlib files
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt

Next we import and initialize several functions and classes from TOPFARM to set up the problem including:

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

  • XYPlotComp. A helper function that shows the evolution of the optimization and the results at each iteration

  • XYBoundaryConstraint. For a boundary specified as a series of connected perimeter vertices

  • CircleBoundaryConstraint. For a circular boundary with a central location and a radius

  • SpacingConstraint. For the inter-turbine spacing distance constraints

  • CostModelComponent. A generic class for setting up a problem objective function

[2]:
# Import topfarm problem, plotting support, constraint classes and generic cost model component
from topfarm import TopFarmProblem
from topfarm.plotting import XYPlotComp
from topfarm.constraint_components.boundary import XYBoundaryConstraint, CircleBoundaryConstraint
from topfarm.constraint_components.spacing import SpacingConstraint
from topfarm.cost_models.cost_model_wrappers import CostModelComponent

Boundary constraints

Next we are going to demonstrate the use of the XYBoundaryConstraint to set up site boundaries of a variety of types including square, rectangle and an arbitrary polygon. Additionally, a “convex hull” example is provided which is a commonly used boundary type for wind farm design optimization problems.

Convex hull

“In mathematics, the convex hull or convex envelope or convex closure of a set X of points in the Euclidean plane or in a Euclidean space (or, more generally, in an affine space over the reals) is the smallest convex set that contains X. For instance, when X is a bounded subset of the plane, the convex hull may be visualized as the shape enclosed by a rubber band stretched around X.” [1]

[3]:
# set up a "boundary" array with arbitrary points for use in the example
boundary = np.array([(0, 0), (1, 1), (3, 0), (3, 2), (0, 2)])

# set up dummy design variables and cost model component.
# This example includes 2 turbines (n_wt=2) located at x,y=0.5,0.5 and 1.5,1.5 respectively

x = [0.5,1.5]
y = [.5,1.5]
dummy_cost = CostModelComponent(input_keys=[],
                                n_wt=2,
                               cost_function=lambda : 1)

# We introduce a simple plotting function so we can quickly plot different types of site boundaries
def plot_boundary(name, constraint_comp):
    tf = TopFarmProblem(
        design_vars={'x':x, 'y':y}, # setting up the turbine positions as design variables
        cost_comp=dummy_cost, # using dummy cost model
        constraints=[constraint_comp], # constraint set up for the boundary type provided
        plot_comp=XYPlotComp()) # support plotting function

    plt.figure()
    plt.title(name)
    tf.plot_comp.plot_constraints() # plot constraints is a helper function in topfarm to plot constraints
    plt.plot(boundary[:,0], boundary[:,1],'.r', label='Boundary points') # plot the boundary points
    plt.axis('equal')
    plt.legend() # add the legend

Now that we have set up our dummy problem, we can illustrate how different boundary types can be created from our boundary vertices.

First we show a convex hull type as described above. Note that for the convex hull, all boundary points are contained within a convex perimeter but one of the boundary points on the interior is not used. When using the XYBoundaryConstraint in an optimization problem, TOPFARM uses convex hull as the default boundary type.

[4]:
plot_boundary('convex_hull', XYBoundaryConstraint(boundary, 'convex_hull'))
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_constraints_12_1.png

Next we show a square type of boundary. In this case the maximum distance between the x and y elements of the vertices is used to establish the perimeter.

[5]:
plot_boundary('square', XYBoundaryConstraint(boundary, 'square'))
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_constraints_14_1.png

Now a rectangle boundary. Here we use the maximum distance on both x and y axes of the boundary coordinates to establish the perimeter.

[6]:
plot_boundary('rectangle', XYBoundaryConstraint(boundary, 'rectangle'))
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_constraints_16_1.png

Now, a polygon boundary, which connects all the points in sequence. Note that this results in a nonconvex boundary. Nonconvex functions in optimization problems introduce complexity that can be challenging to handle and often require more sophisticated algorithms and higher computational expense.

[7]:
plot_boundary('polygon', XYBoundaryConstraint(boundary, 'polygon'))
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_constraints_18_1.png

Finally, a circular boundary, where the midpoint of the circle and the radius are specified by the user.

[8]:
plot_boundary('Circle',CircleBoundaryConstraint((1.5,1),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
../_images/notebooks_constraints_20_1.png

With a simple for loop we can quickly plot all boundary types

[9]:
for boundary_type in ['convex_hull','square','rectangle','polygon']:
    plot_boundary(boundary_type, XYBoundaryConstraint(boundary, boundary_type))
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
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
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
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_constraints_22_1.png
../_images/notebooks_constraints_22_2.png
../_images/notebooks_constraints_22_3.png
../_images/notebooks_constraints_22_4.png

Exercise

Play around with a new set of boundary vertices and construct different perimeters to explore the functionality. See if you can make even more complex polygon shapes.

[10]:
# Make your own set of vertices - anything you would like!
boundary = np.array([(0, 0), (2, 1), (4, 7), (1, 1), (0, 2)])

# Then see what types of perimeters they generate
for boundary_type in ['convex_hull','square','rectangle','polygon']:
    plot_boundary(boundary_type, XYBoundaryConstraint(boundary, boundary_type))
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
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
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
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_constraints_24_1.png
../_images/notebooks_constraints_24_2.png
../_images/notebooks_constraints_24_3.png
../_images/notebooks_constraints_24_4.png

Spacing constraints

The next most common constraint in a wind farm design optimization problem is on the allowable inter-turbine spacing in the farm. Instinctively, turbines will try to spread out within the wind farm to minimize wake losses, but a minimum constraint can also help to ensure that turbines not get placed too close together.

The following provides a simple example of implementation of a minimum spacing constraint.

[11]:
# set up dummy design variables and cost model component.
# This example includes 2 turbines (n_wt=2) located at x,y=0.5,0.5 and 1.5,1.5 respectively

x = [0.5,1.5]
y = [.5,1.5]
dummy_cost = CostModelComponent(input_keys=[],
                                n_wt=2,
                               cost_function=lambda : 1)

# a function to plot a spacing constraint for a Topfarm problem
def plot_spacing(name, constraint_comp):
    tf = TopFarmProblem(
        design_vars={'x':x, 'y':y}, # setting up the turbine positions as design variables
        cost_comp=dummy_cost, # using dummy cost model
        constraints=[constraint_comp], # constraint set up for the boundary type provided
        plot_comp=XYPlotComp()) # support plotting function
    tf.evaluate()

    plt.figure()
    plt.title(name)
    tf.plot_comp.plot_constraints() # plot constraints is a helper function in topfarm to plot constraints
    plt.plot(x,y,'.b', label='Wind turbines') # plot the turbine locations
    plt.axis('equal')
    plt.legend() # add the legend
    plt.ylim([0,3])

To employ the spacing constraint, you must specify the minimum distance between turbines in meters.

[12]:
plot_spacing('spacing', SpacingConstraint(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
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_constraints_28_1.png
../_images/notebooks_constraints_28_2.png

Exercise

Play around with the spacing constraint size

[13]:
plot_spacing('spacing', SpacingConstraint(2))
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
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_constraints_30_1.png
../_images/notebooks_constraints_30_2.png