From 1b9f88d371d266832cb7b215db601be4c7d47dfb Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 26 Mar 2026 15:17:53 -0600 Subject: [PATCH 1/2] renamed outputs in converter openloop controllers --- .../demand_openloop_converter_controller.py | 8 +++++--- .../flexible_demand_openloop_controller.py | 10 +++++----- .../converters/openloop_controller_base.py | 14 +++++++------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/h2integrate/control/control_strategies/converters/demand_openloop_converter_controller.py b/h2integrate/control/control_strategies/converters/demand_openloop_converter_controller.py index 7615e2c21..392f39b11 100644 --- a/h2integrate/control/control_strategies/converters/demand_openloop_converter_controller.py +++ b/h2integrate/control/control_strategies/converters/demand_openloop_converter_controller.py @@ -66,12 +66,14 @@ def compute(self, inputs, outputs): remaining_demand = inputs[f"{commodity}_demand"] - inputs[f"{commodity}_in"] # Calculate missed load and curtailed production - outputs[f"{commodity}_unmet_demand"] = np.where(remaining_demand > 0, remaining_demand, 0) - outputs[f"{commodity}_unused_commodity"] = np.where( + outputs[f"unmet_{commodity}_demand_out"] = np.where( + remaining_demand > 0, remaining_demand, 0 + ) + outputs[f"unused_{commodity}_out"] = np.where( remaining_demand < 0, -1 * remaining_demand, 0 ) # Calculate actual output based on demand met and curtailment outputs[f"{commodity}_set_point"] = ( - inputs[f"{commodity}_in"] - outputs[f"{commodity}_unused_commodity"] + inputs[f"{commodity}_in"] - outputs[f"unused_{commodity}_out"] ) diff --git a/h2integrate/control/control_strategies/converters/flexible_demand_openloop_controller.py b/h2integrate/control/control_strategies/converters/flexible_demand_openloop_controller.py index 1cc5e8bba..3ed9fae0d 100644 --- a/h2integrate/control/control_strategies/converters/flexible_demand_openloop_controller.py +++ b/h2integrate/control/control_strategies/converters/flexible_demand_openloop_controller.py @@ -268,10 +268,10 @@ def compute(self, inputs, outputs): if self.config.min_utilization == 1.0: # Calculate missed load and curtailed production - outputs[f"{commodity}_unmet_demand"] = np.where( + outputs[f"unmet_{commodity}_demand_out"] = np.where( remaining_demand > 0, remaining_demand, 0 ) - outputs[f"{commodity}_unused_commodity"] = np.where( + outputs[f"unused_{commodity}_out"] = np.where( remaining_demand < 0, -1 * remaining_demand, 0 ) else: @@ -289,14 +289,14 @@ def compute(self, inputs, outputs): outputs[f"{commodity}_flexible_demand_profile"] = flexible_demand_profile flexible_remaining_demand = flexible_demand_profile - inputs[f"{commodity}_in"] - outputs[f"{commodity}_unmet_demand"] = np.where( + outputs[f"unmet_{commodity}_demand_out"] = np.where( flexible_remaining_demand > 0, flexible_remaining_demand, 0 ) - outputs[f"{commodity}_unused_commodity"] = np.where( + outputs[f"unused_{commodity}_out"] = np.where( flexible_remaining_demand < 0, -1 * flexible_remaining_demand, 0 ) # Calculate actual output based on demand met and curtailment outputs[f"{commodity}_set_point"] = ( - inputs[f"{commodity}_in"] - outputs[f"{commodity}_unused_commodity"] + inputs[f"{commodity}_in"] - outputs[f"unused_{commodity}_out"] ) diff --git a/h2integrate/control/control_strategies/converters/openloop_controller_base.py b/h2integrate/control/control_strategies/converters/openloop_controller_base.py index a4818301d..993ba76c6 100644 --- a/h2integrate/control/control_strategies/converters/openloop_controller_base.py +++ b/h2integrate/control/control_strategies/converters/openloop_controller_base.py @@ -82,7 +82,7 @@ def setup(self): ) self.add_output( - f"{commodity}_unmet_demand", + f"unmet_{commodity}_demand_out", val=self.config.demand_profile, shape=(n_timesteps), units=self.config.commodity_rate_units, @@ -90,7 +90,7 @@ def setup(self): ) self.add_output( - f"{commodity}_unused_commodity", + f"unused_{commodity}_out", val=0.0, shape=(n_timesteps), units=self.config.commodity_rate_units, @@ -105,11 +105,11 @@ def setup(self): desc=f"Production profile of {commodity}", ) - self.add_output( - f"total_{commodity}_unmet_demand", - units=self.config.commodity_rate_units, - desc="Total unmet demand", - ) + # self.add_output( + # f"total_{commodity}_unmet_demand", + # units=self.config.commodity_rate_units, + # desc="Total unmet demand", + # ) def compute(): """This method must be implemented by subclasses to define the From 2b3d0c1480fb959e6425e8d421aa7eea77f60037 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 26 Mar 2026 15:26:41 -0600 Subject: [PATCH 2/2] updated tests and examples with updated naming --- examples/19_simple_dispatch/run_wind_battery.py | 4 ++-- examples/23_solar_wind_ng_demand/plant_config.yaml | 2 +- examples/24_solar_battery_grid/README.md | 12 ++++++------ .../demand_openloop_converter_controller.py | 6 +++--- .../control/test/test_openloop_controllers.py | 10 +++++----- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/examples/19_simple_dispatch/run_wind_battery.py b/examples/19_simple_dispatch/run_wind_battery.py index 7fff85678..8e2ecdaf6 100644 --- a/examples/19_simple_dispatch/run_wind_battery.py +++ b/examples/19_simple_dispatch/run_wind_battery.py @@ -42,14 +42,14 @@ ) ax[1].plot( range(start_hour, end_hour), - model.prob.get_val("battery.electricity_unused_commodity", units="MW")[start_hour:end_hour], + model.prob.get_val("battery.unused_electricity_out", units="MW")[start_hour:end_hour], linestyle=":", label="Unused Electricity commodity (MW)", linewidth=2, ) ax[1].plot( range(start_hour, end_hour), - model.prob.get_val("battery.electricity_unmet_demand", units="MW")[start_hour:end_hour], + model.prob.get_val("battery.unmet_electricity_demand_out", units="MW")[start_hour:end_hour], linestyle=":", label="Electricity Unmet Demand (MW)", linewidth=2, diff --git a/examples/23_solar_wind_ng_demand/plant_config.yaml b/examples/23_solar_wind_ng_demand/plant_config.yaml index 555b9b13e..69fc71a98 100644 --- a/examples/23_solar_wind_ng_demand/plant_config.yaml +++ b/examples/23_solar_wind_ng_demand/plant_config.yaml @@ -27,7 +27,7 @@ technology_interconnections: # connect NG feedstock to NG plant - [combiner, electrical_load_demand, [electricity_out, electricity_in]] # subtract wind and solar from demand - - [electrical_load_demand, natural_gas_plant, [electricity_unmet_demand, electricity_demand]] + - [electrical_load_demand, natural_gas_plant, [unmet_electricity_demand_out, electricity_demand]] # give remaining load demand to natural gas plant - [combiner, fin_combiner, electricity, cable] - [natural_gas_plant, fin_combiner, electricity, cable] diff --git a/examples/24_solar_battery_grid/README.md b/examples/24_solar_battery_grid/README.md index 2cdf79ef6..cce607d62 100644 --- a/examples/24_solar_battery_grid/README.md +++ b/examples/24_solar_battery_grid/README.md @@ -53,8 +53,8 @@ The grid performance model handles: - `electricity_in`: Power flowing INTO the grid (selling to grid) - limited by interconnection size - `electricity_out`: Power flowing OUT OF the grid (buying from grid) - limited by interconnection size - `electricity_sold`: Actual electricity sold (up to interconnection limit) -- `electricity_unmet_demand`: Demand that couldn't be met due to interconnection limit -- `electricity_excess`: Electricity that couldn't be sold due to interconnection limit +- `unmet_electricity_demand_out`: Demand that couldn't be met due to interconnection limit +- `unused_electricity_out`: Electricity that couldn't be sold due to interconnection limit **Cost Model:** - CapEx: Based on interconnection size ($/kW) plus fixed costs @@ -72,16 +72,16 @@ Solar → Battery → [Grid Buy (purchases) | Grid Sell (sales)] - Solar generates electricity - Battery stores excess and follows 100 MW demand profile -- Grid Buy purchases electricity when battery cannot meet demand (via `electricity_unmet_demand` → `electricity_demand` connection) -- Grid Sell accepts excess electricity when battery has surplus (via `electricity_unused_commodity` → `electricity_in` connection) +- Grid Buy purchases electricity when battery cannot meet demand (via `unmet_electricity_demand_out` → `electricity_demand` connection) +- Grid Sell accepts excess electricity when battery has surplus (via `unused_electricity_out` → `electricity_in` connection) ### Technology Interconnections ```yaml technology_interconnections: [ ["solar", "battery", "electricity", "cable"], - ["battery", "grid_buy", ["electricity_unmet_demand", "electricity_demand"]], - ["battery", "grid_sell", ["electricity_unused_commodity", "electricity_in"]] + ["battery", "grid_buy", ["unmet_electricity_demand_out", "electricity_demand"]], + ["battery", "grid_sell", ["unused_electricity_out", "electricity_in"]] ] ``` diff --git a/h2integrate/control/control_strategies/converters/demand_openloop_converter_controller.py b/h2integrate/control/control_strategies/converters/demand_openloop_converter_controller.py index 392f39b11..8af224688 100644 --- a/h2integrate/control/control_strategies/converters/demand_openloop_converter_controller.py +++ b/h2integrate/control/control_strategies/converters/demand_openloop_converter_controller.py @@ -54,9 +54,9 @@ def compute(self, inputs, outputs): outputs (dict-like): Mapping of output variable names where results will be written, including: - * ``{commodity}_unmet_demand``: Unmet demand. - * ``{commodity}_unused_commodity``: Curtailed production. - * ``{commodity}_out``: Actual output delivered. + * ``unmet_{commodity}_demand_out``: Unmet demand. + * ``unused_{commodity}_out``: Curtailed production. + * ``{commodity}_set_point``: Actual output delivered. Notes: All variables operate on a per-timestep basis and typically have diff --git a/h2integrate/control/test/test_openloop_controllers.py b/h2integrate/control/test/test_openloop_controllers.py index 402f201b9..e96b8f76c 100644 --- a/h2integrate/control/test/test_openloop_controllers.py +++ b/h2integrate/control/test/test_openloop_controllers.py @@ -562,12 +562,12 @@ def test_demand_converter_controller(subtests): ) with subtests.test("Check curtailment"): - assert prob.get_val("hydrogen_unused_commodity") == pytest.approx( + assert prob.get_val("unused_hydrogen_out") == pytest.approx( [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0] ) with subtests.test("Check missed load"): - assert prob.get_val("hydrogen_unmet_demand") == pytest.approx( + assert prob.get_val("unmet_hydrogen_demand_out") == pytest.approx( [5.0, 4.0, 3.0, 2.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0] ) @@ -644,7 +644,7 @@ def test_flexible_demand_converter_controller(subtests, variable_h2_production_p assert np.all(flexible_total_demand <= end_use_rated_demand) with subtests.test("Check curtailment"): # failed - assert np.sum(prob.get_val("hydrogen_unused_commodity", units="kg")) == pytest.approx(6.6) + assert np.sum(prob.get_val("unused_hydrogen_out", units="kg")) == pytest.approx(6.6) # check ramping constraints and turndown constraints are met with subtests.test("Check turndown ratio constraint"): @@ -672,13 +672,13 @@ def test_flexible_demand_converter_controller(subtests, variable_h2_production_p # any commodity in) with subtests.test("Check that flexible demand is greater than hydrogen_in"): hydrogen_available = variable_h2_production_profile - prob.get_val( - "hydrogen_unused_commodity", units="kg" + "unused_hydrogen_out", units="kg" ) assert np.all(flexible_total_demand >= hydrogen_available) with subtests.test("Check that remaining demand was calculated properly"): unmet_demand = flexible_total_demand - hydrogen_available - assert np.all(unmet_demand == prob.get_val("hydrogen_unmet_demand", units="kg")) + assert np.all(unmet_demand == prob.get_val("unmet_hydrogen_demand_out", units="kg")) @pytest.mark.regression