13. Design of Experiments Example(s)

Many WISDEM studies involve a Design of Experiments, where inputs are parameterized across a range of values or sampled from a distribution. At each combinatoric point, WISDEM is run either as a single evaluation or as an optimization to create a family of optimized designs. A WISDEM user can execute a design of experiments either through WISDEM’s integration with OpenMDAO or by creating a customized wrapper that controls the parameterization in an outer loop and calls WISDEM directly. Examples of both approaches are provided.

Design of Experiments with OpenMDAO

As an alternative to its optimization drivers, OpenMDAO provides a DOEDriver to run a design of experiments. WISDEM can invoke this capability through user options in the analysis options file. To activate a design of experiments run in WISDEM, set the design variable flag to True for the input variables that should be parameterized in the run. In the doe_with_openmdao.py example provided, the blade chord is the only design variable. Next, in the driver section, the following options are available:

  optimization:
    flag: False           # Flag to enable optimization
  design_of_experiments:
    flag: True            # Flag to enable design of experiments
    run_parallel: False   # Flag to run using parallel processing
    generator: Uniform    # Type of input generator: [Uniform, FullFact, PlackettBurman, BoxBehnken, LatinHypercube]
    num_samples: 5        # number of samples (Uniform and LatinHypercube only)
    seed: 12345           # random number generator seed (Uniform and LatinHypercube only)

recorder:
    flag: True              # Flag to activate OpenMDAO recorder
    file_name: log_opt.sql  # Name of OpenMDAO recorder

With the OpenMDAO DOEDriver, the results are stored in the recorded SQL-file, so be sure the recorder option is set to True. The OpenMDAO docs can guide how to open and read the data from the file.

Design of Experiments via Python Scripting (and Multiprocessing)

Every study is unique and WISDEM users often prefer to customize their own wrapper that executes a design of experiments with specific variables or sampling or output analysis. The doe_custom.py example one way to implement this.

import os
import numpy as np
import multiprocessing as mp
import time
from wisdem import run_wisdem

parallel_flag = True

def parallel_runner(k, fgeom, fmodel, fanal, dover):
    iwt_opt, _, _ = run_wisdem(fgeom, fmodel, fanal, overridden_values=dover)
    return k, float(iwt_opt['financese.turbine_aep'][0])

def driver():
    start = time.time()

    # File management
    mydir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))  # get path to examples dir
    fname_wt_input = os.path.join(mydir, "02_reference_turbines", "IEA-15-240-RWT.yaml")
    fname_modeling_options = os.path.join(mydir, "02_reference_turbines", "modeling_options_iea15.yaml")
    fname_analysis_options = os.path.join(mydir, "02_reference_turbines", "analysis_options.yaml")

    # Initial run
    wt_opt, modeling_options, analysis_options = run_wisdem(fname_wt_input, fname_modeling_options,
                                                            fname_analysis_options)

    # Set parametric values
    blade_cones = np.deg2rad( np.arange(0, 5, 2) )
    shaft_tilts = np.deg2rad( np.arange(0, 5, 2) )

    # Set to grid of points and then sequence of run values
    Blades, Shafts = np.meshgrid(blade_cones, shaft_tilts)
    Blades = Blades.flatten()
    Shafts = Shafts.flatten()
    npts = Blades.size

    # Run parametric loop with overrides
    aep_output = np.zeros( npts ) # Initialize output container
    myargs = [] # Container for parallel run arguments
    for k in range(npts):
        overrides = {'hub.cone':Blades[k], 'drivetrain.uptilt': Shafts[k]}
        myargs.append([k, fname_wt_input, fname_modeling_options, fname_analysis_options, overrides])

        # Run cases in serial this way
        if not parallel_flag:
            iwt_opt, _, _ = run_wisdem(fname_wt_input, fname_modeling_options,
                                       fname_analysis_options, overridden_values=overrides)
            aep_output[k] = float(iwt_opt['financese.turbine_aep'][0])

    # Run cases in parallel this way
    if parallel_flag:
        ncore = max(1, mp.cpu_count() - 2)
        pool = mp.Pool(processes=ncore)
        results = pool.starmap(parallel_runner, myargs)
        for k in results:
            aep_output[ k[0] ] = k[1]
    
    print(aep_output)
    finish = time.time()
    print((finish-start)/60.0)

if __name__ == "__main__":
    driver()
    

In this example, the blade cone angle and shaft tilt angle are varied from zero to five degrees (steps of two) and the turbine annual energy production (AEP) is logged at each point in the parametric grid. The WISDEM runs can be called in serial within the for loop over the input values, or the arguments themselves are stored and then sent to a parallel processing “pool” using the multiprocessing library.