Skip to content

Commit 7790243

Browse files
Updating the electrolyzer plant model for v2 and new electrolyzer code release (#180)
* Adding wind and hydrogen example * Initial electrolyzer example * Electrolyzer reference signal * Update with documentation * Adding plots for electrolyzer * Updating example and plotting outputs, adding verbose print out statements * Update readme for example 06 * Remove unused imports * Fix ruff formatting * Fix electrolyzer implementation test * Install main electrolyzer repo (main branch) * Update initialization to not require all electrolyzer input values and update regression test values for better initialization * Fix ruff formatting --------- Co-authored-by: misi9170 <michael.sinner@nrel.gov>
1 parent 4af35c2 commit 7790243

12 files changed

Lines changed: 7970 additions & 106 deletions

File tree

.github/workflows/continuous-integration-workflow.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
run: |
2323
python -m pip install --upgrade pip
2424
pip install -e ".[develop]"
25-
pip install git+https://github.com/NREL/electrolyzer.git@638d890
25+
pip install git+https://github.com/NREL/electrolyzer.git
2626
pip install https://github.com/NREL/SEAS/blob/v1/SEAS.tar.gz?raw=true
2727
# - uses: pre-commit/action@v3.0.0
2828
- name: Run ruff

docs/_toc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ parts:
3939
- file: wind
4040
- file: battery
4141
- file: solar_pv
42+
- file: electrolyzer
4243
- caption: Usage
4344
chapters:
4445
- file: order_of_op

docs/electrolyzer.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Electrolyzer Plant
2+
3+
The hydrogen electrolyzer modules use the [electrolyzer](https://github.com/NREL/electrolyzer) package developed by the National Renewable Energy Laboratory to predict hydrogen output of hydrogen electrolyzer plants. This repot contains models for PEM and Alkaline electrolyzer cell types.
4+
5+
To create a hydrogen electrolzer plant, set `component_type` = `ElectrolyzerPlant` in the input dictionary (.yaml file).
6+
7+
8+
## Inputs
9+
10+
#### Required Parameters
11+
The parameters listed below are required unless otherwise specifice as *Optional*.
12+
13+
- `general`: General simulation parameters.
14+
- `initial_conditions`: Initial conditions for the simulation including:
15+
- `power_available_kw`: Initial power available to the electrolyzer [kW]
16+
- `electrolyzer`: Electrolyzer plant specific parameters including:
17+
- `initialize`: boolean. Whether to initialize the electrolyzer.
18+
- `initial_power_kW`: Initial power input to the electrolyzer [kW].
19+
- `supervisor`:
20+
- `system_rating_MW`: Total system rating in MW.
21+
- `n_stacks`: Number of electrolyzer stacks in the plant.
22+
- `stack`: Electrolyzer stack parameters including:
23+
- `cell_type`: Type of electrolyzer cell (e.g., PEM, Alkaline).
24+
- `max_current`: Maximum current of the stack [A].
25+
- `temperature`: Stack operating temperature [degC].
26+
- `n_cells`: Number of cells per stack.
27+
- `min_power`: Minimum power for electrolyzer operation [kW].
28+
- `stack_rating_kW`: Stack rated power [kW].
29+
- `include_degradation_penalty`: *Optional* Whether to include degradation penalty.
30+
- `hydrogen_degradation_penalty`: *Optional* boolean, wether degradation is applied to hydrogen (True) or power (False)
31+
- `cell_params`: Electrolyzer cell parameters including:
32+
- `cell_area`: Area of individual cells in the stack [cm^2].
33+
- `turndown_ratio`: Minimum turndown ratio for stack operation [between 0 and 1].
34+
- `max current_density`: Maximum current density [A/cm^2].
35+
- `p_anode`: Anode operating pressure [bar].
36+
- `p_cathode`: Cathode operating pressure [bar].
37+
- `alpha_a`: anode charge transfer coefficient.
38+
- `alpha_c`: cathode charge transfer coefficient.
39+
- `i_0_a`: anode exchange current density [A/cm^2].
40+
- `i_0_c`: cathode exchange current density [A/cm^2].
41+
- `e_m`: membrane thickness [cm].
42+
- `R_ohmic_elec`: electrolyte resistance [A*cm^2].
43+
- `f_1`: faradaic coefficien [mA^2/cm^4].
44+
- `f_2`: faradaic coefficien [mA^2/cm^4].
45+
- `degradation`: Electrolyzer degradation parameters including:
46+
- `eol_eff_percent_loss`: End of life efficiency percent loss [%].
47+
- `PEM_params` or `ALK_params`: Degradation parameters specific to PEM or Alkaline cells:
48+
- `rate_steady`: Rate of voltage degradation under steady operation alone
49+
- `rate_fatigue`: Rate of voltage degradation under variable operation alone
50+
- `rate_onoff`: Rate of voltage degradation per on/off cycle
51+
- `controller`: Electrolyzer control parameters including:
52+
- `control_type`: Controller type for electrolyzer plant operation.
53+
- `costs`: *Optional* Cost parameters for the electrolyzer plant including:
54+
- `plant_params`:
55+
- `plant_life`: integer, Plant life in years
56+
- `pem_location`: Location of the PEM electrolyzer. Options are
57+
[onshore, offshore, in-turbine]
58+
- `grid_connected`: boolean, Whether the plant is connected to the grid or not
59+
- `feedstock`: Parameters related to the feedstock including:
60+
- `water_feedstock_cost`: Cost of water per kg of water
61+
- `water_per_kgH2`: Amount of water required per kg of hydrogen produced
62+
- `opex`: Operational expenditure parameters including:
63+
- `var_OM`: Variable operation and maintenance cost per kW
64+
- `fixed_OM`: Fixed operation and maintenance cost per kW-year
65+
- `stack_replacement`: Parameters related to stack replacement costs including:
66+
- `d_eol`: End of life cell voltage value [V]
67+
- `stack_replacement_percent`: Stack replacement cost as a percentage of CapEx [0,1]
68+
- `capex`: Capital expenditure parameters including:
69+
- `capex_learning_rate`: Capital expenditure learning rate.
70+
- `ref_cost_bop`: Reference cost of balance of plant per kW.
71+
- `ref_size_bop`: Reference size of balance of plant in kW.
72+
- `ref_cost_pem`: Reference cost of PEM electrolyzer stack per kW.
73+
- `ref_size_pem`: Reference size of PEM electrolyzer stack in kW.
74+
- `finances`: Financial parameters including:
75+
- `discount_rate`: Discount rate for financial calculations [%].
76+
- `install_factor`: Installation factor for capital expenditure [0,1].
77+
- `log_channels`: List of output channels to log (see [Logging Configuration](elec-logging-configuration) below)
78+
79+
80+
## Outputs
81+
(elec-logging-configuration)=
82+
**Logging Configuration**
83+
84+
The `log_channels` parameter controls which outputs are written to the HDF5 output file. This is a list of channel names. The `power` channel is always logged, even if not explicitly specified.
85+
86+
87+
### Available Channels
88+
89+
**Scalar Channels:**
90+
- `H2_output`: Total hydrogen produced during the last timestep
91+
- `H2_mfr`: Mass flow rate of the hydrogen production in kg/s
92+
- `power`: Power that the electrolyzer plant used to create hydrogen (kW). This follows the convention that power consumed is negative power.
93+
- `Power_input_kw`: Power allocated to the electrolyzer plant to use (kW)
94+
- `stacks_on`: Total number of stacks producing hydrogen
95+
96+
**Array Channels:**
97+
- `stacks_waiting`: Boolean list of the stacks that are waiting to start producing hydrogen (True for stacks waiting, False for stacks not waiting)
98+
99+
100+
**Example:**
101+
```yaml
102+
ele:
103+
component_type: ElectrolyzerPlant
104+
log_channels:
105+
- power
106+
- H2_output
107+
- H2_mfr
108+
initial_conditions:
109+
- power_available_kw: 3000
110+
electrolyzer:
111+
# ... other parameters
112+
```
113+
114+
If `log_channels` is not specified, only `power` will be logged.
115+
116+
## References
117+
1. Z. Tully, G. Starke, K. Johnson and J. King, "An Investigation of Heuristic Control Strategies for Multi-Electrolyzer Wind-Hydrogen Systems Considering Degradation," 2023 IEEE Conference on Control Technology and Applications (CCTA), Bridgetown, Barbados, 2023, pp. 817-822, doi: 10.1109/CCTA54093.2023.10252187.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Example 06: Wind and hydrogen hybrid plant
2+
3+
## Description
4+
5+
Example of a wind and hydrogen hybrid plant where power that the wind farm produces goes directly to hydrogen electrolysis
6+
7+
## Setup
8+
9+
No manual setup is required. The example automatically generates the necessary input files (wind data, FLORIS configuration, and turbine model) in the centralized `examples/inputs/` folder when first run.
10+
11+
12+
## Running
13+
14+
To run the example, execute the following command in the terminal:
15+
16+
```bash
17+
python hercules_runscript.py
18+
```
19+
## Outputs
20+
21+
To plot the outputs run the following command in the terminal:
22+
23+
```bash
24+
python plot_outputs.py
25+
```
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Input YAML for hercules
2+
3+
# Name
4+
name: example_06
5+
6+
###
7+
# Describe this simulation setup
8+
description: Wind and hydrogen
9+
10+
dt: 1.0
11+
starttime_utc: "2024-06-24T16:59:08Z" # Jun 24, 2024 16:59:08 UTC (Zulu time)
12+
endtime_utc: "2024-06-24T18:59:00Z" # ≈2 hours later (Jun 26, 2024 16:59:00 UTC)
13+
# endtime_utc: "2024-06-26T16:59:00Z" # ≈48 hours later (Jun 26, 2024 16:59:00 UTC)
14+
verbose: False
15+
16+
17+
plant:
18+
interconnect_limit: 45000 # kW
19+
20+
21+
wind_farm: # The name of the Wind_MesoToPower wind farm
22+
component_type: Wind_MesoToPowerPrecomFloris
23+
floris_input_file: ../inputs/floris_input_large.yaml
24+
wind_input_filename: ../inputs/wind_input_large.ftr
25+
turbine_file_name: ../inputs/turbine_filter_model.yaml
26+
log_file_name: outputs/log_wind_sim.log
27+
log_channels:
28+
- power
29+
- wind_speed_mean_background
30+
- wind_speed_mean_withwakes
31+
- wind_direction_mean
32+
- turbine_powers
33+
- wind_speeds_withwakes
34+
- wind_speeds_background
35+
- turbine_power_setpoints
36+
floris_update_time_s: 300.0 # Update wakes every 5 minutes
37+
38+
# Electrolyzer plant input file
39+
electrolyzer: # The name of the py_sim object
40+
41+
component_type: ElectrolyzerPlant
42+
general:
43+
verbose: True # default
44+
initial_conditions:
45+
# Initial power input to electrolyzer
46+
power_available_kw: 20000
47+
electrolyzer:
48+
initialize: True
49+
initial_power_kW: 20000
50+
supervisor:
51+
n_stacks: 40
52+
system_rating_MW: 40
53+
stack:
54+
cell_type: PEM
55+
# Maximum current of Stack (A)
56+
max_current: 2000
57+
# Stack operating temperature (degC)
58+
temperature: 60
59+
# Number of Cells per Stack
60+
n_cells: 100
61+
# Stack rated power
62+
stack_rating_kW: 1000
63+
# Determines whether degradation is applied to the Stack operation
64+
include_degradation_penalty: False
65+
# hydrogen_degradation_penalty: True
66+
# cell_params:
67+
# cell_type: PEM
68+
# max_current_density: 2
69+
# PEM_params:
70+
# cell_area: 1000
71+
# turndown_ratio: 0.1
72+
# max_current_density: 2.0
73+
# p_anode: 1.01325
74+
# p_cathode: 30
75+
# alpha_a: 2
76+
# alpha_c: 0.5
77+
# i_0_a: 2.0e-7
78+
# i_0_c: 2.0e-3
79+
# e_m: 0.02
80+
# R_ohmic_elec: 50.0e-3
81+
# f_1: 250
82+
# f_2: 0.996
83+
# degradation:
84+
# eol_eff_percent_loss: 20
85+
# PEM_params:
86+
# rate_steady: 1.42e-10
87+
# rate_fatigue: 3.33e-07
88+
# rate_onoff: 1.47e-04
89+
controller:
90+
# Controller type for electrolyzer plant operation
91+
control_type: PowerSharingRotation
92+
# policy:
93+
# eager_on: False
94+
# eager_off: False
95+
# sequential: False
96+
# even_dist: False
97+
# baseline: True
98+
# costs:
99+
log_channels:
100+
- power
101+
- H2_output
102+
- H2_mfr
103+
- power_input_kw
104+
105+
106+
controller:
107+
num_turbines: 9 # Should match AMR-Wind! Ideally, would come from AMR-wind
108+
nominal_plant_power_kW: 45000 # Plant power in kW
109+
nominal_hydrogen_rate_kgps: 0.208 # in kg/s [kg per day/24/3600 * stack number]
110+
hydrogen_controller_gain: 1
111+
112+
external_data_file: inputs/hydrogen_ref_signal.csv
113+
114+
115+
116+
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import numpy as np
2+
from hercules.hercules_model import HerculesModel
3+
from hercules.utilities_examples import ensure_example_inputs_exist, prepare_output_directory
4+
from whoc.controllers import (
5+
HydrogenPlantController,
6+
WindFarmPowerTrackingController,
7+
)
8+
from whoc.interfaces import HerculesV2Interface
9+
10+
prepare_output_directory()
11+
12+
# Ensure example inputs exist
13+
ensure_example_inputs_exist()
14+
15+
# Initialize the Hercules model
16+
hmodel = HerculesModel("hercules_input.yaml")
17+
18+
19+
# Define a simple controller that sets all deratings to full rating
20+
# and then sets the derating of turbine 000 to 500, toggling every other 100 seconds.
21+
class ControllerLimitSolar:
22+
"""Limits the solar power to keep the total power below the interconnect limit."""
23+
24+
def __init__(self, h_dict):
25+
"""Initialize the controller.
26+
27+
Args:
28+
h_dict (dict): The hercules input dictionary.
29+
"""
30+
self.interconnect_limit = h_dict["plant"]["interconnect_limit"]
31+
pass
32+
33+
def step(self, h_dict):
34+
"""Execute one control step.
35+
36+
Args:
37+
h_dict (dict): The hercules input dictionary.
38+
39+
Returns:
40+
dict: The updated hercules input dictionary.
41+
"""
42+
# Set wind turbine power setpoints to full rating
43+
h_dict["wind_farm"]["turbine_power_setpoints"] = 5000 * np.ones(
44+
h_dict["wind_farm"]["n_turbines"]
45+
)
46+
47+
# Get the total wind farm power
48+
wind_farm_power = h_dict["wind_farm"]["power"]
49+
50+
# Charge or discharge the battery, reversing every other hour
51+
# With hybrid plant sign convention: Positive = discharge, Negative = charge
52+
if h_dict["time"] % 3600 < 1800:
53+
h_dict["battery"]["power_setpoint"] = 10000 # Discharge the battery
54+
else:
55+
h_dict["battery"]["power_setpoint"] = -10000 # Charge the battery
56+
57+
# Get the limit for battery power
58+
# Battery power + wind power must be less than the interconnect limit
59+
battery_power_upper_limit = self.interconnect_limit - wind_farm_power
60+
battery_power_lower_limit = -1 * self.interconnect_limit - wind_farm_power
61+
62+
# Set the solar power limit
63+
h_dict["battery"]["power_setpoint"] = np.clip(
64+
h_dict["battery"]["power_setpoint"],
65+
battery_power_lower_limit,
66+
battery_power_upper_limit,
67+
)
68+
69+
return h_dict
70+
71+
# Establish controllers based on options
72+
interface = HerculesV2Interface(hmodel.h_dict)
73+
74+
print("Setting up controller.")
75+
wind_controller = WindFarmPowerTrackingController(interface, hmodel.h_dict)
76+
# solar_controller = (
77+
# SolarPassthroughController(interface, hmodel.h_dict) if include_solar
78+
# else None
79+
# )
80+
# battery_controller = (
81+
# BatteryPassthroughController(interface, hmodel.h_dict) if include_battery
82+
# else None
83+
# )
84+
controller = HydrogenPlantController(
85+
interface,
86+
hmodel.h_dict,
87+
generator_controller=wind_controller
88+
)
89+
90+
# Assign the controller to the Hercules model
91+
hmodel.assign_controller(controller)
92+
print("Controller assigned.")
93+
94+
# Run the simulation
95+
hmodel.run()
96+
97+
hmodel.logger.info("Process completed successfully")

0 commit comments

Comments
 (0)