From cdf5f67c69c521e8cbc95583d8488a3390333628 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 10 Feb 2026 16:34:14 -0700 Subject: [PATCH 01/20] added capacity and price as inputs to feedstock component --- h2integrate/core/feedstocks.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/h2integrate/core/feedstocks.py b/h2integrate/core/feedstocks.py index c23e44d6e..8ff7c5cec 100644 --- a/h2integrate/core/feedstocks.py +++ b/h2integrate/core/feedstocks.py @@ -36,13 +36,18 @@ def setup(self): n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] feedstock_type = self.config.feedstock_type + self.add_input( + f"{feedstock_type}_capacity", val=self.config.rated_capacity, units=self.config.units + ) self.add_output(f"{feedstock_type}_out", shape=n_timesteps, units=self.config.units) def compute(self, inputs, outputs): feedstock_type = self.config.feedstock_type n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] # Generate feedstock array operating at full capacity for the full year - outputs[f"{feedstock_type}_out"] = np.full(n_timesteps, self.config.rated_capacity) + outputs[f"{feedstock_type}_out"] = np.full( + n_timesteps, inputs[f"{feedstock_type}_capacity"][0] + ) @define(kw_only=True) @@ -86,10 +91,17 @@ def setup(self): units=self.config.units, desc=f"Consumption profile of {feedstock_type}", ) + self.add_input( + "price", + val=self.config.price, + shape=int(n_timesteps), + units=f"USD/(({self.config.units})*h)", + desc=f"Price profile of {feedstock_type}", + ) def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): feedstock_type = self.config.feedstock_type - price = self.config.price + price = inputs["price"] hourly_consumption = inputs[f"{feedstock_type}_consumed"] cost_per_year = sum(price * hourly_consumption) From de5ef009bb0521183e1d8b69ed919fa002b13799 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:03:48 -0700 Subject: [PATCH 02/20] updated feedstock mdoel to have standard outputs --- h2integrate/core/feedstocks.py | 97 ++++++++++++++++++------ h2integrate/core/test/test_feedstocks.py | 4 +- 2 files changed, 77 insertions(+), 24 deletions(-) diff --git a/h2integrate/core/feedstocks.py b/h2integrate/core/feedstocks.py index 8ff7c5cec..095def74c 100644 --- a/h2integrate/core/feedstocks.py +++ b/h2integrate/core/feedstocks.py @@ -11,14 +11,15 @@ class FeedstockPerformanceConfig(BaseConfig): """Config class for feedstock. Attributes: - name (str): feedstock name + feedstock_type (str): feedstock name units (str): feedstock usage units (such as "galUS" or "kg") rated_capacity (float): The rated capacity of the feedstock in `units`/hour. This is used to size the feedstock supply to meet the plant's needs. """ - feedstock_type: str = field() - units: str = field() + feedstock_type: str = field() # TODO: replace with feedstock_commodity + units: str = field() # TODO: replace with commodity_rate_units + # TODO: add commodity_amount_units rated_capacity: float = field() @@ -33,20 +34,24 @@ def setup(self): merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), additional_cls_name=self.__class__.__name__, ) - n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] - feedstock_type = self.config.feedstock_type + self.n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) + self.commodity = self.config.feedstock_type + self.commodity_rate_units = self.config.units self.add_input( - f"{feedstock_type}_capacity", val=self.config.rated_capacity, units=self.config.units + f"rated_{self.commodity}_production", + val=self.config.rated_capacity, + units=self.commodity_rate_units, + ) + + self.add_output( + f"{self.commodity}_out", shape=self.n_timesteps, units=self.commodity_rate_units ) - self.add_output(f"{feedstock_type}_out", shape=n_timesteps, units=self.config.units) def compute(self, inputs, outputs): - feedstock_type = self.config.feedstock_type - n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] # Generate feedstock array operating at full capacity for the full year - outputs[f"{feedstock_type}_out"] = np.full( - n_timesteps, inputs[f"{feedstock_type}_capacity"][0] + outputs[f"{self.commodity}_out"] = np.full( + self.n_timesteps, inputs[f"rated_{self.commodity}_production"][0] ) @@ -55,7 +60,7 @@ class FeedstockCostConfig(CostModelBaseConfig): """Config class for feedstock. Attributes: - name (str): feedstock name + feedstock_type (str): feedstock name units (str): feedstock usage units (such as "galUS" or "kg") price (scalar or list): The cost of the feedstock in USD/`units`). If scalar, cost is assumed to be constant for each timestep and each year. @@ -79,30 +84,76 @@ def setup(self): merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), additional_cls_name=self.__class__.__name__, ) - n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] + self.n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) + # Set cost outputs super().setup() - feedstock_type = self.config.feedstock_type + self.commodity = self.config.feedstock_type + self.commodity_rate_units = self.config.units + self.commodity_amount_units = f"({self.config.units})*h" + + # Feedstock available from performance model, used to calculate CF self.add_input( - f"{feedstock_type}_consumed", + f"{self.commodity}_out", val=0, shape=self.n_timesteps, units=self.commodity_rate_units + ) + + # Feedstock consumed, used to calculate VarOpEx and CF + self.add_input( + f"{self.commodity}_consumed", val=0.0, - shape=int(n_timesteps), - units=self.config.units, - desc=f"Consumption profile of {feedstock_type}", + shape=self.n_timesteps, + units=self.commodity_rate_units, + desc=f"Consumption profile of {self.commodity}", ) self.add_input( "price", val=self.config.price, - shape=int(n_timesteps), - units=f"USD/(({self.config.units})*h)", - desc=f"Price profile of {feedstock_type}", + shape=self.n_timesteps, + units=f"USD/({self.commodity_amount_units})", + desc=f"Price profile of {self.commodity}", + ) + + # TODO: add outputs like those in PerformanceModelBaseClass + self.dt = self.options["plant_config"]["plant"]["simulation"]["dt"] + self.plant_life = int(self.options["plant_config"]["plant"]["plant_life"]) + hours_per_year = 8760 + hours_simulated = (self.dt / 3600) * self.n_timesteps + self.fraction_of_year_simulated = hours_simulated / hours_per_year + + self.add_output( + f"total_{self.commodity}_consumed", val=0.0, units=self.commodity_amount_units + ) + self.add_output( + f"annual_{self.commodity}_consumed", + val=0.0, + shape=self.plant_life, + units=f"({self.commodity_amount_units})/year", + ) + self.add_output( + "capacity_factor", + val=0.0, + shape=self.plant_life, + units="unitless", + desc="Capacity factor", ) def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - feedstock_type = self.config.feedstock_type + # Calculate performance based on consumption + + outputs["capacity_factor"] = ( + inputs[f"{self.commodity}_consumed"].sum() / inputs[f"{self.commodity}_out"].sum() + ) + outputs[f"total_{self.commodity}_consumed"] = inputs[f"{self.commodity}_consumed"].sum() * ( + self.dt / 3600 + ) + outputs[f"annual_{self.commodity}_consumed"] = outputs[ + f"total_{self.commodity}_consumed" + ] * (1 / self.fraction_of_year_simulated) + + # Calculate costs price = inputs["price"] - hourly_consumption = inputs[f"{feedstock_type}_consumed"] + hourly_consumption = inputs[f"{self.commodity}_consumed"] cost_per_year = sum(price * hourly_consumption) outputs["CapEx"] = self.config.start_up_cost diff --git a/h2integrate/core/test/test_feedstocks.py b/h2integrate/core/test/test_feedstocks.py index 95d53bbf5..a1eca7ecc 100644 --- a/h2integrate/core/test/test_feedstocks.py +++ b/h2integrate/core/test/test_feedstocks.py @@ -57,7 +57,9 @@ def create_basic_feedstock_config( } } - plant_config = {"plant": {"plant_life": 30, "simulation": {"n_timesteps": 8760}}} + plant_config = { + "plant": {"plant_life": 30, "simulation": {"n_timesteps": 8760, "dt": 3600}} + } driver_config = {} From 2066e16815a2ee16aca28c5813e798f3d44ed14a Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:11:59 -0700 Subject: [PATCH 03/20] added commodity_amount_units to FeedstockCostConfig --- h2integrate/core/feedstocks.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/h2integrate/core/feedstocks.py b/h2integrate/core/feedstocks.py index 095def74c..87408d965 100644 --- a/h2integrate/core/feedstocks.py +++ b/h2integrate/core/feedstocks.py @@ -17,9 +17,8 @@ class FeedstockPerformanceConfig(BaseConfig): This is used to size the feedstock supply to meet the plant's needs. """ - feedstock_type: str = field() # TODO: replace with feedstock_commodity - units: str = field() # TODO: replace with commodity_rate_units - # TODO: add commodity_amount_units + feedstock_type: str = field() # TODO: rename to commodity_type + units: str = field() # TODO: rename to commodity_rate_units rated_capacity: float = field() @@ -62,20 +61,26 @@ class FeedstockCostConfig(CostModelBaseConfig): Attributes: feedstock_type (str): feedstock name units (str): feedstock usage units (such as "galUS" or "kg") - price (scalar or list): The cost of the feedstock in USD/`units`). + price (scalar or list): The cost of the feedstock in USD/`commodity_amount_units`). If scalar, cost is assumed to be constant for each timestep and each year. If list, then it can be the cost per timestep of the simulation - annual_cost (float, optional): fixed cost associated with the feedstock in USD/year start_up_cost (float, optional): one-time capital cost associated with the feedstock in USD. cost_year (int): dollar-year for costs. + commodity_amount_units (str | None, optional): the amount units of the commodity (i.e., + "kg", "kW" or "galUS"). If None, will be set as `units`*h """ - feedstock_type: str = field() - units: str = field() + feedstock_type: str = field() # TODO: rename to commodity_type + units: str = field() # TODO: rename to commodity_rate_units price: int | float | list = field() annual_cost: float = field(default=0.0) start_up_cost: float = field(default=0.0) + commodity_amount_units: str | None = field(default=None) + + def __attrs_post_init__(self): + if self.commodity_amount_units is None: + self.commodity_amount_units = f"({self.units})*h" class FeedstockCostModel(CostModelBaseClass): @@ -91,7 +96,7 @@ def setup(self): self.commodity = self.config.feedstock_type self.commodity_rate_units = self.config.units - self.commodity_amount_units = f"({self.config.units})*h" + self.commodity_amount_units = self.config.commodity_amount_units # Feedstock available from performance model, used to calculate CF self.add_input( From 387641868c6d66bc48c8e3a9af739521997d3cc7 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:39:11 -0700 Subject: [PATCH 04/20] added standard output to feedstock cost model and connected commodity_out between performance and cost model --- examples/test/test_all_examples.py | 4 ++ h2integrate/core/h2integrate_model.py | 5 ++ h2integrate/core/test/test_feedstocks.py | 66 ++++++++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/examples/test/test_all_examples.py b/examples/test/test_all_examples.py index 5d36e9fea..3114a3d89 100644 --- a/examples/test/test_all_examples.py +++ b/examples/test/test_all_examples.py @@ -810,6 +810,10 @@ def test_natural_gas_example(subtests): expected_opex = 4.2 * ng_consumed.sum() # price = 4.2 $/MMBtu assert pytest.approx(ng_opex, rel=1e-6) == expected_opex + with subtests.test("Check feedstock capacity factor"): + ng_cf = model.prob.get_val("ng_feedstock.capacity_factor", units="unitless").mean() + assert pytest.approx(ng_cf, rel=1e-6) == 0.5676562763739097 + def test_wind_solar_electrolyzer_example(subtests): # Change the current working directory to the example's directory diff --git a/h2integrate/core/h2integrate_model.py b/h2integrate/core/h2integrate_model.py index 5c9923280..f8074e951 100644 --- a/h2integrate/core/h2integrate_model.py +++ b/h2integrate/core/h2integrate_model.py @@ -911,6 +911,11 @@ def connect_technologies(self): f"{dest_tech}.{transport_item}_consumed", f"{source_tech}.{transport_item}_consumed", ) + # Connect the feedstock performance model output to the cost model input + self.plant.connect( + f"{source_tech}_source.{transport_item}_out", + f"{source_tech}.{transport_item}_out", + ) if perf_model_name == "FeedstockPerformanceModel": source_tech = f"{source_tech}_source" diff --git a/h2integrate/core/test/test_feedstocks.py b/h2integrate/core/test/test_feedstocks.py index a1eca7ecc..8592e3cb0 100644 --- a/h2integrate/core/test/test_feedstocks.py +++ b/h2integrate/core/test/test_feedstocks.py @@ -9,11 +9,77 @@ from pathlib import Path import numpy as np +import pytest import openmdao.api as om +from pytest import fixture from h2integrate.core.feedstocks import FeedstockCostModel, FeedstockPerformanceModel +@fixture +def plant_config(): + return { + "plant": { + "plant_life": 30, + "simulation": { + "n_timesteps": 8760, + "dt": 3600, + }, + }, + } + + +@fixture +def ng_tech_input_config(): + tech_config = { + "model_inputs": { + "shared_parameters": { + "feedstock_type": "natural_gas", + "units": "MMBtu/h", + }, + "performance_parameters": { + "rated_capacity": 100.0, + }, + "cost_parameters": { + "price": 4.2, # USD/MMBtu + "annual_cost": 0, + "start_up_cost": 0, + "cost_year": 2023, + "commodity_amount_units": "MMBtu", # optional + }, + } + } + return tech_config + + +def test_feedstock_standard_outputs(plant_config, ng_tech_input_config, subtests): + perf_model = FeedstockPerformanceModel( + plant_config=plant_config, tech_config=ng_tech_input_config, driver_config={} + ) + cost_model = FeedstockCostModel( + plant_config=plant_config, tech_config=ng_tech_input_config, driver_config={} + ) + prob = om.Problem() + prob.model.add_subsystem("ng_feedstock_source", perf_model) + prob.model.add_subsystem("ng_feedstock", cost_model) + # Connect the feedstock performance model output to the cost model input + prob.model.connect( + "ng_feedstock_source.natural_gas_out", + "ng_feedstock.natural_gas_out", + ) + + prob.setup() + # Set some consumption values + consumption = np.full(8760, 50.0) # 50 MMBtu/hour + prob.set_val("ng_feedstock.natural_gas_consumed", consumption) + prob.run_model() + with subtests.test("Check feedstock capacity factor"): + ng_cf = prob.get_val("ng_feedstock.capacity_factor", units="unitless").mean() + assert pytest.approx(ng_cf, rel=1e-6) == 0.5 + # TODO: add subtests for rated_natural_gas_production, total_natural_gas_consumed, + # and annual_natural_gas_consumed + + class TestFeedstocks(unittest.TestCase): """Test cases for feedstock models.""" From dd162353af830e7f058ae26e9f26a3bc5063477e Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:47:40 -0700 Subject: [PATCH 05/20] renamed config inputs for feedstocks --- examples/16_natural_gas/tech_config.yaml | 4 +-- examples/21_iron_mn_to_il/tech_config.yaml | 36 +++++++++---------- .../23_solar_wind_ng_demand/tech_config.yaml | 4 +-- h2integrate/core/feedstocks.py | 18 +++++----- h2integrate/core/test/test_feedstocks.py | 8 ++--- 5 files changed, 35 insertions(+), 35 deletions(-) diff --git a/examples/16_natural_gas/tech_config.yaml b/examples/16_natural_gas/tech_config.yaml index d1dc5e313..891b8e45e 100644 --- a/examples/16_natural_gas/tech_config.yaml +++ b/examples/16_natural_gas/tech_config.yaml @@ -63,8 +63,8 @@ technologies: model: "FeedstockCostModel" model_inputs: shared_parameters: - feedstock_type: "natural_gas" - units: "MMBtu" + commodity: "natural_gas" + commodity_rate_units: "MMBtu" performance_parameters: rated_capacity: 750. #MMBtu cost_parameters: diff --git a/examples/21_iron_mn_to_il/tech_config.yaml b/examples/21_iron_mn_to_il/tech_config.yaml index 1265eaf36..c6083d209 100644 --- a/examples/21_iron_mn_to_il/tech_config.yaml +++ b/examples/21_iron_mn_to_il/tech_config.yaml @@ -9,8 +9,8 @@ technologies: model: "FeedstockCostModel" model_inputs: shared_parameters: - feedstock_type: "electricity" - units: "MW" + commodity: "electricity" + commodity_rate_units: "MW" performance_parameters: rated_capacity: 30. # MW, need 27.913 MW per timestep for iron ore cost_parameters: @@ -26,8 +26,8 @@ technologies: model: "FeedstockCostModel" model_inputs: shared_parameters: - feedstock_type: "crude_ore" - units: "t/h" + commodity: "crude_ore" + commodity_rate_units: "t/h" performance_parameters: rated_capacity: 2000. # need 828.50385048 t/h cost_parameters: @@ -67,8 +67,8 @@ technologies: model: "FeedstockCostModel" model_inputs: shared_parameters: - feedstock_type: "natural_gas" - units: "MMBtu" + commodity: "natural_gas" + commodity_rate_units: "MMBtu" performance_parameters: rated_capacity: 1270. # need 1268.934 MMBtu at each timestep cost_parameters: @@ -84,8 +84,8 @@ technologies: model: "FeedstockCostModel" model_inputs: shared_parameters: - feedstock_type: "water" - units: "galUS" #galUS/h + commodity: "water" + commodity_rate_units: "galUS" #galUS/h performance_parameters: rated_capacity: 40000. # need 38710.49649 galUS/h cost_parameters: @@ -101,8 +101,8 @@ technologies: model: "FeedstockCostModel" model_inputs: shared_parameters: - feedstock_type: "reformer_catalyst" - units: "(m**3)" #m**3/h + commodity: "reformer_catalyst" + commodity_rate_units: "(m**3)" #m**3/h performance_parameters: rated_capacity: 0.001 # need 0.00056546 m**3/h cost_parameters: @@ -118,8 +118,8 @@ technologies: model: "FeedstockCostModel" model_inputs: shared_parameters: - feedstock_type: "electricity" - units: "kW" + commodity: "electricity" + commodity_rate_units: "kW" performance_parameters: rated_capacity: 27000. # need 26949.46472431 kW cost_parameters: @@ -149,8 +149,8 @@ technologies: model: "FeedstockCostModel" model_inputs: shared_parameters: - feedstock_type: "electricity" - units: "kW" + commodity: "electricity" + commodity_rate_units: "kW" performance_parameters: rated_capacity: 56650. # need 56642.327357 kW cost_parameters: @@ -166,8 +166,8 @@ technologies: model: "FeedstockCostModel" model_inputs: shared_parameters: - feedstock_type: "water" - units: "galUS" #galUS/h + commodity: "water" + commodity_rate_units: "galUS" #galUS/h performance_parameters: rated_capacity: 10000. # need 9083.687924154146 galUS/h cost_parameters: @@ -183,8 +183,8 @@ technologies: model: "FeedstockCostModel" model_inputs: shared_parameters: - feedstock_type: "natural_gas" - units: "MMBtu" + commodity: "natural_gas" + commodity_rate_units: "MMBtu" performance_parameters: rated_capacity: 280. # need 276.5242929731515 MMBtu at each timestep cost_parameters: diff --git a/examples/23_solar_wind_ng_demand/tech_config.yaml b/examples/23_solar_wind_ng_demand/tech_config.yaml index 3bafb8e92..0e9f4330b 100644 --- a/examples/23_solar_wind_ng_demand/tech_config.yaml +++ b/examples/23_solar_wind_ng_demand/tech_config.yaml @@ -69,8 +69,8 @@ technologies: model: FeedstockCostModel model_inputs: shared_parameters: - feedstock_type: natural_gas - units: MMBtu + commodity: natural_gas + commodity_rate_units: MMBtu performance_parameters: rated_capacity: 750. #MMBtu cost_parameters: diff --git a/h2integrate/core/feedstocks.py b/h2integrate/core/feedstocks.py index 87408d965..32ba40c65 100644 --- a/h2integrate/core/feedstocks.py +++ b/h2integrate/core/feedstocks.py @@ -17,8 +17,8 @@ class FeedstockPerformanceConfig(BaseConfig): This is used to size the feedstock supply to meet the plant's needs. """ - feedstock_type: str = field() # TODO: rename to commodity_type - units: str = field() # TODO: rename to commodity_rate_units + commodity: str = field() # TODO: rename to commodity + commodity_rate_units: str = field() # TODO: rename to commodity_rate_units rated_capacity: float = field() @@ -34,8 +34,8 @@ def setup(self): additional_cls_name=self.__class__.__name__, ) self.n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) - self.commodity = self.config.feedstock_type - self.commodity_rate_units = self.config.units + self.commodity = self.config.commodity + self.commodity_rate_units = self.config.commodity_rate_units self.add_input( f"rated_{self.commodity}_production", @@ -71,8 +71,8 @@ class FeedstockCostConfig(CostModelBaseConfig): "kg", "kW" or "galUS"). If None, will be set as `units`*h """ - feedstock_type: str = field() # TODO: rename to commodity_type - units: str = field() # TODO: rename to commodity_rate_units + commodity: str = field() # TODO: rename to commodity_type + commodity_rate_units: str = field() # TODO: rename to commodity_rate_units price: int | float | list = field() annual_cost: float = field(default=0.0) start_up_cost: float = field(default=0.0) @@ -80,7 +80,7 @@ class FeedstockCostConfig(CostModelBaseConfig): def __attrs_post_init__(self): if self.commodity_amount_units is None: - self.commodity_amount_units = f"({self.units})*h" + self.commodity_amount_units = f"({self.commodity_rate_units})*h" class FeedstockCostModel(CostModelBaseClass): @@ -94,8 +94,8 @@ def setup(self): # Set cost outputs super().setup() - self.commodity = self.config.feedstock_type - self.commodity_rate_units = self.config.units + self.commodity = self.config.commodity + self.commodity_rate_units = self.config.commodity_rate_units self.commodity_amount_units = self.config.commodity_amount_units # Feedstock available from performance model, used to calculate CF diff --git a/h2integrate/core/test/test_feedstocks.py b/h2integrate/core/test/test_feedstocks.py index 8592e3cb0..066669efe 100644 --- a/h2integrate/core/test/test_feedstocks.py +++ b/h2integrate/core/test/test_feedstocks.py @@ -34,8 +34,8 @@ def ng_tech_input_config(): tech_config = { "model_inputs": { "shared_parameters": { - "feedstock_type": "natural_gas", - "units": "MMBtu/h", + "commodity": "natural_gas", + "commodity_rate_units": "MMBtu/h", }, "performance_parameters": { "rated_capacity": 100.0, @@ -108,8 +108,8 @@ def create_basic_feedstock_config( tech_config = { "model_inputs": { "shared_parameters": { - "feedstock_type": feedstock_type, - "units": units, + "commodity": feedstock_type, + "commodity_rate_units": units, }, "performance_parameters": { "rated_capacity": rated_capacity, From 78506c1c4b5ca588774f6d883b29720acda0fc6d Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:52:38 -0700 Subject: [PATCH 06/20] updated ex 23 which is untested and cleaned up feedstocks.py --- .../flexible_demand_tech_config.yaml | 4 ++-- .../23_solar_wind_ng_demand/plant_config.yaml | 13 ------------- h2integrate/core/feedstocks.py | 15 +++++++-------- 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/examples/23_solar_wind_ng_demand/flexible_demand_tech_config.yaml b/examples/23_solar_wind_ng_demand/flexible_demand_tech_config.yaml index fba5c6511..6ac2f160c 100644 --- a/examples/23_solar_wind_ng_demand/flexible_demand_tech_config.yaml +++ b/examples/23_solar_wind_ng_demand/flexible_demand_tech_config.yaml @@ -69,8 +69,8 @@ technologies: model: FeedstockCostModel model_inputs: shared_parameters: - feedstock_type: natural_gas - units: MMBtu + commodity: natural_gas + commodity_rate_units: MMBtu performance_parameters: rated_capacity: 750. #MMBtu cost_parameters: diff --git a/examples/23_solar_wind_ng_demand/plant_config.yaml b/examples/23_solar_wind_ng_demand/plant_config.yaml index 70a590349..33b872ceb 100644 --- a/examples/23_solar_wind_ng_demand/plant_config.yaml +++ b/examples/23_solar_wind_ng_demand/plant_config.yaml @@ -5,19 +5,6 @@ sites: site: latitude: 30.6617 longitude: -101.7096 - year: 2013 - - # array of polygons defining boundaries with x/y coords - boundaries: [ - { - x: [0.0, 1000.0, 1000.0, 0.0], - y: [0.0, 0.0, 100.0, 1000.0], - }, - { - x: [2000.0, 2500.0, 2000.0], - y: [2000.0, 2000.0, 2500.0], - } - ] resources: wind_resource: diff --git a/h2integrate/core/feedstocks.py b/h2integrate/core/feedstocks.py index 32ba40c65..9db3095f3 100644 --- a/h2integrate/core/feedstocks.py +++ b/h2integrate/core/feedstocks.py @@ -12,13 +12,13 @@ class FeedstockPerformanceConfig(BaseConfig): Attributes: feedstock_type (str): feedstock name - units (str): feedstock usage units (such as "galUS" or "kg") - rated_capacity (float): The rated capacity of the feedstock in `units`/hour. + commodity_rate_units (str): feedstock usage units (such as "galUS/h", "kW", or "kg/h") + rated_capacity (float): The rated capacity of the feedstock in `commodity_rate_units`. This is used to size the feedstock supply to meet the plant's needs. """ - commodity: str = field() # TODO: rename to commodity - commodity_rate_units: str = field() # TODO: rename to commodity_rate_units + commodity: str = field() + commodity_rate_units: str = field() rated_capacity: float = field() @@ -60,7 +60,7 @@ class FeedstockCostConfig(CostModelBaseConfig): Attributes: feedstock_type (str): feedstock name - units (str): feedstock usage units (such as "galUS" or "kg") + commodity_rate_units (str): feedstock usage units (such as "galUS/h", "kW", or "kg/h") price (scalar or list): The cost of the feedstock in USD/`commodity_amount_units`). If scalar, cost is assumed to be constant for each timestep and each year. If list, then it can be the cost per timestep of the simulation @@ -71,8 +71,8 @@ class FeedstockCostConfig(CostModelBaseConfig): "kg", "kW" or "galUS"). If None, will be set as `units`*h """ - commodity: str = field() # TODO: rename to commodity_type - commodity_rate_units: str = field() # TODO: rename to commodity_rate_units + commodity: str = field() + commodity_rate_units: str = field() price: int | float | list = field() annual_cost: float = field(default=0.0) start_up_cost: float = field(default=0.0) @@ -119,7 +119,6 @@ def setup(self): desc=f"Price profile of {self.commodity}", ) - # TODO: add outputs like those in PerformanceModelBaseClass self.dt = self.options["plant_config"]["plant"]["simulation"]["dt"] self.plant_life = int(self.options["plant_config"]["plant"]["plant_life"]) hours_per_year = 8760 From 791e4630b89bc31c8d17dcc5bb6d6415ddb8f705 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 10 Feb 2026 17:55:49 -0700 Subject: [PATCH 07/20] minor docstring updates and added comments --- h2integrate/core/feedstocks.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/h2integrate/core/feedstocks.py b/h2integrate/core/feedstocks.py index 9db3095f3..8dc5631c0 100644 --- a/h2integrate/core/feedstocks.py +++ b/h2integrate/core/feedstocks.py @@ -11,7 +11,7 @@ class FeedstockPerformanceConfig(BaseConfig): """Config class for feedstock. Attributes: - feedstock_type (str): feedstock name + commodity(str): feedstock name commodity_rate_units (str): feedstock usage units (such as "galUS/h", "kW", or "kg/h") rated_capacity (float): The rated capacity of the feedstock in `commodity_rate_units`. This is used to size the feedstock supply to meet the plant's needs. @@ -59,7 +59,7 @@ class FeedstockCostConfig(CostModelBaseConfig): """Config class for feedstock. Attributes: - feedstock_type (str): feedstock name + commodity (str): feedstock name commodity_rate_units (str): feedstock usage units (such as "galUS/h", "kW", or "kg/h") price (scalar or list): The cost of the feedstock in USD/`commodity_amount_units`). If scalar, cost is assumed to be constant for each timestep and each year. @@ -155,6 +155,9 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): f"total_{self.commodity}_consumed" ] * (1 / self.fraction_of_year_simulated) + # NOTE: once we standardize feedstock consumption outputs in models, this should + # be updated to handle consumption that varies over years of operation + # Calculate costs price = inputs["price"] hourly_consumption = inputs[f"{self.commodity}_consumed"] From a1e94fae91310fd00f6bd13f316b522304af0a85 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:05:47 -0700 Subject: [PATCH 08/20] added in notes of questions for reviewers to feedstocks.py --- h2integrate/core/feedstocks.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/h2integrate/core/feedstocks.py b/h2integrate/core/feedstocks.py index 8dc5631c0..a1bcf6fb8 100644 --- a/h2integrate/core/feedstocks.py +++ b/h2integrate/core/feedstocks.py @@ -11,7 +11,7 @@ class FeedstockPerformanceConfig(BaseConfig): """Config class for feedstock. Attributes: - commodity(str): feedstock name + commodity (str): feedstock name commodity_rate_units (str): feedstock usage units (such as "galUS/h", "kW", or "kg/h") rated_capacity (float): The rated capacity of the feedstock in `commodity_rate_units`. This is used to size the feedstock supply to meet the plant's needs. @@ -37,6 +37,7 @@ def setup(self): self.commodity = self.config.commodity self.commodity_rate_units = self.config.commodity_rate_units + # NOTE: should below be renamed to f"{self.commodity}_capacity"? self.add_input( f"rated_{self.commodity}_production", val=self.config.rated_capacity, @@ -99,6 +100,8 @@ def setup(self): self.commodity_amount_units = self.config.commodity_amount_units # Feedstock available from performance model, used to calculate CF + # NOTE: should f"{self.commodity}_out" be renamed to f"{self.commodity}_available"? + # f"{self.commodity}_out" is connected from the FeedstockPerformanceModel output self.add_input( f"{self.commodity}_out", val=0, shape=self.n_timesteps, units=self.commodity_rate_units ) @@ -142,21 +145,36 @@ def setup(self): desc="Capacity factor", ) + # NOTE: should below be added as an output? + # self.add_output( + # f"rated_{self.commodity}_production", + # val=0, + # units=self.commodity_rate_units, + # ) + def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Calculate performance based on consumption + # Capacity factor is the total amount consumed / the total amount available outputs["capacity_factor"] = ( inputs[f"{self.commodity}_consumed"].sum() / inputs[f"{self.commodity}_out"].sum() ) + + # Sum the amount consumed outputs[f"total_{self.commodity}_consumed"] = inputs[f"{self.commodity}_consumed"].sum() * ( self.dt / 3600 ) + + # Estimate annual consumption based on consumption over the simulation + # NOTE: once we standardize feedstock consumption outputs in models, this should + # be updated to handle consumption that varies over years of operation outputs[f"annual_{self.commodity}_consumed"] = outputs[ f"total_{self.commodity}_consumed" ] * (1 / self.fraction_of_year_simulated) - # NOTE: once we standardize feedstock consumption outputs in models, this should - # be updated to handle consumption that varies over years of operation + # NOTE: if we want to add f"rated_{self.commodity}_production" as an output, + # should it be calculated as below: + # outputs[f"rated_{self.commodity}_production"] = inputs[f"{self.commodity}_out"].max() # Calculate costs price = inputs["price"] From 4747d2fd8696fa25a04a19d37ea718856b1ca760 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:08:42 -0700 Subject: [PATCH 09/20] updated MMBtu units to MMBtu/h --- examples/16_natural_gas/tech_config.yaml | 11 ++++++----- examples/21_iron_mn_to_il/tech_config.yaml | 8 ++++---- .../flexible_demand_tech_config.yaml | 4 ++-- examples/23_solar_wind_ng_demand/tech_config.yaml | 4 ++-- h2integrate/converters/iron/iron_dri_plant.py | 4 ++-- .../converters/natural_gas/natural_gas_cc_ct.py | 6 +++--- h2integrate/converters/steel/steel_eaf_plant.py | 4 ++-- h2integrate/core/test/test_feedstocks.py | 4 ++-- h2integrate/transporters/pipe.py | 2 +- 9 files changed, 24 insertions(+), 23 deletions(-) diff --git a/examples/16_natural_gas/tech_config.yaml b/examples/16_natural_gas/tech_config.yaml index 891b8e45e..eba86dde7 100644 --- a/examples/16_natural_gas/tech_config.yaml +++ b/examples/16_natural_gas/tech_config.yaml @@ -64,14 +64,15 @@ technologies: model_inputs: shared_parameters: commodity: "natural_gas" - commodity_rate_units: "MMBtu" + commodity_rate_units: "MMBtu/h" performance_parameters: - rated_capacity: 750. #MMBtu + rated_capacity: 750. #MMBtu/h cost_parameters: cost_year: 2023 - price: 4.2 - annual_cost: 0. - start_up_cost: 100000. + commodity_amount_units: "MMBtu" + price: 4.2 # USD/commodity_amount_units + annual_cost: 0. #USD + start_up_cost: 100000. #USD natural_gas_plant: performance_model: model: "NaturalGasPerformanceModel" diff --git a/examples/21_iron_mn_to_il/tech_config.yaml b/examples/21_iron_mn_to_il/tech_config.yaml index c6083d209..fc0e557cc 100644 --- a/examples/21_iron_mn_to_il/tech_config.yaml +++ b/examples/21_iron_mn_to_il/tech_config.yaml @@ -68,9 +68,9 @@ technologies: model_inputs: shared_parameters: commodity: "natural_gas" - commodity_rate_units: "MMBtu" + commodity_rate_units: "MMBtu/h" performance_parameters: - rated_capacity: 1270. # need 1268.934 MMBtu at each timestep + rated_capacity: 1270. # need 1268.934 MMBtu/h cost_parameters: cost_year: 2022 price: 4.0 #USD 4.0/MMBtu @@ -184,9 +184,9 @@ technologies: model_inputs: shared_parameters: commodity: "natural_gas" - commodity_rate_units: "MMBtu" + commodity_rate_units: "MMBtu/h" performance_parameters: - rated_capacity: 280. # need 276.5242929731515 MMBtu at each timestep + rated_capacity: 280. # need 276.5242929731515 MMBtu/h cost_parameters: cost_year: 2022 price: 4.0 #USD 4.0/MMBtu diff --git a/examples/23_solar_wind_ng_demand/flexible_demand_tech_config.yaml b/examples/23_solar_wind_ng_demand/flexible_demand_tech_config.yaml index 6ac2f160c..fc46917c3 100644 --- a/examples/23_solar_wind_ng_demand/flexible_demand_tech_config.yaml +++ b/examples/23_solar_wind_ng_demand/flexible_demand_tech_config.yaml @@ -70,9 +70,9 @@ technologies: model_inputs: shared_parameters: commodity: natural_gas - commodity_rate_units: MMBtu + commodity_rate_units: "MMBtu/h" performance_parameters: - rated_capacity: 750. #MMBtu + rated_capacity: 750. #MMBtu/h cost_parameters: cost_year: 2023 price: 4.2 #USD/MMBtu diff --git a/examples/23_solar_wind_ng_demand/tech_config.yaml b/examples/23_solar_wind_ng_demand/tech_config.yaml index 0e9f4330b..3803536f3 100644 --- a/examples/23_solar_wind_ng_demand/tech_config.yaml +++ b/examples/23_solar_wind_ng_demand/tech_config.yaml @@ -70,9 +70,9 @@ technologies: model_inputs: shared_parameters: commodity: natural_gas - commodity_rate_units: MMBtu + commodity_rate_units: "MMBtu/h" performance_parameters: - rated_capacity: 750. #MMBtu + rated_capacity: 750. #MMBtu/h cost_parameters: cost_year: 2023 price: 4.2 #USD/MMBtu diff --git a/h2integrate/converters/iron/iron_dri_plant.py b/h2integrate/converters/iron/iron_dri_plant.py index 8f8de9ca8..25a631ff9 100644 --- a/h2integrate/converters/iron/iron_dri_plant.py +++ b/h2integrate/converters/iron/iron_dri_plant.py @@ -50,7 +50,7 @@ class HydrogenIronReductionPlantPerformanceComponent(IronReductionPlantBasePerfo def setup(self): self.product = "h2_dri" self.feedstocks_to_units = { - "natural_gas": "MMBtu", + "natural_gas": "MMBtu/h", "water": "galUS", # "galUS/h" "iron_ore": "t/h", "electricity": "kW", @@ -72,7 +72,7 @@ class NaturalGasIronReductionPlantPerformanceComponent(IronReductionPlantBasePer def setup(self): self.feedstocks_to_units = { - "natural_gas": "MMBtu", + "natural_gas": "MMBtu/h", "water": "galUS", # "galUS/h" "iron_ore": "t/h", "electricity": "kW", diff --git a/h2integrate/converters/natural_gas/natural_gas_cc_ct.py b/h2integrate/converters/natural_gas/natural_gas_cc_ct.py index e1a77e4b4..20214b6e0 100644 --- a/h2integrate/converters/natural_gas/natural_gas_cc_ct.py +++ b/h2integrate/converters/natural_gas/natural_gas_cc_ct.py @@ -76,7 +76,7 @@ def setup(self): "natural_gas_consumed", val=0.0, shape=n_timesteps, - units="MMBtu", + units="MMBtu/h", desc="Natural gas consumed by the plant", ) @@ -84,7 +84,7 @@ def setup(self): self.add_input( "heat_rate_mmbtu_per_mwh", val=self.config.heat_rate_mmbtu_per_mwh, - units="MMBtu/MW/h", + units="MMBtu/(MW*h)", desc="Plant heat rate in MMBtu/MWh", ) @@ -110,7 +110,7 @@ def setup(self): "natural_gas_in", val=0.0, shape=n_timesteps, - units="MMBtu", + units="MMBtu/h", desc="Natural gas input energy", ) diff --git a/h2integrate/converters/steel/steel_eaf_plant.py b/h2integrate/converters/steel/steel_eaf_plant.py index 6b8a1b216..6aa5b5e93 100644 --- a/h2integrate/converters/steel/steel_eaf_plant.py +++ b/h2integrate/converters/steel/steel_eaf_plant.py @@ -47,7 +47,7 @@ class HydrogenEAFPlantPerformanceComponent(ElectricArcFurnacePlantBasePerformanc def setup(self): self.product = "h2_eaf" self.feedstocks_to_units = { - "natural_gas": "MMBtu", + "natural_gas": "MMBtu/h", "water": "galUS", # "galUS/h" "carbon": "t/h", "lime": "t/h", @@ -69,7 +69,7 @@ class NaturalGasEAFPlantPerformanceComponent(ElectricArcFurnacePlantBasePerforma def setup(self): self.feedstocks_to_units = { - "natural_gas": "MMBtu", + "natural_gas": "MMBtu/h", "water": "galUS", # "galUS/h" "pig_iron": "t/h", "electricity": "kW", diff --git a/h2integrate/core/test/test_feedstocks.py b/h2integrate/core/test/test_feedstocks.py index 066669efe..512796694 100644 --- a/h2integrate/core/test/test_feedstocks.py +++ b/h2integrate/core/test/test_feedstocks.py @@ -98,7 +98,7 @@ def tearDown(self): def create_basic_feedstock_config( self, feedstock_type="natural_gas", - units="MMBtu", + units="MMBtu/h", rated_capacity=100.0, price=4.2, annual_cost=0.0, @@ -219,7 +219,7 @@ def test_multiple_different_type_feedstocks(self): """Test feedstocks of different types (natural gas, electricity, water).""" # Natural gas feedstock ng_config, plant_config, driver_config = self.create_basic_feedstock_config( - feedstock_type="natural_gas", units="MMBtu", rated_capacity=100.0, price=4.2 + feedstock_type="natural_gas", units="MMBtu/h", rated_capacity=100.0, price=4.2 ) # Electricity feedstock diff --git a/h2integrate/transporters/pipe.py b/h2integrate/transporters/pipe.py index 8b208b6e3..e05dc1b68 100644 --- a/h2integrate/transporters/pipe.py +++ b/h2integrate/transporters/pipe.py @@ -33,7 +33,7 @@ def setup(self): self.output_name = transport_item + "_out" if transport_item == "natural_gas": - units = "MMBtu" + units = "MMBtu/h" elif transport_item == "reformer_catalyst": units = "(m**3)" elif transport_item == "water": From a42b63769f6896cc840977b92da2160b39ebd794 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 12 Feb 2026 10:35:25 -0700 Subject: [PATCH 10/20] fixed iron and steel tests --- h2integrate/converters/iron/test/test_rosner_dri.py | 4 ++-- h2integrate/converters/steel/test/test_rosner_eaf.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/h2integrate/converters/iron/test/test_rosner_dri.py b/h2integrate/converters/iron/test/test_rosner_dri.py index e04bfe7e7..051111c0f 100644 --- a/h2integrate/converters/iron/test/test_rosner_dri.py +++ b/h2integrate/converters/iron/test/test_rosner_dri.py @@ -65,7 +65,7 @@ def ng_feedstock_availability_costs(): }, "natural_gas": { "rated_capacity": 1270, # need 1268.934 MMBtu at each timestep - "units": "MMBtu", + "units": "MMBtu/h", "price": 0.0, }, "reformer_catalyst": { @@ -98,7 +98,7 @@ def h2_feedstock_availability_costs(): }, "natural_gas": { "rated_capacity": 81.0, # need 80.101596 MMBtu at each timestep - "units": "MMBtu", + "units": "MMBtu/h", "price": 0.0, }, "hydrogen": { diff --git a/h2integrate/converters/steel/test/test_rosner_eaf.py b/h2integrate/converters/steel/test/test_rosner_eaf.py index f5e0ab1a5..e0b37fb47 100644 --- a/h2integrate/converters/steel/test/test_rosner_eaf.py +++ b/h2integrate/converters/steel/test/test_rosner_eaf.py @@ -65,7 +65,7 @@ def ng_feedstock_availability_costs(): }, "natural_gas": { "rated_capacity": 277, # need 276.5024456918746 MMBtu at each timestep - "units": "MMBtu", + "units": "MMBtu/h", "price": 0.0, }, "water": { @@ -93,7 +93,7 @@ def h2_feedstock_availability_costs(): }, "natural_gas": { "rated_capacity": 13.0, # need 12.136117946872957 MMBtu at each timestep - "units": "MMBtu", + "units": "MMBtu/h", "price": 0.0, }, "carbon": { From 015c288aa8cdf55971815195a09c3364b4590c3f Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:53:36 -0700 Subject: [PATCH 11/20] made changes to feedstock --- h2integrate/core/feedstocks.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/h2integrate/core/feedstocks.py b/h2integrate/core/feedstocks.py index a1bcf6fb8..849b0ffee 100644 --- a/h2integrate/core/feedstocks.py +++ b/h2integrate/core/feedstocks.py @@ -37,9 +37,8 @@ def setup(self): self.commodity = self.config.commodity self.commodity_rate_units = self.config.commodity_rate_units - # NOTE: should below be renamed to f"{self.commodity}_capacity"? self.add_input( - f"rated_{self.commodity}_production", + f"{self.commodity}_capacity", val=self.config.rated_capacity, units=self.commodity_rate_units, ) @@ -51,7 +50,7 @@ def setup(self): def compute(self, inputs, outputs): # Generate feedstock array operating at full capacity for the full year outputs[f"{self.commodity}_out"] = np.full( - self.n_timesteps, inputs[f"rated_{self.commodity}_production"][0] + self.n_timesteps, inputs[f"{self.commodity}_capacity"][0] ) @@ -145,12 +144,11 @@ def setup(self): desc="Capacity factor", ) - # NOTE: should below be added as an output? - # self.add_output( - # f"rated_{self.commodity}_production", - # val=0, - # units=self.commodity_rate_units, - # ) + self.add_output( + f"rated_{self.commodity}_production", + val=0, + units=self.commodity_rate_units, + ) def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Calculate performance based on consumption @@ -172,9 +170,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): f"total_{self.commodity}_consumed" ] * (1 / self.fraction_of_year_simulated) - # NOTE: if we want to add f"rated_{self.commodity}_production" as an output, - # should it be calculated as below: - # outputs[f"rated_{self.commodity}_production"] = inputs[f"{self.commodity}_out"].max() + outputs[f"rated_{self.commodity}_production"] = inputs[f"{self.commodity}_out"].max() # Calculate costs price = inputs["price"] From 9b9c10c3070589044a4f9fa0e1e8a04633bc10f7 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Mon, 6 Apr 2026 12:06:30 -0600 Subject: [PATCH 12/20] fixed tests --- h2integrate/core/feedstocks.py | 44 ++-- h2integrate/core/test/test_feedstocks.py | 263 +---------------------- 2 files changed, 28 insertions(+), 279 deletions(-) diff --git a/h2integrate/core/feedstocks.py b/h2integrate/core/feedstocks.py index 21db3fb01..7e691cc4a 100644 --- a/h2integrate/core/feedstocks.py +++ b/h2integrate/core/feedstocks.py @@ -88,7 +88,7 @@ def setup(self): merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), additional_cls_name=self.__class__.__name__, ) - n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] + self.n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) plant_life = int(self.options["plant_config"]["plant"]["plant_life"]) # Set cost outputs @@ -97,20 +97,23 @@ def setup(self): self.add_input( f"{self.config.commodity}_consumed", val=0.0, - shape=int(n_timesteps), + shape=self.n_timesteps, units=self.config.commodity_rate_units, desc=f"Consumption profile of {self.config.commodity}", ) self.add_input( - f"{self.commodity}_out", val=0, shape=self.n_timesteps, units=self.commodity_rate_units + f"{self.config.commodity}_out", + val=0, + shape=self.n_timesteps, + units=self.config.commodity_rate_units, ) self.add_input( "price", val=self.config.price, shape=self.n_timesteps, - units=f"USD/({self.commodity_amount_units})", - desc=f"Price profile of {self.commodity}", + units=f"USD/({self.config.commodity_amount_units})", + desc=f"Price profile of {self.config.commodity}", ) self.dt = self.options["plant_config"]["plant"]["simulation"]["dt"] @@ -120,13 +123,15 @@ def setup(self): self.fraction_of_year_simulated = hours_simulated / hours_per_year self.add_output( - f"total_{self.commodity}_consumed", val=0.0, units=self.commodity_amount_units + f"total_{self.config.commodity}_consumed", + val=0.0, + units=self.config.commodity_amount_units, ) self.add_output( - f"annual_{self.commodity}_consumed", + f"annual_{self.config.commodity}_consumed", val=0.0, shape=self.plant_life, - units=f"({self.commodity_amount_units})/year", + units=f"({self.config.commodity_amount_units})/year", ) self.add_output( "capacity_factor", @@ -137,9 +142,9 @@ def setup(self): ) self.add_output( - f"rated_{self.commodity}_production", + f"rated_{self.config.commodity}_production", val=0, - units=self.commodity_rate_units, + units=self.config.commodity_rate_units, ) # lifetime estimate of item replacements, represented as a fraction of the capacity. @@ -150,26 +155,29 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Capacity factor is the total amount consumed / the total amount available outputs["capacity_factor"] = ( - inputs[f"{self.commodity}_consumed"].sum() / inputs[f"{self.commodity}_out"].sum() + inputs[f"{self.config.commodity}_consumed"].sum() + / inputs[f"{self.config.commodity}_out"].sum() ) # Sum the amount consumed - outputs[f"total_{self.commodity}_consumed"] = inputs[f"{self.commodity}_consumed"].sum() * ( - self.dt / 3600 - ) + outputs[f"total_{self.config.commodity}_consumed"] = inputs[ + f"{self.config.commodity}_consumed" + ].sum() * (self.dt / 3600) # Estimate annual consumption based on consumption over the simulation # NOTE: once we standardize feedstock consumption outputs in models, this should # be updated to handle consumption that varies over years of operation - outputs[f"annual_{self.commodity}_consumed"] = outputs[ - f"total_{self.commodity}_consumed" + outputs[f"annual_{self.config.commodity}_consumed"] = outputs[ + f"total_{self.config.commodity}_consumed" ] * (1 / self.fraction_of_year_simulated) - outputs[f"rated_{self.commodity}_production"] = inputs[f"{self.commodity}_out"].max() + outputs[f"rated_{self.config.commodity}_production"] = inputs[ + f"{self.config.commodity}_out" + ].max() # Calculate costs price = inputs["price"] - hourly_consumption = inputs[f"{self.commodity}_consumed"] + hourly_consumption = inputs[f"{self.config.commodity}_consumed"] cost_per_year = sum(price * hourly_consumption) outputs["CapEx"] = self.config.start_up_cost diff --git a/h2integrate/core/test/test_feedstocks.py b/h2integrate/core/test/test_feedstocks.py index f5eea7d8c..28291df6f 100644 --- a/h2integrate/core/test/test_feedstocks.py +++ b/h2integrate/core/test/test_feedstocks.py @@ -5,9 +5,6 @@ including natural gas, electricity, water, and other feedstock types. """ -import pathlib as Path -import unittest - import numpy as np import pytest import openmdao.api as om @@ -52,6 +49,7 @@ def ng_tech_input_config(): return tech_config +@pytest.mark.unit def test_feedstock_standard_outputs(plant_config, ng_tech_input_config, subtests): perf_model = FeedstockPerformanceModel( plant_config=plant_config, tech_config=ng_tech_input_config, driver_config={} @@ -80,263 +78,6 @@ def test_feedstock_standard_outputs(plant_config, ng_tech_input_config, subtests # and annual_natural_gas_consumed -class TestFeedstocks(unittest.TestCase): - """Test cases for feedstock models.""" - - def setUp(self): - """Set up test fixtures.""" - self.test_dir = Path(__file__).parent / "test_feedstock_configs" - self.test_dir.mkdir(exist_ok=True) - - def tearDown(self): - """Clean up test files.""" - if self.test_dir.exists(): - import shutil - - shutil.rmtree(self.test_dir) - - def create_basic_feedstock_config( - self, - feedstock_type="natural_gas", - units="MMBtu/h", - rated_capacity=100.0, - price=4.2, - annual_cost=0.0, - start_up_cost=100000.0, - ): - """Create a basic feedstock configuration for testing.""" - tech_config = { - "model_inputs": { - "shared_parameters": { - "commodity": feedstock_type, - "commodity_rate_units": units, - }, - "performance_parameters": { - "rated_capacity": rated_capacity, - }, - "cost_parameters": { - "price": price, - "annual_cost": annual_cost, - "start_up_cost": start_up_cost, - "cost_year": 2023, - }, - } - } - - plant_config = { - "plant": {"plant_life": 30, "simulation": {"n_timesteps": 8760, "dt": 3600}} - } - - driver_config = {} - - return tech_config, plant_config, driver_config - - def test_single_feedstock_natural_gas(self): - """Test a single natural gas feedstock with basic parameters.""" - tech_config, plant_config, driver_config = self.create_basic_feedstock_config() - - # Test performance model - perf_model = FeedstockPerformanceModel() - perf_model.options["tech_config"] = tech_config - perf_model.options["plant_config"] = plant_config - perf_model.options["driver_config"] = driver_config - - prob = om.Problem() - prob.model.add_subsystem("feedstock_perf", perf_model) - prob.setup() - prob.run_model() - - # Check that output is generated correctly - ng_output = prob.get_val("feedstock_perf.natural_gas_out") - self.assertEqual(len(ng_output), 8760) - self.assertTrue(np.all(ng_output == 100.0)) # rated_capacity - - # Test cost model - cost_model = FeedstockCostModel() - cost_model.options["tech_config"] = tech_config - cost_model.options["plant_config"] = plant_config - cost_model.options["driver_config"] = driver_config - - prob_cost = om.Problem() - prob_cost.model.add_subsystem("feedstock_cost", cost_model) - prob_cost.setup() - - # Set some consumption values - consumption = np.full(8760, 50.0) # 50 MMBtu/hour - prob_cost.set_val("feedstock_cost.natural_gas_consumed", consumption) - prob_cost.run_model() - - # Check outputs - capex = prob_cost.get_val("feedstock_cost.CapEx")[0] - opex = prob_cost.get_val("feedstock_cost.VarOpEx")[0] - - self.assertEqual(capex, 100000.0) # start_up_cost - expected_opex = 0.0 + 4.2 * consumption.sum() # annual_cost + price * consumption - self.assertAlmostEqual(opex, expected_opex, places=5) - - def test_multiple_same_type_feedstocks(self): - """Test multiple feedstocks of the same type with different parameters.""" - # Test two natural gas feedstocks with different capacities and prices - tech_config1, plant_config, driver_config = self.create_basic_feedstock_config( - rated_capacity=50.0, price=4.0, start_up_cost=50000.0 - ) - tech_config2, _, _ = self.create_basic_feedstock_config( - rated_capacity=150.0, price=4.5, start_up_cost=150000.0 - ) - - # Test both feedstocks can coexist and have different outputs - perf_model1 = FeedstockPerformanceModel() - perf_model1.options.update( - { - "tech_config": tech_config1, - "plant_config": plant_config, - "driver_config": driver_config, - } - ) - - perf_model2 = FeedstockPerformanceModel() - perf_model2.options.update( - { - "tech_config": tech_config2, - "plant_config": plant_config, - "driver_config": driver_config, - } - ) - - prob = om.Problem() - prob.model.add_subsystem("feedstock1", perf_model1) - prob.model.add_subsystem("feedstock2", perf_model2) - prob.setup() - prob.run_model() - - ng_output1 = prob.get_val("feedstock1.natural_gas_out") - ng_output2 = prob.get_val("feedstock2.natural_gas_out") - - self.assertTrue(np.all(ng_output1 == 50.0)) - self.assertTrue(np.all(ng_output2 == 150.0)) - - def test_multiple_different_type_feedstocks(self): - """Test feedstocks of different types (natural gas, electricity, water).""" - # Natural gas feedstock - ng_config, plant_config, driver_config = self.create_basic_feedstock_config( - feedstock_type="natural_gas", units="MMBtu/h", rated_capacity=100.0, price=4.2 - ) - - # Electricity feedstock - elec_config, _, _ = self.create_basic_feedstock_config( - feedstock_type="electricity", units="MW*h", rated_capacity=50.0, price=0.05 - ) - - # Water feedstock - water_config, _, _ = self.create_basic_feedstock_config( - feedstock_type="water", units="galUS", rated_capacity=1000.0, price=0.001 - ) - - # Test all three feedstock types - perf_ng = FeedstockPerformanceModel() - perf_ng.options.update( - {"tech_config": ng_config, "plant_config": plant_config, "driver_config": driver_config} - ) - - perf_elec = FeedstockPerformanceModel() - perf_elec.options.update( - { - "tech_config": elec_config, - "plant_config": plant_config, - "driver_config": driver_config, - } - ) - - perf_water = FeedstockPerformanceModel() - perf_water.options.update( - { - "tech_config": water_config, - "plant_config": plant_config, - "driver_config": driver_config, - } - ) - - prob = om.Problem() - prob.model.add_subsystem("ng_feedstock", perf_ng) - prob.model.add_subsystem("elec_feedstock", perf_elec) - prob.model.add_subsystem("water_feedstock", perf_water) - prob.setup() - prob.run_model() - - # Check outputs - ng_out = prob.get_val("ng_feedstock.natural_gas_out") - elec_out = prob.get_val("elec_feedstock.electricity_out") - water_out = prob.get_val("water_feedstock.water_out") - - self.assertTrue(np.all(ng_out == 100.0)) - self.assertTrue(np.all(elec_out == 50.0)) - self.assertTrue(np.all(water_out == 1000.0)) - - def test_variable_pricing(self): - """Test feedstock with variable pricing (array of prices).""" - # Create hourly price array that varies throughout the year - hourly_prices = np.full(8760, 4.2) - # Add some variation - higher prices during peak hours - for i in range(8760): - hour_of_day = i % 24 - if 16 <= hour_of_day <= 20: # Peak hours - hourly_prices[i] = 6.0 - elif 22 <= hour_of_day or hour_of_day <= 6: # Off-peak hours - hourly_prices[i] = 3.0 - - tech_config, plant_config, driver_config = self.create_basic_feedstock_config( - price=hourly_prices.tolist() - ) - - cost_model = FeedstockCostModel() - cost_model.options["tech_config"] = tech_config - cost_model.options["plant_config"] = plant_config - cost_model.options["driver_config"] = driver_config - - prob = om.Problem() - prob.model.add_subsystem("feedstock_cost", cost_model) - prob.setup() - - # Set consumption pattern - consumption = np.full(8760, 30.0) # 30 MMBtu/hour - prob.set_val("feedstock_cost.natural_gas_consumed", consumption) - prob.run_model() - - # Check that OpEx reflects variable pricing - opex = prob.get_val("feedstock_cost.VarOpEx")[0] - expected_opex = 0.0 + np.sum(hourly_prices * consumption) - self.assertAlmostEqual(opex, expected_opex, places=5) - - # OpEx should be different from constant pricing - constant_price_opex = 0.0 + 4.2 * consumption.sum() - self.assertNotAlmostEqual(opex, constant_price_opex, places=2) - - def test_zero_cost_feedstock(self): - """Test feedstock with zero costs (free resource).""" - tech_config, plant_config, driver_config = self.create_basic_feedstock_config( - price=0.0, annual_cost=0.0, start_up_cost=0.0 - ) - - cost_model = FeedstockCostModel() - cost_model.options["tech_config"] = tech_config - cost_model.options["plant_config"] = plant_config - cost_model.options["driver_config"] = driver_config - - prob = om.Problem() - prob.model.add_subsystem("feedstock_cost", cost_model) - prob.setup() - - consumption = np.full(8760, 100.0) - prob.set_val("feedstock_cost.natural_gas_consumed", consumption) - prob.run_model() - - capex = prob.get_val("feedstock_cost.CapEx")[0] - opex = prob.get_val("feedstock_cost.OpEx")[0] - - self.assertEqual(capex, 0.0) - self.assertEqual(opex, 0.0) - - def create_basic_feedstock_config( feedstock_type="natural_gas", units="MMBtu/h", @@ -363,7 +104,7 @@ def create_basic_feedstock_config( }, } } - plant_config = {"plant": {"plant_life": 30, "simulation": {"n_timesteps": 8760}}} + plant_config = {"plant": {"plant_life": 30, "simulation": {"n_timesteps": 8760, "dt": 3600}}} driver_config = {} return tech_config, plant_config, driver_config From 2e3c34fbcfe2bd87444a81b2cf702ad296b7ccc7 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Mon, 6 Apr 2026 12:07:59 -0600 Subject: [PATCH 13/20] removed unused comment --- h2integrate/core/feedstocks.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/h2integrate/core/feedstocks.py b/h2integrate/core/feedstocks.py index 7e691cc4a..9958bd3f5 100644 --- a/h2integrate/core/feedstocks.py +++ b/h2integrate/core/feedstocks.py @@ -151,8 +151,6 @@ def setup(self): self.add_output("replacement_schedule", val=0.0, shape=plant_life, units="unitless") def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - # Calculate performance based on consumption - # Capacity factor is the total amount consumed / the total amount available outputs["capacity_factor"] = ( inputs[f"{self.config.commodity}_consumed"].sum() From 706d856e1717ebe847e11efbd82a5e3e3d216d1e Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Mon, 6 Apr 2026 12:47:18 -0600 Subject: [PATCH 14/20] removed shape from price input --- h2integrate/core/feedstocks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/h2integrate/core/feedstocks.py b/h2integrate/core/feedstocks.py index 9958bd3f5..a834c4780 100644 --- a/h2integrate/core/feedstocks.py +++ b/h2integrate/core/feedstocks.py @@ -111,7 +111,6 @@ def setup(self): self.add_input( "price", val=self.config.price, - shape=self.n_timesteps, units=f"USD/({self.config.commodity_amount_units})", desc=f"Price profile of {self.config.commodity}", ) From 1c1842aecce09d8f509b6690902c6d4abcfd22f5 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:18:30 -0600 Subject: [PATCH 15/20] added integration test for feedstock integrated with finance model --- examples/12_ammonia_synloop/plant_config.yaml | 5 ++++- examples/12_ammonia_synloop/tech_config.yaml | 2 +- examples/test/test_all_examples.py | 7 +++++++ h2integrate/tools/profast_tools.py | 15 ++++++++++----- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/examples/12_ammonia_synloop/plant_config.yaml b/examples/12_ammonia_synloop/plant_config.yaml index b836b5335..219fa6120 100644 --- a/examples/12_ammonia_synloop/plant_config.yaml +++ b/examples/12_ammonia_synloop/plant_config.yaml @@ -74,5 +74,8 @@ finance_parameters: - battery - electrolyzer - h2_storage - - n2_feedstock - ammonia + n2: + commodity: nitrogen + commodity_stream: n2_feedstock + technologies: [n2_feedstock] diff --git a/examples/12_ammonia_synloop/tech_config.yaml b/examples/12_ammonia_synloop/tech_config.yaml index b4d737c49..652b6c3b7 100644 --- a/examples/12_ammonia_synloop/tech_config.yaml +++ b/examples/12_ammonia_synloop/tech_config.yaml @@ -152,7 +152,7 @@ technologies: rated_capacity: 50.0 # metric tonnes of N2/hour cost_parameters: cost_year: 2022 - price: 0.0 + price: 5.0 annual_cost: 0. start_up_cost: 0.0 electricity_feedstock: diff --git a/examples/test/test_all_examples.py b/examples/test/test_all_examples.py index ca7ab7672..37634c7af 100644 --- a/examples/test/test_all_examples.py +++ b/examples/test/test_all_examples.py @@ -388,6 +388,13 @@ def test_ammonia_synloop_example(subtests, temp_copy_of_example): ) == 1.1018637096646757 ) + with subtests.test("Check LCON"): + assert ( + pytest.approx( + model.prob.get_val("finance_subgroup_n2.LCON", units="USD/t")[0], rel=1e-6 + ) + == 5.03140888 + ) @pytest.mark.integration diff --git a/h2integrate/tools/profast_tools.py b/h2integrate/tools/profast_tools.py index 8aed16123..33d0c810b 100644 --- a/h2integrate/tools/profast_tools.py +++ b/h2integrate/tools/profast_tools.py @@ -134,11 +134,16 @@ def make_price_breakdown(price_breakdown, pf_config): total_price_capex += cash_outflow_prices.loc[ cash_outflow_prices["Name"] == item, "NPV" ].tolist()[0] - for item in capital_items: - capex_fraction[item] = ( - cash_outflow_prices.loc[cash_outflow_prices["Name"] == item, "NPV"].tolist()[0] - / total_price_capex - ) + if total_price_capex != 0: + for item in capital_items: + capex_fraction[item] = ( + cash_outflow_prices.loc[cash_outflow_prices["Name"] == item, "NPV"].tolist()[0] + / total_price_capex + ) + else: + for item in capital_items: + capex_fraction[item] = 0 + cap_expense = ( price_breakdown.loc[price_breakdown["Name"] == "Repayment of debt", "NPV"].tolist()[0] + price_breakdown.loc[price_breakdown["Name"] == "Interest expense", "NPV"].tolist()[0] From c9bf5f7f5b82a7635b0506b9916d724ba0b12d59 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:21:44 -0600 Subject: [PATCH 16/20] updated feedstocks.md --- docs/technology_models/feedstocks.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/technology_models/feedstocks.md b/docs/technology_models/feedstocks.md index 2d722b351..e4e11f037 100644 --- a/docs/technology_models/feedstocks.md +++ b/docs/technology_models/feedstocks.md @@ -19,6 +19,7 @@ Each feedstock type requires two model components: - Calculates consumption costs based on actual usage - Takes `{commodity}_consumed` as input - Located after all consuming technologies in the chain + - Calculates the capacity factor of the consumed feedstock ### Technology Interconnections @@ -56,8 +57,8 @@ ng_feedstock: commodity_amount_units: "MMBtu" # optional, if not specified defaults to `commodity_rate_units*h` cost_year: 2023 price: 4.2 # cost in USD/commodity_amount_units - annual_cost: 0. - start_up_cost: 100000. + annual_cost: 0. #cost in USD/year + start_up_cost: 100000. #cost in USD ``` ### Performance Model Parameters From 7d316b1ed213303f9bc1ae5e89a10870c1ab2163 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:22:59 -0600 Subject: [PATCH 17/20] changed based on reviewer feedback --- CHANGELOG.md | 2 ++ docs/technology_models/feedstocks.md | 8 ++++++++ h2integrate/core/feedstocks.py | 6 ++++++ h2integrate/core/test/test_feedstocks.py | 8 ++++---- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 002bb2f9b..9ca20cb3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ - Added oxygen production metrics and as outputs to `ECOElectrolyzerPerformanceModel` [PR 642](https://github.com/NatLabRockies/H2Integrate/pull/642) - Bugfix to allow for one resource to be connected to multiple technologies [PR 655](https://github.com/NatLabRockies/H2Integrate/pull/655) - Removed the last of the logic that was based on technology names rather than model classes [PR 654](https://github.com/NatLabRockies/H2Integrate/pull/654) +- Added standardized outputs to feedstock model [PR 523](https://github.com/NatLabRockies/H2Integrate/pull/523) + ## 0.7.1 [March 13, 2026] diff --git a/docs/technology_models/feedstocks.md b/docs/technology_models/feedstocks.md index e4e11f037..b6b4b688d 100644 --- a/docs/technology_models/feedstocks.md +++ b/docs/technology_models/feedstocks.md @@ -82,3 +82,11 @@ ng_feedstock: ```{tip} The `price` parameter is flexible - you can specify constant pricing with a single value or time-varying pricing with an array of values matching the number of simulation timesteps. ``` + +### Consumed Feedstock Outputs +The feedstock model outputs cost and performance information about the consumed feedstock. The most notable outputs are: +- `VarOpEx`: cost the feedstock consumed (in `USD/yr`) +- `total_{commodity}_consumed`: total feedstock consumed over simulation (in `commodity_amount_units`) +- `annual_{commodity}_consumed`: annual feedstock consumed (in `commodity_amount_units/yr`) +- `rated_{commodity}_production`: this is equal to the the `rated_capacity` of the feedstock model (in `commodity_rate_units`) +- `capacity_factor`: ratio of the feedstock consumed to the maximum feedstock available diff --git a/h2integrate/core/feedstocks.py b/h2integrate/core/feedstocks.py index a834c4780..23987a79c 100644 --- a/h2integrate/core/feedstocks.py +++ b/h2integrate/core/feedstocks.py @@ -120,18 +120,23 @@ def setup(self): hours_per_year = 8760 hours_simulated = (self.dt / 3600) * self.n_timesteps self.fraction_of_year_simulated = hours_simulated / hours_per_year + # since feedstocks are consumed, some outputs are appended + # with 'consumed' rather than 'produced' self.add_output( f"total_{self.config.commodity}_consumed", val=0.0, units=self.config.commodity_amount_units, ) + self.add_output( f"annual_{self.config.commodity}_consumed", val=0.0, shape=self.plant_life, units=f"({self.config.commodity_amount_units})/year", ) + + # Capacity factor is feedstock_consumed/max_feedstock_available self.add_output( "capacity_factor", val=0.0, @@ -140,6 +145,7 @@ def setup(self): desc="Capacity factor", ) + # The should be equal to the commodity_capacity input of the FeedstockPerformanceModel self.add_output( f"rated_{self.config.commodity}_production", val=0, diff --git a/h2integrate/core/test/test_feedstocks.py b/h2integrate/core/test/test_feedstocks.py index 28291df6f..80a90d829 100644 --- a/h2integrate/core/test/test_feedstocks.py +++ b/h2integrate/core/test/test_feedstocks.py @@ -27,7 +27,7 @@ def plant_config(): @fixture -def ng_tech_input_config(): +def ng_feedstock_input_config(): tech_config = { "model_inputs": { "shared_parameters": { @@ -50,12 +50,12 @@ def ng_tech_input_config(): @pytest.mark.unit -def test_feedstock_standard_outputs(plant_config, ng_tech_input_config, subtests): +def test_feedstock_standard_outputs(plant_config, ng_feedstock_input_config, subtests): perf_model = FeedstockPerformanceModel( - plant_config=plant_config, tech_config=ng_tech_input_config, driver_config={} + plant_config=plant_config, tech_config=ng_feedstock_input_config, driver_config={} ) cost_model = FeedstockCostModel( - plant_config=plant_config, tech_config=ng_tech_input_config, driver_config={} + plant_config=plant_config, tech_config=ng_feedstock_input_config, driver_config={} ) prob = om.Problem() prob.model.add_subsystem("ng_feedstock_source", perf_model) From 1637abb1a108547026be7b15c8245674cdb48015 Mon Sep 17 00:00:00 2001 From: elenya-grant <116225007+elenya-grant@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:28:34 -0600 Subject: [PATCH 18/20] added subtests to test_feedstocks --- h2integrate/core/test/test_feedstocks.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/h2integrate/core/test/test_feedstocks.py b/h2integrate/core/test/test_feedstocks.py index 80a90d829..886658c6f 100644 --- a/h2integrate/core/test/test_feedstocks.py +++ b/h2integrate/core/test/test_feedstocks.py @@ -74,8 +74,22 @@ def test_feedstock_standard_outputs(plant_config, ng_feedstock_input_config, sub with subtests.test("Check feedstock capacity factor"): ng_cf = prob.get_val("ng_feedstock.capacity_factor", units="unitless").mean() assert pytest.approx(ng_cf, rel=1e-6) == 0.5 - # TODO: add subtests for rated_natural_gas_production, total_natural_gas_consumed, - # and annual_natural_gas_consumed + with subtests.test("Check feedstock rated production"): + rated_production_source = prob.get_val( + "ng_feedstock_source.natural_gas_capacity", units="MMBtu/h" + ) + rated_production = prob.get_val( + "ng_feedstock.rated_natural_gas_production", units="MMBtu/h" + ) + assert pytest.approx(rated_production, rel=1e-6) == rated_production_source + with subtests.test("Check feedstock total consumption"): + total_consumption = prob.get_val("ng_feedstock.total_natural_gas_consumed", units="MMBtu") + assert pytest.approx(total_consumption, rel=1e-6) == consumption.sum() + with subtests.test("Check feedstock annual consumption"): + annual_consumption = prob.get_val( + "ng_feedstock.annual_natural_gas_consumed", units="MMBtu/yr" + ) + assert pytest.approx(annual_consumption, rel=1e-6) == consumption.sum() def create_basic_feedstock_config( From 3e5b254d483929ce38d4b3ad3c24840127eb604b Mon Sep 17 00:00:00 2001 From: kbrunik Date: Fri, 10 Apr 2026 16:03:00 -0500 Subject: [PATCH 19/20] small changes to feedstock doc --- docs/technology_models/feedstocks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/technology_models/feedstocks.md b/docs/technology_models/feedstocks.md index b6b4b688d..b1e3a7c7e 100644 --- a/docs/technology_models/feedstocks.md +++ b/docs/technology_models/feedstocks.md @@ -1,6 +1,6 @@ # Feedstock Models -Feedstock models in H2Integrate represent any resource input that is consumed by technologies in your plant, such as natural gas, water, electricity from the grid, or any other material input. +Feedstock models in H2Integrate represent any resource input that is consumed by technologies in your plant that comes from outside your designed system and not generated internally, such as natural gas, water, electricity from the grid, or any other material input. The feedstock modeling approach provides a flexible way to track resource consumption and calculate associated costs for any type of input material or energy source. Please see the example `16_natural_gas` in the `examples` directory for a complete setup using natural gas as a feedstock. @@ -85,7 +85,7 @@ The `price` parameter is flexible - you can specify constant pricing with a sing ### Consumed Feedstock Outputs The feedstock model outputs cost and performance information about the consumed feedstock. The most notable outputs are: -- `VarOpEx`: cost the feedstock consumed (in `USD/yr`) +- `VarOpEx`: cost of the feedstock consumed (in `USD/yr`) - `total_{commodity}_consumed`: total feedstock consumed over simulation (in `commodity_amount_units`) - `annual_{commodity}_consumed`: annual feedstock consumed (in `commodity_amount_units/yr`) - `rated_{commodity}_production`: this is equal to the the `rated_capacity` of the feedstock model (in `commodity_rate_units`) From c0615d6d6a8ec7063d04f1dd8d084744490cde4a Mon Sep 17 00:00:00 2001 From: kbrunik Date: Fri, 10 Apr 2026 16:04:54 -0500 Subject: [PATCH 20/20] docs --- docs/technology_models/feedstocks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/technology_models/feedstocks.md b/docs/technology_models/feedstocks.md index b1e3a7c7e..dc5db0986 100644 --- a/docs/technology_models/feedstocks.md +++ b/docs/technology_models/feedstocks.md @@ -1,6 +1,6 @@ # Feedstock Models -Feedstock models in H2Integrate represent any resource input that is consumed by technologies in your plant that comes from outside your designed system and not generated internally, such as natural gas, water, electricity from the grid, or any other material input. +Feedstock models in H2Integrate represent any resource input that is consumed by technologies in your plant that comes from outside your designed system boundary (and not generated internally), such as natural gas, water, electricity from the grid, or any other material input. The feedstock modeling approach provides a flexible way to track resource consumption and calculate associated costs for any type of input material or energy source. Please see the example `16_natural_gas` in the `examples` directory for a complete setup using natural gas as a feedstock.