Design of experiments in H2I#

One of the key features of H2Integrate is the ability to perform a design of experiments (DOE) for hybrid energy systems.

The design of experiments process uses the driver_config.yaml file to define the design sweep, including the design variables, constraints, and objective functions. Detailed information on setting up the driver_config.yaml file can be found in the user guide

Driver config file#

The driver config file defines the analysis type and the optimization or design of experiments settings. For completeness, here is an example of a driver config file for a design of experiments problem:

 1description: Driver config for CSVGen design of experiments
 2name: driver_config
 3
 4general:
 5  folder_output: ex_22_out
 6  create_om_reports: false
 7driver:
 8  design_of_experiments:
 9    flag: true
10    debug_print: true
11    generator: "csvgen"
12    filename: "site_solar_doe.csv"
13    run_parallel: false
14design_variables:
15  solar:
16    system_capacity_DC:
17      flag: true
18      lower: 5000.0
19      upper: 75000.0
20      units: "kW"
21  site:
22    latitude:
23      flag: True
24      units: "deg"
25      lower: -90.0
26      upper: 90.0
27    longitude:
28      flag: True
29      units: "deg"
30      lower: -180.0
31      upper: 180.0
32
33objective:
34  name: finance_subgroup_electricity.LCOE_optimistic
35recorder:
36    file: cases.sql
37    overwrite_recorder: true
38    flag: true
39    includes: ['*']
40    excludes: ['*wind_resource*']

Types of Generators#

H2Integrate currently supports the following types of generators:

Documentation for each generator type can be found on OpenMDAO’s documentation page.

Uniform#

driver:
  design_of_experiments:
    flag: True
    generator: "uniform" #type of generator to use
    num_samples: 10 #input is specific to this generator
    seed: #input is specific to this generator

FullFactorial#

driver:
  design_of_experiments:
    flag: True
    generator: "fullfact" #type of generator to use
    levels: 2 #input is specific to this generator

The levels input is the number of evenly spaced levels between each design variable lower and upper bound.

You can check the values that will be used for a specific design variable by running:

import numpy as np

design_variable_values = np.linspace(lower_bound,upper_bound,levels)

PlackettBurman#

driver:
  design_of_experiments:
    flag: True
    generator: "plackettburman" #type of generator to use

BoxBehnken#

driver:
  design_of_experiments:
    flag: True
    generator: "boxbehnken" #type of generator to use

LatinHypercube#

driver:
  design_of_experiments:
    flag: True
    generator: "latinhypercube" #type of generator to use
    num_samples:  10 #input is specific to this generator
    criterion: "center"  #input is specific to this generator
    seed: #input is specific to this generator

CSV#

This method is useful if there are specific combinations of designs variables that you want to sweep. An example is shown here:

driver:
  design_of_experiments:
    flag: True
    generator: "csvgen" #type of generator to use
    filename: "cases_to_run.csv" #input is specific to this generator

The filename input is the filepath to the csv file to read cases from. The first row of the csv file should contain the names of the design variables. The rest of the rows should contain the values of that design variable you want to run (such as solar.system_capacity_DC or electrolyzer.n_clusters). The values in the csv file are expected to be in the same units specified for that design variable.

Note

You should check the csv file for potential formatting issues before running a simulation. This can be done using the check_file_format_for_csv_generator method in h2integrate/core/utilities.py. Usage of this method is shown in the 20_solar_electrolyzer_doe example in the examples folder.

Demonstration Using Solar and Electrolyzer Capacities#

This csvgen generator example reflects the work to produce the examples/20_solar_electrolyzer_doe example.

We use the examples/20_solar_electrolyzer_doe/driver_config.yaml to run a design of experiments for varying combinations of solar power and hydrogen electrolyzer capacities.

 4general:
 5  folder_output: ex_20_out
 6  create_om_reports: true
 7driver:
 8  design_of_experiments:
 9    flag: true
10    debug_print: true
11    generator: "csvgen"
12    filename: "csv_doe_cases.csv"
13    run_parallel: false
14design_variables:
15  solar:
16    system_capacity_DC:
17      flag: true
18      lower: 5000.0
19      upper: 5000000.0
20      units: "kW"
21  electrolyzer:
22    n_clusters:
23      flag: true
24      lower: 1
25      upper: 25
26      units: "unitless"

The different combinations of solar and electrolyzer capacities are listed in the csv file examples/20_solar_electrolyzer_doe/csv_doe_cases.csv:

solar.system_capacity_DC,electrolyzer.n_clusters
25000.0,5.0
50000.0,5.0
100000.0,5.0
200000.0,5.0
50000.0,10.0
100000.0,10.0
200000.0,10.0
250000.0,10.0
300000.0,10.0
500000.0,10.0

Next, we’ll import the required models and functions to complete run a successful design of experiments.

# Import necessary methods and packages
from pathlib import Path

from h2integrate.core.utilities import check_file_format_for_csv_generator, load_yaml
from h2integrate.core.dict_utils import update_defaults
from h2integrate.core.h2integrate_model import H2IntegrateModel
from h2integrate.core.inputs.validation import load_driver_yaml, write_yaml
Setup and first attempt#

First, we need to update the relative file references to ensure the demonstration works.

EXAMPLE_DIR = Path("../../examples/20_solar_electrolyzer_doe").resolve()

config = load_yaml(EXAMPLE_DIR / "20_solar_electrolyzer_doe.yaml")

driver_config = load_yaml(EXAMPLE_DIR / config["driver_config"])
csv_config_fn = EXAMPLE_DIR / driver_config["driver"]["design_of_experiments"]["filename"]
config["driver_config"] = driver_config
config["driver_config"]["driver"]["design_of_experiments"]["filename"] = csv_config_fn

config["technology_config"] = load_yaml(EXAMPLE_DIR / config["technology_config"])
config["plant_config"] = load_yaml(EXAMPLE_DIR / config["plant_config"])

As-is, the model produces a UserWarning that it will not successfully run with the existing configuration, as shown below.

model = H2IntegrateModel(config)
model.run()
---------------------------------------------------------------------------
UserWarning                               Traceback (most recent call last)
Cell In[3], line 1
----> 1 model = H2IntegrateModel(config)
      2 model.run()

File ~/checkouts/readthedocs.org/user_builds/h2integrate/envs/531/lib/python3.11/site-packages/h2integrate/core/h2integrate_model.py:73, in H2IntegrateModel.__init__(self, config_input)
     69 self.connect_technologies()
     71 # create driver model
     72 # might be an analysis or optimization
---> 73 self.create_driver_model()

File ~/checkouts/readthedocs.org/user_builds/h2integrate/envs/531/lib/python3.11/site-packages/h2integrate/core/h2integrate_model.py:1199, in H2IntegrateModel.create_driver_model(self)
   1197 myopt = PoseOptimization(self.driver_config)
   1198 if "driver" in self.driver_config:
-> 1199     myopt.set_driver(self.prob)
   1200     myopt.set_objective(self.prob)
   1201     myopt.set_design_variables(self.prob)

File ~/checkouts/readthedocs.org/user_builds/h2integrate/envs/531/lib/python3.11/site-packages/h2integrate/core/pose_optimization.py:337, in PoseOptimization.set_driver(self, opt_prob)
    333     valid_file = check_file_format_for_csv_generator(
    334         doe_options["filename"], self.config, check_only=True
    335     )
    336     if not valid_file:
--> 337         raise UserWarning(
    338             f"There may be issues with the csv file {doe_options['filename']}, "
    339             f"which may cause errors within OpenMDAO. "
    340             "To check this csv file or create a new one, run the function "
    341             "h2integrate.core.utilities.check_file_format_for_csv_generator()."
    342         )
    343     generator = om.CSVGenerator(
    344         filename=doe_options["filename"],
    345     )
    346 else:

UserWarning: There may be issues with the csv file /home/docs/checkouts/readthedocs.org/user_builds/h2integrate/checkouts/531/examples/20_solar_electrolyzer_doe/csv_doe_cases.csv, which may cause errors within OpenMDAO. To check this csv file or create a new one, run the function h2integrate.core.utilities.check_file_format_for_csv_generator().
Fixing the bug#

The UserWarning tells us that there may be an issue with our csv file. We will use the recommended method to create a new csv file that doesn’t have formatting issues.

We’ll take the following steps to try and fix the bug:

  1. Run the check_file_format_for_csv_generator method mentioned in the UserWarning and create a new csv file that is hopefully free of errors

  2. Make a new driver config file that has “filename” point to the new csv file created in Step 1.

  3. Make a new top-level config file that points to the updated driver config file created in Step 2.

# Step 1
new_csv_filename = check_file_format_for_csv_generator(
    csv_config_fn,
    driver_config,
    check_only=False,
    overwrite_file=False,
)

new_csv_filename.name
'csv_doe_cases0.csv'

Let’s see the updates to combinations.

solar.system_capacity_DC,electrolyzer.n_clusters
25000.0,5.0
50000.0,5.0
100000.0,5.0
200000.0,5.0
50000.0,10.0
100000.0,10.0
200000.0,10.0
250000.0,10.0
300000.0,10.0
500000.0,10.0
# Step 2
updated_driver = update_defaults(
  driver_config["driver"],
  "filename",
  str(EXAMPLE_DIR / new_csv_filename.name),
)
driver_config["driver"].update(updated_driver)

# Step 3
config["driver_config"] = driver_config

Re-running#

Now that we’ve completed the debugging and fixing steps, lets try to run the simulation again but with our new files.

model = H2IntegrateModel(config)
model.run()
Driver debug print for iter coord: rank0:DOEDriver_CSV|0
--------------------------------------------------------
Design Vars
{'electrolyzer.n_clusters': array([5.]),
 'solar.system_capacity_DC': array([25000.])}
Nonlinear constraints
None

Linear constraints
None

Objectives
{'finance_subgroup_hydrogen.LCOH_optimistic': array([7.61626063])}
Driver debug print for iter coord: rank0:DOEDriver_CSV|1
--------------------------------------------------------
Design Vars
{'electrolyzer.n_clusters': array([5.]),
 'solar.system_capacity_DC': array([50000.])}
Nonlinear constraints
None

Linear constraints
None

Objectives
{'finance_subgroup_hydrogen.LCOH_optimistic': array([4.92406874])}
Driver debug print for iter coord: rank0:DOEDriver_CSV|2
--------------------------------------------------------
Design Vars
{'electrolyzer.n_clusters': array([5.]),
 'solar.system_capacity_DC': array([100000.])}
Nonlinear constraints
None

Linear constraints
None

Objectives
{'finance_subgroup_hydrogen.LCOH_optimistic': array([4.66453898])}
Driver debug print for iter coord: rank0:DOEDriver_CSV|3
--------------------------------------------------------
Design Vars
{'electrolyzer.n_clusters': array([5.]),
 'solar.system_capacity_DC': array([200000.])}
Nonlinear constraints
None

Linear constraints
None

Objectives
{'finance_subgroup_hydrogen.LCOH_optimistic': array([6.58504271])}
Driver debug print for iter coord: rank0:DOEDriver_CSV|4
--------------------------------------------------------
Design Vars
{'electrolyzer.n_clusters': array([10.]),
 'solar.system_capacity_DC': array([50000.])}
Nonlinear constraints
None

Linear constraints
None

Objectives
{'finance_subgroup_hydrogen.LCOH_optimistic': array([7.60179634])}
Driver debug print for iter coord: rank0:DOEDriver_CSV|5
--------------------------------------------------------
Design Vars
{'electrolyzer.n_clusters': array([10.]),
 'solar.system_capacity_DC': array([100000.])}
Nonlinear constraints
None

Linear constraints
None

Objectives
{'finance_subgroup_hydrogen.LCOH_optimistic': array([4.92097224])}
Driver debug print for iter coord: rank0:DOEDriver_CSV|6
--------------------------------------------------------
Design Vars
{'electrolyzer.n_clusters': array([10.]),
 'solar.system_capacity_DC': array([200000.])}
Nonlinear constraints
None

Linear constraints
None

Objectives
{'finance_subgroup_hydrogen.LCOH_optimistic': array([4.66301442])}
Driver debug print for iter coord: rank0:DOEDriver_CSV|7
--------------------------------------------------------
Design Vars
{'electrolyzer.n_clusters': array([10.]),
 'solar.system_capacity_DC': array([250000.])}
Nonlinear constraints
None

Linear constraints
None

Objectives
{'finance_subgroup_hydrogen.LCOH_optimistic': array([5.11037226])}
Driver debug print for iter coord: rank0:DOEDriver_CSV|8
--------------------------------------------------------
Design Vars
{'electrolyzer.n_clusters': array([10.]),
 'solar.system_capacity_DC': array([300000.])}
Nonlinear constraints
None

Linear constraints
None

Objectives
{'finance_subgroup_hydrogen.LCOH_optimistic': array([5.58724086])}
Driver debug print for iter coord: rank0:DOEDriver_CSV|9
--------------------------------------------------------
Design Vars
{'electrolyzer.n_clusters': array([10.]),
 'solar.system_capacity_DC': array([500000.])}
Nonlinear constraints
None

Linear constraints
None

Objectives
{'finance_subgroup_hydrogen.LCOH_optimistic': array([7.61137828])}
/home/docs/checkouts/readthedocs.org/user_builds/h2integrate/envs/531/lib/python3.11/site-packages/openmdao/core/group.py:1267: DerivativesWarning:Constraints or objectives [plant.finance_subgroup_hydrogen.hydrogen_finance_optimistic.LCOH_optimistic] cannot be impacted by the design variables of the problem because no partials were defined for them in their parent component(s).