From 0e7567e799cf115d49ab5a72d4ca787b542f5122 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Thu, 4 Sep 2025 15:31:01 -0600 Subject: [PATCH 1/4] Making pipe and cable substance-agnostic --- h2integrate/transporters/cable.py | 17 ++++++++++++----- h2integrate/transporters/pipe.py | 19 ++++++++++++++----- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/h2integrate/transporters/cable.py b/h2integrate/transporters/cable.py index 1261b70b4..03ac11a13 100644 --- a/h2integrate/transporters/cable.py +++ b/h2integrate/transporters/cable.py @@ -1,4 +1,7 @@ import openmdao.api as om +from attrs import field + +from h2integrate.core.validators import contains class CablePerformanceModel(om.ExplicitComponent): @@ -6,21 +9,25 @@ class CablePerformanceModel(om.ExplicitComponent): Pass-through cable with no losses. """ + transport_item: str = field(validator=contains(["electricity"])) + def setup(self): + self.input_name = self.transport_item + "_in" + self.output_name = self.transport_item + "_out" self.add_input( - "electricity_in", + self.input_name, val=0.0, shape_by_conn=True, - copy_shape="electricity_out", + copy_shape=self.output_name, units="kW", ) self.add_output( - "electricity_out", + self.output_name, val=0.0, shape_by_conn=True, - copy_shape="electricity_in", + copy_shape=self.input_name, units="kW", ) def compute(self, inputs, outputs): - outputs["electricity_out"] = inputs["electricity_in"] + outputs[self.output_name] = inputs[self.input_name] diff --git a/h2integrate/transporters/pipe.py b/h2integrate/transporters/pipe.py index 077e080db..0734feb2a 100644 --- a/h2integrate/transporters/pipe.py +++ b/h2integrate/transporters/pipe.py @@ -1,4 +1,7 @@ import openmdao.api as om +from attrs import field + +from h2integrate.core.validators import contains class PipePerformanceModel(om.ExplicitComponent): @@ -6,21 +9,27 @@ class PipePerformanceModel(om.ExplicitComponent): Pass-through pipe with no losses. """ + transport_item: str = field( + validator=contains(["hydrogen", "co2", "methanol", "ammonia", "nitrogen"]) + ) + def setup(self): + self.input_name = self.transport_item + "_in" + self.output_name = self.transport_item + "_out" self.add_input( - "hydrogen_in", + self.input_name, val=0.0, shape_by_conn=True, - copy_shape="hydrogen_out", + copy_shape=self.output_name, units="kg/s", ) self.add_output( - "hydrogen_out", + self.output_name, val=0.0, shape_by_conn=True, - copy_shape="hydrogen_in", + copy_shape=self.input_name, units="kg/s", ) def compute(self, inputs, outputs): - outputs["hydrogen_out"] = inputs["hydrogen_in"] + outputs[self.output_name] = inputs[self.input_name] From aeb745253a14b16c81faaaf1261cd0a576e2e0c5 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Thu, 4 Sep 2025 16:27:38 -0600 Subject: [PATCH 2/4] Missed one line added to h2integrate_model.py --- h2integrate/core/h2integrate_model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/h2integrate/core/h2integrate_model.py b/h2integrate/core/h2integrate_model.py index 12607ed8e..a12b09f88 100644 --- a/h2integrate/core/h2integrate_model.py +++ b/h2integrate/core/h2integrate_model.py @@ -464,6 +464,7 @@ def connect_technologies(self): # Create the transport object connection_component = self.supported_models[transport_type]() + connection_component.transport_item = transport_item # Add the connection component to the model self.plant.add_subsystem(connection_name, connection_component) From 91fe4b25a851966328038c8bb8992d5f28af0520 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Thu, 4 Sep 2025 16:30:37 -0600 Subject: [PATCH 3/4] Changelog update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bc025463..a374693d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ - Updated option to pass variables in technology interconnections to allow for different variable names from source to destination in the format `[source_tech, dest_tech, (source_tech_variable, dest_tech_variable)]` - Added `simulation` section under `plant_config['plant']` that has information such as number of timesteps in the simulation, time step interval in seconds, simulation start time, and time zone. - Added `"custom_electrolyzer_cost"` model, an electrolyzer cost model that allows for user-defined capex and opex values +- Made `pipe` and `cable` substance-agnostic rather than hard-coded for `hydrogen` and `electricity` ## 0.3.0 [May 2 2025] From fc77ff9dccec7b630191b4a4bc3aea66edc4faa8 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Thu, 4 Sep 2025 18:27:38 -0600 Subject: [PATCH 4/4] Added pipe test --- examples/05_wind_h2_opt/driver_config.yaml | 6 ++-- h2integrate/core/h2integrate_model.py | 5 +-- h2integrate/transporters/cable.py | 10 +++--- h2integrate/transporters/pipe.py | 14 ++++---- h2integrate/transporters/test/test_pipe.py | 40 ++++++++++++++++++++++ 5 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 h2integrate/transporters/test/test_pipe.py diff --git a/examples/05_wind_h2_opt/driver_config.yaml b/examples/05_wind_h2_opt/driver_config.yaml index 086eb1077..f2ae451a4 100644 --- a/examples/05_wind_h2_opt/driver_config.yaml +++ b/examples/05_wind_h2_opt/driver_config.yaml @@ -7,11 +7,11 @@ general: driver: optimization: flag: True + solver: COBYLA tol: 0.1 catol: 10000 max_iter: 100 - solver: COBYLA - rhobeg: 30 + rhobeg: 10 debug_print: True design_variables: @@ -35,4 +35,4 @@ objective: recorder: flag: True file: "wind_h2_opt.sql" - includes: ["LCOH"] + includes: ["*"] diff --git a/h2integrate/core/h2integrate_model.py b/h2integrate/core/h2integrate_model.py index a12b09f88..85730d076 100644 --- a/h2integrate/core/h2integrate_model.py +++ b/h2integrate/core/h2integrate_model.py @@ -463,8 +463,9 @@ def connect_technologies(self): connection_name = f"{source_tech}_to_{dest_tech}_{transport_type}" # Create the transport object - connection_component = self.supported_models[transport_type]() - connection_component.transport_item = transport_item + connection_component = self.supported_models[transport_type]( + transport_item=transport_item + ) # Add the connection component to the model self.plant.add_subsystem(connection_name, connection_component) diff --git a/h2integrate/transporters/cable.py b/h2integrate/transporters/cable.py index 03ac11a13..cd25dcfe6 100644 --- a/h2integrate/transporters/cable.py +++ b/h2integrate/transporters/cable.py @@ -1,7 +1,4 @@ import openmdao.api as om -from attrs import field - -from h2integrate.core.validators import contains class CablePerformanceModel(om.ExplicitComponent): @@ -9,11 +6,12 @@ class CablePerformanceModel(om.ExplicitComponent): Pass-through cable with no losses. """ - transport_item: str = field(validator=contains(["electricity"])) + def initialize(self): + self.options.declare("transport_item", values=["electricity"]) def setup(self): - self.input_name = self.transport_item + "_in" - self.output_name = self.transport_item + "_out" + self.input_name = self.options["transport_item"] + "_in" + self.output_name = self.options["transport_item"] + "_out" self.add_input( self.input_name, val=0.0, diff --git a/h2integrate/transporters/pipe.py b/h2integrate/transporters/pipe.py index 0734feb2a..f1ef7300d 100644 --- a/h2integrate/transporters/pipe.py +++ b/h2integrate/transporters/pipe.py @@ -1,7 +1,4 @@ import openmdao.api as om -from attrs import field - -from h2integrate.core.validators import contains class PipePerformanceModel(om.ExplicitComponent): @@ -9,13 +6,14 @@ class PipePerformanceModel(om.ExplicitComponent): Pass-through pipe with no losses. """ - transport_item: str = field( - validator=contains(["hydrogen", "co2", "methanol", "ammonia", "nitrogen"]) - ) + def initialize(self): + self.options.declare( + "transport_item", values=["hydrogen", "co2", "methanol", "ammonia", "nitrogen"] + ) def setup(self): - self.input_name = self.transport_item + "_in" - self.output_name = self.transport_item + "_out" + self.input_name = self.options["transport_item"] + "_in" + self.output_name = self.options["transport_item"] + "_out" self.add_input( self.input_name, val=0.0, diff --git a/h2integrate/transporters/test/test_pipe.py b/h2integrate/transporters/test/test_pipe.py new file mode 100644 index 000000000..32718fb12 --- /dev/null +++ b/h2integrate/transporters/test/test_pipe.py @@ -0,0 +1,40 @@ +import pytest +import openmdao.api as om +from pytest import approx + +from h2integrate.transporters.pipe import PipePerformanceModel + + +def test_pipe_with_hydrogen(): + """Test the pipe transport with hydrogen as transport_item.""" + + # Create the pipe component with hydrogen as transport item + pipe = PipePerformanceModel(transport_item="hydrogen") + + # Create OpenMDAO problem and add the component + prob = om.Problem() + prob.model.add_subsystem("pipe", pipe, promotes=["*"]) + + # Add independent variable component for input + ivc = om.IndepVarComp() + ivc.add_output("hydrogen_in", val=10.0, units="kg/s") + prob.model.add_subsystem("ivc", ivc, promotes=["*"]) + + # Setup and run the model + prob.setup() + prob.set_val("hydrogen_in", 10.0, units="kg/s") + prob.run_model() + + # Check that output equals input (pass-through pipe with no losses) + hydrogen_in = prob.get_val("hydrogen_in", units="kg/s") + hydrogen_out = prob.get_val("hydrogen_out", units="kg/s") + + assert hydrogen_out == approx(hydrogen_in, rel=1e-10) + assert hydrogen_out == approx(10.0, rel=1e-10) + + +def test_pipe_with_invalid_transport_item(): + """Test that pipe raises an error with invalid transport_item.""" + with pytest.raises(ValueError) as excinfo: + PipePerformanceModel(transport_item="invalid_item") + assert "Value ('invalid_item')" in str(excinfo.value)