diff --git a/examples/01_onshore_steel_mn/tech_config.yaml b/examples/01_onshore_steel_mn/tech_config.yaml index 22811d68a..f70f9c965 100644 --- a/examples/01_onshore_steel_mn/tech_config.yaml +++ b/examples/01_onshore_steel_mn/tech_config.yaml @@ -65,7 +65,7 @@ technologies: model_inputs: performance_parameters: commodity: "electricity" - commodity_units: "kW" + commodity_rate_units: "kW" dispatch_rule_parameters: commodity_name: "electricity" commodity_storage_units: "kW" diff --git a/examples/02_texas_ammonia/tech_config.yaml b/examples/02_texas_ammonia/tech_config.yaml index d2cc1d20d..d2caac5f6 100644 --- a/examples/02_texas_ammonia/tech_config.yaml +++ b/examples/02_texas_ammonia/tech_config.yaml @@ -64,7 +64,7 @@ technologies: model_inputs: performance_parameters: commodity: "electricity" - commodity_units: "kW" + commodity_rate_units: "kW" dispatch_rule_parameters: commodity_name: "electricity" commodity_storage_units: "kW" diff --git a/examples/03_methanol/co2_hydrogenation/tech_config_co2h.yaml b/examples/03_methanol/co2_hydrogenation/tech_config_co2h.yaml index a96beb5d5..fd1e441f2 100644 --- a/examples/03_methanol/co2_hydrogenation/tech_config_co2h.yaml +++ b/examples/03_methanol/co2_hydrogenation/tech_config_co2h.yaml @@ -51,7 +51,7 @@ technologies: model_inputs: performance_parameters: commodity: "electricity" - commodity_units: "kW" + commodity_rate_units: "kW" electrolyzer: performance_model: model: "ECOElectrolyzerPerformanceModel" diff --git a/examples/09_co2/direct_ocean_capture/tech_config.yaml b/examples/09_co2/direct_ocean_capture/tech_config.yaml index dad6870c1..4d0f29090 100644 --- a/examples/09_co2/direct_ocean_capture/tech_config.yaml +++ b/examples/09_co2/direct_ocean_capture/tech_config.yaml @@ -57,7 +57,7 @@ technologies: model_inputs: performance_parameters: commodity: "electricity" - commodity_units: "kW" + commodity_rate_units: "kW" dispatch_rule_parameters: commodity_name: "electricity" commodity_storage_units: "kW" diff --git a/examples/12_ammonia_synloop/tech_config.yaml b/examples/12_ammonia_synloop/tech_config.yaml index ea41e075a..e0da2e8f7 100644 --- a/examples/12_ammonia_synloop/tech_config.yaml +++ b/examples/12_ammonia_synloop/tech_config.yaml @@ -64,7 +64,7 @@ technologies: model_inputs: performance_parameters: commodity: "electricity" - commodity_units: "kW" + commodity_rate_units: "kW" dispatch_rule_parameters: commodity_name: "electricity" commodity_storage_units: "kW" diff --git a/examples/15_wind_solar_electrolyzer/tech_config.yaml b/examples/15_wind_solar_electrolyzer/tech_config.yaml index 0652f8f26..4a922eac4 100644 --- a/examples/15_wind_solar_electrolyzer/tech_config.yaml +++ b/examples/15_wind_solar_electrolyzer/tech_config.yaml @@ -63,7 +63,7 @@ technologies: model_inputs: performance_parameters: commodity: "electricity" - commodity_units: "kW" + commodity_rate_units: "kW" electrolyzer: performance_model: model: "ECOElectrolyzerPerformanceModel" 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..2c07a2c4f 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 @@ -115,4 +115,4 @@ technologies: model_inputs: performance_parameters: commodity: electricity - commodity_units: kW + commodity_rate_units: kW diff --git a/examples/23_solar_wind_ng_demand/tech_config.yaml b/examples/23_solar_wind_ng_demand/tech_config.yaml index 3bafb8e92..3784d4d40 100644 --- a/examples/23_solar_wind_ng_demand/tech_config.yaml +++ b/examples/23_solar_wind_ng_demand/tech_config.yaml @@ -109,4 +109,4 @@ technologies: model_inputs: performance_parameters: commodity: electricity - commodity_units: kW + commodity_rate_units: kW diff --git a/examples/26_floris/tech_config.yaml b/examples/26_floris/tech_config.yaml index 14d309164..2a3b5533f 100644 --- a/examples/26_floris/tech_config.yaml +++ b/examples/26_floris/tech_config.yaml @@ -67,4 +67,4 @@ technologies: model_inputs: performance_parameters: commodity: electricity - commodity_units: kW + commodity_rate_units: kW diff --git a/examples/27_site_doe_diff/tech_config.yaml b/examples/27_site_doe_diff/tech_config.yaml index a0ada11f2..44306a84a 100644 --- a/examples/27_site_doe_diff/tech_config.yaml +++ b/examples/27_site_doe_diff/tech_config.yaml @@ -62,4 +62,4 @@ technologies: model_inputs: performance_parameters: commodity: "electricity" - commodity_units: "kW" + commodity_rate_units: "kW" diff --git a/examples/29_wind_ard/h2i_inputs/tech_config.yaml b/examples/29_wind_ard/h2i_inputs/tech_config.yaml index 4c3d746ab..0ba394f31 100644 --- a/examples/29_wind_ard/h2i_inputs/tech_config.yaml +++ b/examples/29_wind_ard/h2i_inputs/tech_config.yaml @@ -93,7 +93,7 @@ technologies: model_inputs: performance_parameters: commodity: electricity - commodity_units: kW + commodity_rate_units: kW battery: performance_model: model: "SimpleGenericStorage" diff --git a/examples/test/test_all_examples.py b/examples/test/test_all_examples.py index 05ef94564..61fdde37e 100644 --- a/examples/test/test_all_examples.py +++ b/examples/test/test_all_examples.py @@ -318,6 +318,33 @@ def test_co2h_methanol_example(subtests): model.post_process() + # Below is used as an integration test for the combiner + with subtests.test("combiner rated production"): + combined_rated_input = model.prob.get_val( + "wind.rated_electricity_production", units="MW" + ) + model.prob.get_val("solar.rated_electricity_production", units="MW") + assert ( + pytest.approx( + model.prob.get_val("combiner.rated_electricity_production", units="MW"), rel=1e-6 + ) + == combined_rated_input + ) + with subtests.test("combiner weighted CF"): + wind_weighted_cf = model.prob.get_val( + "wind.rated_electricity_production", units="MW" + ) * model.prob.get_val("wind.capacity_factor", units="unitless") + solar_weighted_cf = model.prob.get_val( + "solar.rated_electricity_production", units="MW" + ) * model.prob.get_val("solar.capacity_factor", units="unitless") + combined_cf = (wind_weighted_cf + solar_weighted_cf) / combined_rated_input + assert ( + pytest.approx( + model.prob.get_val("combiner.electricity_capacity_factor", units="unitless"), + rel=1e-6, + ) + == combined_cf + ) + # Check levelized cost of methanol (LCOM) with subtests.test("Check CO2 Hydrogenation LCOM"): assert pytest.approx(model.prob.get_val("methanol.LCOM")[0], rel=1e-6) == 1.7555607442 diff --git a/h2integrate/core/h2integrate_model.py b/h2integrate/core/h2integrate_model.py index 5c9923280..d704bca5e 100644 --- a/h2integrate/core/h2integrate_model.py +++ b/h2integrate/core/h2integrate_model.py @@ -754,7 +754,9 @@ def create_finance_model(self): "model_inputs": { "performance_parameters": { "commodity": commodity, - "commodity_units": "kW" if commodity == "electricity" else "kg/h", + "commodity_rate_units": "kW" + if commodity == "electricity" + else "kg/h", } } } @@ -970,6 +972,15 @@ def connect_technologies(self): f"{connection_name}.{transport_item}_out", f"{dest_tech}.{transport_item}_in{combiner_counts[dest_tech]}", ) + # Connect the source tech design and performance info to the combiner + self.plant.connect( + f"{source_tech}.rated_{transport_item}_production", + f"{dest_tech}.rated_{transport_item}_production{combiner_counts[dest_tech]}", + ) + self.plant.connect( + f"{source_tech}.capacity_factor", + f"{dest_tech}.{transport_item}_capacity_factor{combiner_counts[dest_tech]}", + ) elif "storage" in dest_tech: # Connect the connection component to the destination technology diff --git a/h2integrate/transporters/generic_combiner.py b/h2integrate/transporters/generic_combiner.py index 3cdfb759e..5daff706d 100644 --- a/h2integrate/transporters/generic_combiner.py +++ b/h2integrate/transporters/generic_combiner.py @@ -15,7 +15,7 @@ class GenericCombinerPerformanceConfig(BaseConfig): """ commodity: str = field(converter=(str.lower, str.strip)) - commodity_units: str = field() + commodity_rate_units: str = field() in_streams: int = field(default=2) @@ -39,25 +39,71 @@ def setup(self): ) n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) + plant_life = int(self.options["plant_config"]["plant"]["plant_life"]) for i in range(1, self.config.in_streams + 1): self.add_input( f"{self.config.commodity}_in{i}", val=0.0, shape=n_timesteps, - units=self.config.commodity_units, + units=self.config.commodity_rate_units, + ) + self.add_input( + f"rated_{self.config.commodity}_production{i}", + val=0.0, + shape=1, + units=self.config.commodity_rate_units, + ) + self.add_input( + f"{self.config.commodity}_capacity_factor{i}", + val=0.0, + shape=plant_life, + units="unitless", ) self.add_output( f"{self.config.commodity}_out", val=0.0, shape=n_timesteps, - units=self.config.commodity_units, + units=self.config.commodity_rate_units, + ) + self.add_output( + f"{self.config.commodity}_capacity_factor", + val=0.0, + shape=plant_life, + units="unitless", + ) + self.add_output( + f"rated_{self.config.commodity}_production", + val=0.0, + shape=1, + units=self.config.commodity_rate_units, ) def compute(self, inputs, outputs): - total = 0.0 + total_out = 0.0 + weighted_cf = 0.0 + total_rated = 0.0 for key, value in inputs.items(): if "_in" in key: - total = total + value - outputs[f"{self.config.commodity}_out"] = total + # add the commodity_in profile + total_out = total_out + value + if key.startswith("rated_"): + # add the rated_commodity_production + total_rated = total_rated + value + if "_capacity_factor" in key: + # get the stream number so we can get the proper rated capacity + stream_number = key.split("capacity_factor")[-1] + rated_capacity = inputs[f"rated_{self.config.commodity}_production{stream_number}"] + # weight the capacity factor with the rated capacity + weighted_cf = weighted_cf + (value * rated_capacity) + + outputs[f"{self.config.commodity}_out"] = total_out + outputs[f"rated_{self.config.commodity}_production"] = total_rated + if total_rated > 0: + # weighted CF = (CF1*S1 + CF2*S2)/(S1 + S2) + # Where S is the rated commodity production of input stream i + # and CF is the capacity factor of input stream i + outputs[f"{self.config.commodity}_capacity_factor"] = weighted_cf / total_rated + else: + outputs[f"{self.config.commodity}_capacity_factor"] = 0.0 diff --git a/h2integrate/transporters/generic_summer.py b/h2integrate/transporters/generic_summer.py index 5d1fc9d98..1c495ed4b 100644 --- a/h2integrate/transporters/generic_summer.py +++ b/h2integrate/transporters/generic_summer.py @@ -16,7 +16,7 @@ class GenericSummerPerformanceConfig(BaseConfig): """ commodity: str = field(converter=(str.lower, str.strip)) - commodity_units: str = field() + commodity_rate_units: str = field() operation_mode: str = field( default="production", converter=(str.lower, str.strip), @@ -46,15 +46,15 @@ def setup(self): if self.config.commodity == "electricity": # NOTE: this should be updated in overhaul required for flexible dt # and flexible simulation length - summed_units = f"{self.config.commodity_units}*h/year" + summed_units = f"{self.config.commodity_rate_units}*h/year" else: - summed_units = f"{self.config.commodity_units}*h/year" + summed_units = f"{self.config.commodity_rate_units}*h/year" self.add_input( f"{self.config.commodity}_in", val=0.0, shape=n_timesteps, - units=self.config.commodity_units, + units=self.config.commodity_rate_units, ) if self.config.operation_mode == "consumption": diff --git a/h2integrate/transporters/test/test_generic_combiner.py b/h2integrate/transporters/test/test_generic_combiner.py index 640ba4aa5..ba25546e8 100644 --- a/h2integrate/transporters/test/test_generic_combiner.py +++ b/h2integrate/transporters/test/test_generic_combiner.py @@ -24,7 +24,7 @@ def plant_config(): def combiner_tech_config_electricity(): elec_combiner_dict = { "model_inputs": { - "performance_parameters": {"commodity": "electricity", "commodity_units": "kW"} + "performance_parameters": {"commodity": "electricity", "commodity_rate_units": "kW"} } } return elec_combiner_dict @@ -36,7 +36,7 @@ def combiner_tech_config_electricity_4_in(): "model_inputs": { "performance_parameters": { "commodity": "electricity", - "commodity_units": "kW", + "commodity_rate_units": "kW", "in_streams": 4, } } @@ -48,7 +48,7 @@ def combiner_tech_config_electricity_4_in(): def combiner_tech_config_hydrogen(): h2_combiner_dict = { "model_inputs": { - "performance_parameters": {"commodity": "hydrogen", "commodity_units": "kg"} + "performance_parameters": {"commodity": "hydrogen", "commodity_rate_units": "kg"} } } return h2_combiner_dict @@ -60,7 +60,7 @@ def summer_tech_config_electricity_consumption(): "model_inputs": { "performance_parameters": { "commodity": "electricity", - "commodity_units": "kW", + "commodity_rate_units": "kW", "operation_mode": "consumption", } } @@ -74,7 +74,7 @@ def summer_tech_config_hydrogen_consumption(): "model_inputs": { "performance_parameters": { "commodity": "hydrogen", - "commodity_units": "kg", + "commodity_rate_units": "kg", "operation_mode": "consumption", } } @@ -88,7 +88,7 @@ def summer_tech_config_electricity_production(): "model_inputs": { "performance_parameters": { "commodity": "electricity", - "commodity_units": "kW", + "commodity_rate_units": "kW", "operation_mode": "production", } } @@ -102,7 +102,7 @@ def summer_tech_config_hydrogen_production(): "model_inputs": { "performance_parameters": { "commodity": "hydrogen", - "commodity_units": "kg", + "commodity_rate_units": "kg", "operation_mode": "production", } } @@ -110,7 +110,9 @@ def summer_tech_config_hydrogen_production(): return h2_summer_dict -def test_generic_combiner_performance_power(plant_config, combiner_tech_config_electricity): +def test_generic_combiner_performance_power( + plant_config, combiner_tech_config_electricity, subtests +): prob = om.Problem() comp = GenericCombinerPerformanceModel( plant_config=plant_config, tech_config=combiner_tech_config_electricity, driver_config={} @@ -119,6 +121,10 @@ def test_generic_combiner_performance_power(plant_config, combiner_tech_config_e ivc = om.IndepVarComp() ivc.add_output("electricity_in1", val=np.zeros(8760), units="kW") ivc.add_output("electricity_in2", val=np.zeros(8760), units="kW") + ivc.add_output("rated_electricity_production1", val=0, units="kW") + ivc.add_output("rated_electricity_production2", val=0, units="kW") + ivc.add_output("electricity_capacity_factor1", val=np.zeros(30), units="unitless") + ivc.add_output("electricity_capacity_factor2", val=np.zeros(30), units="unitless") prob.model.add_subsystem("ivc", ivc, promotes=["*"]) prob.setup() @@ -126,12 +132,30 @@ def test_generic_combiner_performance_power(plant_config, combiner_tech_config_e electricity_input1 = rng.random(8760) electricity_input2 = rng.random(8760) electricity_output = electricity_input1 + electricity_input2 - + rated_electricity_output = np.max(electricity_input1) + np.max(electricity_input2) + cf_input1 = np.sum(electricity_input1) / (np.max(electricity_input1) * len(electricity_input1)) + cf_input2 = np.sum(electricity_input2) / (np.max(electricity_input2) * len(electricity_input2)) prob.set_val("electricity_in1", electricity_input1, units="kW") prob.set_val("electricity_in2", electricity_input2, units="kW") + prob.set_val("rated_electricity_production1", np.max(electricity_input1), units="kW") + prob.set_val("rated_electricity_production2", np.max(electricity_input2), units="kW") + prob.set_val("electricity_capacity_factor1", cf_input1 * np.ones(30), units="unitless") + prob.set_val("electricity_capacity_factor2", cf_input2 * np.ones(30), units="unitless") prob.run_model() - assert prob.get_val("electricity_out", units="kW") == approx(electricity_output, rel=1e-5) + with subtests.test("combined electricity_out"): + assert prob.get_val("electricity_out", units="kW") == approx(electricity_output, rel=1e-5) + with subtests.test("combined rated_electricity_production"): + assert prob.get_val("rated_electricity_production", units="kW") == approx( + rated_electricity_output, rel=1e-5 + ) + with subtests.test("combined electricity_capacity_factor"): + combined_cf = np.sum(electricity_output) / ( + rated_electricity_output * len(electricity_output) + ) + assert prob.get_val("electricity_capacity_factor", units="unitless") == approx( + combined_cf, rel=1e-5 + ) def test_generic_combiner_performance_power_4_in( @@ -304,7 +328,7 @@ def test_generic_summer_default_mode_is_production(plant_config): "model_inputs": { "performance_parameters": { "commodity": "electricity", - "commodity_units": "kW", + "commodity_rate_units": "kW", # Note: operation_mode not specified, should default to production } }