Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
26a6ec6
Reading in Stinn input tables
jmartin4u Nov 6, 2025
3a803fd
Sucessfully testing Stinn model
jmartin4u Nov 7, 2025
6fa32fd
Merge remote-tracking branch 'upstream/develop' into ewin-dev
jmartin4u Nov 7, 2025
9d4ff83
Making humbert perf model
jmartin4u Nov 7, 2025
84aad1c
Adding humbert/stinn cost model inputs only
jmartin4u Nov 11, 2025
674cd47
Progress on humbert cost
jmartin4u Nov 11, 2025
0728b2d
Humbert Stinn complete, not debugged
jmartin4u Nov 11, 2025
9601cce
Debugging electrowinning
jmartin4u Nov 12, 2025
3351d51
Merge remote-tracking branch 'upstream/develop' into ewin-dev
jmartin4u Nov 12, 2025
6310a12
Debugging ewinning
jmartin4u Nov 12, 2025
6455234
Merge remote-tracking branch 'upstream/develop' into ewin-dev
jmartin4u Nov 12, 2025
4f978b8
Electrowinning cases running, need to add DRI case
jmartin4u Nov 12, 2025
bd5cdb4
Merge remote-tracking branch 'upstream/develop' into ewin-dev
jmartin4u Dec 4, 2025
9388822
Cleanup of old trailing underscores
jmartin4u Dec 5, 2025
db6864b
Fix input csv names
jmartin4u Jan 2, 2026
c130111
Merge remote-tracking branch 'upstream/develop' into ewin-merge
jmartin4u Jan 2, 2026
0b41cbf
Integrate ewin example with feedstocks
jmartin4u Jan 2, 2026
ab63af7
Moving functions out of h2i module and renumbering example
jmartin4u Jan 2, 2026
47b6535
Merge branch 'develop' into ewin-merge
johnjasa Jan 5, 2026
149ee57
Small fixes to PR
jmartin4u Jan 12, 2026
efa47cc
start of unit test
kbrunik Jan 12, 2026
5b44977
Merge branch 'ewin-merge' of https://github.com/jmartin4nrel/H2Integr…
kbrunik Jan 12, 2026
502aa8c
Testing example
jmartin4u Jan 13, 2026
8c79a30
Expanding unit tests
jmartin4u Jan 13, 2026
95c5e70
Merge branch 'develop' into ewin-merge
kbrunik Jan 13, 2026
d30a44a
fix import
kbrunik Jan 13, 2026
3992372
Docstrings begun
jmartin4u Jan 13, 2026
6845b13
Docstrings plus a little reorg
jmartin4u Jan 14, 2026
9229ed7
Everything but the docs page
jmartin4u Jan 14, 2026
bde4c2d
Finished documentation
jmartin4u Jan 14, 2026
d3af390
Split off humbert cost_model
jmartin4u Jan 14, 2026
4e51c3b
Config docstrings
jmartin4u Jan 14, 2026
504785a
Merge remote-tracking branch 'upstream/develop' into ewin-merge
jmartin4u Jan 14, 2026
821ba8b
Merge branch 'develop' into ewin-merge
johnjasa Jan 16, 2026
674dac1
Refactoring iron ewinning calcs for clarity
johnjasa Jan 16, 2026
c34f4e5
Addressing reviews
jmartin4u Jan 22, 2026
71ea58a
Merge branch 'develop' into ewin-merge
johnjasa Jan 24, 2026
a7e62c7
site -> sites
jmartin4u Jan 24, 2026
f4b24f5
Updating test values
jmartin4u Jan 26, 2026
c4cc271
Merge remote-tracking branch 'upstream/develop' into ewin-merge
jmartin4u Jan 27, 2026
09fa9a2
Merge branch 'develop' into ewin-merge
johnjasa Jan 27, 2026
98bb8c5
Added autodoc to the electrowinning docs
johnjasa Jan 27, 2026
fe7d704
Merge branch 'develop' into ewin-merge
johnjasa Jan 27, 2026
2a0d6d5
Merge remote-tracking branch 'upstream/develop' into ewin-merge
jmartin4u Jan 27, 2026
6464330
Merge branch 'develop' into ewin-merge
johnjasa Feb 2, 2026
85a11a7
Merge branch 'develop' into ewin-merge
kbrunik Feb 3, 2026
39b9ba7
merged in develop
elenya-grant Feb 3, 2026
89b1c5f
updated iron electrowinning tech config
elenya-grant Feb 3, 2026
4b43951
updated plant config for electrowinning example
elenya-grant Feb 3, 2026
71967dc
minor udpates to cost model
elenya-grant Feb 3, 2026
f8ef558
minor updates to cost and performance models
elenya-grant Feb 3, 2026
1544cbd
fixed initialization error in cost model
elenya-grant Feb 4, 2026
1c5a82a
removed duplicate costs and updated to use PerformanceModelBaseClass
elenya-grant Feb 4, 2026
9038c6e
updated example test values
elenya-grant Feb 4, 2026
1ea3e07
Merge branch 'develop' into ewin-merge
johnjasa Feb 4, 2026
61dced2
Addressing elenya comments
jmartin4u Feb 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/27_iron_electrowinning/27_iron_electrowinning.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: "H2Integrate_config"

system_summary: "This reference hybrid plant is located in Minnesota and contains wind, solar, and battery storage technologies. The system is designed to produce hydrogen using an electrolyzer and also produce steel using a grid-connected plant."

driver_config: "driver_config.yaml"
technology_config: "tech_config.yaml"
plant_config: "plant_config.yaml"
5 changes: 5 additions & 0 deletions examples/27_iron_electrowinning/driver_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: "driver_config"
description: "This analysis runs a hybrid plant to match the first example in H2Integrate"

general:
folder_output: outputs
76 changes: 76 additions & 0 deletions examples/27_iron_electrowinning/plant_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: "plant_config"
description: "This plant is located in MN, USA..."

site:
latitude: 41.717
longitude: -88.398

# 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],
}
]


# array of arrays containing left-to-right technology
# interconnections; can support bidirectional connections
# with the reverse definition.
# this will naturally grow as we mature the interconnected tech
technology_interconnections: [
# connect feedstocks to iron mine
["grid_feedstock","iron_mine","electricity","cable"],
["mine_feedstock","iron_mine","crude_ore","pipe"],
# connect feedstocks to iron plant
["iron_mine","iron_plant","iron_ore","iron_transport"],
["ewin_grid_feedstock","iron_plant","electricity","cable"],
]

plant:
plant_life: 30
finance_parameters:
finance_groups:
finance_model: "ProFastComp"
model_inputs:
params:
analysis_start_year: 2032
installation_time: 36 # months
inflation_rate: 0.0 # 0 for nominal analysis
discount_rate: 0.09 # nominal return based on 2024 ATB baseline workbook for land-based wind
debt_equity_ratio: 2.62 # 2024 ATB uses 72.4% debt for land-based wind
property_tax_and_insurance: 0.03 # percent of CAPEX estimated based on https://www.nrel.gov/docs/fy25osti/91775.pdf https://www.house.mn.gov/hrd/issinfo/clsrates.aspx
total_income_tax_rate: 0.257 # 0.257 tax rate in 2024 atb baseline workbook, value here is based on federal (21%) and state in MN (9.8)
capital_gains_tax_rate: 0.15 # H2FAST default
sales_tax_rate: 0.07375 # total state and local sales tax in St. Louis County https://taxmaps.state.mn.us/salestax/
debt_interest_rate: 0.07 # based on 2024 ATB nominal interest rate for land-based wind
debt_type: "Revolving debt" # can be "Revolving debt" or "One time loan". Revolving debt is H2FAST default and leads to much lower LCOH
loan_period_if_used: 0 # H2FAST default, not used for revolving debt
cash_onhand_months: 1 # H2FAST default
admin_expense: 0.00 # percent of sales H2FAST default
capital_items:
depr_type: "MACRS" # can be "MACRS" or "Straight line"
depr_period: 5 # 5 years - for clean energy facilities as specified by the IRS MACRS schedule https://www.irs.gov/publications/p946#en_US_2020_publink1000107507
refurb: [0.]
cost_adjustment_parameters:
cost_year_adjustment_inflation: 0.025
target_dollar_year: 2022
finance_subgroups:
iron_ore:
commodity: "iron_ore"
commodity_stream: "iron_mine"
technologies: ["iron_mine", "grid_feedstock", "mine_feedstock"]
sponge_iron:
commodity: "sponge_iron"
commodity_stream: "iron_plant"
technologies:
- "iron_mine"
- "grid_feedstock"
- "mine_feedstock"
- "iron_transport"
- "iron_plant"
- "ewin_grid_feedstock"
29 changes: 29 additions & 0 deletions examples/27_iron_electrowinning/run_iron.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from pathlib import Path

from h2integrate.tools.run_cases import modify_tech_config, load_tech_config_cases
from h2integrate.core.h2integrate_model import H2IntegrateModel


# Create H2Integrate model
model = H2IntegrateModel("27_iron_electrowinning.yaml")

# Load cases
case_file = Path("test_inputs.csv")
cases = load_tech_config_cases(case_file)

# Modify and run the model for different cases
casenames = [
"AHE",
"MSE",
"MOE",
]
lcois = []

for casename in casenames:
model = modify_tech_config(model, cases[casename])
model.run()
model.post_process()
lcois.append(float(model.model.get_val("finance_subgroup_sponge_iron.price_sponge_iron")[0]))

# Compare the LCOIs from each electrowinning type
print(lcois)
90 changes: 90 additions & 0 deletions examples/27_iron_electrowinning/tech_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: "technology_config"
description: "This hybrid plant produces iron"

technologies:
grid_feedstock: #electricity feedstock for iron ore
performance_model:
model: "feedstock_performance"
cost_model:
model: "feedstock_cost"
model_inputs:
shared_parameters:
feedstock_type: "electricity"
units: "MW"
performance_parameters:
rated_capacity: 30. # MW, need 27.913 MW per timestep for iron ore
cost_parameters:
cost_year: 2022
price: 58.02 #USD/MW
annual_cost: 0.
start_up_cost: 0.

mine_feedstock: #iron ore feedstock
performance_model:
model: "feedstock_performance"
cost_model:
model: "feedstock_cost"
model_inputs:
shared_parameters:
feedstock_type: "crude_ore"
units: "t/h"
performance_parameters:
rated_capacity: 2000. # need 828.50385048 t/h
cost_parameters:
cost_year: 2022
price: 0.0
annual_cost: 0.
start_up_cost: 0.

iron_mine:
performance_model:
model: "iron_mine_performance_martin"
cost_model:
model: "iron_mine_cost_martin"
model_inputs:
shared_parameters:
mine: "Northshore"
taconite_pellet_type: "drg"
max_ore_production_rate_tonnes_per_hr: 250

iron_transport:
performance_model:
model: "iron_transport_performance"
cost_model:
model: "iron_transport_cost"
model_inputs:
performance_parameters:
find_closest_ship_site: False
shipment_site: "Chicago"
cost_parameters:
transport_year: 2022
cost_year: 2022

ewin_grid_feedstock: #electricity feedstock for iron dri
performance_model:
model: "feedstock_performance"
cost_model:
model: "feedstock_cost"
model_inputs:
shared_parameters:
feedstock_type: "electricity"
units: "kW"
performance_parameters:
rated_capacity: 600000.
cost_parameters:
cost_year: 2022
price: 0.05802 #USD/kW
annual_cost: 0.
start_up_cost: 0.

iron_plant:
performance_model:
model: "humbert_electrowinning_performance"
cost_model:
model: "humbert_stinn_electrowinning_cost"
model_inputs:
shared_parameters:
electrolysis_type: "ahe"
performance_parameters:
ore_fe_wt_pct: 65
capacity_mw: 600
2 changes: 2 additions & 0 deletions examples/27_iron_electrowinning/test_inputs.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Index 0,Index 1,Index 2,Index 3,Index 4,Type,AHE,MSE,MOE
technologies,iron_plant,model_inputs,shared_parameters,electrolysis_type,str,ahe,mse,moe
105 changes: 105 additions & 0 deletions h2integrate/converters/iron/humbert_ewin_perf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import numpy as np
import openmdao.api as om
from attrs import field, define

from h2integrate.core.utilities import BaseConfig, merge_shared_inputs
from h2integrate.core.validators import contains


@define
class HumbertEwinConfig(BaseConfig):
electrolysis_type: str = field(
kw_only=True, converter=(str.lower, str.strip), validator=contains(["ahe", "mse", "moe"])
) # product selection
ore_fe_wt_pct: float = field(kw_only=True)
capacity_mw: float = field(kw_only=True)


class HumbertEwinPerformanceComponent(om.ExplicitComponent):
"""
Humbert: doi.org/10.1007/s40831-024-00878-3
"""

def initialize(self):
self.options.declare("driver_config", types=dict)
self.options.declare("plant_config", types=dict)
self.options.declare("tech_config", types=dict)

def setup(self):
n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]
self.config = HumbertEwinConfig.from_dict(
merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"),
strict=False,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why strict=False - this is normally only used (as far I I know) for dispatch/storage models because theres more than 2 models connected. But in this case, it appears that its only a performance and cost model, so strict=True would be appropriate

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I need to change to strict=True or remove the whole statement - will leave in your hands!

)

ewin_type = self.config.electrolysis_type
# Lookup specific energy consumption from Humbert Table 10
if ewin_type == "ahe":
spec_energy_cons_lo = 2.781
spec_energy_cons_hi = 3.779
elif ewin_type == "mse":
spec_energy_cons_lo = 2.720
spec_energy_cons_hi = 3.138
elif ewin_type == "moe":
spec_energy_cons_lo = 2.89
spec_energy_cons_hi = 4.45
spec_energy_cons_fe = (spec_energy_cons_lo + spec_energy_cons_hi) / 2 # kWh/kg_Fe

self.add_input("electricity_in", val=0.0, shape=n_timesteps, units="kW")
self.add_input("iron_ore_in", val=0.0, shape=n_timesteps, units="kg/h")
self.add_input("ore_fe_wt_pct", val=self.config.ore_fe_wt_pct, units="percent")
self.add_input("spec_energy_cons_fe", val=spec_energy_cons_fe, units="kW*h/kg")
self.add_input("capacity", val=self.config.capacity_mw, units="MW")

self.add_output(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you add iron_ore_consumed as an output here and then calculate it in the compute() method?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding

"electricity_consumed",
val=0.0,
shape=n_timesteps,
units="kW",
desc="Electricity consumed",
)
self.add_output("limiting_input", val=0.0, shape=n_timesteps, units=None)
self.add_output("sponge_iron_out", val=0.0, shape=n_timesteps, units="kg/h")
self.add_output("total_sponge_iron_produced", val=0.0, units="kg/year")
self.add_output("output_capacity", val=0.0, units="kg/year")

def compute(self, inputs, outputs):
n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]

# Parse inputs
elec_in = inputs["electricity_in"]
ore_in = inputs["iron_ore_in"]
pct_fe = inputs["ore_fe_wt_pct"]
kwh_kg_fe = inputs["spec_energy_cons_fe"]
cap_kw = inputs["capacity"] * 1000

# If no connected input, set ore / electricity to max needed
if self.get_source("electricity_in")[:9] == "_auto_ivc":
elec_in = np.full(n_timesteps, cap_kw)
if self.get_source("iron_ore_in")[:9] == "_auto_ivc":
ore_in = np.full(n_timesteps, cap_kw / kwh_kg_fe / pct_fe * 100)

# Calculate max iron production for each input
fe_from_ore = ore_in * pct_fe / 100
fe_from_elec = elec_in / kwh_kg_fe

# Limiting iron production per hour by each input
fe_prod = np.minimum.reduce([fe_from_ore, fe_from_elec])
limiters = np.argmin([fe_from_ore, fe_from_elec], axis=0)

# Limiting iron production per hour by capacity
fe_prod = np.minimum.reduce([fe_prod, np.full(len(fe_prod), cap_kw / kwh_kg_fe)])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fe_prod = np.minimum.reduce([fe_prod, np.full(len(fe_prod), cap_kw / kwh_kg_fe)])
fe_prod = np.minimum(fe_prod, cap_kw / kwh_kg_fe)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this keep the output an array if fe_prod is an array but cap_kw / kwh_kg_fe is a scalar?

cap_lim = 1 - np.argmax([fe_prod, np.full(len(fe_prod), cap_kw / kwh_kg_fe)], axis=0)

# Determine what the limiting factor is for each hour
limiters = np.maximum.reduce([cap_lim * 2, limiters])
outputs["limiting_input"] = limiters

# Determine actual electricity consumption from iron consumption
elec_consume = fe_prod * kwh_kg_fe

# Return iron production
outputs["sponge_iron_out"] = fe_prod
outputs["electricity_consumed"] = elec_consume
outputs["total_sponge_iron_produced"] = np.sum(fe_prod)
outputs["output_capacity"] = cap_kw / kwh_kg_fe * 8760
Loading
Loading