From 6f542614efce91d18bdc2dd40ff8e03107508285 Mon Sep 17 00:00:00 2001 From: kbrunik Date: Sat, 11 Apr 2026 09:57:20 -0500 Subject: [PATCH 1/3] update from pig iron to sponge iron --- docs/technology_models/iron_dri.md | 4 + docs/technology_models/steel_eaf.md | 9 ++- .../iron_dri/plant_config.yaml | 6 +- .../21_iron_examples/iron_dri/run_iron.py | 22 +++--- .../iron_dri/tech_config.yaml | 6 +- .../iron_mapping/driver_config.yaml | 4 +- .../iron_mapping/ex_out/cases.csv | 22 +++--- .../iron_mapping/plant_config.yaml | 4 +- .../21_iron_examples/iron_mapping/run_iron.py | 2 +- .../iron_mapping/tech_config.yaml | 6 +- examples/test/test_all_examples.py | 4 +- h2integrate/converters/iron/iron_dri_base.py | 76 +++++++++---------- h2integrate/converters/iron/iron_dri_plant.py | 8 +- .../converters/iron/test/test_rosner_dri.py | 48 ++++++------ .../converters/steel/steel_eaf_base.py | 10 +-- .../converters/steel/steel_eaf_plant.py | 4 +- .../converters/steel/test/test_rosner_eaf.py | 9 ++- 17 files changed, 129 insertions(+), 115 deletions(-) diff --git a/docs/technology_models/iron_dri.md b/docs/technology_models/iron_dri.md index a2bbd367e..378d9e558 100644 --- a/docs/technology_models/iron_dri.md +++ b/docs/technology_models/iron_dri.md @@ -12,6 +12,10 @@ The models implemented in H2I are: - `HydrogenIronReductionPlantPerformanceComponent` - `HydrogenIronReductionPlantCostComponent` +```{note} +The DRI model outputs sponge iron, which is low in carbon content. The LBNL model calls the outputs pig iron, but that's typically produced using a blast furnace rather than through the DRI process and has higher carbon impurities. +``` + Citation: ```bibtex @article{rosner2023green, diff --git a/docs/technology_models/steel_eaf.md b/docs/technology_models/steel_eaf.md index 28638556b..2f7d25967 100644 --- a/docs/technology_models/steel_eaf.md +++ b/docs/technology_models/steel_eaf.md @@ -3,8 +3,9 @@ H2I contains two steel electric arc furnace (EAF) models, one is for a facility that utilizes iron pellets from natural gas (NG) direct iron reduction and another facility that utilizes iron pellets from hydrogen (H2)direct iron reduction. The main difference is the required feedstocks for processing the pellets. -- NG-EAF requires: natural gas, water, pig iron and electricity. -- H2-EAF requires: natural gas, water, carbon, lime, pig iron and electricity. +- NG-EAF requires: natural gas, water, sponge iron and electricity. +- H2-EAF requires: natural gas, water, carbon, lime, sponge iron and electricity. + The original models were constructed in Aspen Pro and translated into Python and added to H2I. The models were developed in conjunction with [Lawrence Berkeley National Laboratory (LBNL)](https://www.lbl.gov/). @@ -16,6 +17,10 @@ The models implemented in H2I are: - `HydrogenEAFPlantPerformanceComponent` - `HydrogenEAFPlantCostComponent` +```{note} +The EAF model use sponge iron as an input rather than pig iron, which is lower in carbon content. The LBNL model calls the input to the EAF pig iron, but that's typically produced using a blast furnace rather than through the DRI process and has higher carbon impurities. +``` + Citation: ```bibtex @article{rosner2023green, diff --git a/examples/21_iron_examples/iron_dri/plant_config.yaml b/examples/21_iron_examples/iron_dri/plant_config.yaml index 83e240172..906fa1776 100644 --- a/examples/21_iron_examples/iron_dri/plant_config.yaml +++ b/examples/21_iron_examples/iron_dri/plant_config.yaml @@ -23,7 +23,7 @@ technology_interconnections: - [eaf_grid_feedstock, steel_plant, electricity, cable] - [eaf_water_feedstock, steel_plant, water, pipe] - [eaf_natural_gas_feedstock, steel_plant, natural_gas, pipe] - - [iron_plant, steel_plant, pig_iron, pig_iron_transport] + - [iron_plant, steel_plant, sponge_iron, sponge_iron_transport] plant: plant_life: 30 simulation: @@ -64,8 +64,8 @@ finance_parameters: - iron_mine - mine_electricity_feedstock - mine_crude_ore_feedstock - pig_iron: - commodity: pig_iron + sponge_iron: + commodity: sponge_iron commodity_stream: iron_plant technologies: - processed_ore_feedstock diff --git a/examples/21_iron_examples/iron_dri/run_iron.py b/examples/21_iron_examples/iron_dri/run_iron.py index 93a73f40b..c24149eb6 100644 --- a/examples/21_iron_examples/iron_dri/run_iron.py +++ b/examples/21_iron_examples/iron_dri/run_iron.py @@ -3,11 +3,11 @@ In this example, iron ore pellets are produced at different iron mine locations in NE Minnesota. These mines send processed ore pellets to a separate iron DRI plant located outside Chicago. Four different cases are generated for four different iron mine setups in the `test_inputs.csv`. -The first two cases generate standard blast furnace gradepellets at two different mine locations. +The first two cases generate standard blast furnace grade pellets at two different mine locations. The second two cases generate DR grade pellets at the same location, with the output capacity varied to show how the capacity of the mine does not affect the levelized cost of iron_ore pellets -(LCOI), nor does it affect the final cost of the pig_iron produced by DRI (LCOP) or the cost of the -steel produced by the EAF (LCOS). +(LCOI), nor does it affect the final cost of the sponge_iron produced by DRI (LCOS) or the cost +of the steel produced by the EAF (LCOS). """ @@ -78,22 +78,26 @@ ) ) lcois_iron.append( - float(model.model.get_val("finance_subgroup_pig_iron.price_pig_iron", units="USD/kg")[0]) + float( + model.model.get_val("finance_subgroup_sponge_iron.price_sponge_iron", units="USD/kg")[0] + ) ) capexes_iron.append( - float(model.model.get_val("finance_subgroup_pig_iron.total_capex_adjusted", units="USD")[0]) + float( + model.model.get_val("finance_subgroup_sponge_iron.total_capex_adjusted", units="USD")[0] + ) ) fopexes_iron.append( float( - model.model.get_val("finance_subgroup_pig_iron.total_opex_adjusted", units="USD/year")[ - 0 - ] + model.model.get_val( + "finance_subgroup_sponge_iron.total_opex_adjusted", units="USD/year" + )[0] ) ) vopexes_iron.append( float( model.model.get_val( - "finance_subgroup_pig_iron.total_varopex_adjusted", units="USD/year" + "finance_subgroup_sponge_iron.total_varopex_adjusted", units="USD/year" )[0] ) ) diff --git a/examples/21_iron_examples/iron_dri/tech_config.yaml b/examples/21_iron_examples/iron_dri/tech_config.yaml index 39e3d0103..4e8056f34 100644 --- a/examples/21_iron_examples/iron_dri/tech_config.yaml +++ b/examples/21_iron_examples/iron_dri/tech_config.yaml @@ -163,18 +163,18 @@ technologies: model: NaturalGasIronReductionPlantCostComponent model_inputs: shared_parameters: - pig_iron_production_rate_tonnes_per_hr: 161.8829908675799 # equivalent to 1418095 t/yr + sponge_iron_production_rate_tonnes_per_hr: 161.8829908675799 # equivalent to 1418095 t/yr performance_parameters: water_density: 1000 # kg/m3 cost_parameters: skilled_labor_cost: 40.85 # 2022 USD/hr unskilled_labor_cost: 30.0 # 2022 USD/hr - pig_iron_transport: + sponge_iron_transport: performance_model: model: GenericTransporterPerformanceModel model_inputs: performance_parameters: - commodity: pig_iron + commodity: sponge_iron commodity_rate_units: kg/h eaf_grid_feedstock: # electricity feedstock for EAF performance_model: diff --git a/examples/21_iron_examples/iron_mapping/driver_config.yaml b/examples/21_iron_examples/iron_mapping/driver_config.yaml index f40426217..a25043f74 100644 --- a/examples/21_iron_examples/iron_mapping/driver_config.yaml +++ b/examples/21_iron_examples/iron_mapping/driver_config.yaml @@ -34,10 +34,10 @@ design_variables: lower: 0 upper: 10 objective: - name: finance_subgroup_pig_iron.LCOP + name: finance_subgroup_sponge_iron.LCOS recorder: file: cases.sql overwrite_recorder: true flag: true - includes: [site.latitude, site.longitude, finance_subgroup_pig_iron.LCOP] + includes: [site.latitude, site.longitude, finance_subgroup_sponge_iron.LCOS] excludes: ['*'] diff --git a/examples/21_iron_examples/iron_mapping/ex_out/cases.csv b/examples/21_iron_examples/iron_mapping/ex_out/cases.csv index c2e7a20ae..d05831d7a 100644 --- a/examples/21_iron_examples/iron_mapping/ex_out/cases.csv +++ b/examples/21_iron_examples/iron_mapping/ex_out/cases.csv @@ -1,4 +1,4 @@ -,site.latitude (deg),site.longitude (deg),dri_grid_feedstock.price (USD/MW/h),hydrogen_feedstock.price (USD/kg),finance_subgroup_pig_iron.LCOP (USD/kg) +,site.latitude (deg),site.longitude (deg),dri_grid_feedstock.price (USD/MW/h),hydrogen_feedstock.price (USD/kg),finance_subgroup_sponge_iron.LCOS (USD/kg) 0,39.179,-94.644,58.24602573,7.819127689,0.7850112572903336 1,40.639,-94.179,51.41859144,6.486845574,0.7039548783993137 2,42.135,-74.231,54.87195632,9.087358988,0.8611145933067907 @@ -45,7 +45,7 @@ 43,46.295,-97.814,50.4352344,6.861520616,0.726439613726291 44,41.415,-84.014,57.72454279,7.748746212,0.7807120287164648 45,40.169,-88.725,55.9963289,6.822567835,0.7246859586288446 -46,41.603,-93.416,50.76824359,6.498809353,0.7046066294238851 +46,41.603,-93.416,50.76824359,6.498809353,0.7046066294238849 47,44.258,-96.218,48.08888229,6.484302926,0.7034453872616688 48,42.783,-75.227,58.89441507,8.518178578,0.8272278321191734 49,43.47,-94.398,48.64393387,6.650122757,0.7135023812546356 @@ -70,7 +70,7 @@ 68,40.403,-92.166,51.16250247,6.74221526,0.7193242468559962 69,38.862,-93.101,52.61488688,6.996040576,0.7347832629480767 70,41.717,-88.398,58.02462706,7.10284254,0.7418012700736446 -71,44.056,-72.423,57.60315341,9.361197001,0.8779170147170843 +71,44.056,-72.423,57.60315341,9.361197001,0.877917014717084 72,45.586,-87.582,57.64037358,7.851745274,0.786913049961754 73,44.721,-93.281,54.55981386,7.286968665,0.7525319703228306 74,40.227,-85.106,55.34456603,7.546306027,0.7682519002330839 @@ -140,7 +140,7 @@ 138,42.209,-95.172,49.93455623,6.639947411,0.7130269507479543 139,40.386,-79.688,63.86411489,9.17986164,0.8676536883118218 140,41.225,-97.815,48.29404304,6.887453274,0.727774096858826 -141,43.101,-88.679,53.03067508,6.615076123,0.7118586114493456 +141,43.101,-88.679,53.03067508,6.615076123,0.7118586114493455 142,46.066,-92.269,61.42938904,8.143522162,0.8049102012304439 143,45.91,-90.383,57.83685565,7.028809928,0.7373176052605699 144,43.227,-75.538,75.42836767,11.27986223,0.9955041042695535 @@ -180,7 +180,7 @@ 178,42.24,-76.323,69.36892469,11.02959371,0.9797667028885654 179,40.26,-95.917,48.50109937,6.553833735,0.7076816387944875 180,45.441,-88.545,55.8526599,6.659344602,0.7148295257785313 -181,41.361,-72.544,53.88391198,7.614258476,0.7721926421678088 +181,41.361,-72.544,53.88391198,7.614258476,0.7721926421678089 182,43.881,-74.532,58.37302204,7.950019899,0.7929166027975763 183,46.375,-93.045,55.3918541,7.021579046,0.7366200908703626 184,39.137,-85.003,61.51175413,8.628449283,0.8341562646653646 @@ -206,7 +206,7 @@ 204,40.577,-81.398,62.90874286,9.207572255,0.8692222190216685 205,46.361,-84.431,60.67660851,8.526102871,0.8278962509744834 206,44.053,-97.747,49.14434791,7.507503977,0.7652491876919038 -207,40.592,-75.951,52.92795272,7.774752517,0.7817668956631634 +207,40.592,-75.951,52.92795272,7.774752517,0.7817668956631636 208,39.147,-79.282,50.22245241,7.648694799,0.7738772007716359 209,38.644,-76.981,60.48685661,9.107058567,0.8629029652934552 210,42.699,-95.913,47.57093505,6.650574037,0.7134148077286483 @@ -214,7 +214,7 @@ 212,46.002,-94.17,55.75347218,7.786657918,0.7827869516303637 213,43.769,-95.151,47.31078488,6.411883727,0.6989958493683212 214,42.828,-83.285,54.80541876,7.452960164,0.7625662120288659 -215,42.306,-85.339,58.19811541,7.738857599,0.7801664835008522 +215,42.306,-85.339,58.19811541,7.738857599,0.7801664835008524 216,45.817,-96.539,52.01958202,7.034215719,0.7370212390937694 217,40.981,-72.087,47.38085805,7.581109738,0.769498384106217 218,43.651,-84.949,58.31734328,7.714266851,0.7786966109612123 @@ -364,7 +364,7 @@ 362,42.695,-97.11,47.29014174,6.582336603,0.7092705952544859 363,46.317,-86.678,50.57965136,7.544514147,0.7676341459041081 364,39.278,-85.55,55.89197093,8.15175868,0.8048144424536288 -365,45.259,-87.002,49.62045856,6.510959335,0.7052163945598876 +365,45.259,-87.002,49.62045856,6.510959335,0.7052163945598875 366,45.913,-83.725,51.24562465,7.70481277,0.7773701205726504 367,38.913,-91.406,53.51277698,6.99251437,0.7346667109256176 368,43.752,-90.126,58.17486708,7.480069934,0.7645611564600783 @@ -399,7 +399,7 @@ 397,38.392,-88.5,57.86180863,8.146670896,0.8047184093570208 398,44.244,-90.231,58.69672689,7.387475541,0.7590342752558699 399,43.469,-97.73,49.2252985,7.077334545,0.7393220482476153 -400,38.105,-94.806,49.62168947,6.735856204,0.7187760205954808 +400,38.105,-94.806,49.62168947,6.735856204,0.7187760205954807 401,41.24,-85.519,53.29636267,7.026106974,0.7366689275159279 402,39.257,-77.09,60.22997497,9.200428081,0.8685049256999342 403,45.223,-83.711,57.52598123,7.826185595,0.7853597674770538 @@ -416,7 +416,7 @@ 414,41.059,-88.915,56.09943255,7.088687211,0.7407418719709922 415,38.728,-75.52,55.35219581,8.633813323,0.8338207659543093 416,39.353,-90.096,58.12244541,7.782775604,0.7828062956046264 -417,45.818,-95.751,48.45444571,6.964379638,0.7324293042045387 +417,45.818,-95.751,48.45444571,6.964379638,0.7324293042045384 418,41.765,-83.501,57.41290156,8.084847126,0.800942906196559 419,41.709,-90.317,58.05576703,6.956987333,0.7330106910756611 420,41.223,-88.31,59.46589028,7.512207926,0.7666369269279907 @@ -491,7 +491,7 @@ 489,46.583,-87.917,52.02200649,6.6587893,0.7143862682010519 490,38.862,-80.212,64.90563199,10.12406768,0.9246932250882576 491,46.506,-90.645,67.55517351,9.152558169,0.8664023503170806 -492,44.595,-84.463,60.91517064,7.760417109,0.7817570027709664 +492,44.595,-84.463,60.91517064,7.760417109,0.7817570027709662 493,45.955,-85.624,49.64725022,7.121041155,0.7420023471036234 494,42.325,-79.325,60.74805435,9.191121493,0.8679992329984643 495,44.135,-83.625,56.50144774,7.908995333,0.7902429391098073 diff --git a/examples/21_iron_examples/iron_mapping/plant_config.yaml b/examples/21_iron_examples/iron_mapping/plant_config.yaml index e0c8b12f0..276c4f636 100644 --- a/examples/21_iron_examples/iron_mapping/plant_config.yaml +++ b/examples/21_iron_examples/iron_mapping/plant_config.yaml @@ -59,8 +59,8 @@ finance_parameters: - iron_mine - mine_electricity_feedstock - mine_crude_ore_feedstock - pig_iron: - commodity: pig_iron + sponge_iron: + commodity: sponge_iron commodity_stream: iron_plant technologies: - processed_ore_feedstock diff --git a/examples/21_iron_examples/iron_mapping/run_iron.py b/examples/21_iron_examples/iron_mapping/run_iron.py index 74f7dc619..8ae15812d 100644 --- a/examples/21_iron_examples/iron_mapping/run_iron.py +++ b/examples/21_iron_examples/iron_mapping/run_iron.py @@ -42,7 +42,7 @@ # NOTE: you can swap './ex_28_out/cases.sql' with './ex_28_out/cases.csv' to read results from csv fig, ax, lcoi_layer_gdf = plot_geospatial_point_heat_map( case_results_fpath=case_results_filepath, - metric_to_plot="finance_subgroup_pig_iron.LCOP (USD/kg)", + metric_to_plot="finance_subgroup_sponge_iron.LCOS (USD/kg)", map_preferences={ "figsize": (10, 8), "colorbar_label": "Levelized Cost of\nIron [$/kg]", diff --git a/examples/21_iron_examples/iron_mapping/tech_config.yaml b/examples/21_iron_examples/iron_mapping/tech_config.yaml index c9949779a..468f658de 100644 --- a/examples/21_iron_examples/iron_mapping/tech_config.yaml +++ b/examples/21_iron_examples/iron_mapping/tech_config.yaml @@ -156,16 +156,16 @@ technologies: model: HydrogenIronReductionPlantCostComponent model_inputs: shared_parameters: - pig_iron_production_rate_tonnes_per_hr: 161.8829908675799 # equivalent to 1418095 t/yr + sponge_iron_production_rate_tonnes_per_hr: 161.8829908675799 # equivalent to 1418095 t/yr performance_parameters: water_density: 1000 # kg/m3 cost_parameters: skilled_labor_cost: 40.85 # 2022 USD/hr unskilled_labor_cost: 30.0 # 2022 USD/hr - pig_iron_transport: + sponge_iron_transport: performance_model: model: GenericTransporterPerformanceModel model_inputs: performance_parameters: - commodity: pig_iron + commodity: sponge_iron commodity_rate_units: kg/h diff --git a/examples/test/test_all_examples.py b/examples/test/test_all_examples.py index 9fd18637e..01a1d2f94 100644 --- a/examples/test/test_all_examples.py +++ b/examples/test/test_all_examples.py @@ -1955,7 +1955,7 @@ def test_iron_mapping_example(subtests, temp_copy_of_example): # Plot LCOI results from cases.sql file, save sql data to csv fig, ax, lcoi_layer_gdf = plot_geospatial_point_heat_map( case_results_fpath=cases_csv_fpath, - metric_to_plot="finance_subgroup_pig_iron.LCOP (USD/kg)", + metric_to_plot="finance_subgroup_sponge_iron.LCOS (USD/kg)", map_preferences={ "figsize": (10, 8), "colorbar_label": "Levelized Cost of\nIron [$/kg]", @@ -2227,7 +2227,7 @@ def test_iron_dri_eaf_example(subtests, temp_copy_of_example): assert pytest.approx(lcoi, rel=1e-4) == 135.3741358811098 with subtests.test("Value check on LCOP"): - lcop = h2i.model.get_val("finance_subgroup_pig_iron.LCOP", units="USD/t")[0] + lcop = h2i.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/t")[0] assert pytest.approx(lcop, rel=1e-4) == 359.670379351 with subtests.test("Value check on LCOS"): diff --git a/h2integrate/converters/iron/iron_dri_base.py b/h2integrate/converters/iron/iron_dri_base.py index 9887b76d7..02c255c2b 100644 --- a/h2integrate/converters/iron/iron_dri_base.py +++ b/h2integrate/converters/iron/iron_dri_base.py @@ -19,20 +19,20 @@ class IronReductionPerformanceBaseConfig(BaseConfig): """Configuration baseclass for IronReductionPlantBasePerformanceComponent. Attributes: - pig_iron_production_rate_tonnes_per_hr (float): capacity of the iron processing plant - in units of metric tonnes of pig iron produced per hour. + sponge_iron_production_rate_tonnes_per_hr (float): capacity of the iron processing plant + in units of metric tonnes of sponge iron produced per hour. water_density (float): water density in kg/m3 to use to calculate water volume from mass. Defaults to 1000.0 """ - pig_iron_production_rate_tonnes_per_hr: float = field() + sponge_iron_production_rate_tonnes_per_hr: float = field() water_density: float = field(default=1000) # kg/m3 class IronReductionPlantBasePerformanceComponent(PerformanceModelBaseClass): def initialize(self): super().initialize() - self.commodity = "pig_iron" + self.commodity = "sponge_iron" self.commodity_rate_units = "t/h" self.commodity_amount_units = "t" @@ -48,9 +48,9 @@ def setup(self): self.add_input( "system_capacity", - val=self.config.pig_iron_production_rate_tonnes_per_hr, + val=self.config.sponge_iron_production_rate_tonnes_per_hr, units="t/h", - desc="Rated pig iron production capacity", + desc="Rated sponge iron production capacity", ) # Add feedstock inputs and outputs, default to 0 --> set using feedstock component @@ -70,10 +70,10 @@ def setup(self): desc=f"{feedstock} consumed for iron reduction", ) - # Default the pig iron demand input as the rated capacity + # Default the sponge iron demand input as the rated capacity self.add_input( - "pig_iron_demand", - val=self.config.pig_iron_production_rate_tonnes_per_hr, + "sponge_iron_demand", + val=self.config.sponge_iron_production_rate_tonnes_per_hr, shape=n_timesteps, units="t/h", desc="Pig iron demand for iron plant", @@ -86,7 +86,7 @@ def setup(self): def format_coeff_df(self, coeff_df): """Update the coefficient dataframe such that feedstock values are converted to units of - feedstock unit / unit pig iron, e.g., 'galUS/t'. Also convert values to standard units + feedstock unit / unit sponge iron, e.g., 'galUS/t'. Also convert values to standard units and that units are compatible with OpenMDAO Units. Filter the dataframe to include only the data necessary for the specified type of reduction. @@ -129,12 +129,12 @@ def format_coeff_df(self, coeff_df): i_update = coeff_df[coeff_df["Unit"] == old_unit].index - # convert from feedstock per unit steel to feedstock per unit pig iron + # convert from feedstock per unit steel to feedstock per unit sponge iron coeff_df.loc[i_update, "Value"] = ( coeff_df.loc[i_update, "Value"] * steel_to_iron_ratio ) - # update the "Type" to specify that units were changed to be per unit pig iron + # update the "Type" to specify that units were changed to be per unit sponge iron coeff_df.loc[i_update, "Type"] = f"{coeff_df.loc[i_update, 'Type'].values[0]}/iron" is_capacity_type = all( @@ -194,7 +194,7 @@ def compute(self, inputs, outputs): # get the feedstocks from feedstocks = self.coeff_df[self.coeff_df["Type"] == "feed/iron"].copy() - # get the feedstock usage rates in units/t pig iron + # get the feedstock usage rates in units/t sponge iron feedstocks_usage_rates = { "natural_gas": feedstocks[feedstocks["Name"] == "Natural Gas"][ "Value" @@ -219,20 +219,20 @@ def compute(self, inputs, outputs): feedstocks["Name"] == "Reformer Catalyst" ]["Value"].sum() - # pig iron demand, saturated at maximum rated system capacity - pig_iron_demand = np.where( - inputs["pig_iron_demand"] > inputs["system_capacity"], + # sponge iron demand, saturated at maximum rated system capacity + sponge_iron_demand = np.where( + inputs["sponge_iron_demand"] > inputs["system_capacity"], inputs["system_capacity"], - inputs["pig_iron_demand"], + inputs["sponge_iron_demand"], ) - # initialize an array of how much pig iron could be produced + # initialize an array of how much sponge iron could be produced # from the available feedstocks and the demand - pig_iron_from_feedstocks = np.zeros( - (len(feedstocks_usage_rates) + 1, len(inputs["pig_iron_demand"])) + sponge_iron_from_feedstocks = np.zeros( + (len(feedstocks_usage_rates) + 1, len(inputs["sponge_iron_demand"])) ) - # first entry is the pig iron demand - pig_iron_from_feedstocks[0] = pig_iron_demand + # first entry is the sponge iron demand + sponge_iron_from_feedstocks[0] = sponge_iron_demand ii = 1 for feedstock_type, consumption_rate in feedstocks_usage_rates.items(): # calculate max inputs/outputs based on rated capacity @@ -244,24 +244,24 @@ def compute(self, inputs, outputs): inputs[f"{feedstock_type}_in"], ) # how much output can be produced from each of the feedstocks - pig_iron_from_feedstocks[ii] = feedstock_available / consumption_rate + sponge_iron_from_feedstocks[ii] = feedstock_available / consumption_rate ii += 1 # output is minimum between available feedstocks and output demand - pig_iron_production = np.minimum.reduce(pig_iron_from_feedstocks) - outputs["pig_iron_out"] = pig_iron_production - outputs["total_pig_iron_produced"] = np.sum(pig_iron_production) - outputs["capacity_factor"] = outputs["total_pig_iron_produced"] / ( + sponge_iron_production = np.minimum.reduce(sponge_iron_from_feedstocks) + outputs["sponge_iron_out"] = sponge_iron_production + outputs["total_sponge_iron_produced"] = np.sum(sponge_iron_production) + outputs["capacity_factor"] = outputs["total_sponge_iron_produced"] / ( inputs["system_capacity"] * self.n_timesteps ) - outputs["rated_pig_iron_production"] = inputs["system_capacity"] - outputs["annual_pig_iron_produced"] = outputs["total_pig_iron_produced"] * ( + outputs["rated_sponge_iron_production"] = inputs["system_capacity"] + outputs["annual_sponge_iron_produced"] = outputs["total_sponge_iron_produced"] * ( 1 / self.fraction_of_year_simulated ) - # feedstock consumption based on actual pig iron produced + # feedstock consumption based on actual sponge iron produced for feedstock_type, consumption_rate in feedstocks_usage_rates.items(): - outputs[f"{feedstock_type}_consumed"] = pig_iron_production * consumption_rate + outputs[f"{feedstock_type}_consumed"] = sponge_iron_production * consumption_rate @define @@ -269,8 +269,8 @@ class IronReductionCostBaseConfig(CostModelBaseConfig): """Configuration baseclass for IronReductionPlantBaseCostComponent. Attributes: - pig_iron_production_rate_tonnes_per_hr (float): capacity of the iron processing plant - in units of metric tonnes of pig iron produced per hour. + sponge_iron_production_rate_tonnes_per_hr (float): capacity of the iron processing plant + in units of metric tonnes of sponge iron produced per hour. cost_year (int): This model uses 2022 as the base year for the cost model. The cost year is updated based on `target_dollar_year` in the plant config to adjust costs based on CPI/CEPCI within this model. This value @@ -279,7 +279,7 @@ class IronReductionCostBaseConfig(CostModelBaseConfig): unskilled_labor_cost (float): Unskilled labor cost in 2022 USD/hr """ - pig_iron_production_rate_tonnes_per_hr: float = field() + sponge_iron_production_rate_tonnes_per_hr: float = field() cost_year: int = field(converter=int) skilled_labor_cost: float = field(validator=gte_zero) unskilled_labor_cost: float = field(validator=gte_zero) @@ -292,7 +292,7 @@ class IronReductionPlantBaseCostComponent(CostModelBaseClass): Attributes: config (IronReductionCostBaseConfig): configuration class coeff_df (pd.DataFrame): cost coefficient dataframe - steel_to_iron_ratio (float): steel/pig iron ratio + steel_to_iron_ratio (float): steel/sponge iron ratio """ def setup(self): @@ -335,12 +335,12 @@ def setup(self): self.add_input( "system_capacity", - val=self.config.pig_iron_production_rate_tonnes_per_hr, + val=self.config.sponge_iron_production_rate_tonnes_per_hr, units="t/h", desc="Pig ore production capacity", ) self.add_input( - "pig_iron_out", + "sponge_iron_out", val=0.0, shape=n_timesteps, units="t/h", @@ -470,7 +470,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): varom = self.coeff_df[self.coeff_df["Type"] == "variable opex"][ "Value" ].sum() # units are USD/mtpy steel - tot_varopex = varom * self.steel_to_iron_ratio * inputs["pig_iron_out"].sum() + tot_varopex = varom * self.steel_to_iron_ratio * inputs["sponge_iron_out"].sum() # Adjust costs to target dollar year tot_capex_adjusted = inflate_cepci(total_capex_usd, dollar_year, self.config.cost_year) diff --git a/h2integrate/converters/iron/iron_dri_plant.py b/h2integrate/converters/iron/iron_dri_plant.py index 25a631ff9..3fb0b2d60 100644 --- a/h2integrate/converters/iron/iron_dri_plant.py +++ b/h2integrate/converters/iron/iron_dri_plant.py @@ -12,7 +12,7 @@ class HydrogenIronReductionPlantCostComponent(IronReductionPlantBaseCostComponen product (str): 'h2_dri' config (HydrogenIronReductionCostConfig): configuration class coeff_df (pd.DataFrame): cost coefficient dataframe - steel_to_iron_ratio (float): steel/pig iron ratio + steel_to_iron_ratio (float): steel/sponge iron ratio """ def setup(self): @@ -28,7 +28,7 @@ class NaturalGasIronReductionPlantCostComponent(IronReductionPlantBaseCostCompon product (str): 'ng_dri' config (NaturalGasIronReductionCostConfig): configuration class coeff_df (pd.DataFrame): cost coefficient dataframe - steel_to_iron_ratio (float): steel/pig iron ratio + steel_to_iron_ratio (float): steel/sponge iron ratio """ def setup(self): @@ -44,7 +44,7 @@ class HydrogenIronReductionPlantPerformanceComponent(IronReductionPlantBasePerfo product (str): 'h2_dri' config (HydrogenIronReductionPerformanceConfig): configuration class coeff_df (pd.DataFrame): performance coefficient dataframe - steel_to_iron_ratio (float): steel/pig iron ratio + steel_to_iron_ratio (float): steel/sponge iron ratio """ def setup(self): @@ -67,7 +67,7 @@ class NaturalGasIronReductionPlantPerformanceComponent(IronReductionPlantBasePer product (str): 'ng_dri' config (NaturalGasIronReductionPerformanceConfig): configuration class coeff_df (pd.DataFrame): performance coefficient dataframe - steel_to_iron_ratio (float): steel/pig iron ratio + steel_to_iron_ratio (float): steel/sponge iron ratio """ def setup(self): diff --git a/h2integrate/converters/iron/test/test_rosner_dri.py b/h2integrate/converters/iron/test/test_rosner_dri.py index 765180fb9..7f16791cd 100644 --- a/h2integrate/converters/iron/test/test_rosner_dri.py +++ b/h2integrate/converters/iron/test/test_rosner_dri.py @@ -16,7 +16,7 @@ def ng_dri_base_config(): tech_config = { "model_inputs": { "shared_parameters": { - "pig_iron_production_rate_tonnes_per_hr": 1418095 / 8760, # t/h + "sponge_iron_production_rate_tonnes_per_hr": 1418095 / 8760, # t/h }, "cost_parameters": { "skilled_labor_cost": 40.85, # 2022 USD/hr @@ -63,7 +63,7 @@ def ng_feedstock_availability_costs(): def h2_feedstock_availability_costs(): feedstocks_dict = { "electricity": { - # (1418095/8760)t-pig_iron/h * 98.17925 kWh/t-pig_iron = 15893.55104 kW + # (1418095/8760)t-sponge_iron/h * 98.17925 kWh/t-sponge_iron = 15893.55104 kW "rated_capacity": 16000, # need 15893.55104 kW "units": "kW", "price": 0.05802, # USD/kW TODO: update @@ -113,7 +113,7 @@ def test_ng_dri_performance_outputs( units=feedstock_info["units"], ) prob.run_model() - commodity = "pig_iron" + commodity = "sponge_iron" commodity_rate_units = "kg/h" commodity_amount_units = "kg" plant_life = int(plant_config["plant"]["plant_life"]) @@ -195,7 +195,7 @@ def test_ng_dri_performance_outputs( def test_ng_dri_performance( plant_config, ng_dri_base_config, ng_feedstock_availability_costs, subtests ): - expected_pig_iron_annual_production_tpd = 3885.1917808219177 # t/d + expected_sponge_iron_annual_production_tpd = 3885.1917808219177 # t/d prob = om.Problem() @@ -215,11 +215,11 @@ def test_ng_dri_performance( ) prob.run_model() - annual_pig_iron = np.sum(prob.get_val("perf.pig_iron_out", units="t/h")) + annual_sponge_iron = np.sum(prob.get_val("perf.sponge_iron_out", units="t/h")) with subtests.test("Annual Pig Iron"): assert ( - pytest.approx(annual_pig_iron / 365, rel=1e-3) - == expected_pig_iron_annual_production_tpd + pytest.approx(annual_sponge_iron / 365, rel=1e-3) + == expected_sponge_iron_annual_production_tpd ) @@ -227,11 +227,11 @@ def test_ng_dri_performance( def test_ng_dri_performance_limited_feedstock( plant_config, ng_dri_base_config, ng_feedstock_availability_costs, subtests ): - expected_pig_iron_annual_production_tpd = 3885.1917808219177 / 2 # t/d + expected_sponge_iron_annual_production_tpd = 3885.1917808219177 / 2 # t/d # make iron ore feedstock half of whats needed water_usage_rate_gal_pr_tonne = 200.60957937294563 water_half_availability_gal_pr_hr = ( - water_usage_rate_gal_pr_tonne * expected_pig_iron_annual_production_tpd / 24 + water_usage_rate_gal_pr_tonne * expected_sponge_iron_annual_production_tpd / 24 ) ng_feedstock_availability_costs["water"].update( {"rated_capacity": water_half_availability_gal_pr_hr} @@ -255,11 +255,11 @@ def test_ng_dri_performance_limited_feedstock( ) prob.run_model() - annual_pig_iron = np.sum(prob.get_val("perf.pig_iron_out", units="t/h")) + annual_sponge_iron = np.sum(prob.get_val("perf.sponge_iron_out", units="t/h")) with subtests.test("Annual Pig Iron"): assert ( - pytest.approx(annual_pig_iron / 365, rel=1e-3) - == expected_pig_iron_annual_production_tpd + pytest.approx(annual_sponge_iron / 365, rel=1e-3) + == expected_sponge_iron_annual_production_tpd ) @@ -269,7 +269,7 @@ def test_ng_dri_performance_cost( ): expected_capex = 403808062.6981323 expected_fixed_om = 60103761.59958463 - expected_pig_iron_annual_production_tpd = 3885.1917808219177 # t/d + expected_sponge_iron_annual_production_tpd = 3885.1917808219177 # t/d prob = om.Problem() @@ -301,11 +301,11 @@ def test_ng_dri_performance_cost( # IronPlantCostComponent: maintenance_materials is included in Fixed OpEx # NaturalGasIronReductionPlantCostComponent: maintenance_materials is the variable O&M - annual_pig_iron = np.sum(prob.get_val("perf.pig_iron_out", units="t/h")) + annual_sponge_iron = np.sum(prob.get_val("perf.sponge_iron_out", units="t/h")) with subtests.test("Annual Pig Iron"): assert ( - pytest.approx(annual_pig_iron / 365, rel=1e-3) - == expected_pig_iron_annual_production_tpd + pytest.approx(annual_sponge_iron / 365, rel=1e-3) + == expected_sponge_iron_annual_production_tpd ) with subtests.test("CapEx"): # expected difference of 0.044534% @@ -325,7 +325,7 @@ def test_ng_dri_performance_cost( def test_h2_dri_performance( plant_config, ng_dri_base_config, h2_feedstock_availability_costs, subtests ): - expected_pig_iron_annual_production_tpd = 3885.1917808219177 # t/d + expected_sponge_iron_annual_production_tpd = 3885.1917808219177 # t/d prob = om.Problem() @@ -345,11 +345,11 @@ def test_h2_dri_performance( ) prob.run_model() - annual_pig_iron = np.sum(prob.get_val("perf.pig_iron_out", units="t/h")) + annual_sponge_iron = np.sum(prob.get_val("perf.sponge_iron_out", units="t/h")) with subtests.test("Annual Pig Iron"): assert ( - pytest.approx(annual_pig_iron / 365, rel=1e-3) - == expected_pig_iron_annual_production_tpd + pytest.approx(annual_sponge_iron / 365, rel=1e-3) + == expected_sponge_iron_annual_production_tpd ) @@ -360,7 +360,7 @@ def test_h2_dri_performance_cost( expected_capex = 246546589.2914324 expected_fixed_om = 53360873.348792635 - expected_pig_iron_annual_production_tpd = 3885.1917808219177 # t/d + expected_sponge_iron_annual_production_tpd = 3885.1917808219177 # t/d prob = om.Problem() @@ -388,11 +388,11 @@ def test_h2_dri_performance_cost( prob.run_model() - annual_pig_iron = np.sum(prob.get_val("perf.pig_iron_out", units="t/h")) + annual_sponge_iron = np.sum(prob.get_val("perf.sponge_iron_out", units="t/h")) with subtests.test("Annual Pig Iron"): assert ( - pytest.approx(annual_pig_iron / 365, rel=1e-3) - == expected_pig_iron_annual_production_tpd + pytest.approx(annual_sponge_iron / 365, rel=1e-3) + == expected_sponge_iron_annual_production_tpd ) with subtests.test("CapEx"): # expected difference of 0.044534% diff --git a/h2integrate/converters/steel/steel_eaf_base.py b/h2integrate/converters/steel/steel_eaf_base.py index c4b2c8354..d90e31e0b 100644 --- a/h2integrate/converters/steel/steel_eaf_base.py +++ b/h2integrate/converters/steel/steel_eaf_base.py @@ -106,21 +106,21 @@ def format_coeff_df(self, coeff_df): # H2 EAF needs natural gas, electricity, carbon, lime, water # NG EAF needs natural gas and electricity. - # add pig iron feedstock to dataframe + # add sponge iron feedstock to dataframe steel_plant_capacity = coeff_df[coeff_df["Name"] == "Steel Production"]["Value"].values[0] iron_plant_capacity = coeff_df[coeff_df["Name"] == "Pig Iron Production"]["Value"].values[0] iron_to_steel_ratio = iron_plant_capacity / steel_plant_capacity # both in metric tons/year # add to dataframe - pig_iron_row = pd.DataFrame( + sponge_iron_row = pd.DataFrame( { "Name": ["Pig Iron"], "Type": ["feed"], "Coeff": ["lin"], - "Unit": ["mt pig iron/mt steel"], + "Unit": ["mt sponge iron/mt steel"], "Value": [iron_to_steel_ratio], } ) - coeff_df = pd.concat([coeff_df, pig_iron_row], ignore_index=True) + coeff_df = pd.concat([coeff_df, sponge_iron_row], ignore_index=True) # capacity units units are mtpy unit_rename_mapper = {"mtpy": "t/yr", "%": "unitless"} @@ -207,7 +207,7 @@ def compute(self, inputs, outputs): "water": feedstocks[feedstocks["Name"] == "Raw Water Withdrawal"][ "Value" ].sum(), # galUS/t - "pig_iron": feedstocks[feedstocks["Name"] == "Pig Iron"]["Value"].sum(), # t/t + "sponge_iron": feedstocks[feedstocks["Name"] == "Pig Iron"]["Value"].sum(), # t/t "electricity": feedstocks[feedstocks["Unit"] == "(kW*h)/t"][ "Value" ].sum(), # electricity diff --git a/h2integrate/converters/steel/steel_eaf_plant.py b/h2integrate/converters/steel/steel_eaf_plant.py index 6aa5b5e93..940faf752 100644 --- a/h2integrate/converters/steel/steel_eaf_plant.py +++ b/h2integrate/converters/steel/steel_eaf_plant.py @@ -51,7 +51,7 @@ def setup(self): "water": "galUS", # "galUS/h" "carbon": "t/h", "lime": "t/h", - "pig_iron": "t/h", + "sponge_iron": "t/h", "electricity": "kW", } super().setup() @@ -71,7 +71,7 @@ def setup(self): self.feedstocks_to_units = { "natural_gas": "MMBtu/h", "water": "galUS", # "galUS/h" - "pig_iron": "t/h", + "sponge_iron": "t/h", "electricity": "kW", } diff --git a/h2integrate/converters/steel/test/test_rosner_eaf.py b/h2integrate/converters/steel/test/test_rosner_eaf.py index d0a851ff6..42bb1153d 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(): "units": "galUS", "price": 1670.0, # cost is $0.441167535/t, equal to $1670.0004398318847/galUS }, - "pig_iron": { + "sponge_iron": { "rated_capacity": 162, # need 161.88297569673742 t/h "units": "t/h", "price": 27.5409 * 1e3, # USD/t TODO UPDATE @@ -103,7 +103,7 @@ def h2_feedstock_availability_costs(): "units": "galUS", "price": 1670.0, # TODO: update cost is $0.441167535/t, equal to $1670.0004398318847/galUS }, - "pig_iron": { + "sponge_iron": { "rated_capacity": 162, # need 161.88297569673742 t/h "units": "t/h", "price": 27.5409 * 1e3, # USD/t TODO: update @@ -235,10 +235,11 @@ def test_ng_eaf_performance( ) prob.run_model() - annual_pig_iron = np.sum(prob.get_val("perf.steel_out", units="t/h")) + annual_sponge_iron = np.sum(prob.get_val("perf.steel_out", units="t/h")) with subtests.test("Annual Steel"): assert ( - pytest.approx(annual_pig_iron / 365, rel=1e-3) == expected_steel_annual_production_tpd + pytest.approx(annual_sponge_iron / 365, rel=1e-3) + == expected_steel_annual_production_tpd ) From 09f45d503402b3490c6f7ab86edaca6ee5abab2a Mon Sep 17 00:00:00 2001 From: kbrunik Date: Sat, 11 Apr 2026 10:04:28 -0500 Subject: [PATCH 2/3] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11cf18f49..5a5207803 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ - Adds `H2IntegrateModel.state` as an `IntEnum` to handle setup and run status checks. [PR 590](https://github.com/NatLabRockies/H2Integrate/pull/590) - Modified CI setup so Windows is temporarily disabled and also so unit, regression, and integration tests are run in separate jobs to speed up testing and provide more information on test failures. [PR 668](https://github.com/NatLabRockies/H2Integrate/pull/668) +- Change commodity in DRI and EAF model from pig iron to sponge iron based on likely carbon content [PR 670](https://github.com/NatLabRockies/H2Integrate/pull/670) ## 0.7.2 [April 9, 2026] From 4e88ec337d2c320f1e67a21875e61c25df69d0e1 Mon Sep 17 00:00:00 2001 From: kbrunik Date: Sat, 11 Apr 2026 10:06:19 -0500 Subject: [PATCH 3/3] update lco naming in test --- examples/test/test_all_examples.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/test/test_all_examples.py b/examples/test/test_all_examples.py index 01a1d2f94..7f09cce4b 100644 --- a/examples/test/test_all_examples.py +++ b/examples/test/test_all_examples.py @@ -2226,9 +2226,9 @@ def test_iron_dri_eaf_example(subtests, temp_copy_of_example): lcoi = h2i.model.get_val("finance_subgroup_iron_ore.LCOI", units="USD/t")[0] assert pytest.approx(lcoi, rel=1e-4) == 135.3741358811098 - with subtests.test("Value check on LCOP"): - lcop = h2i.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/t")[0] - assert pytest.approx(lcop, rel=1e-4) == 359.670379351 + with subtests.test("Value check on LCOS"): + lcos = h2i.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/t")[0] + assert pytest.approx(lcos, rel=1e-4) == 359.670379351 with subtests.test("Value check on LCOS"): lcos = h2i.model.get_val("finance_subgroup_steel.LCOS", units="USD/t")[0]