Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- Added base class (`StorageOpenLoopControlBase`) and base configuration class (`StorageOpenLoopControlBaseConfig`) for open-loop storage control strategies and updated the existing open-loop storage control strategies to inherit these [PR 619](https://github.com/NatLabRockies/H2Integrate/pull/619)
- Added a generic cost model for converters [PR 622](https://github.com/NatLabRockies/H2Integrate/pull/622)
- Updated the `StorageAutoSizingModel` model to be compatible with Pyomo control strategies [PR 621](https://github.com/NatLabRockies/H2Integrate/pull/621)
- Removed a few usages of `shape_by_conn` due to issues with OpenMDAO v3.43.0 release on some computers [PR 632](https://github.com/NatLabRockies/H2Integrate/pull/632)

## 0.7.1 [March 13, 2026]

Expand Down
2 changes: 1 addition & 1 deletion h2integrate/core/h2integrate_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ def connect_technologies(self):
pass
else:
connection_component = self.supported_models[transport_type](
transport_item=transport_item
transport_item=transport_item, plant_config=self.plant_config
)

# Add the connection component to the model
Expand Down
8 changes: 4 additions & 4 deletions h2integrate/transporters/cable.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ class CablePerformanceModel(om.ExplicitComponent):

def initialize(self):
self.options.declare("transport_item", values=["electricity"])
self.options.declare("plant_config", types=dict)

def setup(self):
n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"])
self.input_name = self.options["transport_item"] + "_in"
self.output_name = self.options["transport_item"] + "_out"
self.add_input(
self.input_name,
val=-1.0,
shape_by_conn=True,
copy_shape=self.output_name,
shape=n_timesteps,
units="kW",
)
self.add_output(
self.output_name,
val=-1.0,
shape_by_conn=True,
copy_shape=self.input_name,
shape=n_timesteps,
units="kW",
)

Expand Down
10 changes: 6 additions & 4 deletions h2integrate/transporters/generic_splitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,12 @@ def setup(self):
additional_cls_name=self.__class__.__name__,
)

n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"])

self.add_input(
f"{self.config.commodity}_in",
val=0.0,
shape_by_conn=True,
shape=n_timesteps,
units=self.config.commodity_rate_units,
)

Expand All @@ -94,22 +96,22 @@ def setup(self):
self.add_input(
"prescribed_commodity_to_priority_tech",
val=self.config.prescribed_commodity_to_priority_tech,
copy_shape=f"{self.config.commodity}_in",
shape=n_timesteps,
units=self.config.commodity_rate_units,
desc="Prescribed amount of commodity to send to the priority technology",
)

self.add_output(
f"{self.config.commodity}_out1",
val=0.0,
copy_shape=f"{self.config.commodity}_in",
shape=n_timesteps,
units=self.config.commodity_rate_units,
desc=f"{self.config.commodity} output to the first technology",
)
self.add_output(
f"{self.config.commodity}_out2",
val=0.0,
copy_shape=f"{self.config.commodity}_in",
shape=n_timesteps,
units=self.config.commodity_rate_units,
desc=f"{self.config.commodity} output to the second technology",
)
Expand Down
9 changes: 5 additions & 4 deletions h2integrate/transporters/pipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ def initialize(self):
"water",
],
)
self.options.declare("plant_config", types=dict)

def setup(self):
transport_item = self.options["transport_item"]
self.input_name = transport_item + "_in"
self.output_name = transport_item + "_out"

n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"])

if transport_item == "natural_gas":
units = "MMBtu/h"
elif transport_item == "water":
Expand All @@ -38,15 +41,13 @@ def setup(self):
self.add_input(
self.input_name,
val=-1.0,
shape_by_conn=True,
copy_shape=self.output_name,
shape=n_timesteps,
units=units,
)
self.add_output(
self.output_name,
val=-1.0,
shape_by_conn=True,
copy_shape=self.input_name,
shape=n_timesteps,
units=units,
)

Expand Down
102 changes: 62 additions & 40 deletions h2integrate/transporters/test/test_generic_splitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,16 @@ def splitter_tech_config_hydrogen():


rng = np.random.default_rng(seed=0)
N_TIMESTEPS = 10


@fixture
def plant_config():
return {"plant": {"simulation": {"n_timesteps": N_TIMESTEPS}}}


@pytest.mark.regression
def test_splitter_ratio_mode_edge_cases_electricity(splitter_tech_config_electricity):
def test_splitter_ratio_mode_edge_cases_electricity(splitter_tech_config_electricity, plant_config):
"""Test the splitter in fraction mode with edge case fractions."""
performance_config = {
"split_mode": "fraction",
Expand All @@ -40,45 +46,45 @@ def test_splitter_ratio_mode_edge_cases_electricity(splitter_tech_config_electri
tech_config = {"model_inputs": {"performance_parameters": performance_config}}

prob = om.Problem()
comp = GenericSplitterPerformanceModel(tech_config=tech_config)
comp = GenericSplitterPerformanceModel(tech_config=tech_config, plant_config=plant_config)
prob.model.add_subsystem("comp", comp, promotes=["*"])
ivc = om.IndepVarComp()
ivc.add_output("electricity_in", val=100.0, units="kW")
ivc.add_output("electricity_in", val=np.full(N_TIMESTEPS, 100.0), units="kW")
ivc.add_output("fraction_to_priority_tech", val=0.0)
prob.model.add_subsystem("ivc", ivc, promotes=["*"])

prob.setup()

electricity_input = 100.0
electricity_input = np.full(N_TIMESTEPS, 100.0)

prob.set_val("electricity_in", electricity_input, units="kW")
prob.set_val("fraction_to_priority_tech", 0.0)
prob.run_model()

assert prob.get_val("electricity_out1", units="kW") == approx(0.0, abs=1e-10)
assert prob.get_val("electricity_out1", units="kW") == approx(np.zeros(N_TIMESTEPS), abs=1e-10)
assert prob.get_val("electricity_out2", units="kW") == approx(electricity_input, rel=1e-5)

prob.set_val("fraction_to_priority_tech", 1.0)
prob.run_model()

assert prob.get_val("electricity_out1", units="kW") == approx(electricity_input, rel=1e-5)
assert prob.get_val("electricity_out2", units="kW") == approx(0.0, abs=1e-10)
assert prob.get_val("electricity_out2", units="kW") == approx(np.zeros(N_TIMESTEPS), abs=1e-10)

prob.set_val("fraction_to_priority_tech", 1.5)
prob.run_model()

assert prob.get_val("electricity_out1", units="kW") == approx(electricity_input, rel=1e-5)
assert prob.get_val("electricity_out2", units="kW") == approx(0.0, abs=1e-10)
assert prob.get_val("electricity_out2", units="kW") == approx(np.zeros(N_TIMESTEPS), abs=1e-10)

prob.set_val("fraction_to_priority_tech", -0.5)
prob.run_model()

assert prob.get_val("electricity_out1", units="kW") == approx(0.0, abs=1e-10)
assert prob.get_val("electricity_out1", units="kW") == approx(np.zeros(N_TIMESTEPS), abs=1e-10)
assert prob.get_val("electricity_out2", units="kW") == approx(electricity_input, rel=1e-5)


@pytest.mark.regression
def test_splitter_prescribed_electricity_mode(splitter_tech_config_electricity):
def test_splitter_prescribed_electricity_mode(splitter_tech_config_electricity, plant_config):
"""Test the splitter in prescribed_electricity mode."""
performance_config = {
"split_mode": "prescribed_commodity",
Expand All @@ -90,17 +96,17 @@ def test_splitter_prescribed_electricity_mode(splitter_tech_config_electricity):
tech_config = {"model_inputs": {"performance_parameters": performance_config}}

prob = om.Problem()
comp = GenericSplitterPerformanceModel(tech_config=tech_config)
comp = GenericSplitterPerformanceModel(tech_config=tech_config, plant_config=plant_config)
prob.model.add_subsystem("comp", comp, promotes=["*"])
ivc = om.IndepVarComp()
ivc.add_output("electricity_in", val=np.zeros(8760), units="kW")
ivc.add_output("prescribed_commodity_to_priority_tech", val=np.zeros(8760), units="kW")
ivc.add_output("electricity_in", val=np.zeros(N_TIMESTEPS), units="kW")
ivc.add_output("prescribed_commodity_to_priority_tech", val=np.zeros(N_TIMESTEPS), units="kW")
prob.model.add_subsystem("ivc", ivc, promotes=["*"])

prob.setup()

electricity_input = rng.random(8760) * 500 + 300
prescribed_electricity = np.full(8760, 200.0)
electricity_input = rng.random(N_TIMESTEPS) * 500 + 300
prescribed_electricity = np.full(N_TIMESTEPS, 200.0)

prob.set_val("electricity_in", electricity_input, units="kW")
prob.set_val("prescribed_commodity_to_priority_tech", prescribed_electricity, units="kW")
Expand All @@ -119,7 +125,9 @@ def test_splitter_prescribed_electricity_mode(splitter_tech_config_electricity):


@pytest.mark.regression
def test_splitter_prescribed_electricity_mode_limited_input(splitter_tech_config_electricity):
def test_splitter_prescribed_electricity_mode_limited_input(
splitter_tech_config_electricity, plant_config
):
"""
Test the splitter in prescribed_electricity mode
when input is less than prescribed electricity.
Expand All @@ -135,31 +143,31 @@ def test_splitter_prescribed_electricity_mode_limited_input(splitter_tech_config
tech_config = {"model_inputs": {"performance_parameters": performance_config}}

prob = om.Problem()
comp = GenericSplitterPerformanceModel(tech_config=tech_config)
comp = GenericSplitterPerformanceModel(tech_config=tech_config, plant_config=plant_config)
prob.model.add_subsystem("comp", comp, promotes=["*"])
ivc = om.IndepVarComp()
ivc.add_output("electricity_in", val=np.zeros(8760), units="kW")
ivc.add_output("prescribed_commodity_to_priority_tech", val=np.zeros(8760), units="kW")
ivc.add_output("electricity_in", val=np.zeros(N_TIMESTEPS), units="kW")
ivc.add_output("prescribed_commodity_to_priority_tech", val=np.zeros(N_TIMESTEPS), units="kW")
prob.model.add_subsystem("ivc", ivc, promotes=["*"])

prob.setup()

electricity_input = np.full(8760, 100.0)
prescribed_electricity = np.full(8760, 150.0)
electricity_input = np.full(N_TIMESTEPS, 100.0)
prescribed_electricity = np.full(N_TIMESTEPS, 150.0)

prob.set_val("electricity_in", electricity_input, units="kW")
prob.set_val("prescribed_commodity_to_priority_tech", prescribed_electricity, units="kW")
prob.run_model()

expected_output1 = electricity_input
expected_output2 = np.zeros(8760)
expected_output2 = np.zeros(N_TIMESTEPS)

assert prob.get_val("electricity_out1", units="kW") == approx(expected_output1, rel=1e-5)
assert prob.get_val("electricity_out2", units="kW") == approx(expected_output2, abs=1e-10)


@pytest.mark.unit
def test_splitter_invalid_mode(splitter_tech_config_electricity):
def test_splitter_invalid_mode(splitter_tech_config_electricity, plant_config):
"""Test that an invalid split mode raises an error."""
performance_config = {
"split_mode": "invalid_mode",
Expand All @@ -174,13 +182,13 @@ def test_splitter_invalid_mode(splitter_tech_config_electricity):
match="Item invalid_mode not found in list",
):
prob = om.Problem()
comp = GenericSplitterPerformanceModel(tech_config=tech_config)
comp = GenericSplitterPerformanceModel(tech_config=tech_config, plant_config=plant_config)
prob.model.add_subsystem("comp", comp, promotes=["*"])
prob.setup()


@pytest.mark.regression
def test_splitter_scalar_inputs(splitter_tech_config_electricity):
def test_splitter_scalar_inputs(splitter_tech_config_electricity, plant_config):
"""Test the splitter with scalar inputs instead of arrays."""
performance_config_ratio = {
"split_mode": "fraction",
Expand All @@ -191,18 +199,22 @@ def test_splitter_scalar_inputs(splitter_tech_config_electricity):
tech_config_ratio = {"model_inputs": {"performance_parameters": performance_config_ratio}}

prob = om.Problem()
comp = GenericSplitterPerformanceModel(tech_config=tech_config_ratio)
comp = GenericSplitterPerformanceModel(tech_config=tech_config_ratio, plant_config=plant_config)
prob.model.add_subsystem("comp", comp, promotes=["*"])
ivc = om.IndepVarComp()
ivc.add_output("electricity_in", val=100.0, units="kW")
ivc.add_output("electricity_in", val=np.full(N_TIMESTEPS, 100.0), units="kW")
ivc.add_output("fraction_to_priority_tech", val=0.4)
prob.model.add_subsystem("ivc", ivc, promotes=["*"])

prob.setup()
prob.run_model()

assert prob.get_val("electricity_out1", units="kW") == approx(40.0, rel=1e-5)
assert prob.get_val("electricity_out2", units="kW") == approx(60.0, rel=1e-5)
assert prob.get_val("electricity_out1", units="kW") == approx(
np.full(N_TIMESTEPS, 40.0), rel=1e-5
)
assert prob.get_val("electricity_out2", units="kW") == approx(
np.full(N_TIMESTEPS, 60.0), rel=1e-5
)

performance_config_prescribed = {
"split_mode": "prescribed_commodity",
Expand All @@ -216,22 +228,32 @@ def test_splitter_scalar_inputs(splitter_tech_config_electricity):
}

prob2 = om.Problem()
comp2 = GenericSplitterPerformanceModel(tech_config=tech_config_prescribed)
comp2 = GenericSplitterPerformanceModel(
tech_config=tech_config_prescribed, plant_config=plant_config
)
prob2.model.add_subsystem("comp", comp2, promotes=["*"])
ivc2 = om.IndepVarComp()
ivc2.add_output("electricity_in", val=100.0, units="kW")
ivc2.add_output("prescribed_commodity_to_priority_tech", val=30.0, units="kW")
ivc2.add_output("electricity_in", val=np.full(N_TIMESTEPS, 100.0), units="kW")
ivc2.add_output(
"prescribed_commodity_to_priority_tech", val=np.full(N_TIMESTEPS, 30.0), units="kW"
)
prob2.model.add_subsystem("ivc", ivc2, promotes=["*"])

prob2.setup()
prob2.run_model()

assert prob2.get_val("electricity_out1", units="kW") == approx(30.0, rel=1e-5)
assert prob2.get_val("electricity_out2", units="kW") == approx(70.0, rel=1e-5)
assert prob2.get_val("electricity_out1", units="kW") == approx(
np.full(N_TIMESTEPS, 30.0), rel=1e-5
)
assert prob2.get_val("electricity_out2", units="kW") == approx(
np.full(N_TIMESTEPS, 70.0), rel=1e-5
)


@pytest.mark.regression
def test_splitter_prescribed_electricity_varied_array(splitter_tech_config_electricity):
def test_splitter_prescribed_electricity_varied_array(
splitter_tech_config_electricity, plant_config
):
"""Test the splitter in prescribed_electricity mode with a varied array (50-100 MW)."""
performance_config = {
"split_mode": "prescribed_commodity",
Expand All @@ -245,20 +267,20 @@ def test_splitter_prescribed_electricity_varied_array(splitter_tech_config_elect
tech_config.update(splitter_tech_config_electricity)

prob = om.Problem()
comp = GenericSplitterPerformanceModel(tech_config=tech_config)
comp = GenericSplitterPerformanceModel(tech_config=tech_config, plant_config=plant_config)
prob.model.add_subsystem("comp", comp, promotes=["*"])
ivc = om.IndepVarComp()
ivc.add_output("electricity_in", val=np.zeros(8760), units="kW")
ivc.add_output("prescribed_commodity_to_priority_tech", val=np.zeros(8760), units="kW")
ivc.add_output("electricity_in", val=np.zeros(N_TIMESTEPS), units="kW")
ivc.add_output("prescribed_commodity_to_priority_tech", val=np.zeros(N_TIMESTEPS), units="kW")
prob.model.add_subsystem("ivc", ivc, promotes=["*"])

prob.setup()

# Generate varied prescribed electricity array between 50-100 MW (50,000-100,000 kW)
prescribed_electricity = rng.random(8760) * 50000 + 50000 # 50-100 MW range
prescribed_electricity = rng.random(N_TIMESTEPS) * 50000 + 50000 # 50-100 MW range

# Input electricity should be higher than prescribed to test both scenarios
electricity_input = rng.random(8760) * 30000 + 120000 # 120-150 MW range
electricity_input = rng.random(N_TIMESTEPS) * 30000 + 120000 # 120-150 MW range

prob.set_val("electricity_in", electricity_input, units="kW")
prob.set_val("prescribed_commodity_to_priority_tech", prescribed_electricity, units="kW")
Expand All @@ -278,7 +300,7 @@ def test_splitter_prescribed_electricity_varied_array(splitter_tech_config_elect
assert total_output == approx(electricity_input, rel=1e-5)

# Test with some time steps where prescribed > available
electricity_input_limited = rng.random(8760) * 30000 + 20000 # 20-50 MW range
electricity_input_limited = rng.random(N_TIMESTEPS) * 30000 + 20000 # 20-50 MW range
prob.set_val("electricity_in", electricity_input_limited, units="kW")
prob.run_model()

Expand Down
Loading
Loading