16. Inverse Design Example
This example walks through an inverse design of the wind turbine rotor and an inverse design of a floating spar.
Rotor Design
This example shows how to redesign a wind turbine rotor so that certain user-defined outputs are matched. This process is often performed when we are trying to reproduce the design of an existing wind turbine where the numerical model has not been shared by the manufacturer or the design of a future wind turbine where we are trying to guess how this design will look like. This inverse design process is commonly required by wind farm developers and operators, who do not receive all turbine characteristics from the turbine manufacturers, but still need to model their turbines numerically.
In this example, we start from a 5MW land-based wind turbine that was developed within the Big Adaptive Rotor project (for more details refer to https://github.com/NREL/BAR_Designs) and we redesign the rotor so that the first frequencies and the blade mass match some user-defined quantities.
The focus of this example is in the file analysis_options_rotor.yaml.
The field general lists the standard output folder and naming convention.
general:
folder_output: inverse
fname_output: BAR_USC_inverse
The field design_variables lists all active design variables parametrizing the blade design. The quantities parametrize aerodynamic twist and chord along blade span and structural layers. Blade structure is defined by many layers. This example is included in the regression tests of WISDEM and a toy-problem is defined where only the layer Shell_skin_outer can vary. More layers are commented. These are Spar_cap_ss, Spar_cap_ps, LE_reinf, TE_reinf_ss, TE_reinf_ps, Shell_skin_inner. In a real inverse design problem, make sure to uncomment them. Also, it is recommended to increase the number of spanwise points for twist, chord, and layers. A good starting point is provided below.
design_variables:
blade:
aero_shape:
twist:
flag: True # Flag to optimize the twist
n_opt: 4 # Number of control points along blade span
max_decrease: 5.0 # Maximum decrease for the twist in [deg] at the n_opt locations
max_increase: 5.0 # Maximum increase for the twist in [deg] at the n_opt locations
index_start: 2 # Lock the first two DVs from blade root
index_end: 4 # All DVs close to blade tip are active
chord:
flag: True # Flag to optimize the chord
n_opt: 4 # Number of control points along blade span
max_decrease: 0.3 # Minimum multiplicative gain on existing chord at the n_opt locations
max_increase: 3. # Maximum multiplicative gain on existing chord at the n_opt locations
index_start: 2 # Lock the first two DVs from blade root
index_end: 4 # All DVs close to blade tip are active
structure:
- layer_name: Shell_skin_outer
n_opt: 4 # Number of control points along blade span. In a real run, change this to 8
max_decrease: 0.2 # Maximum nondimensional decrease at the n_opt locations
max_increase: 5. # Maximum nondimensional increase at the n_opt locations
index_start: 2 # In a real run, change this to 2
index_end: 4 # In a real run, change this to 8
# - layer_name: Spar_cap_ss
# n_opt: 8 # Number of control points along blade span
# max_decrease: 0.2 # Maximum nondimensional decrease at the n_opt locations
# max_increase: 5. # Maximum nondimensional increase at the n_opt locations
# index_start: 1 # Lock the first DV from blade root
# index_end: 7 # The last DV at blade tip is locked
# - layer_name: Spar_cap_ps
# n_opt: 8 # Number of control points along blade span
# max_decrease: 0.2 # Maximum nondimensional decrease at the n_opt locations
# max_increase: 5. # Maximum nondimensional increase at the n_opt locations
# index_start: 1 # Lock the first DV from blade root
# index_end: 7 # The last DV at blade tip is locked
# - layer_name: LE_reinf
# n_opt: 8 # Number of control points along blade span
# max_decrease: 0.2 # Maximum nondimensional decrease at the n_opt locations
# max_increase: 5. # Maximum nondimensional increase at the n_opt locations
# index_start: 1 # Lock the first DV from blade root
# index_end: 7 # The last DV at blade tip is locked
# - layer_name: TE_reinf_ss
# n_opt: 8 # Number of control points along blade span
# max_decrease: 0.2 # Maximum nondimensional decrease at the n_opt locations
# max_increase: 5. # Maximum nondimensional increase at the n_opt locations
# index_start: 1 # Lock the first DV from blade root
# index_end: 7 # The last DV at blade tip is locked
# - layer_name: TE_reinf_ps
# n_opt: 8 # Number of control points along blade span
# max_decrease: 0.2 # Maximum nondimensional decrease at the n_opt locations
# max_increase: 5. # Maximum nondimensional increase at the n_opt locations
# index_start: 1 # Lock the first DV from blade root
# index_end: 7 # The last DV at blade tip is locked
# - layer_name: Shell_skin_inner
# n_opt: 8 # Number of control points along blade span
# max_decrease: 0.2 # Maximum nondimensional decrease at the n_opt locations
# max_increase: 5. # Maximum nondimensional increase at the n_opt locations
# index_start: 3 # Lock the first DV from blade root
# index_end: 8 # The last DV at blade tip is locked
Twist can be parametrized at 8 stations along span, where the active design variables are the outer 6, whereas the inner 2 are locked (where the blade is cylindrical or almost cylindrical and twist is not well defined). At the 6 control points, twist can increase up to 0.087 rad (5 deg) and decrease up to 5 deg. Similarly, chord should be parametrized at 8 stations (keep only the outer 6 free) and can go up and down from 300% to 30% of the initial values. The structural layers that are commented in example, when uncommented, could vary between 20% and 500% of the initial value. Start and end indices are chosen to ensure manufacturability of the blade (skin has to be thick at the root to accommodate bolts, spar caps and reinforcements need to start and end thin, etc).
The merit_figure of the study is LCOE. While LCOE is not always the right merit figure (the solution space is usually very flat and possibly multi-modal), in this example we use it to combine aerodynamic and structural considerations into a single metric.
merit_figure: LCOE
The field constraints lists several constraints, the ones marked with flag: True are active. The active ones limit
maximum strains along the length of the spar caps
ultimate blade tip deflection not violating minimum tower clearance, with a standard safety factor of 1.35 * 1.05 = 1.4175
minimum margin to stall of 5 degrees (0.087 rad)
maximum blade root flapwise moment coefficient of 0.16
first blade flapwise frequency between 0.48 and 0.52 Hz (static natural frequency, 0 rpm, no aerodynamics). In the real world, users might have be able to infer this value from SCADA data.
first blade edgewise frequency between 0.73 and 0.77 Hz (static natural frequency, 0 rpm, no aerodynamics). As above, in the real world, users might have be able to infer this value from SCADA data.
first blade torsional frequency between 6.1 and 6.5 Hz
blade mass between 34,500 and 35,500 kg. This value is often released publicly, or made available in shipping documents.
constraints:
blade:
strains_spar_cap_ss:
flag: True # Flag to impose constraints on maximum strains (absolute value) in the spar cap on the blade suction side
max: 3500.e-6 # Value of maximum strains [-]
index_start: 1 # Do not enforce constraint at the first station from blade root of the n_opt from spar_cap_ss
index_end: 3 # Do not enforce constraint at the last station at blade tip of the n_opt from spar_cap_ss
strains_spar_cap_ps:
flag: True # Flag to impose constraints on maximum strains (absolute value) in the spar cap on the blade pressure side
max: 3500.e-6 # Value of maximum strains [-]
index_start: 1 # Do not enforce constraint at the first station from blade root of the n_opt from spar_cap_ps
index_end: 3 # Do not enforce constraint at the last station at blade tip of the n_opt from spar_cap_ps
strains_te_ss:
flag: False # Flag to impose constraints on maximum strains (absolute value) in the spar cap on the blade suction side
max: 3500.e-6 # Value of maximum strains [-]
index_start: 1 # Do not enforce constraint at the first station from blade root of the n_opt from spar_cap_ss
index_end: 3 # Do not enforce constraint at the last station at blade tip of the n_opt from spar_cap_ss
strains_te_ps:
flag: False # Flag to impose constraints on maximum strains (absolute value) in the spar cap on the blade pressure side
max: 3500.e-6 # Value of maximum strains [-]
index_start: 1 # Do not enforce constraint at the first station from blade root of the n_opt from spar_cap_ps
index_end: 3 # Do not enforce constraint at the last station at blade tip of the n_opt from spar_cap_ps
tip_deflection:
flag: True
margin: 1.4175
stall:
flag: True # Constraint on minimum stall margin
margin: 5.0 # Value of minimum stall margin in [deg]
moment_coefficient:
flag: True # Constraint on minimum stall margin
max: 0.16
frequency:
first_flap:
flag: True
target: 0.5
acceptable_error: 0.02
first_edge:
flag: True
target: 0.75
acceptable_error: 0.02
# second_flap:
# flag: False
# target: 1.3
# acceptable_error: 0.1
# second_edge:
# flag: False
# target: 2.1
# acceptable_error: 0.1
first_torsion:
flag: True
target: 6.3
acceptable_error: 0.2
mass:
flag: True
target: 35000.
acceptable_error: 500.
The optimizer is the standard SciPy SLSQP, and the openmdao recorder is turned on. Note that a real optimization will need a much higher value for max_iter (100 is a good start).
driver:
optimization:
flag: True # Flag to enable optimization
tol: 1.e-5 # Optimality tolerance
# max_major_iter: 10 # Maximum number of major design iterations (SNOPT)
# max_minor_iter: 100 # Maximum number of minor design iterations (SNOPT)
max_iter: 1 # Maximum number of iterations (SLSQP)
solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN'
step_size: 1.e-3 # Step size for finite differencing
form: central # Finite differencing mode, either forward or central
recorder:
flag: True # Flag to activate OpenMDAO recorder
file_name: log_opt.sql # Name of OpenMDAO recorder
For more details about this example, please feel free to post questions on GitHub!
Spar Design
In this example we setup an optimization problem to redesign a floating spar. The design variables parametrize the keel and the freeboard of the spar, where both quantities are bound between -40 and -15 meters. In addition, the ballast is optimized and can vary between 1 kg and 10,000 kg. Lastly, the length of the mooring line can vary between 100 and 1,000 meters.
design_variables:
floating:
joints:
z_coordinate:
- names: [spar_keel]
lower_bound: -40.0
upper_bound: -15.0
flag: True
- names: [spar_freeboard]
lower_bound: -40.0
upper_bound: -15.0
flag: True
members:
groups:
- names: [spar]
flag: True
ballast:
lower_bound: 1.0
upper_bound: 1e4
mooring:
line_length:
flag: True
lower_bound: 100.0
upper_bound: 1000.0
In this optimization problem, we set the optimizer to minimize the difference between certain outputs and their target values. WISDEM allows to do that by setting inverse_design as the merit_figure and then by setting a user-defined list of outputs under inverse_design. In this example, these outputs are:
the mass of the platform hull
the mass of the mooring system
the location of the center of mass of the floating spar
merit_figure: inverse_design
# constraints
inverse_design:
floatingse.platform_hull_mass:
ref_value: 2.5e6
units: kg
floatingse.mooring_mass:
ref_value: 4.5e5
units: kg
floatingse.system_structural_center_of_mass:
ref_value: [0., -90.]
indices: [1, 2]
units: m
The optimizer is the standard SciPy SLSQP. Note that a real optimization will need a much higher value for max_iter (100 is a good start).
driver:
optimization:
flag: True # Flag to enable optimization
tol: 1.e-4 # Optimality tolerance
solver: SLSQP # Optimization solver. Other options are 'SLSQP' - 'CONMIN'
step_size: 1.e-6 # Step size for finite differencing
form: forward # Finite differencing mode, either forward or central
max_iter: 1 # Maximum number of iterations (SLSQP)
Once again, for more details about this example, please feel free to post questions on GitHub!