From 26a6ec6921c559966efa080d67e63055b670e7de Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Thu, 6 Nov 2025 15:19:06 -0700 Subject: [PATCH 01/29] Reading in Stinn input tables --- .../technologies/iron/stinn/cost_model.py | 60 ++++++++++++------- .../technologies/iron/stinn/table1.csv | 43 +++++++++++++ .../technologies/iron/stinn/table1.txt | 43 +++++++++++++ 3 files changed, 124 insertions(+), 22 deletions(-) create mode 100644 h2integrate/simulation/technologies/iron/stinn/table1.csv create mode 100644 h2integrate/simulation/technologies/iron/stinn/table1.txt diff --git a/h2integrate/simulation/technologies/iron/stinn/cost_model.py b/h2integrate/simulation/technologies/iron/stinn/cost_model.py index 65d6a2b9d..77c6ae6b6 100644 --- a/h2integrate/simulation/technologies/iron/stinn/cost_model.py +++ b/h2integrate/simulation/technologies/iron/stinn/cost_model.py @@ -16,6 +16,22 @@ CD = Path(__file__).parent +def capex_calc(a1n, a1d, a1t, a2n, a2d, a2t, a3, T, P, p, z, F, j, A, e, M, Q, V, N): + # Pre-costs calculation + term1 = a1n / (1 + np.exp(a1d * (T - a1t))) + term2 = a2n / (1 + np.exp(a2d * (T - a2t))) + + pre_costs = term1 * P**0.8 + + # Electrolysis and product handling contribution to total cost + electrolysis_product_handling = term2 * ((p * z * F) / (j * A * e * M)) ** 0.9 + + # Power rectifying contribution + power_rectifying_contribution = a3 * Q * V**0.15 * N**0.5 + + return pre_costs, electrolysis_product_handling, power_rectifying_contribution + + def main(config): """ Calculates the total direct capital cost of an electrowinning system in 2018 US dollars. @@ -50,23 +66,31 @@ def main(config): and power rectification. total_costs (float): Sum of pre-costs and electrowinning costs. """ + # Load inputs + inputs_fp = CD / config.cost_model["inputs_fp"] + inputs_df = pd.read_csv(inputs_fp) + inputs_df = inputs_df.set_index("Metal") + + # Parse sodium inputs + inputs_df.loc["Na"] + # Load coefficients coeffs_fp = CD / config.cost_model["coeffs_fp"] coeffs_df = pd.read_csv(coeffs_fp) coeffs = coeffs_df.set_index("Name")["Coeff"].to_dict() # Extract coefficients - alpha_1_numerator = coeffs["alpha_1_numerator"] - alpha_1_denominator = coeffs["alpha_1_denominator"] - alpha_1_temp_offset = coeffs["alpha_1_temp_offset"] - alpha_2_numerator = coeffs["alpha_2_numerator"] - alpha_2_denominator = coeffs["alpha_2_denominator"] - alpha_2_temp_offset = coeffs["alpha_2_temp_offset"] - alpha_3 = coeffs["alpha_3"] + a1n = coeffs["alpha_1_numerator"] + a1d = coeffs["alpha_1_denominator"] + a1t = coeffs["alpha_1_temp_offset"] + a2n = coeffs["alpha_2_numerator"] + a2d = coeffs["alpha_2_denominator"] + a2t = coeffs["alpha_2_temp_offset"] + a3 = coeffs["alpha_3"] # Assign inputs from config T = config.electrolysis_temp # Electrolysis temperature (°C) - P = config.pressure # Pressure (assumed unit) + P = config.capacity # installed capacity (kt/y) p = config.production_rate # Production rate (kg/s) z = config.electron_moles # Moles of electrons per mole of product F = config.faraday_const # Electric charge per mole of electrons (C/mol) @@ -78,20 +102,12 @@ def main(config): V = config.cell_voltage # Cell operating voltage (V) N = config.rectifier_lines # Number of rectifier lines - # Pre-costs calculation - term1 = alpha_1_numerator / (1 + np.exp(alpha_1_denominator * (T - alpha_1_temp_offset))) - term2 = alpha_2_numerator / (1 + np.exp(alpha_2_denominator * (T - alpha_2_temp_offset))) - - pre_costs = term1 * P**0.8 - - # Electrolysis and product handling contribution to total cost - electrolysis_product_handling = ((p * z * F) / (j * A * e * M)) ** 0.9 - - # Power rectifying contribution - power_rectifying_contribution = alpha_3 * Q * V**0.15 * N**0.5 + pre_costs, electrolysis_product_handling, power_rectifying_contribution = capex_calc( + a1n, a1d, a1t, a2n, a2d, a2t, a3, T, P, p, z, F, j, A, e, M, Q, V, N + ) # Electrowinning costs - electrowinning_costs = term2 * electrolysis_product_handling + power_rectifying_contribution + electrowinning_costs = electrolysis_product_handling + power_rectifying_contribution # Return individual costs for modularity return { @@ -105,10 +121,10 @@ def main(config): class Config: def __init__(self): - self.cost_model = {"coeffs_fp": "cost_coeffs.csv"} + self.cost_model = {"coeffs_fp": "cost_coeffs.csv", "inputs_fp": "table1.csv"} # Example values for each variable (replace with actual values) self.electrolysis_temp = 1000 # Temperature in °C, example value - self.pressure = 1.5 # Pressure, example value + self.capacity = 1.5 # Pressure, example value self.production_rate = 1.0 # Total production rate, kg/s self.electron_moles = 3 # Moles of electrons per mole of product, example value self.faraday_const = ( diff --git a/h2integrate/simulation/technologies/iron/stinn/table1.csv b/h2integrate/simulation/technologies/iron/stinn/table1.csv new file mode 100644 index 000000000..d5adcf03c --- /dev/null +++ b/h2integrate/simulation/technologies/iron/stinn/table1.csv @@ -0,0 +1,43 @@ +Metal,Capacity [kt/y],Capex [Year USD],Capex/ Capacity [year USD/ (kt/y)],Year,Year CEI,2018 CEI,Capex [2018 USD],Capex/ Capacity [2018 USD/ (kt/y)],Cell Count,Temperature [C],Current Density [A/m^2],Current efficiency,Operating potential [V],Specific energy [kWh/kg],Electrode area/ cell [m^2],Current / cell [kA],Power / cell [MW],Electrons per product,Product molar mass [kg],Yearly productivity / cell [kt/y],Notes +Fe AHE,0.181074198,"$1,077 ","$1,077 ",2018,607,607,"$1,077 ","$1,077 ",1,80,1000,0.9,3.2,4.644283995,30,30,0.096,3,0.055845,0.181074198,Humbert Cost Kempler Operating Conditions +Fe MSE,7.41,"$10,947 ","$10,947 ",2018,607,607,"$10,947 ","$10,947 ",1,900,6000,0.9,1.79,0.635,30,300,0.537,3,0.055845,7.41,Humbert +Fe MOE,0.587,"$4,902 ","$4,902 ",2018,607,607,"$4,902 ","$4,902 ",1,1600,10000,0.9,0.82,3.67,30,300,0.246,2,0.055845,0.587,Humbert +Al,415,"$2,937,550,460 ","$7,070 ",2008,576,607,"$3,095,647,794 ","$7,451 ",517,1000,10000,0.95,4.18,16.5,30,300,1.254,3,0.027,0.80270793,1 line Rectifier +Al,415,"$2,920,930,656 ","$7,030 ",2008,576,607,"$3,078,133,521 ","$7,408 ",517,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,1 line Rectifier +Al,588,"$3,691,655,708 ","$6,275 ",2008,576,607,"$3,890,338,568 ","$6,613 ",732,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,1 line Rectifier +Al,588,"$3,753,428,433 ","$6,380 ",2008,576,607,"$3,955,435,866 ","$6,723 ",732,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,1 line Rectifier +Al,735,"$4,894,017,677 ","$6,655 ",2008,576,607,"$5,157,410,990 ","$7,013 ",915,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,1 line Rectifier +Al,735,"$4,360,860,229 ","$5,930 ",2008,576,607,"$4,595,559,304 ","$6,249 ",915,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,2 line Rectifier +Al,882,"$4,994,766,050 ","$5,660 ",2008,576,607,"$5,263,581,584 ","$5,965 ",1098,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,2 line Rectifier +Al,882,"$5,599,256,288 ","$6,345 ",2008,576,607,"$5,900,605,150 ","$6,686 ",1098,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,1 line Rectifier +Al,1177,"$6,712,636,117 ","$5,705 ",2008,576,607,"$7,073,906,463 ","$6,012 ",1465,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,2 line Rectifier +Al,1177,"$6,518,493,267 ","$5,540 ",2008,576,607,"$6,869,314,953 ","$5,838 ",1465,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,2 line Rectifier +Al,1691,"$8,676,861,694 ","$5,130 ",2008,576,607,"$9,143,845,570 ","$5,406 ",2105,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,2 line Rectifier +Al,500,"$3,250,000,000 ","$6,500 ",2001,394,607,"$5,006,979,695 ","$10,014 ",622,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,2 line Rectifier +Al,353,"$1,899,846,000 ","$5,382 ",2008,576,608,"$2,005,393,000 ","$5,681 ",439,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,1 line Rectifier +Mg,50,"$400,000,000 ","$8,000 ",2001,394,607,"$616,243,655 ","$12,325 ",41,750,6000,0.9,6,,60,360,2.16,2,0.024,1.23,IG Cell +Mg,109,"$359,700,000 ","$3,300 ",1979,239,607,"$913,547,699 ","$8,381 ",88,750,6000,0.9,6,,60,360,2.16,2,0.024,1.23,IG Cell +Mg,22,"$72,600,000 ","$3,300 ",1979,239,607,"$184,385,774 ","$8,381 ",18,750,6000,0.9,6,,60,360,2.16,2,0.024,1.23,IG Cell +Mg,4.5,"$14,850,000 ","$3,300 ",1979,239,607,"$37,715,272 ","$8,381 ",4,750,6000,0.9,6,,60,360,2.16,2,0.024,1.23,IG Cell +Mg,22.5,"$74,250,000 ","$3,300 ",1979,239,607,"$188,576,360 ","$8,381 ",18,750,6000,0.9,6,,60,360,2.16,2,0.024,1.23,IG Cell +Na,50,"$82,500,000 ","$1,650 ",1979,239,607,"$209,529,289 ","$4,191 ",14,600,10000,0.85,5.7,,60,600,3.42,1,0.023,3.67,Downs Cell +Zn,100,"$250,000,000 ","$2,500 ",2006,500,607,"$303,500,000 ","$3,035 ",766,50,300,0.85,3.5,,50,15,0.053,2,0.065,0.13, +Zn,100,"$60,000,000 ",$600 ,1974,164.4,607,"$221,532,847 ","$2,215 ",766,50,300,0.85,3.5,,50,15,0.053,2,0.065,0.13, +Zn,120,"$508,500,000 ","$4,238 ",2011,590,607,"$523,151,695 ","$4,360 ",919,50,300,0.85,3.5,,50,15,0.053,2,0.065,0.13, +Cu,1.1,"$2,600,000 ","$2,398 ",1980,261,607,"$6,046,743 ","$5,577 ",15,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW +Cu,1.7,"$3,700,000 ","$2,239 ",1980,261,607,"$8,604,981 ","$5,207 ",23,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW +Cu,5,"$12,400,000 ","$2,481 ",1980,261,607,"$28,838,314 ","$5,770 ",70,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW +Cu,4.6,"$9,800,000 ","$2,140 ",1980,261,607,"$22,791,571 ","$4,977 ",64,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW +Cu,15.1,"$26,000,000 ","$1,727 ",1980,261,607,"$60,467,433 ","$4,016 ",210,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW +Cu,22.7,"$32,700,000 ","$1,441 ",1980,261,607,"$76,049,425 ","$3,351 ",316,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW +Cu,21.2,"$33,500,000 ","$1,579 ",1980,261,607,"$77,909,962 ","$3,672 ",296,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW +Cu,27,"$100,000,000 ","$3,703 ",1998,390,607,"$155,641,026 ","$5,764 ",377,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW +Cu,1.3,"$3,000,000 ","$2,253 ",1980,390,607,"$4,669,231 ","$3,507 ",19,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW +Cu,14.6,"$25,300,000 ","$1,728 ",1980,390,607,"$39,377,179 ","$2,689 ",204,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW +Cu,50,"$268,500,000 ","$5,370 ",2014,580,607,"$280,999,138 ","$5,620 ",697,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW +Cu,60,"$298,000,000 ","$4,966 ",2007,530,607,"$341,294,340 ","$5,688 ",837,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW +Cl2,200,"$106,000,000 ",$530 ,1980,261,607,"$246,521,073 ","$1,233 ",126,90,2700,0.96,3.79,,55,149,0.563,2,0.071,1.59,diaphragm +Cl2,200,"$111,500,000 ",$558 ,1980,261,607,"$259,312,261 ","$1,297 ",126,90,2700,0.96,3.79,,55,149,0.563,2,0.071,1.59,membrane +Cl2,200,"$112,800,000 ",$564 ,1980,261,607,"$262,335,632 ","$1,312 ",126,90,2700,0.96,3.79,,55,149,0.563,2,0.071,1.59,membrane + inert anodes +Cl2,166,"$111,000,000 ",$669 ,1990,358,607,"$188,203,911 ","$1,134 ",105,90,2700,0.96,3.79,,55,149,0.563,2,0.071,1.59, +Cl2,880,"$194,868,534 ",$221 ,1980,261,607,"$453,200,000 ",$515 ,554,90,2700,0.96,3.79,,55,149,0.563,2,0.071,1.59, diff --git a/h2integrate/simulation/technologies/iron/stinn/table1.txt b/h2integrate/simulation/technologies/iron/stinn/table1.txt new file mode 100644 index 000000000..62cea5061 --- /dev/null +++ b/h2integrate/simulation/technologies/iron/stinn/table1.txt @@ -0,0 +1,43 @@ + Capacity [kt/y] Capex [Year USD] Capex/ Capacity [year USD/ (kt/y)] Year Year CEI 2018 CEI Capex [2018 USD] Capex/ Capacity [2018 USD/ (kt/y)] Cell Count Temperature [C] Current Density [A/m^2] Current efficiency Operating potential [V] Specific energy [kWh/kg] Electrode area/ cell [m^2] Current / cell [kA] Power / cell [MW] Electrons per product Product molar mass [kg] Yearly productivity / cell [kt/y] Notes +Fe AHE "$1,077 " "$1,077 " 2018 607 607 "$1,077 " "$1,077 " 0.055845 Humbert +Fe MSE 7.41 "$10,947 " "$10,947 " 2018 607 607 "$10,947 " "$10,947 " 1 900 10000 0.9 1.79 0.635 30 300 0.537 3 0.055845 7.41 Humbert +Fe MOE 0.587 "$4,902 " "$4,902 " 2018 607 607 "$4,902 " "$4,902 " 1 1600 10000 0.9 0.82 3.67 30 300 0.246 2 0.055845 0.587 Humbert +Al 415 "$2,937,550,460 " "$7,070 " 2008 576 607 "$3,095,647,794 " "$7,451 " 517 1000 10000 0.95 4.18 16.5 30 300 1.254 3 0.027 0.80270793 1 line Rectifier + 415 "$2,920,930,656 " "$7,030 " 2008 576 607 "$3,078,133,521 " "$7,408 " 517 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 1 line Rectifier + 588 "$3,691,655,708 " "$6,275 " 2008 576 607 "$3,890,338,568 " "$6,613 " 732 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 1 line Rectifier + 588 "$3,753,428,433 " "$6,380 " 2008 576 607 "$3,955,435,866 " "$6,723 " 732 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 1 line Rectifier + 735 "$4,894,017,677 " "$6,655 " 2008 576 607 "$5,157,410,990 " "$7,013 " 915 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 1 line Rectifier + 735 "$4,360,860,229 " "$5,930 " 2008 576 607 "$4,595,559,304 " "$6,249 " 915 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 2 line Rectifier + 882 "$4,994,766,050 " "$5,660 " 2008 576 607 "$5,263,581,584 " "$5,965 " 1098 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 2 line Rectifier + 882 "$5,599,256,288 " "$6,345 " 2008 576 607 "$5,900,605,150 " "$6,686 " 1098 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 1 line Rectifier + 1177 "$6,712,636,117 " "$5,705 " 2008 576 607 "$7,073,906,463 " "$6,012 " 1465 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 2 line Rectifier + 1177 "$6,518,493,267 " "$5,540 " 2008 576 607 "$6,869,314,953 " "$5,838 " 1465 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 2 line Rectifier + 1691 "$8,676,861,694 " "$5,130 " 2008 576 607 "$9,143,845,570 " "$5,406 " 2105 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 2 line Rectifier + 500 "$3,250,000,000 " "$6,500 " 2001 394 607 "$5,006,979,695 " "$10,014 " 622 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 2 line Rectifier + 353 "$1,899,846,000 " "$5,382 " 2008 576 608 "$2,005,393,000 " "$5,681 " 439 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 1 line Rectifier +Mg 50 "$400,000,000 " "$8,000 " 2001 394 607 "$616,243,655 " "$12,325 " 41 750 6000 0.9 6 60 360 2.16 2 0.024 1.23 IG Cell + 109 "$359,700,000 " "$3,300 " 1979 239 607 "$913,547,699 " "$8,381 " 88 750 6000 0.9 6 60 360 2.16 2 0.024 1.23 IG Cell + 22 "$72,600,000 " "$3,300 " 1979 239 607 "$184,385,774 " "$8,381 " 18 750 6000 0.9 6 60 360 2.16 2 0.024 1.23 IG Cell + 4.5 "$14,850,000 " "$3,300 " 1979 239 607 "$37,715,272 " "$8,381 " 4 750 6000 0.9 6 60 360 2.16 2 0.024 1.23 IG Cell + 22.5 "$74,250,000 " "$3,300 " 1979 239 607 "$188,576,360 " "$8,381 " 18 750 6000 0.9 6 60 360 2.16 2 0.024 1.23 IG Cell +Na 50 "$82,500,000 " "$1,650 " 1979 239 607 "$209,529,289 " "$4,191 " 14 600 10000 0.85 5.7 60 600 3.42 1 0.023 3.67 Downs Cell +Zn 100 "$250,000,000 " "$2,500 " 2006 500 607 "$303,500,000 " "$3,035 " 766 50 300 0.85 3.5 50 15 0.053 2 0.065 0.13 + 100 "$60,000,000 " $600 1974 164.4 607 "$221,532,847 " "$2,215 " 766 50 300 0.85 3.5 50 15 0.053 2 0.065 0.13 + 120 "$508,500,000 " "$4,238 " 2011 590 607 "$523,151,695 " "$4,360 " 919 50 300 0.85 3.5 50 15 0.053 2 0.065 0.13 +Cu 1.1 "$2,600,000 " "$2,398 " 1980 261 607 "$6,046,743 " "$5,577 " 15 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW + 1.7 "$3,700,000 " "$2,239 " 1980 261 607 "$8,604,981 " "$5,207 " 23 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW + 5 "$12,400,000 " "$2,481 " 1980 261 607 "$28,838,314 " "$5,770 " 70 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW + 4.6 "$9,800,000 " "$2,140 " 1980 261 607 "$22,791,571 " "$4,977 " 64 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW + 15.1 "$26,000,000 " "$1,727 " 1980 261 607 "$60,467,433 " "$4,016 " 210 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW + 22.7 "$32,700,000 " "$1,441 " 1980 261 607 "$76,049,425 " "$3,351 " 316 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW + 21.2 "$33,500,000 " "$1,579 " 1980 261 607 "$77,909,962 " "$3,672 " 296 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW + 27 "$100,000,000 " "$3,703 " 1998 390 607 "$155,641,026 " "$5,764 " 377 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW + 1.3 "$3,000,000 " "$2,253 " 1980 390 607 "$4,669,231 " "$3,507 " 19 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW + 14.6 "$25,300,000 " "$1,728 " 1980 390 607 "$39,377,179 " "$2,689 " 204 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW + 50 "$268,500,000 " "$5,370 " 2014 580 607 "$280,999,138 " "$5,620 " 697 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW + 60 "$298,000,000 " "$4,966 " 2007 530 607 "$341,294,340 " "$5,688 " 837 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW +Cl2 200 "$106,000,000 " $530 1980 261 607 "$246,521,073 " "$1,233 " 126 90 2700 0.96 3.79 55 149 0.563 2 0.071 1.59 diaphragm + 200 "$111,500,000 " $558 1980 261 607 "$259,312,261 " "$1,297 " 126 90 2700 0.96 3.79 55 149 0.563 2 0.071 1.59 membrane + 200 "$112,800,000 " $564 1980 261 607 "$262,335,632 " "$1,312 " 126 90 2700 0.96 3.79 55 149 0.563 2 0.071 1.59 membrane + inert anodes + 166 "$111,000,000 " $669 1990 358 607 "$188,203,911 " "$1,134 " 105 90 2700 0.96 3.79 55 149 0.563 2 0.071 1.59 + 880 "$194,868,534 " $221 1980 261 607 "$453,200,000 " $515 554 90 2700 0.96 3.79 55 149 0.563 2 0.071 1.59 From 3a803fd8b4a38ef06d2305cbb75891c2bd4fa404 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Fri, 7 Nov 2025 15:49:22 -0700 Subject: [PATCH 02/29] Sucessfully testing Stinn model --- .../technologies/iron/stinn/cost_coeffs.csv | 12 ++- .../technologies/iron/stinn/cost_model.py | 88 +++++++++++++++---- .../technologies/iron/stinn/table1.csv | 86 +++++++++--------- 3 files changed, 121 insertions(+), 65 deletions(-) diff --git a/h2integrate/simulation/technologies/iron/stinn/cost_coeffs.csv b/h2integrate/simulation/technologies/iron/stinn/cost_coeffs.csv index d63844b77..60ba31aee 100644 --- a/h2integrate/simulation/technologies/iron/stinn/cost_coeffs.csv +++ b/h2integrate/simulation/technologies/iron/stinn/cost_coeffs.csv @@ -1,8 +1,12 @@ Name,Type,Coeff,Unit -alpha_1_numerator,capital,51010.,- -alpha_1_denominator,capital,-3.823e-3,- +alpha_1_numerator,capital,51010,- +alpha_1_denominator,capital,-3.82E-03,- alpha_1_temp_offset,capital,631,- -alpha_2_numerator,capital,5634000.,- -alpha_2_denominator,capital,-7.813e-3,- +alpha_2_numerator,capital,5634000,- +alpha_2_denominator,capital,-7.81E-03,- alpha_2_temp_offset,capital,349,- alpha_3,capital,750000,- +exp_1,capital,0.8, +exp_2,capital,0.9, +exp_3,capital,0.15, +exp_4,capital,0.5, diff --git a/h2integrate/simulation/technologies/iron/stinn/cost_model.py b/h2integrate/simulation/technologies/iron/stinn/cost_model.py index 77c6ae6b6..ab0967be0 100644 --- a/h2integrate/simulation/technologies/iron/stinn/cost_model.py +++ b/h2integrate/simulation/technologies/iron/stinn/cost_model.py @@ -11,25 +11,31 @@ import numpy as np import pandas as pd +import matplotlib.pyplot as plt CD = Path(__file__).parent +faraday_const = 96485.3321 # Electric charge per mole of electrons (Faraday constant), C/mol -def capex_calc(a1n, a1d, a1t, a2n, a2d, a2t, a3, T, P, p, z, F, j, A, e, M, Q, V, N): + +def capex_calc( + a1n, a1d, a1t, a2n, a2d, a2t, a3n, e1, e2, e3, e4, T, P, p, z, F, j, A, e, M, Q, V, N +): # Pre-costs calculation - term1 = a1n / (1 + np.exp(a1d * (T - a1t))) - term2 = a2n / (1 + np.exp(a2d * (T - a2t))) + a1 = a1n / (1 + np.exp(a1d * (T - a1t))) + a2 = a2n / (1 + np.exp(a2d * (T - a2t))) + a3 = a3n * Q - pre_costs = term1 * P**0.8 + pre_costs = a1 * P**e1 # Electrolysis and product handling contribution to total cost - electrolysis_product_handling = term2 * ((p * z * F) / (j * A * e * M)) ** 0.9 + electrolysis_product_handling = a2 * ((p * z * F) / (j * A * e * M)) ** e2 # Power rectifying contribution - power_rectifying_contribution = a3 * Q * V**0.15 * N**0.5 + power_rectifying_contribution = a3 * V**e3 * N**e4 - return pre_costs, electrolysis_product_handling, power_rectifying_contribution + return pre_costs, electrolysis_product_handling, power_rectifying_contribution, a1, a2, a3 def main(config): @@ -71,9 +77,6 @@ def main(config): inputs_df = pd.read_csv(inputs_fp) inputs_df = inputs_df.set_index("Metal") - # Parse sodium inputs - inputs_df.loc["Na"] - # Load coefficients coeffs_fp = CD / config.cost_model["coeffs_fp"] coeffs_df = pd.read_csv(coeffs_fp) @@ -86,14 +89,58 @@ def main(config): a2n = coeffs["alpha_2_numerator"] a2d = coeffs["alpha_2_denominator"] a2t = coeffs["alpha_2_temp_offset"] - a3 = coeffs["alpha_3"] + a3n = coeffs["alpha_3"] + e1 = coeffs["exp_1"] + e2 = coeffs["exp_2"] + e3 = coeffs["exp_3"] + e4 = coeffs["exp_4"] + + metal_dict = { + "Fe": [0.5, 0, 0], + "Al": [0, 0, 0], + "Mg": [0, 0.5, 1], + "Na": [0, 1, 0], + "Zn": [0, 1, 1], + "Cu": [0, 0.5, 0], + "Cl2": [0, 0, 0.5], + } + + # Cycle through metals + for metal, color in metal_dict.items(): + metal_df = inputs_df.loc[[metal]] + cap = metal_df["Capacity [kt/y]"].values + capex_per_cap = metal_df["Capex/ Capacity [2018 USD/ (t/y)]"].values + plt.plot(cap, capex_per_cap, ".", color=color) + + T = metal_df["Temperature [C]"].values + P = cap * 1000 + p = P * 1000 / 8760 / 3600 + z = metal_df["Electrons per product"].values + f = faraday_const + j = metal_df["Current Density [A/m^2]"].values + A = metal_df["Electrode area/ cell [m^2]"].values + e = metal_df["Current efficiency"].values + M = metal_df["Product molar mass [kg]"].values + Q = metal_df["Power / cell [MW]"].values + V = metal_df["Operating potential [V]"].values + N = metal_df["Cell Count"].values + Q = Q * N + + F, E, R, a1, a2, a3 = capex_calc( + a1n, a1d, a1t, a2n, a2d, a2t, a3n, e1, e2, e3, e4, T, P, p, z, f, j, A, e, M, Q, V, 1 + ) + + plt.plot(cap, (F + E + R) / P, "-", color=color) + # plt.plot(T, a1/10**4, '.', color=metal_dict["Al"]) + # plt.plot(T, a2/10**6, '.', color=metal_dict["Cl2"]) + # plt.plot(T, a3/10**6, '.', color=metal_dict["Cu"]) # Assign inputs from config T = config.electrolysis_temp # Electrolysis temperature (°C) - P = config.capacity # installed capacity (kt/y) + P = config.capacity # installed capacity (t/y) p = config.production_rate # Production rate (kg/s) z = config.electron_moles # Moles of electrons per mole of product - F = config.faraday_const # Electric charge per mole of electrons (C/mol) + f = faraday_const # Electric charge per mole of electrons (C/mol) j = config.current_density # Current density (A/m²) A = config.electrode_area # Electrode area (m²) e = config.current_efficiency # Current efficiency (dimensionless) @@ -102,13 +149,21 @@ def main(config): V = config.cell_voltage # Cell operating voltage (V) N = config.rectifier_lines # Number of rectifier lines - pre_costs, electrolysis_product_handling, power_rectifying_contribution = capex_calc( - a1n, a1d, a1t, a2n, a2d, a2t, a3, T, P, p, z, F, j, A, e, M, Q, V, N + pre_costs, electrolysis_product_handling, power_rectifying_contribution, a1, a2, a3 = ( + capex_calc( + a1n, a1d, a1t, a2n, a2d, a2t, a3, e1, e2, e3, e4, T, P, p, z, f, j, A, e, M, Q, V, N + ) ) # Electrowinning costs electrowinning_costs = electrolysis_product_handling + power_rectifying_contribution + # Total costs + pre_costs + electrowinning_costs + + plt.xscale("log") + plt.show() + # Return individual costs for modularity return { "pre_costs": pre_costs, @@ -127,9 +182,6 @@ def __init__(self): self.capacity = 1.5 # Pressure, example value self.production_rate = 1.0 # Total production rate, kg/s self.electron_moles = 3 # Moles of electrons per mole of product, example value - self.faraday_const = ( - 96485 # Electric charge per mole of electrons (Faraday constant), C/mol - ) self.current_density = 5000 # Current density, A/m², example value self.electrode_area = 30.0 # Electrode area, m², example value self.current_efficiency = 0.95 # Current efficiency (dimensionless), example value diff --git a/h2integrate/simulation/technologies/iron/stinn/table1.csv b/h2integrate/simulation/technologies/iron/stinn/table1.csv index d5adcf03c..24c13ae84 100644 --- a/h2integrate/simulation/technologies/iron/stinn/table1.csv +++ b/h2integrate/simulation/technologies/iron/stinn/table1.csv @@ -1,43 +1,43 @@ -Metal,Capacity [kt/y],Capex [Year USD],Capex/ Capacity [year USD/ (kt/y)],Year,Year CEI,2018 CEI,Capex [2018 USD],Capex/ Capacity [2018 USD/ (kt/y)],Cell Count,Temperature [C],Current Density [A/m^2],Current efficiency,Operating potential [V],Specific energy [kWh/kg],Electrode area/ cell [m^2],Current / cell [kA],Power / cell [MW],Electrons per product,Product molar mass [kg],Yearly productivity / cell [kt/y],Notes -Fe AHE,0.181074198,"$1,077 ","$1,077 ",2018,607,607,"$1,077 ","$1,077 ",1,80,1000,0.9,3.2,4.644283995,30,30,0.096,3,0.055845,0.181074198,Humbert Cost Kempler Operating Conditions -Fe MSE,7.41,"$10,947 ","$10,947 ",2018,607,607,"$10,947 ","$10,947 ",1,900,6000,0.9,1.79,0.635,30,300,0.537,3,0.055845,7.41,Humbert -Fe MOE,0.587,"$4,902 ","$4,902 ",2018,607,607,"$4,902 ","$4,902 ",1,1600,10000,0.9,0.82,3.67,30,300,0.246,2,0.055845,0.587,Humbert -Al,415,"$2,937,550,460 ","$7,070 ",2008,576,607,"$3,095,647,794 ","$7,451 ",517,1000,10000,0.95,4.18,16.5,30,300,1.254,3,0.027,0.80270793,1 line Rectifier -Al,415,"$2,920,930,656 ","$7,030 ",2008,576,607,"$3,078,133,521 ","$7,408 ",517,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,1 line Rectifier -Al,588,"$3,691,655,708 ","$6,275 ",2008,576,607,"$3,890,338,568 ","$6,613 ",732,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,1 line Rectifier -Al,588,"$3,753,428,433 ","$6,380 ",2008,576,607,"$3,955,435,866 ","$6,723 ",732,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,1 line Rectifier -Al,735,"$4,894,017,677 ","$6,655 ",2008,576,607,"$5,157,410,990 ","$7,013 ",915,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,1 line Rectifier -Al,735,"$4,360,860,229 ","$5,930 ",2008,576,607,"$4,595,559,304 ","$6,249 ",915,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,2 line Rectifier -Al,882,"$4,994,766,050 ","$5,660 ",2008,576,607,"$5,263,581,584 ","$5,965 ",1098,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,2 line Rectifier -Al,882,"$5,599,256,288 ","$6,345 ",2008,576,607,"$5,900,605,150 ","$6,686 ",1098,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,1 line Rectifier -Al,1177,"$6,712,636,117 ","$5,705 ",2008,576,607,"$7,073,906,463 ","$6,012 ",1465,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,2 line Rectifier -Al,1177,"$6,518,493,267 ","$5,540 ",2008,576,607,"$6,869,314,953 ","$5,838 ",1465,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,2 line Rectifier -Al,1691,"$8,676,861,694 ","$5,130 ",2008,576,607,"$9,143,845,570 ","$5,406 ",2105,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,2 line Rectifier -Al,500,"$3,250,000,000 ","$6,500 ",2001,394,607,"$5,006,979,695 ","$10,014 ",622,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,2 line Rectifier -Al,353,"$1,899,846,000 ","$5,382 ",2008,576,608,"$2,005,393,000 ","$5,681 ",439,1000,10000,0.95,4.18,,30,300,1.25,3,0.027,0.85,1 line Rectifier -Mg,50,"$400,000,000 ","$8,000 ",2001,394,607,"$616,243,655 ","$12,325 ",41,750,6000,0.9,6,,60,360,2.16,2,0.024,1.23,IG Cell -Mg,109,"$359,700,000 ","$3,300 ",1979,239,607,"$913,547,699 ","$8,381 ",88,750,6000,0.9,6,,60,360,2.16,2,0.024,1.23,IG Cell -Mg,22,"$72,600,000 ","$3,300 ",1979,239,607,"$184,385,774 ","$8,381 ",18,750,6000,0.9,6,,60,360,2.16,2,0.024,1.23,IG Cell -Mg,4.5,"$14,850,000 ","$3,300 ",1979,239,607,"$37,715,272 ","$8,381 ",4,750,6000,0.9,6,,60,360,2.16,2,0.024,1.23,IG Cell -Mg,22.5,"$74,250,000 ","$3,300 ",1979,239,607,"$188,576,360 ","$8,381 ",18,750,6000,0.9,6,,60,360,2.16,2,0.024,1.23,IG Cell -Na,50,"$82,500,000 ","$1,650 ",1979,239,607,"$209,529,289 ","$4,191 ",14,600,10000,0.85,5.7,,60,600,3.42,1,0.023,3.67,Downs Cell -Zn,100,"$250,000,000 ","$2,500 ",2006,500,607,"$303,500,000 ","$3,035 ",766,50,300,0.85,3.5,,50,15,0.053,2,0.065,0.13, -Zn,100,"$60,000,000 ",$600 ,1974,164.4,607,"$221,532,847 ","$2,215 ",766,50,300,0.85,3.5,,50,15,0.053,2,0.065,0.13, -Zn,120,"$508,500,000 ","$4,238 ",2011,590,607,"$523,151,695 ","$4,360 ",919,50,300,0.85,3.5,,50,15,0.053,2,0.065,0.13, -Cu,1.1,"$2,600,000 ","$2,398 ",1980,261,607,"$6,046,743 ","$5,577 ",15,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW -Cu,1.7,"$3,700,000 ","$2,239 ",1980,261,607,"$8,604,981 ","$5,207 ",23,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW -Cu,5,"$12,400,000 ","$2,481 ",1980,261,607,"$28,838,314 ","$5,770 ",70,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW -Cu,4.6,"$9,800,000 ","$2,140 ",1980,261,607,"$22,791,571 ","$4,977 ",64,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW -Cu,15.1,"$26,000,000 ","$1,727 ",1980,261,607,"$60,467,433 ","$4,016 ",210,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW -Cu,22.7,"$32,700,000 ","$1,441 ",1980,261,607,"$76,049,425 ","$3,351 ",316,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW -Cu,21.2,"$33,500,000 ","$1,579 ",1980,261,607,"$77,909,962 ","$3,672 ",296,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW -Cu,27,"$100,000,000 ","$3,703 ",1998,390,607,"$155,641,026 ","$5,764 ",377,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW -Cu,1.3,"$3,000,000 ","$2,253 ",1980,390,607,"$4,669,231 ","$3,507 ",19,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW -Cu,14.6,"$25,300,000 ","$1,728 ",1980,390,607,"$39,377,179 ","$2,689 ",204,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW -Cu,50,"$268,500,000 ","$5,370 ",2014,580,607,"$280,999,138 ","$5,620 ",697,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW -Cu,60,"$298,000,000 ","$4,966 ",2007,530,607,"$341,294,340 ","$5,688 ",837,40,300,0.8,3.5,,30,9,0.032,2,0.064,0.07,SX-EW -Cl2,200,"$106,000,000 ",$530 ,1980,261,607,"$246,521,073 ","$1,233 ",126,90,2700,0.96,3.79,,55,149,0.563,2,0.071,1.59,diaphragm -Cl2,200,"$111,500,000 ",$558 ,1980,261,607,"$259,312,261 ","$1,297 ",126,90,2700,0.96,3.79,,55,149,0.563,2,0.071,1.59,membrane -Cl2,200,"$112,800,000 ",$564 ,1980,261,607,"$262,335,632 ","$1,312 ",126,90,2700,0.96,3.79,,55,149,0.563,2,0.071,1.59,membrane + inert anodes -Cl2,166,"$111,000,000 ",$669 ,1990,358,607,"$188,203,911 ","$1,134 ",105,90,2700,0.96,3.79,,55,149,0.563,2,0.071,1.59, -Cl2,880,"$194,868,534 ",$221 ,1980,261,607,"$453,200,000 ",$515 ,554,90,2700,0.96,3.79,,55,149,0.563,2,0.071,1.59, +Metal,Capacity [kt/y],Capex [Year USD],Capex/ Capacity [year USD/ (t/y)],Year,Year CEI,2018 CEI,Capex [2018 USD],Capex/ Capacity [2018 USD/ (t/y)],Cell Count,Temperature [C],Current Density [A/m^2],Current efficiency,Operating potential [V],Specific energy [kWh/kg],Electrode area/ cell [m^2],Current / cell [kA],Power / cell [MW],Electrons per product,Product molar mass [kg],Yearly productivity / cell [kt/y],Notes +Fe,100,71130000,711.3,2018,607,607,71130000,711.3,555.556,80,1000,0.9,3.2,4.64,30,30,0.1,3,0.06,0.18,Humbert Cost Kempler Operating Conditions +Fe,100,1094700000,10947,2018,607,607,1094700000,10947,13.495,900,6000,0.9,1.79,0.64,30,300,0.54,3,0.06,7.41,Humbert +Fe,100,101750000,1017.5,2018,607,607,101750000,1017.5,169.492,1600,10000,0.9,0.82,3.67,30,300,0.25,2,0.06,0.59,Humbert +Al,415,2937550460,7070,2008,576,607,3095647794,7451,517,1000,10000,0.95,4.18,16.5,30,300,1.25,3,0.03,0.8,1 line Rectifier +Al,415,2920930656,7030,2008,576,607,3078133521,7408,517,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,1 line Rectifier +Al,588,3691655708,6275,2008,576,607,3890338568,6613,732,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,1 line Rectifier +Al,588,3753428433,6380,2008,576,607,3955435866,6723,732,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,1 line Rectifier +Al,735,4894017677,6655,2008,576,607,5157410990,7013,915,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,1 line Rectifier +Al,735,4360860229,5930,2008,576,607,4595559304,6249,915,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,2 line Rectifier +Al,882,4994766050,5660,2008,576,607,5263581584,5965,1098,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,2 line Rectifier +Al,882,5599256288,6345,2008,576,607,5900605150,6686,1098,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,1 line Rectifier +Al,1177,6712636117,5705,2008,576,607,7073906463,6012,1465,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,2 line Rectifier +Al,1177,6518493267,5540,2008,576,607,6869314953,5838,1465,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,2 line Rectifier +Al,1691,8676861694,5130,2008,576,607,9143845570,5406,2105,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,2 line Rectifier +Al,500,3250000000,6500,2001,394,607,5006979695,10014,622,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,2 line Rectifier +Al,353,1899846000,5382,2008,576,608,2005393000,5681,439,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,1 line Rectifier +Mg,50,400000000,8000,2001,394,607,616243655,12325,41,750,6000,0.9,6,,60,360,2.16,2,0.02,1.23,IG Cell +Mg,109,359700000,3300,1979,239,607,913547699,8381,88,750,6000,0.9,6,,60,360,2.16,2,0.02,1.23,IG Cell +Mg,22,72600000,3300,1979,239,607,184385774,8381,18,750,6000,0.9,6,,60,360,2.16,2,0.02,1.23,IG Cell +Mg,4.5,14850000,3300,1979,239,607,37715272,8381,4,750,6000,0.9,6,,60,360,2.16,2,0.02,1.23,IG Cell +Mg,22.5,74250000,3300,1979,239,607,188576360,8381,18,750,6000,0.9,6,,60,360,2.16,2,0.02,1.23,IG Cell +Na,50,82500000,1650,1979,239,607,209529289,4191,14,600,10000,0.85,5.7,,60,600,3.42,1,0.02,3.67,Downs Cell +Zn,100,250000000,2500,2006,500,607,303500000,3035,766,50,300,0.85,3.5,,50,15,0.05,2,0.07,0.13, +Zn,100,60000000,600,1974,164.4,607,221532847,2215,766,50,300,0.85,3.5,,50,15,0.05,2,0.07,0.13, +Zn,120,508500000,4238,2011,590,607,523151695,4360,919,50,300,0.85,3.5,,50,15,0.05,2,0.07,0.13, +Cu,1.1,2600000,2398,1980,261,607,6046743,5577,15,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW +Cu,1.7,3700000,2239,1980,261,607,8604981,5207,23,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW +Cu,5,12400000,2481,1980,261,607,28838314,5770,70,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW +Cu,4.6,9800000,2140,1980,261,607,22791571,4977,64,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW +Cu,15.1,26000000,1727,1980,261,607,60467433,4016,210,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW +Cu,22.7,32700000,1441,1980,261,607,76049425,3351,316,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW +Cu,21.2,33500000,1579,1980,261,607,77909962,3672,296,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW +Cu,27,100000000,3703,1998,390,607,155641026,5764,377,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW +Cu,1.3,3000000,2253,1980,390,607,4669231,3507,19,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW +Cu,14.6,25300000,1728,1980,390,607,39377179,2689,204,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW +Cu,50,268500000,5370,2014,580,607,280999138,5620,697,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW +Cu,60,298000000,4966,2007,530,607,341294340,5688,837,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW +Cl2,200,106000000,530,1980,261,607,246521073,1233,126,90,2700,0.96,3.79,,55,149,0.56,2,0.07,1.59,diaphragm +Cl2,200,111500000,558,1980,261,607,259312261,1297,126,90,2700,0.96,3.79,,55,149,0.56,2,0.07,1.59,membrane +Cl2,200,112800000,564,1980,261,607,262335632,1312,126,90,2700,0.96,3.79,,55,149,0.56,2,0.07,1.59,membrane + inert anodes +Cl2,166,111000000,669,1990,358,607,188203911,1134,105,90,2700,0.96,3.79,,55,149,0.56,2,0.07,1.59, +Cl2,880,194868534,221,1980,261,607,453200000,515,554,90,2700,0.96,3.79,,55,149,0.56,2,0.07,1.59, From 9d4ff8333c20f6378b11849575a2f30bf6c6234f Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Fri, 7 Nov 2025 16:26:54 -0700 Subject: [PATCH 03/29] Making humbert perf model --- .../converters/iron/humbert_ewin_perf.py | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 h2integrate/converters/iron/humbert_ewin_perf.py diff --git a/h2integrate/converters/iron/humbert_ewin_perf.py b/h2integrate/converters/iron/humbert_ewin_perf.py new file mode 100644 index 000000000..e36f6d4c0 --- /dev/null +++ b/h2integrate/converters/iron/humbert_ewin_perf.py @@ -0,0 +1,82 @@ +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, + ) + + 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") + 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, shape=n_timesteps, units="MW") + + self.add_output("limiting_input", val=0.0, shape=n_timesteps, units="kg") + self.add_output("hot_iron_out", val=0.0, shape=n_timesteps, units="kg") + + def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): + # 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 + + # 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 NH3 production per hour by capacity + fe_prod = np.minimum.reduce([fe_prod, np.full(len(fe_prod), cap_kw / kwh_kg_fe)]) + 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 + + # Return iron production + outputs["hot_iron_out"] = fe_prod From 84aad1cfb45f5943b0ba69982bbce7868e9094eb Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Mon, 10 Nov 2025 17:41:27 -0700 Subject: [PATCH 04/29] Adding humbert/stinn cost model inputs only --- .../converters/iron/humbert_ewin_perf.py | 4 + .../iron/humbert_stinn_ewin_cost.py | 177 ++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 h2integrate/converters/iron/humbert_stinn_ewin_cost.py diff --git a/h2integrate/converters/iron/humbert_ewin_perf.py b/h2integrate/converters/iron/humbert_ewin_perf.py index e36f6d4c0..274d7b7e6 100644 --- a/h2integrate/converters/iron/humbert_ewin_perf.py +++ b/h2integrate/converters/iron/humbert_ewin_perf.py @@ -53,6 +53,8 @@ def setup(self): self.add_output("limiting_input", val=0.0, shape=n_timesteps, units="kg") self.add_output("hot_iron_out", val=0.0, shape=n_timesteps, units="kg") + self.add_output("total_hot_iron_produced", val=0.0, units="kg/year") + self.add_output("output_capacity", val=0.0, units="kg/year") def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Parse inputs @@ -80,3 +82,5 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Return iron production outputs["hot_iron_out"] = fe_prod + outputs["total_hot_iron_produced"] = np.sum(fe_prod) + outputs["output_capacity"] = cap_kw / kwh_kg_fe * 8760 diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py new file mode 100644 index 000000000..a80cabcb3 --- /dev/null +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -0,0 +1,177 @@ +import numpy as np +import openmdao.api as om +from attrs import field, define + +from h2integrate.core.utilities import merge_shared_inputs +from h2integrate.core.validators import contains +from h2integrate.core.model_baseclasses import CostModelBaseClass + + +# Physical constants +f = 96485.3321 # Faraday constant: Electric charge per mole of electrons (Faraday constant), C/mol +M = 0.055845 # Electrolysis product molar mass (kg/mol) + +# Default coefficents for Stinn capex model +a1n = 51010 +a1d = -3.82e-03 +a1t = -631 +a2n = 5634000 +a2d = -7.1e-03 +a2t = 349 +a3n = 750000 +e1 = 0.8 +e2 = 0.9 +e3 = 0.15 +e4 = 0.5 + + +def stinn_capex_calc( + a1n, a1d, a1t, a2n, a2d, a2t, a3n, e1, e2, e3, e4, T, P, p, z, F, j, A, e, M, Q, V, N +): + """ + Equation (7) from Stinn: doi.org/10.1149.2/2.F06202IF + default values for coefficients defined as globals + """ + # Pre-costs calculation + a1 = a1n / (1 + np.exp(a1d * (T - a1t))) + a2 = a2n / (1 + np.exp(a2d * (T - a2t))) + a3 = a3n * Q + + pre_costs = a1 * P**e1 + + # Electrolysis and product handling contribution to total cost + electrolysis_product_handling = a2 * ((p * z * F) / (j * A * e * M)) ** e2 + + # Power rectifying contribution + power_rectifying_contribution = a3 * V**e3 * N**e4 + + return pre_costs, electrolysis_product_handling, power_rectifying_contribution, a1, a2, a3 + + +@define +class HumbertStinnEwinCostConfig(CostModelBaseClass): + electrolysis_type: str = field( + kw_only=True, converter=(str.lower, str.strip), validator=contains(["ahe", "mse", "moe"]) + ) # product selection + + +class HumbertStinnEwinCostComponent(om.ExplicitComponent): + """ + Humbert: doi.org/10.1007/s40831-024-00878-3 + Stinn: doi.org/10.1149.2/2.F06202IF + """ + + 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): + self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] + self.config = HumbertStinnEwinCostConfig.from_dict( + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + strict=False, + ) + + ewin_type = self.config.electrolysis_type + + # Lookup specific inputs for electrowinning types from Humbert SI except where noted + if ewin_type == "ahe": + # AHE - Capex + T = 100 # Electrolysis temperature (°C) + z = 2 # Moles of electrons per mole of iron product + V = 1.7 # Cell operating voltage (V) + j = 1000 # Current density (A/m²) + A = 250 # Electrode area per cell (m²) + e = 0.66 # Current efficiency (dimensionless) + N = 12 # Number of rectifier lines + # E_spec taken from Humbert Table 10 - average of Low and High estimates + E_spec = (1.869 + 2.72) / 2 # Specific energy of electrolysis (kWh/kg-Fe) + + # AHE - Opex + labor = (0.44 + 1.54) / 2 # Labor rate (op.-hr/tonne) + NaOH_ratio = 25130.2 * 0.1 / 2e6 # Ratio of NaOH consumption to annual iron production + CaCl2_ratio = 0 # Ratio of CaCl2 consumption to annual iron production + limestone_ratio = 0 # Ratio of limestone consumption to annual iron production + anode_ratio = 0 # Ratio of annode mass to annual iron production + # Anode replacement interval not considered by Humbert, 3 years assumed here + anode_replace_year = 3 # Replacement interval of anodes, years + + elif ewin_type == "mse": + # MSE - Capex + T = 900 # Temperature (deg C) + z = 3 # Moles of electrons per mole of iron product + V = 3 # Cell operating voltage (V) + j = 300 # Current density (A/m²) + A = 250 # Electrode area per cell (m²) + e = 0.66 # Current efficiency (dimensionless) + N = 8 # Number of rectifier lines + # E_spec taken from Humbert Table 10 - average of Low and High estimates + E_spec = (1.81 + 2.08) / 2 # Specific energy of electrolysis (kWh/kg-Fe) + + # MSE - Opex + labor = (0.44 + 1.54) / 2 # Labor rate (op.-hr/tonne) + NaOH_ratio = 0 # Ratio of NaOH consumption to annual iron production + CaCl2_ratio = 23138 * 0.1 / 2e6 # Ratio of CaCl2 consumption to annual iron production + limestone_ratio = 0 # Ratio of limestone consumption to annual iron production + anode_ratio = 1589.3 / 2e6 # Ratio of annode mass to annual iron production + # Anode replacement interval not considered by Humbert, 3 years assumed here + anode_replace_year = 3 # Replacement interval of anodes, years + + elif ewin_type == "moe": + # MOE - Capex + T = 1600 # Temperature (deg C) + z = 2 # Moles of electrons per mole of iron product + V = 4.22 # Cell operating voltage (V) + j = 10000 # Current density (A/m²) + A = 30 # Electrode area per cell (m²) + e = 0.95 # Current efficiency (dimensionless) + N = 6 # Number of rectifier lines + # E_spec taken from Humbert Table 10 - average of Low and High estimates + E_spec = (2.89 + 4.45) / 2 # Specific energy of electrolysis (kWh/kg-Fe) + + # AHE - Opex + labor = 0.358 # Labor rate (op.-hr/tonne) + NaOH_ratio = 0 # Ratio of NaOH consumption to annual iron production + CaCl2_ratio = 0 # Ratio of CaCl2 consumption to annual iron production + limestone_ratio = 0 # Ratio of limestone consumption to annual iron production + anode_ratio = 8365.6 / 2e6 # Ratio of annode mass to annual iron production + # Anode replacement interval not considered by Humbert, 3 years assumed here + anode_replace_year = 3 # Replacement interval of anodes, years + + self.add_input("electrolysis_temp", val=T, units="C") + self.add_input("output_capacity", val=0.0, units="Mg/year") # Mg = tonnes + + # Temporary to make ruff happy + self.inputs = [ + T, + z, + j, + A, + e, + M, + E_spec, + V, + N, + labor, + NaOH_ratio, + CaCl2_ratio, + limestone_ratio, + anode_ratio, + anode_replace_year, + ] + + def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): + stinn_capex_calc( + a1n, + a1d, + a1t, + a2n, + a2d, + a2t, + a3n, + e1, + e2, + e3, + e4, # T, P, p, z, F, j, A, e, M, Q, V, N + ) From 674cd4746a007ccb3bb053c6f9fdb80b8e8b3271 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Tue, 11 Nov 2025 09:32:21 -0700 Subject: [PATCH 05/29] Progress on humbert cost --- .../iron/humbert_stinn_ewin_cost.py | 90 ++++++++++++------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index a80cabcb3..37e9f6660 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -3,7 +3,7 @@ from attrs import field, define from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import contains +from h2integrate.core.validators import contains, must_equal from h2integrate.core.model_baseclasses import CostModelBaseClass @@ -53,6 +53,8 @@ class HumbertStinnEwinCostConfig(CostModelBaseClass): electrolysis_type: str = field( kw_only=True, converter=(str.lower, str.strip), validator=contains(["ahe", "mse", "moe"]) ) # product selection + # Set cost year to 2018 - fixed for Humbert modeling + cost_year: int = field(default=2018, converter=int, validator=must_equal(2018)) class HumbertStinnEwinCostComponent(om.ExplicitComponent): @@ -95,7 +97,7 @@ def setup(self): limestone_ratio = 0 # Ratio of limestone consumption to annual iron production anode_ratio = 0 # Ratio of annode mass to annual iron production # Anode replacement interval not considered by Humbert, 3 years assumed here - anode_replace_year = 3 # Replacement interval of anodes, years + anode_replace_int = 3 # Replacement interval of anodes (years) elif ewin_type == "mse": # MSE - Capex @@ -116,7 +118,7 @@ def setup(self): limestone_ratio = 0 # Ratio of limestone consumption to annual iron production anode_ratio = 1589.3 / 2e6 # Ratio of annode mass to annual iron production # Anode replacement interval not considered by Humbert, 3 years assumed here - anode_replace_year = 3 # Replacement interval of anodes, years + anode_replace_int = 3 # Replacement interval of anodes (years) elif ewin_type == "moe": # MOE - Capex @@ -137,41 +139,61 @@ def setup(self): limestone_ratio = 0 # Ratio of limestone consumption to annual iron production anode_ratio = 8365.6 / 2e6 # Ratio of annode mass to annual iron production # Anode replacement interval not considered by Humbert, 3 years assumed here - anode_replace_year = 3 # Replacement interval of anodes, years + anode_replace_int = 3 # Replacement interval of anodes (years) - self.add_input("electrolysis_temp", val=T, units="C") self.add_input("output_capacity", val=0.0, units="Mg/year") # Mg = tonnes - # Temporary to make ruff happy - self.inputs = [ - T, - z, - j, - A, - e, - M, - E_spec, - V, - N, - labor, - NaOH_ratio, - CaCl2_ratio, - limestone_ratio, - anode_ratio, - anode_replace_year, - ] + # Set inputs for Stinn Capex model + self.add_input("electrolysis_temp", val=T, units="C") + self.add_input("electron_moles", val=z, units=None) + self.add_input("current_density", val=j, units="A/m**2") + self.add_input("electrode_area", val=A, units="m**2") + self.add_input("current_efficiency", val=e, units=None) + self.add_input("cell_voltage", val=V, units="V") + self.add_input("rectifier_lines", val=N, units=None) + self.add_input("specific_energy", val=E_spec, units="kW*h/kg") + + # Set inputs for Humbert Opex model + self.add_input("labor_rate", val=labor, units="hr/Mg") + self.add_input("NaOH_ratio", val=NaOH_ratio, units=None) + self.add_input("CaCl2_ratio", val=CaCl2_ratio, units=None) + self.add_input("limestone_ratio", val=limestone_ratio, units=None) + self.add_input("anode_ratio", val=anode_ratio, units=None) + self.add_input("anode_replacement_interval", val=anode_replace_int, units="year") + + # Set outputs for Stinn Capex model + self.add_output("processing_capex", val=0.0, units="USD") + self.add_output("electrolysis_capex", val=0.0, units="USD") + self.add_output("rectifier_capex", val=0.0, units="USD") + + # Set outputs for Humbert Opex model + self.add_output("labor_opex", val=0.0, units="USD") + self.add_output("NaOH_opex", val=0.0, units="USD") + self.add_output("CaCl2_opex", val=0.0, units="USD") + self.add_output("limestone_opex", val=0.0, units="USD") + self.add_output("anode_opex", val=0.0, units="USD") def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): + # Parse inputs for Stinn Capex model + T = inputs["electrolysis_temp"] + z = inputs["electron_moles"] + j = inputs["current_density"] + A = inputs["electrode_area"] + e = inputs["current_efficiency"] + V = inputs["cell_voltage"] + N = inputs["rectifier_lines"] + E_spec = inputs["specific_energy"] + P = inputs["output_capacity"] + p = P * 1000 / 8760 / 3600 # kg/s + + # Calculate total power + j_cell = A * j # current/cell [A] + Q_cell = j_cell * V # power/cell [W] + P_cell = Q_cell * 8760 / E_spec # annual production capacity/cell [kg] + N_cell = P * 1e6 / P_cell # number of cells [-] + Q = Q_cell * N_cell / 1e6 # total installed power [MW] + + # Execute Stinn capex model stinn_capex_calc( - a1n, - a1d, - a1t, - a2n, - a2d, - a2t, - a3n, - e1, - e2, - e3, - e4, # T, P, p, z, F, j, A, e, M, Q, V, N + a1n, a1d, a1t, a2n, a2d, a2t, a3n, e1, e2, e3, e4, T, P, p, z, f, j, A, e, M, Q, V, N ) From 0728b2de552e4445d02494a095cfb2039ddd2c43 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Tue, 11 Nov 2025 16:38:49 -0700 Subject: [PATCH 06/29] Humbert Stinn complete, not debugged --- .../iron/humbert_stinn_ewin_cost.py | 93 +++++++++++++------ 1 file changed, 65 insertions(+), 28 deletions(-) diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index 37e9f6660..be88bf016 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -8,35 +8,34 @@ # Physical constants -f = 96485.3321 # Faraday constant: Electric charge per mole of electrons (Faraday constant), C/mol -M = 0.055845 # Electrolysis product molar mass (kg/mol) - -# Default coefficents for Stinn capex model -a1n = 51010 -a1d = -3.82e-03 -a1t = -631 -a2n = 5634000 -a2d = -7.1e-03 -a2t = 349 -a3n = 750000 -e1 = 0.8 -e2 = 0.9 -e3 = 0.15 -e4 = 0.5 - - -def stinn_capex_calc( - a1n, a1d, a1t, a2n, a2d, a2t, a3n, e1, e2, e3, e4, T, P, p, z, F, j, A, e, M, Q, V, N -): +F = 96485.3321 # Faraday constant: Electric charge per mole of electrons (Faraday constant), C/mol +M = 0.055845 # Fe molar mass (kg/mol) + + +def stinn_capex_calc(T, P, p, z, j, A, e, Q, V, N): """ Equation (7) from Stinn: doi.org/10.1149.2/2.F06202IF default values for coefficients defined as globals """ - # Pre-costs calculation + # Default coefficents + a1n = 51010 + a1d = -3.82e-03 + a1t = -631 + a2n = 5634000 + a2d = -7.1e-03 + a2t = 349 + a3n = 750000 + e1 = 0.8 + e2 = 0.9 + e3 = 0.15 + e4 = 0.5 + + # Alpha coefficients a1 = a1n / (1 + np.exp(a1d * (T - a1t))) a2 = a2n / (1 + np.exp(a2d * (T - a2t))) a3 = a3n * Q + # Pre-costs calculation pre_costs = a1 * P**e1 # Electrolysis and product handling contribution to total cost @@ -48,12 +47,36 @@ def stinn_capex_calc( return pre_costs, electrolysis_product_handling, power_rectifying_contribution, a1, a2, a3 +def humbert_opex_calc( + capacity, positions, NaOH_ratio, CaCl2_ratio, limestone_ratio, anode_ratio, anode_interval +): + """ + Calculations in Excel spreadsheet SI of Humbert doi.org/10.1149.2/2.F06202IF + """ + # Default costs - adjusted to 2018 to match Stinn via CPI + labor_rate = 55.90 # USD/person-hour + NaOH_cost = 415.179 # USD/tonne + CaCl2_cost = 207.59 # USD/tonne + limestone_cost = 0 + anode_cost = 1660.716 # USD/tonne + hours = 2000 # hours/position-year + + # All linear OpEx for now - TODO: apply scaling models + labor_opex = labor_rate * capacity * positions * hours # Labor OpEx USD/year + NaOH_varopex = NaOH_ratio * capacity * NaOH_cost # NaOH VarOpEx USD/year + CaCl2_varopex = CaCl2_ratio * capacity * CaCl2_cost # CaCl2 VarOpEx USD/year + limestone_varopex = limestone_ratio * capacity * limestone_cost # CaCl2 VarOpEx USD/year + anode_varopex = anode_ratio * capacity * anode_cost / anode_interval # Anode VarOpEx USD/year + + return labor_opex, NaOH_varopex, CaCl2_varopex, limestone_varopex, anode_varopex + + @define class HumbertStinnEwinCostConfig(CostModelBaseClass): electrolysis_type: str = field( kw_only=True, converter=(str.lower, str.strip), validator=contains(["ahe", "mse", "moe"]) ) # product selection - # Set cost year to 2018 - fixed for Humbert modeling + # Set cost year to 2018 - fixed for Stinn modeling cost_year: int = field(default=2018, converter=int, validator=must_equal(2018)) @@ -91,7 +114,7 @@ def setup(self): E_spec = (1.869 + 2.72) / 2 # Specific energy of electrolysis (kWh/kg-Fe) # AHE - Opex - labor = (0.44 + 1.54) / 2 # Labor rate (op.-hr/tonne) + positions = 739.2 / 2e6 # Labor rate (position-years/tonne) NaOH_ratio = 25130.2 * 0.1 / 2e6 # Ratio of NaOH consumption to annual iron production CaCl2_ratio = 0 # Ratio of CaCl2 consumption to annual iron production limestone_ratio = 0 # Ratio of limestone consumption to annual iron production @@ -112,7 +135,7 @@ def setup(self): E_spec = (1.81 + 2.08) / 2 # Specific energy of electrolysis (kWh/kg-Fe) # MSE - Opex - labor = (0.44 + 1.54) / 2 # Labor rate (op.-hr/tonne) + positions = 499.2 / 2e6 # Labor rate (position-years/tonne) NaOH_ratio = 0 # Ratio of NaOH consumption to annual iron production CaCl2_ratio = 23138 * 0.1 / 2e6 # Ratio of CaCl2 consumption to annual iron production limestone_ratio = 0 # Ratio of limestone consumption to annual iron production @@ -133,7 +156,7 @@ def setup(self): E_spec = (2.89 + 4.45) / 2 # Specific energy of electrolysis (kWh/kg-Fe) # AHE - Opex - labor = 0.358 # Labor rate (op.-hr/tonne) + positions = 230.4 / 2e6 # Labor rate (position-years/tonne) NaOH_ratio = 0 # Ratio of NaOH consumption to annual iron production CaCl2_ratio = 0 # Ratio of CaCl2 consumption to annual iron production limestone_ratio = 0 # Ratio of limestone consumption to annual iron production @@ -154,7 +177,7 @@ def setup(self): self.add_input("specific_energy", val=E_spec, units="kW*h/kg") # Set inputs for Humbert Opex model - self.add_input("labor_rate", val=labor, units="hr/Mg") + self.add_input("positions", val=positions, units="year/Mg") self.add_input("NaOH_ratio", val=NaOH_ratio, units=None) self.add_input("CaCl2_ratio", val=CaCl2_ratio, units=None) self.add_input("limestone_ratio", val=limestone_ratio, units=None) @@ -194,6 +217,20 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): Q = Q_cell * N_cell / 1e6 # total installed power [MW] # Execute Stinn capex model - stinn_capex_calc( - a1n, a1d, a1t, a2n, a2d, a2t, a3n, e1, e2, e3, e4, T, P, p, z, f, j, A, e, M, Q, V, N + capex_breakdown = stinn_capex_calc(T, P, p, z, j, A, e, Q, V, N) + outputs["CapEx"] = np.sum(capex_breakdown[:3]) + + # Parse inputs for Humbert Opex model + positions = inputs["positions"] + NaOH_ratio = inputs["NaOH_ratio"] + CaCl2_ratio = inputs["CaCl2_ratio"] + limestone_ratio = inputs["limestone_ratio"] + anode_ratio = inputs["anode_ratio"] + anode_interval = inputs["anode_replacement_interval"] + + # Execute Humbert opex model + opex_breakdown = humbert_opex_calc( + P, positions, NaOH_ratio, CaCl2_ratio, limestone_ratio, anode_ratio, anode_interval ) + outputs["OpEx"] = np.sum(opex_breakdown) + outputs["VarOpEx"] = np.sum(opex_breakdown[1:]) From 9601cce1ae1d733d1a58be08ca14c17bdb42c2d9 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Tue, 11 Nov 2025 17:16:53 -0700 Subject: [PATCH 07/29] Debugging electrowinning --- .../plant_config_modular.yaml | 2 +- .../22_iron_electrowinning.yaml | 7 ++ .../22_iron_electrowinning/driver_config.yaml | 5 ++ .../22_iron_electrowinning/plant_config.yaml | 67 +++++++++++++++ examples/22_iron_electrowinning/run_iron.py | 29 +++++++ .../22_iron_electrowinning/tech_config.yaml | 81 +++++++++++++++++++ .../22_iron_electrowinning/test_inputs.csv | 2 + .../converters/iron/humbert_ewin_perf.py | 6 +- .../iron/humbert_stinn_ewin_cost.py | 33 +++++--- h2integrate/core/supported_models.py | 4 + 10 files changed, 221 insertions(+), 15 deletions(-) create mode 100644 examples/22_iron_electrowinning/22_iron_electrowinning.yaml create mode 100644 examples/22_iron_electrowinning/driver_config.yaml create mode 100644 examples/22_iron_electrowinning/plant_config.yaml create mode 100644 examples/22_iron_electrowinning/run_iron.py create mode 100644 examples/22_iron_electrowinning/tech_config.yaml create mode 100644 examples/22_iron_electrowinning/test_inputs.csv diff --git a/examples/21_iron_mn_to_il/plant_config_modular.yaml b/examples/21_iron_mn_to_il/plant_config_modular.yaml index 64e4dfdbf..4907e3895 100644 --- a/examples/21_iron_mn_to_il/plant_config_modular.yaml +++ b/examples/21_iron_mn_to_il/plant_config_modular.yaml @@ -24,7 +24,7 @@ site: technology_interconnections: [ ["iron_mine","iron_transport","total_iron_ore_produced"], ["iron_transport","iron_plant","iron_transport_cost"], - ["finance_subgroup_iron_ore","iron_plant","price_iron_ore"], + ["finance_subgroup_iron_ore","iron_plant","price_iron_ore_"], ] plant: diff --git a/examples/22_iron_electrowinning/22_iron_electrowinning.yaml b/examples/22_iron_electrowinning/22_iron_electrowinning.yaml new file mode 100644 index 000000000..33588912d --- /dev/null +++ b/examples/22_iron_electrowinning/22_iron_electrowinning.yaml @@ -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" diff --git a/examples/22_iron_electrowinning/driver_config.yaml b/examples/22_iron_electrowinning/driver_config.yaml new file mode 100644 index 000000000..d3b06a03e --- /dev/null +++ b/examples/22_iron_electrowinning/driver_config.yaml @@ -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 diff --git a/examples/22_iron_electrowinning/plant_config.yaml b/examples/22_iron_electrowinning/plant_config.yaml new file mode 100644 index 000000000..f241ea1b3 --- /dev/null +++ b/examples/22_iron_electrowinning/plant_config.yaml @@ -0,0 +1,67 @@ +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: [ + ["iron_mine","iron_transport","total_iron_ore_produced"], + # ["iron_transport","iron_plant","iron_transport_cost"], + ["finance_subgroup_iron_ore","iron_plant","price_iron_ore_"], + ["wind","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"] + hot_iron: + commodity: "hot_iron" + commodity_stream: "iron_plant" + technologies: ["iron_plant"] # diff --git a/examples/22_iron_electrowinning/run_iron.py b/examples/22_iron_electrowinning/run_iron.py new file mode 100644 index 000000000..8f8b18389 --- /dev/null +++ b/examples/22_iron_electrowinning/run_iron.py @@ -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("22_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 = [ + "Case 1", + "Case 2", + "Case 3", +] +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_hot_iron.price_hot_iron")[0])) + +# Compare the LCOIs from iron_wrapper and modular iron +print(lcois) diff --git a/examples/22_iron_electrowinning/tech_config.yaml b/examples/22_iron_electrowinning/tech_config.yaml new file mode 100644 index 000000000..299636c57 --- /dev/null +++ b/examples/22_iron_electrowinning/tech_config.yaml @@ -0,0 +1,81 @@ +name: "technology_config" +description: "This hybrid plant produces iron" + +technologies: + iron_mine: + performance_model: + model: "iron_mine_performance" + cost_model: + model: "iron_mine_cost" + model_inputs: + shared_parameters: + mine: "Northshore" + taconite_pellet_type: "drg" + performance_parameters: + ore_cf_estimate: 0.9 + model_name: "martin_ore" + cost_parameters: + LCOE: 58.02 + LCOH: 7.10 + model_name: "martin_ore" + varom_model_name: "martin_ore" + installation_years: 3 + operational_year: 2035 + plant_life: 30 + gen_inflation: 0.025 + financial_assumptions: + total income tax rate: 0.2574 + capital gains tax rate: 0.15 + leverage after tax nominal discount rate: 0.10893 + debt equity ratio of initial financing: 0.624788 + debt interest rate: 0.050049 + 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 + 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 + wind: + performance_model: + model: "pysam_wind_plant_performance" + cost_model: + model: "atb_wind_cost" + model_inputs: + performance_parameters: + num_turbines: 100 + turbine_rating_kw: 6000 + rotor_diameter: 170. + hub_height: 115. + create_model_from: "default" + config_name: "WindPowerSingleOwner" + pysam_options: !include pysam_options_6000MW.yaml + run_recalculate_power_curve: False + layout: + layout_mode: "basicgrid" + layout_options: + row_D_spacing: 7.0 + turbine_D_spacing: 7.0 + rotation_angle_deg: 0.0 + row_phase_offset: 0.0 + layout_shape: "square" + cost_parameters: + capex_per_kW: 1500.0 + opex_per_kW_per_year: 45 + cost_year: 2019 diff --git a/examples/22_iron_electrowinning/test_inputs.csv b/examples/22_iron_electrowinning/test_inputs.csv new file mode 100644 index 000000000..0da86ddac --- /dev/null +++ b/examples/22_iron_electrowinning/test_inputs.csv @@ -0,0 +1,2 @@ +Index 0,Index 1,Index 2,Index 3,Index 4,Type,Case 1,Case 2,Case 3 +technologies,iron_plant,model_inputs,shared_parameters,electrolysis_type,str,ahe,mse,moe diff --git a/h2integrate/converters/iron/humbert_ewin_perf.py b/h2integrate/converters/iron/humbert_ewin_perf.py index 274d7b7e6..d1199aa60 100644 --- a/h2integrate/converters/iron/humbert_ewin_perf.py +++ b/h2integrate/converters/iron/humbert_ewin_perf.py @@ -46,13 +46,13 @@ def setup(self): 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") + 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, shape=n_timesteps, units="MW") - self.add_output("limiting_input", val=0.0, shape=n_timesteps, units="kg") - self.add_output("hot_iron_out", val=0.0, shape=n_timesteps, units="kg") + self.add_output("limiting_input", val=0.0, shape=n_timesteps, units=None) + self.add_output("hot_iron_out", val=0.0, shape=n_timesteps, units="kg/h") self.add_output("total_hot_iron_produced", val=0.0, units="kg/year") self.add_output("output_capacity", val=0.0, units="kg/year") diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index be88bf016..a9c3062d6 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -1,8 +1,7 @@ import numpy as np -import openmdao.api as om from attrs import field, define -from h2integrate.core.utilities import merge_shared_inputs +from h2integrate.core.utilities import CostModelBaseConfig, merge_shared_inputs from h2integrate.core.validators import contains, must_equal from h2integrate.core.model_baseclasses import CostModelBaseClass @@ -48,7 +47,15 @@ def stinn_capex_calc(T, P, p, z, j, A, e, Q, V, N): def humbert_opex_calc( - capacity, positions, NaOH_ratio, CaCl2_ratio, limestone_ratio, anode_ratio, anode_interval + capacity, + positions, + NaOH_ratio, + CaCl2_ratio, + limestone_ratio, + anode_ratio, + anode_interval, + ore_in, + ore_price, ): """ Calculations in Excel spreadsheet SI of Humbert doi.org/10.1149.2/2.F06202IF @@ -67,12 +74,13 @@ def humbert_opex_calc( CaCl2_varopex = CaCl2_ratio * capacity * CaCl2_cost # CaCl2 VarOpEx USD/year limestone_varopex = limestone_ratio * capacity * limestone_cost # CaCl2 VarOpEx USD/year anode_varopex = anode_ratio * capacity * anode_cost / anode_interval # Anode VarOpEx USD/year + ore_varopex = np.sum(ore_in * ore_price) # Ore VarOpEx USD/year - return labor_opex, NaOH_varopex, CaCl2_varopex, limestone_varopex, anode_varopex + return labor_opex, NaOH_varopex, CaCl2_varopex, limestone_varopex, anode_varopex, ore_varopex @define -class HumbertStinnEwinCostConfig(CostModelBaseClass): +class HumbertStinnEwinCostConfig(CostModelBaseConfig): electrolysis_type: str = field( kw_only=True, converter=(str.lower, str.strip), validator=contains(["ahe", "mse", "moe"]) ) # product selection @@ -80,7 +88,7 @@ class HumbertStinnEwinCostConfig(CostModelBaseClass): cost_year: int = field(default=2018, converter=int, validator=must_equal(2018)) -class HumbertStinnEwinCostComponent(om.ExplicitComponent): +class HumbertStinnEwinCostComponent(CostModelBaseClass): """ Humbert: doi.org/10.1007/s40831-024-00878-3 Stinn: doi.org/10.1149.2/2.F06202IF @@ -97,6 +105,7 @@ def setup(self): merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=False, ) + super().setup() ewin_type = self.config.electrolysis_type @@ -165,6 +174,7 @@ def setup(self): anode_replace_int = 3 # Replacement interval of anodes (years) self.add_input("output_capacity", val=0.0, units="Mg/year") # Mg = tonnes + self.add_input("price_iron_ore_", val=0.0, units="USD/Mg") # Set inputs for Stinn Capex model self.add_input("electrolysis_temp", val=T, units="C") @@ -190,11 +200,12 @@ def setup(self): self.add_output("rectifier_capex", val=0.0, units="USD") # Set outputs for Humbert Opex model - self.add_output("labor_opex", val=0.0, units="USD") - self.add_output("NaOH_opex", val=0.0, units="USD") - self.add_output("CaCl2_opex", val=0.0, units="USD") - self.add_output("limestone_opex", val=0.0, units="USD") - self.add_output("anode_opex", val=0.0, units="USD") + self.add_output("labor_opex", val=0.0, units="USD/year") + self.add_output("NaOH_opex", val=0.0, units="USD/year") + self.add_output("CaCl2_opex", val=0.0, units="USD/year") + self.add_output("limestone_opex", val=0.0, units="USD/year") + self.add_output("anode_opex", val=0.0, units="USD/year") + self.add_output("ore_opex", val=0.0, units="USD/year") def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Parse inputs for Stinn Capex model diff --git a/h2integrate/core/supported_models.py b/h2integrate/core/supported_models.py index b6cd1fed2..6f6709020 100644 --- a/h2integrate/core/supported_models.py +++ b/h2integrate/core/supported_models.py @@ -37,6 +37,7 @@ ) from h2integrate.converters.hydrogen.wombat_model import WOMBATElectrolyzerModel from h2integrate.storage.battery.atb_battery_cost import ATBBatteryCostModel +from h2integrate.converters.iron.humbert_ewin_perf import HumbertEwinPerformanceComponent from h2integrate.converters.ammonia.ammonia_synloop import ( AmmoniaSynLoopCostModel, AmmoniaSynLoopPerformanceModel, @@ -62,6 +63,7 @@ SimpleAmmoniaCostModel, SimpleAmmoniaPerformanceModel, ) +from h2integrate.converters.iron.humbert_stinn_ewin_cost import HumbertStinnEwinCostComponent from h2integrate.converters.methanol.co2h_methanol_plant import ( CO2HMethanolPlantCostModel, CO2HMethanolPlantFinanceModel, @@ -150,6 +152,8 @@ "iron_mine_cost": IronMineCostComponent, "iron_plant_performance": IronPlantPerformanceComponent, "iron_plant_cost": IronPlantCostComponent, + "humbert_electrowinning_performance": HumbertEwinPerformanceComponent, + "humbert_stinn_electrowinning_cost": HumbertStinnEwinCostComponent, "reverse_osmosis_desalination_performance": ReverseOsmosisPerformanceModel, "reverse_osmosis_desalination_cost": ReverseOsmosisCostModel, "simple_ammonia_performance": SimpleAmmoniaPerformanceModel, From 6310a12ea39ad1ecf42bb91c6b558a076838babb Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Tue, 11 Nov 2025 17:32:58 -0700 Subject: [PATCH 08/29] Debugging ewinning --- .../22_iron_electrowinning/plant_config.yaml | 13 + .../pysam_options_6000MW.yaml | 413 ++++++++++++++++++ 2 files changed, 426 insertions(+) create mode 100644 examples/22_iron_electrowinning/pysam_options_6000MW.yaml diff --git a/examples/22_iron_electrowinning/plant_config.yaml b/examples/22_iron_electrowinning/plant_config.yaml index f241ea1b3..b30a08cd5 100644 --- a/examples/22_iron_electrowinning/plant_config.yaml +++ b/examples/22_iron_electrowinning/plant_config.yaml @@ -17,6 +17,14 @@ site: } ] + resources: + wind_resource: + resource_model: "wind_toolkit_v2_api" + resource_parameters: + latitude: 35.2018863 + longitude: -101.945027 + resource_year: 2012 + # array of arrays containing left-to-right technology # interconnections; can support bidirectional connections # with the reverse definition. @@ -28,6 +36,11 @@ technology_interconnections: [ ["wind","iron_plant","electricity","cable"] ] +resource_to_tech_connections: [ + # connect the wind resource to the wind technology + ['wind_resource', 'wind', 'wind_resource_data'], +] + plant: plant_life: 30 finance_parameters: diff --git a/examples/22_iron_electrowinning/pysam_options_6000MW.yaml b/examples/22_iron_electrowinning/pysam_options_6000MW.yaml new file mode 100644 index 000000000..8b59bf2b4 --- /dev/null +++ b/examples/22_iron_electrowinning/pysam_options_6000MW.yaml @@ -0,0 +1,413 @@ +Turbine: + wind_resource_shear: 0.14 + wind_turbine_ct_curve: + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.912 + - 0.89325 + - 0.8745 + - 0.8675 + - 0.8605 + - 0.85025 + - 0.84 + - 0.83275 + - 0.8255 + - 0.82175 + - 0.818 + - 0.8175 + - 0.817 + - 0.81425 + - 0.8115 + - 0.8115 + - 0.8115 + - 0.8085 + - 0.8055 + - 0.7985 + - 0.7915 + - 0.7765 + - 0.7615 + - 0.73625 + - 0.711 + - 0.68025 + - 0.6495 + - 0.61525 + - 0.581 + - 0.54625 + - 0.5115 + - 0.47825 + - 0.445 + - 0.41675 + - 0.3885 + - 0.36275 + - 0.337 + - 0.31625 + - 0.2955 + - 0.27675 + - 0.258 + - 0.24325 + - 0.2285 + - 0.21725 + - 0.206 + - 0.19325 + - 0.1805 + - 0.1735 + - 0.1665 + - 0.15775 + - 0.149 + - 0.143 + - 0.137 + - 0.13175 + - 0.1265 + - 0.119 + - 0.1115 + - 0.10925 + - 0.107 + - 0.10275 + - 0.0985 + - 0.09425 + - 0.09 + - 0.086 + - 0.082 + - 0.08075 + - 0.0795 + - 0.076 + - 0.0725 + - 0.06975 + - 0.067 + - 0.06325 + - 0.0595 + - 0.05825 + - 0.057 + - 0.0535 + - 0.05 + - 0.049 + - 0.048 + - 0.04475 + - 0.0415 + - 0.0405 + - 0.0395 + - 0.039 + - 0.0385 + - 0.03525 + - 0.032 + - 0.0315 + - 0.031 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + wind_turbine_hub_ht: 115.0 + wind_turbine_max_cp: 0.474457866 + wind_turbine_powercurve_powerout: + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 181.0 + - 229.0 + - 294.0 + - 377.0 + - 473.0 + - 581.0 + - 698.0 + - 822.0 + - 952.0 + - 1090.0 + - 1241.0 + - 1410.0 + - 1598.0 + - 1805.0 + - 2027.0 + - 2262.0 + - 2509.0 + - 2769.0 + - 3050.0 + - 3356.0 + - 3687.0 + - 4027.0 + - 4355.0 + - 4650.0 + - 4897.0 + - 5101.0 + - 5275.0 + - 5429.0 + - 5571.0 + - 5699.0 + - 5806.0 + - 5886.0 + - 5934.0 + - 5957.0 + - 5966.0 + - 5970.0 + - 5976.0 + - 5984.0 + - 5992.0 + - 5999.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 6000.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + - 0.0 + wind_turbine_powercurve_windspeeds: + - 0.0 + - 0.25 + - 0.5 + - 0.75 + - 1.0 + - 1.25 + - 1.5 + - 1.75 + - 2.0 + - 2.25 + - 2.5 + - 2.75 + - 3.0 + - 3.25 + - 3.5 + - 3.75 + - 4.0 + - 4.25 + - 4.5 + - 4.75 + - 5.0 + - 5.25 + - 5.5 + - 5.75 + - 6.0 + - 6.25 + - 6.5 + - 6.75 + - 7.0 + - 7.25 + - 7.5 + - 7.75 + - 8.0 + - 8.25 + - 8.5 + - 8.75 + - 9.0 + - 9.25 + - 9.5 + - 9.75 + - 10.0 + - 10.25 + - 10.5 + - 10.75 + - 11.0 + - 11.25 + - 11.5 + - 11.75 + - 12.0 + - 12.25 + - 12.5 + - 12.75 + - 13.0 + - 13.25 + - 13.5 + - 13.75 + - 14.0 + - 14.25 + - 14.5 + - 14.75 + - 15.0 + - 15.25 + - 15.5 + - 15.75 + - 16.0 + - 16.25 + - 16.5 + - 16.75 + - 17.0 + - 17.25 + - 17.5 + - 17.75 + - 18.0 + - 18.25 + - 18.5 + - 18.75 + - 19.0 + - 19.25 + - 19.5 + - 19.75 + - 20.0 + - 20.25 + - 20.5 + - 20.75 + - 21.0 + - 21.25 + - 21.5 + - 21.75 + - 22.0 + - 22.25 + - 22.5 + - 22.75 + - 23.0 + - 23.25 + - 23.5 + - 23.75 + - 24.0 + - 24.25 + - 24.5 + - 24.75 + - 25.0 + - 26.0 + - 27.0 + - 28.0 + - 29.0 + - 30.0 + - 31.0 + - 32.0 + - 33.0 + - 34.0 + - 35.0 + - 36.0 + - 37.0 + - 38.0 + - 39.0 + - 40.0 + - 41.0 + - 42.0 + - 43.0 + - 44.0 + - 45.0 + - 46.0 + - 47.0 + - 48.0 + - 49.0 +Farm: + wind_farm_wake_model: 0.0 + wind_resource_turbulence_coeff: 0.1 +Losses: + avail_bop_loss: 0.5 + avail_grid_loss: 1.5 + avail_turb_loss: 3.58 + elec_eff_loss: 1.91 + elec_parasitic_loss: 0.1 + env_degrad_loss: 1.8 + env_env_loss: 0.4 + env_exposure_loss: 0.0 + env_icing_loss: 0.21 + ops_env_loss: 1.0 + ops_grid_loss: 0.84 + ops_load_loss: 0.99 + ops_strategies_loss: 0.0 + turb_generic_loss: 1.7 + turb_hysteresis_loss: 0.4 + turb_perf_loss: 1.1 + turb_specific_loss: 0.81 + wake_ext_loss: 1.1 + wake_future_loss: 0.0 + wake_int_loss: 0.0 +Resource: + weibull_k_factor: 2.0 + weibull_reference_height: 50.0 + weibull_wind_speed: 7.25 + wind_resource_model_choice: 0.0 +Uncertainty: + total_uncert: 12.085 From 4f978b89b21555e3baf4b4fb8d02c8ec85178173 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Wed, 12 Nov 2025 09:51:46 -0700 Subject: [PATCH 09/29] Electrowinning cases running, need to add DRI case --- .../22_iron_electrowinning/plant_config.yaml | 11 +++-- examples/22_iron_electrowinning/run_iron.py | 2 +- .../converters/iron/humbert_ewin_perf.py | 20 ++++++--- .../iron/humbert_stinn_ewin_cost.py | 43 +++++++++++++++++-- h2integrate/finances/profast_base.py | 1 + 5 files changed, 63 insertions(+), 14 deletions(-) diff --git a/examples/22_iron_electrowinning/plant_config.yaml b/examples/22_iron_electrowinning/plant_config.yaml index b30a08cd5..02b799c1c 100644 --- a/examples/22_iron_electrowinning/plant_config.yaml +++ b/examples/22_iron_electrowinning/plant_config.yaml @@ -31,8 +31,9 @@ site: # this will naturally grow as we mature the interconnected tech technology_interconnections: [ ["iron_mine","iron_transport","total_iron_ore_produced"], - # ["iron_transport","iron_plant","iron_transport_cost"], + ["iron_transport","iron_plant","iron_transport_cost"], ["finance_subgroup_iron_ore","iron_plant","price_iron_ore_"], + ["finance_subgroup_electricity","iron_plant","price_electricity_"], ["wind","iron_plant","electricity","cable"] ] @@ -70,11 +71,15 @@ finance_parameters: cost_year_adjustment_inflation: 0.025 target_dollar_year: 2022 finance_subgroups: + electricity: + commodity: "electricity" + commodity_stream: "wind" + technologies: ["wind"] iron_ore: commodity: "iron_ore" commodity_stream: "iron_mine" technologies: ["iron_mine"] - hot_iron: - commodity: "hot_iron" + sponge_iron: + commodity: "sponge_iron" commodity_stream: "iron_plant" technologies: ["iron_plant"] # diff --git a/examples/22_iron_electrowinning/run_iron.py b/examples/22_iron_electrowinning/run_iron.py index 8f8b18389..e698b6ec5 100644 --- a/examples/22_iron_electrowinning/run_iron.py +++ b/examples/22_iron_electrowinning/run_iron.py @@ -23,7 +23,7 @@ model = modify_tech_config(model, cases[casename]) model.run() model.post_process() - lcois.append(float(model.model.get_val("finance_subgroup_hot_iron.price_hot_iron")[0])) + lcois.append(float(model.model.get_val("finance_subgroup_sponge_iron.price_sponge_iron_")[0])) # Compare the LCOIs from iron_wrapper and modular iron print(lcois) diff --git a/h2integrate/converters/iron/humbert_ewin_perf.py b/h2integrate/converters/iron/humbert_ewin_perf.py index d1199aa60..2974ff024 100644 --- a/h2integrate/converters/iron/humbert_ewin_perf.py +++ b/h2integrate/converters/iron/humbert_ewin_perf.py @@ -49,14 +49,16 @@ def setup(self): 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, shape=n_timesteps, units="MW") + self.add_input("capacity", val=self.config.capacity_mw, units="MW") self.add_output("limiting_input", val=0.0, shape=n_timesteps, units=None) - self.add_output("hot_iron_out", val=0.0, shape=n_timesteps, units="kg/h") - self.add_output("total_hot_iron_produced", val=0.0, units="kg/year") + 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, discrete_inputs, discrete_outputs): + 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"] @@ -64,6 +66,12 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): 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 @@ -81,6 +89,6 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): outputs["limiting_input"] = limiters # Return iron production - outputs["hot_iron_out"] = fe_prod - outputs["total_hot_iron_produced"] = np.sum(fe_prod) + outputs["sponge_iron_out"] = fe_prod + outputs["total_sponge_iron_produced"] = np.sum(fe_prod) outputs["output_capacity"] = cap_kw / kwh_kg_fe * 8760 diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index a9c3062d6..4eea4d719 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -56,6 +56,8 @@ def humbert_opex_calc( anode_interval, ore_in, ore_price, + elec_in, + elec_price, ): """ Calculations in Excel spreadsheet SI of Humbert doi.org/10.1149.2/2.F06202IF @@ -74,9 +76,18 @@ def humbert_opex_calc( CaCl2_varopex = CaCl2_ratio * capacity * CaCl2_cost # CaCl2 VarOpEx USD/year limestone_varopex = limestone_ratio * capacity * limestone_cost # CaCl2 VarOpEx USD/year anode_varopex = anode_ratio * capacity * anode_cost / anode_interval # Anode VarOpEx USD/year - ore_varopex = np.sum(ore_in * ore_price) # Ore VarOpEx USD/year + ore_varopex = np.sum(ore_in * ore_price, keepdims=True) # Ore VarOpEx USD/year + elec_varopex = np.sum(elec_in * elec_price, keepdims=True) # Electricity VarOpEx USD/year - return labor_opex, NaOH_varopex, CaCl2_varopex, limestone_varopex, anode_varopex, ore_varopex + return ( + labor_opex, + NaOH_varopex, + CaCl2_varopex, + limestone_varopex, + anode_varopex, + ore_varopex, + elec_varopex, + ) @define @@ -100,7 +111,7 @@ def initialize(self): self.options.declare("tech_config", types=dict) def setup(self): - self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] + n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] self.config = HumbertStinnEwinCostConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=False, @@ -173,8 +184,13 @@ def setup(self): # Anode replacement interval not considered by Humbert, 3 years assumed here anode_replace_int = 3 # Replacement interval of anodes (years) + # Set up connected inputs self.add_input("output_capacity", val=0.0, units="Mg/year") # Mg = tonnes + self.add_input("iron_ore_in", val=0.0, shape=n_timesteps, units="kg/h") + self.add_input("iron_transport_cost", val=0.0, units="USD/t") self.add_input("price_iron_ore_", val=0.0, units="USD/Mg") + self.add_input("electricity_in", val=0.0, shape=n_timesteps, units="kW") + self.add_input("price_electricity_", val=0.0, units="USD/kW/h") # Set inputs for Stinn Capex model self.add_input("electrolysis_temp", val=T, units="C") @@ -206,6 +222,7 @@ def setup(self): self.add_output("limestone_opex", val=0.0, units="USD/year") self.add_output("anode_opex", val=0.0, units="USD/year") self.add_output("ore_opex", val=0.0, units="USD/year") + self.add_output("elec_opex", val=0.0, units="USD/year") def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Parse inputs for Stinn Capex model @@ -238,10 +255,28 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): limestone_ratio = inputs["limestone_ratio"] anode_ratio = inputs["anode_ratio"] anode_interval = inputs["anode_replacement_interval"] + ore_in = inputs["iron_ore_in"] + ore_price = inputs["price_iron_ore_"] + ore_transport_cost = inputs["iron_transport_cost"] + elec_in = inputs["electricity_in"] + elec_price = inputs["price_electricity_"] + + # Add ore transport cost TODO: turn iron_transport into proper transporter + ore_price += ore_transport_cost # Execute Humbert opex model opex_breakdown = humbert_opex_calc( - P, positions, NaOH_ratio, CaCl2_ratio, limestone_ratio, anode_ratio, anode_interval + P, + positions, + NaOH_ratio, + CaCl2_ratio, + limestone_ratio, + anode_ratio, + anode_interval, + ore_in, + ore_price, + elec_in, + elec_price, ) outputs["OpEx"] = np.sum(opex_breakdown) outputs["VarOpEx"] = np.sum(opex_breakdown[1:]) diff --git a/h2integrate/finances/profast_base.py b/h2integrate/finances/profast_base.py index 227d5ffb2..3d5de2be9 100644 --- a/h2integrate/finances/profast_base.py +++ b/h2integrate/finances/profast_base.py @@ -605,6 +605,7 @@ def populate_profast(self, inputs): "methanol", "iron_ore", "pig_iron", + "sponge_iron", ] # create years of operation list From 938882298d23770f5f429c104af1c43ee9a9a5b6 Mon Sep 17 00:00:00 2001 From: Jonathan Martin <94018654+jmartin4nrel@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:06:29 -0700 Subject: [PATCH 10/29] Cleanup of old trailing underscores --- examples/22_iron_electrowinning/plant_config.yaml | 4 ++-- examples/22_iron_electrowinning/run_iron.py | 8 ++++---- h2integrate/converters/iron/humbert_stinn_ewin_cost.py | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/22_iron_electrowinning/plant_config.yaml b/examples/22_iron_electrowinning/plant_config.yaml index 02b799c1c..aa93a4d5d 100644 --- a/examples/22_iron_electrowinning/plant_config.yaml +++ b/examples/22_iron_electrowinning/plant_config.yaml @@ -32,8 +32,8 @@ site: technology_interconnections: [ ["iron_mine","iron_transport","total_iron_ore_produced"], ["iron_transport","iron_plant","iron_transport_cost"], - ["finance_subgroup_iron_ore","iron_plant","price_iron_ore_"], - ["finance_subgroup_electricity","iron_plant","price_electricity_"], + ["finance_subgroup_iron_ore","iron_plant","price_iron_ore"], + ["finance_subgroup_electricity","iron_plant","price_electricity"], ["wind","iron_plant","electricity","cable"] ] diff --git a/examples/22_iron_electrowinning/run_iron.py b/examples/22_iron_electrowinning/run_iron.py index e698b6ec5..98b098907 100644 --- a/examples/22_iron_electrowinning/run_iron.py +++ b/examples/22_iron_electrowinning/run_iron.py @@ -13,9 +13,9 @@ # Modify and run the model for different cases casenames = [ - "Case 1", - "Case 2", - "Case 3", + "AHE", + "MSE", + "MOE", ] lcois = [] @@ -23,7 +23,7 @@ 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])) + lcois.append(float(model.model.get_val("finance_subgroup_sponge_iron.price_sponge_iron")[0])) # Compare the LCOIs from iron_wrapper and modular iron print(lcois) diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index 4eea4d719..10b21a054 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -188,9 +188,9 @@ def setup(self): self.add_input("output_capacity", val=0.0, units="Mg/year") # Mg = tonnes self.add_input("iron_ore_in", val=0.0, shape=n_timesteps, units="kg/h") self.add_input("iron_transport_cost", val=0.0, units="USD/t") - self.add_input("price_iron_ore_", val=0.0, units="USD/Mg") + self.add_input("price_iron_ore", val=0.0, units="USD/Mg") self.add_input("electricity_in", val=0.0, shape=n_timesteps, units="kW") - self.add_input("price_electricity_", val=0.0, units="USD/kW/h") + self.add_input("price_electricity", val=0.0, units="USD/kW/h") # Set inputs for Stinn Capex model self.add_input("electrolysis_temp", val=T, units="C") @@ -256,10 +256,10 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): anode_ratio = inputs["anode_ratio"] anode_interval = inputs["anode_replacement_interval"] ore_in = inputs["iron_ore_in"] - ore_price = inputs["price_iron_ore_"] + ore_price = inputs["price_iron_ore"] ore_transport_cost = inputs["iron_transport_cost"] elec_in = inputs["electricity_in"] - elec_price = inputs["price_electricity_"] + elec_price = inputs["price_electricity"] # Add ore transport cost TODO: turn iron_transport into proper transporter ore_price += ore_transport_cost From db6864bd32b2231dff873636bf17696c7a248b6b Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Fri, 2 Jan 2026 11:35:49 -0700 Subject: [PATCH 11/29] Fix input csv names --- examples/22_iron_electrowinning/test_inputs.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/22_iron_electrowinning/test_inputs.csv b/examples/22_iron_electrowinning/test_inputs.csv index 0da86ddac..4104fc42c 100644 --- a/examples/22_iron_electrowinning/test_inputs.csv +++ b/examples/22_iron_electrowinning/test_inputs.csv @@ -1,2 +1,2 @@ -Index 0,Index 1,Index 2,Index 3,Index 4,Type,Case 1,Case 2,Case 3 +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 From 0b41cbf7e4e8ef225db6093018205c764d7bf7c3 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Fri, 2 Jan 2026 12:53:27 -0700 Subject: [PATCH 12/29] Integrate ewin example with feedstocks --- .../22_iron_electrowinning/plant_config.yaml | 37 +++---- examples/22_iron_electrowinning/run_iron.py | 2 +- .../22_iron_electrowinning/tech_config.yaml | 103 ++++++++++-------- .../converters/iron/humbert_ewin_perf.py | 13 ++- 4 files changed, 83 insertions(+), 72 deletions(-) diff --git a/examples/22_iron_electrowinning/plant_config.yaml b/examples/22_iron_electrowinning/plant_config.yaml index aa93a4d5d..422aaf2e8 100644 --- a/examples/22_iron_electrowinning/plant_config.yaml +++ b/examples/22_iron_electrowinning/plant_config.yaml @@ -17,29 +17,18 @@ site: } ] - resources: - wind_resource: - resource_model: "wind_toolkit_v2_api" - resource_parameters: - latitude: 35.2018863 - longitude: -101.945027 - resource_year: 2012 # 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: [ - ["iron_mine","iron_transport","total_iron_ore_produced"], - ["iron_transport","iron_plant","iron_transport_cost"], - ["finance_subgroup_iron_ore","iron_plant","price_iron_ore"], - ["finance_subgroup_electricity","iron_plant","price_electricity"], - ["wind","iron_plant","electricity","cable"] -] - -resource_to_tech_connections: [ - # connect the wind resource to the wind technology - ['wind_resource', 'wind', 'wind_resource_data'], + # 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: @@ -71,15 +60,17 @@ finance_parameters: cost_year_adjustment_inflation: 0.025 target_dollar_year: 2022 finance_subgroups: - electricity: - commodity: "electricity" - commodity_stream: "wind" - technologies: ["wind"] iron_ore: commodity: "iron_ore" commodity_stream: "iron_mine" - technologies: ["iron_mine"] + technologies: ["iron_mine", "grid_feedstock", "mine_feedstock"] sponge_iron: commodity: "sponge_iron" commodity_stream: "iron_plant" - technologies: ["iron_plant"] # + technologies: + - "iron_mine" + - "grid_feedstock" + - "mine_feedstock" + - "iron_transport" + - "iron_plant" + - "ewin_grid_feedstock" diff --git a/examples/22_iron_electrowinning/run_iron.py b/examples/22_iron_electrowinning/run_iron.py index 98b098907..57af7f584 100644 --- a/examples/22_iron_electrowinning/run_iron.py +++ b/examples/22_iron_electrowinning/run_iron.py @@ -25,5 +25,5 @@ model.post_process() lcois.append(float(model.model.get_val("finance_subgroup_sponge_iron.price_sponge_iron")[0])) -# Compare the LCOIs from iron_wrapper and modular iron +# Compare the LCOIs from each electrowinning type print(lcois) diff --git a/examples/22_iron_electrowinning/tech_config.yaml b/examples/22_iron_electrowinning/tech_config.yaml index 299636c57..30129173c 100644 --- a/examples/22_iron_electrowinning/tech_config.yaml +++ b/examples/22_iron_electrowinning/tech_config.yaml @@ -2,33 +2,51 @@ 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" + model: "iron_mine_performance_martin" cost_model: - model: "iron_mine_cost" + model: "iron_mine_cost_martin" model_inputs: shared_parameters: mine: "Northshore" taconite_pellet_type: "drg" - performance_parameters: - ore_cf_estimate: 0.9 - model_name: "martin_ore" - cost_parameters: - LCOE: 58.02 - LCOH: 7.10 - model_name: "martin_ore" - varom_model_name: "martin_ore" - installation_years: 3 - operational_year: 2035 - plant_life: 30 - gen_inflation: 0.025 - financial_assumptions: - total income tax rate: 0.2574 - capital gains tax rate: 0.15 - leverage after tax nominal discount rate: 0.10893 - debt equity ratio of initial financing: 0.624788 - debt interest rate: 0.050049 + max_ore_production_rate_tonnes_per_hr: 250 + iron_transport: performance_model: model: "iron_transport_performance" @@ -41,6 +59,24 @@ technologies: 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" @@ -52,30 +88,3 @@ technologies: performance_parameters: ore_fe_wt_pct: 65 capacity_mw: 600 - wind: - performance_model: - model: "pysam_wind_plant_performance" - cost_model: - model: "atb_wind_cost" - model_inputs: - performance_parameters: - num_turbines: 100 - turbine_rating_kw: 6000 - rotor_diameter: 170. - hub_height: 115. - create_model_from: "default" - config_name: "WindPowerSingleOwner" - pysam_options: !include pysam_options_6000MW.yaml - run_recalculate_power_curve: False - layout: - layout_mode: "basicgrid" - layout_options: - row_D_spacing: 7.0 - turbine_D_spacing: 7.0 - rotation_angle_deg: 0.0 - row_phase_offset: 0.0 - layout_shape: "square" - cost_parameters: - capex_per_kW: 1500.0 - opex_per_kW_per_year: 45 - cost_year: 2019 diff --git a/h2integrate/converters/iron/humbert_ewin_perf.py b/h2integrate/converters/iron/humbert_ewin_perf.py index 2974ff024..8983bd70c 100644 --- a/h2integrate/converters/iron/humbert_ewin_perf.py +++ b/h2integrate/converters/iron/humbert_ewin_perf.py @@ -51,6 +51,13 @@ def setup(self): 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( + "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") @@ -80,7 +87,7 @@ def compute(self, inputs, outputs): fe_prod = np.minimum.reduce([fe_from_ore, fe_from_elec]) limiters = np.argmin([fe_from_ore, fe_from_elec], axis=0) - # Limiting NH3 production per hour by capacity + # Limiting iron production per hour by capacity fe_prod = np.minimum.reduce([fe_prod, np.full(len(fe_prod), cap_kw / kwh_kg_fe)]) cap_lim = 1 - np.argmax([fe_prod, np.full(len(fe_prod), cap_kw / kwh_kg_fe)], axis=0) @@ -88,7 +95,11 @@ def compute(self, inputs, outputs): 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 From ab63af7ab42035a40fe507ab40628a9b3c6396cb Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Fri, 2 Jan 2026 13:10:50 -0700 Subject: [PATCH 13/29] Moving functions out of h2i module and renumbering example --- .../27_iron_electrowinning.yaml} | 0 .../driver_config.yaml | 0 .../plant_config.yaml | 0 .../pysam_options_6000MW.yaml | 0 .../run_iron.py | 2 +- .../tech_config.yaml | 0 .../test_inputs.csv | 0 .../iron/humbert_stinn_ewin_cost.py | 85 +---------------- .../converters/iron/stinn/cost_model.py | 92 +++++++++++++++++-- 9 files changed, 87 insertions(+), 92 deletions(-) rename examples/{22_iron_electrowinning/22_iron_electrowinning.yaml => 27_iron_electrowinning/27_iron_electrowinning.yaml} (100%) rename examples/{22_iron_electrowinning => 27_iron_electrowinning}/driver_config.yaml (100%) rename examples/{22_iron_electrowinning => 27_iron_electrowinning}/plant_config.yaml (100%) rename examples/{22_iron_electrowinning => 27_iron_electrowinning}/pysam_options_6000MW.yaml (100%) rename examples/{22_iron_electrowinning => 27_iron_electrowinning}/run_iron.py (92%) rename examples/{22_iron_electrowinning => 27_iron_electrowinning}/tech_config.yaml (100%) rename examples/{22_iron_electrowinning => 27_iron_electrowinning}/test_inputs.csv (100%) diff --git a/examples/22_iron_electrowinning/22_iron_electrowinning.yaml b/examples/27_iron_electrowinning/27_iron_electrowinning.yaml similarity index 100% rename from examples/22_iron_electrowinning/22_iron_electrowinning.yaml rename to examples/27_iron_electrowinning/27_iron_electrowinning.yaml diff --git a/examples/22_iron_electrowinning/driver_config.yaml b/examples/27_iron_electrowinning/driver_config.yaml similarity index 100% rename from examples/22_iron_electrowinning/driver_config.yaml rename to examples/27_iron_electrowinning/driver_config.yaml diff --git a/examples/22_iron_electrowinning/plant_config.yaml b/examples/27_iron_electrowinning/plant_config.yaml similarity index 100% rename from examples/22_iron_electrowinning/plant_config.yaml rename to examples/27_iron_electrowinning/plant_config.yaml diff --git a/examples/22_iron_electrowinning/pysam_options_6000MW.yaml b/examples/27_iron_electrowinning/pysam_options_6000MW.yaml similarity index 100% rename from examples/22_iron_electrowinning/pysam_options_6000MW.yaml rename to examples/27_iron_electrowinning/pysam_options_6000MW.yaml diff --git a/examples/22_iron_electrowinning/run_iron.py b/examples/27_iron_electrowinning/run_iron.py similarity index 92% rename from examples/22_iron_electrowinning/run_iron.py rename to examples/27_iron_electrowinning/run_iron.py index 57af7f584..21e95d181 100644 --- a/examples/22_iron_electrowinning/run_iron.py +++ b/examples/27_iron_electrowinning/run_iron.py @@ -5,7 +5,7 @@ # Create H2Integrate model -model = H2IntegrateModel("22_iron_electrowinning.yaml") +model = H2IntegrateModel("27_iron_electrowinning.yaml") # Load cases case_file = Path("test_inputs.csv") diff --git a/examples/22_iron_electrowinning/tech_config.yaml b/examples/27_iron_electrowinning/tech_config.yaml similarity index 100% rename from examples/22_iron_electrowinning/tech_config.yaml rename to examples/27_iron_electrowinning/tech_config.yaml diff --git a/examples/22_iron_electrowinning/test_inputs.csv b/examples/27_iron_electrowinning/test_inputs.csv similarity index 100% rename from examples/22_iron_electrowinning/test_inputs.csv rename to examples/27_iron_electrowinning/test_inputs.csv diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index 10b21a054..05871a7e6 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -4,90 +4,7 @@ from h2integrate.core.utilities import CostModelBaseConfig, merge_shared_inputs from h2integrate.core.validators import contains, must_equal from h2integrate.core.model_baseclasses import CostModelBaseClass - - -# Physical constants -F = 96485.3321 # Faraday constant: Electric charge per mole of electrons (Faraday constant), C/mol -M = 0.055845 # Fe molar mass (kg/mol) - - -def stinn_capex_calc(T, P, p, z, j, A, e, Q, V, N): - """ - Equation (7) from Stinn: doi.org/10.1149.2/2.F06202IF - default values for coefficients defined as globals - """ - # Default coefficents - a1n = 51010 - a1d = -3.82e-03 - a1t = -631 - a2n = 5634000 - a2d = -7.1e-03 - a2t = 349 - a3n = 750000 - e1 = 0.8 - e2 = 0.9 - e3 = 0.15 - e4 = 0.5 - - # Alpha coefficients - a1 = a1n / (1 + np.exp(a1d * (T - a1t))) - a2 = a2n / (1 + np.exp(a2d * (T - a2t))) - a3 = a3n * Q - - # Pre-costs calculation - pre_costs = a1 * P**e1 - - # Electrolysis and product handling contribution to total cost - electrolysis_product_handling = a2 * ((p * z * F) / (j * A * e * M)) ** e2 - - # Power rectifying contribution - power_rectifying_contribution = a3 * V**e3 * N**e4 - - return pre_costs, electrolysis_product_handling, power_rectifying_contribution, a1, a2, a3 - - -def humbert_opex_calc( - capacity, - positions, - NaOH_ratio, - CaCl2_ratio, - limestone_ratio, - anode_ratio, - anode_interval, - ore_in, - ore_price, - elec_in, - elec_price, -): - """ - Calculations in Excel spreadsheet SI of Humbert doi.org/10.1149.2/2.F06202IF - """ - # Default costs - adjusted to 2018 to match Stinn via CPI - labor_rate = 55.90 # USD/person-hour - NaOH_cost = 415.179 # USD/tonne - CaCl2_cost = 207.59 # USD/tonne - limestone_cost = 0 - anode_cost = 1660.716 # USD/tonne - hours = 2000 # hours/position-year - - # All linear OpEx for now - TODO: apply scaling models - labor_opex = labor_rate * capacity * positions * hours # Labor OpEx USD/year - NaOH_varopex = NaOH_ratio * capacity * NaOH_cost # NaOH VarOpEx USD/year - CaCl2_varopex = CaCl2_ratio * capacity * CaCl2_cost # CaCl2 VarOpEx USD/year - limestone_varopex = limestone_ratio * capacity * limestone_cost # CaCl2 VarOpEx USD/year - anode_varopex = anode_ratio * capacity * anode_cost / anode_interval # Anode VarOpEx USD/year - ore_varopex = np.sum(ore_in * ore_price, keepdims=True) # Ore VarOpEx USD/year - elec_varopex = np.sum(elec_in * elec_price, keepdims=True) # Electricity VarOpEx USD/year - - return ( - labor_opex, - NaOH_varopex, - CaCl2_varopex, - limestone_varopex, - anode_varopex, - ore_varopex, - elec_varopex, - ) +from h2integrate.converters.iron.stinn.cost_model import stinn_capex_calc, humbert_opex_calc @define diff --git a/h2integrate/converters/iron/stinn/cost_model.py b/h2integrate/converters/iron/stinn/cost_model.py index ab0967be0..84a483939 100644 --- a/h2integrate/converters/iron/stinn/cost_model.py +++ b/h2integrate/converters/iron/stinn/cost_model.py @@ -16,10 +16,92 @@ CD = Path(__file__).parent +# Physical constants faraday_const = 96485.3321 # Electric charge per mole of electrons (Faraday constant), C/mol +F = 96485.3321 # Faraday constant: Electric charge per mole of electrons (Faraday constant), C/mol +M = 0.055845 # Fe molar mass (kg/mol) -def capex_calc( +def stinn_capex_calc(T, P, p, z, j, A, e, Q, V, N): + """ + Equation (7) from Stinn: doi.org/10.1149.2/2.F06202IF + default values for coefficients defined as globals + """ + # Default coefficents + a1n = 51010 + a1d = -3.82e-03 + a1t = -631 + a2n = 5634000 + a2d = -7.1e-03 + a2t = 349 + a3n = 750000 + e1 = 0.8 + e2 = 0.9 + e3 = 0.15 + e4 = 0.5 + + # Alpha coefficients + a1 = a1n / (1 + np.exp(a1d * (T - a1t))) + a2 = a2n / (1 + np.exp(a2d * (T - a2t))) + a3 = a3n * Q + + # Pre-costs calculation + pre_costs = a1 * P**e1 + + # Electrolysis and product handling contribution to total cost + electrolysis_product_handling = a2 * ((p * z * F) / (j * A * e * M)) ** e2 + + # Power rectifying contribution + power_rectifying_contribution = a3 * V**e3 * N**e4 + + return pre_costs, electrolysis_product_handling, power_rectifying_contribution, a1, a2, a3 + + +def humbert_opex_calc( + capacity, + positions, + NaOH_ratio, + CaCl2_ratio, + limestone_ratio, + anode_ratio, + anode_interval, + ore_in, + ore_price, + elec_in, + elec_price, +): + """ + Calculations in Excel spreadsheet SI of Humbert doi.org/10.1149.2/2.F06202IF + """ + # Default costs - adjusted to 2018 to match Stinn via CPI + labor_rate = 55.90 # USD/person-hour + NaOH_cost = 415.179 # USD/tonne + CaCl2_cost = 207.59 # USD/tonne + limestone_cost = 0 + anode_cost = 1660.716 # USD/tonne + hours = 2000 # hours/position-year + + # All linear OpEx for now - TODO: apply scaling models + labor_opex = labor_rate * capacity * positions * hours # Labor OpEx USD/year + NaOH_varopex = NaOH_ratio * capacity * NaOH_cost # NaOH VarOpEx USD/year + CaCl2_varopex = CaCl2_ratio * capacity * CaCl2_cost # CaCl2 VarOpEx USD/year + limestone_varopex = limestone_ratio * capacity * limestone_cost # CaCl2 VarOpEx USD/year + anode_varopex = anode_ratio * capacity * anode_cost / anode_interval # Anode VarOpEx USD/year + ore_varopex = np.sum(ore_in * ore_price, keepdims=True) # Ore VarOpEx USD/year + elec_varopex = np.sum(elec_in * elec_price, keepdims=True) # Electricity VarOpEx USD/year + + return ( + labor_opex, + NaOH_varopex, + CaCl2_varopex, + limestone_varopex, + anode_varopex, + ore_varopex, + elec_varopex, + ) + + +def plot_capex_calc( a1n, a1d, a1t, a2n, a2d, a2t, a3n, e1, e2, e3, e4, T, P, p, z, F, j, A, e, M, Q, V, N ): # Pre-costs calculation @@ -96,7 +178,6 @@ def main(config): e4 = coeffs["exp_4"] metal_dict = { - "Fe": [0.5, 0, 0], "Al": [0, 0, 0], "Mg": [0, 0.5, 1], "Na": [0, 1, 0], @@ -126,14 +207,11 @@ def main(config): N = metal_df["Cell Count"].values Q = Q * N - F, E, R, a1, a2, a3 = capex_calc( + F, E, R, a1, a2, a3 = plot_capex_calc( a1n, a1d, a1t, a2n, a2d, a2t, a3n, e1, e2, e3, e4, T, P, p, z, f, j, A, e, M, Q, V, 1 ) plt.plot(cap, (F + E + R) / P, "-", color=color) - # plt.plot(T, a1/10**4, '.', color=metal_dict["Al"]) - # plt.plot(T, a2/10**6, '.', color=metal_dict["Cl2"]) - # plt.plot(T, a3/10**6, '.', color=metal_dict["Cu"]) # Assign inputs from config T = config.electrolysis_temp # Electrolysis temperature (°C) @@ -150,7 +228,7 @@ def main(config): N = config.rectifier_lines # Number of rectifier lines pre_costs, electrolysis_product_handling, power_rectifying_contribution, a1, a2, a3 = ( - capex_calc( + plot_capex_calc( a1n, a1d, a1t, a2n, a2d, a2t, a3, e1, e2, e3, e4, T, P, p, z, f, j, A, e, M, Q, V, N ) ) From 149ee57ba37a2a12975c831364c6861a4c32c679 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Mon, 12 Jan 2026 13:56:39 -0700 Subject: [PATCH 14/29] Small fixes to PR --- .../21_iron_mn_to_il/plant_config_old.yaml | 2 +- .../pysam_options_6000MW.yaml | 413 ------------------ .../converters/iron/stinn/cost_model.py | 6 +- 3 files changed, 4 insertions(+), 417 deletions(-) delete mode 100644 examples/27_iron_electrowinning/pysam_options_6000MW.yaml diff --git a/examples/21_iron_mn_to_il/plant_config_old.yaml b/examples/21_iron_mn_to_il/plant_config_old.yaml index 52de27387..86a345298 100644 --- a/examples/21_iron_mn_to_il/plant_config_old.yaml +++ b/examples/21_iron_mn_to_il/plant_config_old.yaml @@ -24,7 +24,7 @@ site: technology_interconnections: [ ["iron_mine","iron_plant","iron_ore","iron_transport"], ["iron_transport","iron_plant","iron_transport_cost"], - ["finance_subgroup_iron_ore","iron_plant","price_iron_ore_"], + ["finance_subgroup_iron_ore","iron_plant","price_iron_ore"], ] plant: diff --git a/examples/27_iron_electrowinning/pysam_options_6000MW.yaml b/examples/27_iron_electrowinning/pysam_options_6000MW.yaml deleted file mode 100644 index 8b59bf2b4..000000000 --- a/examples/27_iron_electrowinning/pysam_options_6000MW.yaml +++ /dev/null @@ -1,413 +0,0 @@ -Turbine: - wind_resource_shear: 0.14 - wind_turbine_ct_curve: - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.912 - - 0.89325 - - 0.8745 - - 0.8675 - - 0.8605 - - 0.85025 - - 0.84 - - 0.83275 - - 0.8255 - - 0.82175 - - 0.818 - - 0.8175 - - 0.817 - - 0.81425 - - 0.8115 - - 0.8115 - - 0.8115 - - 0.8085 - - 0.8055 - - 0.7985 - - 0.7915 - - 0.7765 - - 0.7615 - - 0.73625 - - 0.711 - - 0.68025 - - 0.6495 - - 0.61525 - - 0.581 - - 0.54625 - - 0.5115 - - 0.47825 - - 0.445 - - 0.41675 - - 0.3885 - - 0.36275 - - 0.337 - - 0.31625 - - 0.2955 - - 0.27675 - - 0.258 - - 0.24325 - - 0.2285 - - 0.21725 - - 0.206 - - 0.19325 - - 0.1805 - - 0.1735 - - 0.1665 - - 0.15775 - - 0.149 - - 0.143 - - 0.137 - - 0.13175 - - 0.1265 - - 0.119 - - 0.1115 - - 0.10925 - - 0.107 - - 0.10275 - - 0.0985 - - 0.09425 - - 0.09 - - 0.086 - - 0.082 - - 0.08075 - - 0.0795 - - 0.076 - - 0.0725 - - 0.06975 - - 0.067 - - 0.06325 - - 0.0595 - - 0.05825 - - 0.057 - - 0.0535 - - 0.05 - - 0.049 - - 0.048 - - 0.04475 - - 0.0415 - - 0.0405 - - 0.0395 - - 0.039 - - 0.0385 - - 0.03525 - - 0.032 - - 0.0315 - - 0.031 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - wind_turbine_hub_ht: 115.0 - wind_turbine_max_cp: 0.474457866 - wind_turbine_powercurve_powerout: - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 181.0 - - 229.0 - - 294.0 - - 377.0 - - 473.0 - - 581.0 - - 698.0 - - 822.0 - - 952.0 - - 1090.0 - - 1241.0 - - 1410.0 - - 1598.0 - - 1805.0 - - 2027.0 - - 2262.0 - - 2509.0 - - 2769.0 - - 3050.0 - - 3356.0 - - 3687.0 - - 4027.0 - - 4355.0 - - 4650.0 - - 4897.0 - - 5101.0 - - 5275.0 - - 5429.0 - - 5571.0 - - 5699.0 - - 5806.0 - - 5886.0 - - 5934.0 - - 5957.0 - - 5966.0 - - 5970.0 - - 5976.0 - - 5984.0 - - 5992.0 - - 5999.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 6000.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - - 0.0 - wind_turbine_powercurve_windspeeds: - - 0.0 - - 0.25 - - 0.5 - - 0.75 - - 1.0 - - 1.25 - - 1.5 - - 1.75 - - 2.0 - - 2.25 - - 2.5 - - 2.75 - - 3.0 - - 3.25 - - 3.5 - - 3.75 - - 4.0 - - 4.25 - - 4.5 - - 4.75 - - 5.0 - - 5.25 - - 5.5 - - 5.75 - - 6.0 - - 6.25 - - 6.5 - - 6.75 - - 7.0 - - 7.25 - - 7.5 - - 7.75 - - 8.0 - - 8.25 - - 8.5 - - 8.75 - - 9.0 - - 9.25 - - 9.5 - - 9.75 - - 10.0 - - 10.25 - - 10.5 - - 10.75 - - 11.0 - - 11.25 - - 11.5 - - 11.75 - - 12.0 - - 12.25 - - 12.5 - - 12.75 - - 13.0 - - 13.25 - - 13.5 - - 13.75 - - 14.0 - - 14.25 - - 14.5 - - 14.75 - - 15.0 - - 15.25 - - 15.5 - - 15.75 - - 16.0 - - 16.25 - - 16.5 - - 16.75 - - 17.0 - - 17.25 - - 17.5 - - 17.75 - - 18.0 - - 18.25 - - 18.5 - - 18.75 - - 19.0 - - 19.25 - - 19.5 - - 19.75 - - 20.0 - - 20.25 - - 20.5 - - 20.75 - - 21.0 - - 21.25 - - 21.5 - - 21.75 - - 22.0 - - 22.25 - - 22.5 - - 22.75 - - 23.0 - - 23.25 - - 23.5 - - 23.75 - - 24.0 - - 24.25 - - 24.5 - - 24.75 - - 25.0 - - 26.0 - - 27.0 - - 28.0 - - 29.0 - - 30.0 - - 31.0 - - 32.0 - - 33.0 - - 34.0 - - 35.0 - - 36.0 - - 37.0 - - 38.0 - - 39.0 - - 40.0 - - 41.0 - - 42.0 - - 43.0 - - 44.0 - - 45.0 - - 46.0 - - 47.0 - - 48.0 - - 49.0 -Farm: - wind_farm_wake_model: 0.0 - wind_resource_turbulence_coeff: 0.1 -Losses: - avail_bop_loss: 0.5 - avail_grid_loss: 1.5 - avail_turb_loss: 3.58 - elec_eff_loss: 1.91 - elec_parasitic_loss: 0.1 - env_degrad_loss: 1.8 - env_env_loss: 0.4 - env_exposure_loss: 0.0 - env_icing_loss: 0.21 - ops_env_loss: 1.0 - ops_grid_loss: 0.84 - ops_load_loss: 0.99 - ops_strategies_loss: 0.0 - turb_generic_loss: 1.7 - turb_hysteresis_loss: 0.4 - turb_perf_loss: 1.1 - turb_specific_loss: 0.81 - wake_ext_loss: 1.1 - wake_future_loss: 0.0 - wake_int_loss: 0.0 -Resource: - weibull_k_factor: 2.0 - weibull_reference_height: 50.0 - weibull_wind_speed: 7.25 - wind_resource_model_choice: 0.0 -Uncertainty: - total_uncert: 12.085 diff --git a/h2integrate/converters/iron/stinn/cost_model.py b/h2integrate/converters/iron/stinn/cost_model.py index 84a483939..55629a24c 100644 --- a/h2integrate/converters/iron/stinn/cost_model.py +++ b/h2integrate/converters/iron/stinn/cost_model.py @@ -134,7 +134,7 @@ def main(config): config (object): Configuration object containing model inputs, including: cost_model (dict): Dictionary with the file path to cost coefficients. electrolysis_temp (float): Electrolysis temperature in degrees Celsius (°C). - pressure (float): System pressure. + intsalled capacity (float): Installed capacity in tonnes per year (t/y). production_rate (float): Production rate in kilograms per second (kg/s). electron_moles (int): Moles of electrons per mole of product. faraday_const (float): Faraday constant in coulombs per mole (C/mol). @@ -149,7 +149,7 @@ def main(config): Returns: dict: A dictionary containing: - pre_costs (float): Pre-costs related to pressure and system preparation. + pre_costs (float): Pre-costs related to capacity and system preparation. electrowinning_costs (float): Costs associated with electrolysis and power rectification. total_costs (float): Sum of pre-costs and electrowinning costs. @@ -257,7 +257,7 @@ def __init__(self): self.cost_model = {"coeffs_fp": "cost_coeffs.csv", "inputs_fp": "table1.csv"} # Example values for each variable (replace with actual values) self.electrolysis_temp = 1000 # Temperature in °C, example value - self.capacity = 1.5 # Pressure, example value + self.capacity = 1.5 # Installed capacity (t/y) self.production_rate = 1.0 # Total production rate, kg/s self.electron_moles = 3 # Moles of electrons per mole of product, example value self.current_density = 5000 # Current density, A/m², example value From efa47cc567555a5563af71b7892e644752f8c1c0 Mon Sep 17 00:00:00 2001 From: kbrunik Date: Mon, 12 Jan 2026 17:36:01 -0600 Subject: [PATCH 15/29] start of unit test --- .../converters/iron/test/test_humbert_ewin.py | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 h2integrate/converters/iron/test/test_humbert_ewin.py diff --git a/h2integrate/converters/iron/test/test_humbert_ewin.py b/h2integrate/converters/iron/test/test_humbert_ewin.py new file mode 100644 index 000000000..f42be6776 --- /dev/null +++ b/h2integrate/converters/iron/test/test_humbert_ewin.py @@ -0,0 +1,101 @@ +import pytest +import openmdao.api as om +from pytest import fixture + +from h2integrate import EXAMPLE_DIR +from h2integrate.core.inputs.validation import load_driver_yaml +from h2integrate.converters.iron.humbert_ewin_perf import HumbertEwinPerformanceComponent + + +@fixture +def plant_config(): + plant_config = { + "plant": { + "plant_life": 30, + "simulation": { + "n_timesteps": 8760, + "dt": 3600, + }, + }, + "finance_parameters": { + "cost_adjustment_parameters": { + "cost_year_adjustment_inflation": 0.025, + "target_dollar_year": 2022, + } + }, + } + return plant_config + + +@fixture +def driver_config(): + driver_config = load_driver_yaml(EXAMPLE_DIR / "27_iron_electrowinning" / "driver_config.yaml") + return driver_config + + +@fixture +def tech_config(): + tech_config = { + "model_inputs": { + "shared_parameters": { + "electrolysis_type": "ahe", + }, + "performance_parameters": { + "ore_fe_wt_pct": 65.0, + "capacity_mw": 600.0, + }, + } + } + return tech_config + + +@fixture +def feedstocks_dict(): + feedstocks_dict = { + "electricity": { + "rated_capacity": 600000.0, # kW + "units": "kW", + "price": 0.05802, # $/kWh + }, + "iron_ore": { + "rated_capacity": 281426, # kg/h + "units": "kg/h", + "price": 27.5409, # USD/kg TODO: update + }, + } + return feedstocks_dict + + +def test_humbert_ewin_performance_component( + plant_config, driver_config, tech_config, feedstocks_dict, subtests +): + expected_sponge_iron_out = 1602439024.4 # kg/year + + prob = om.Problem() + + iron_ewin_perf = HumbertEwinPerformanceComponent( + plant_config=plant_config, tech_config=tech_config, driver_config={} + ) + + prob.model.add_subsystem("perf", iron_ewin_perf, promotes=["*"]) + prob.setup() + + for feedstock_name, feedstock_info in feedstocks_dict.items(): + prob.set_val( + f"perf.{feedstock_name}_in", + feedstock_info["rated_capacity"], + units=feedstock_info["units"], + ) + + prob.run_model() + + annual_sponge_iron = prob.get_val("perf.total_sponge_iron_produced", units="kg/year") + + with subtests.test("sponge_iron_out"): + assert ( + pytest.approx( + annual_sponge_iron, + rel=1e-3, + ) + == expected_sponge_iron_out + ) From 502aa8cceadb519713b761be80256b7af4a5c980 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Mon, 12 Jan 2026 17:05:06 -0700 Subject: [PATCH 16/29] Testing example --- examples/test/test_all_examples.py | 30 ++++++++++++++ h2integrate/converters/iron/stinn/table1.txt | 43 -------------------- 2 files changed, 30 insertions(+), 43 deletions(-) delete mode 100644 h2integrate/converters/iron/stinn/table1.txt diff --git a/examples/test/test_all_examples.py b/examples/test/test_all_examples.py index d8de2fbe3..f1277068c 100644 --- a/examples/test/test_all_examples.py +++ b/examples/test/test_all_examples.py @@ -1415,3 +1415,33 @@ def test_21_iron_dri_eaf_example(subtests): with subtests.test("Value check on LCOS"): lcos = h2i.model.get_val("finance_subgroup_steel.LCOS", units="USD/t")[0] assert pytest.approx(lcos, rel=1e-4) == 524.8228189073025 + + +def test_27_iron_electrowinning_example(subtests): + from h2integrate.tools.run_cases import modify_tech_config, load_tech_config_cases + + os.chdir(EXAMPLE_DIR / "27_iron_electrowinning") + + model = H2IntegrateModel("27_iron_electrowinning.yaml") + + # Load cases + case_file = Path("test_inputs.csv") + cases = load_tech_config_cases(case_file) + + with subtests.test("Value check on AHE"): + model = modify_tech_config(model, cases["AHE"]) + model.run() + lcoi = model.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/kg")[0] + assert pytest.approx(lcoi, rel=1e-4) == 1.057242764725443 + + with subtests.test("Value check on MSE"): + model = modify_tech_config(model, cases["MSE"]) + model.run() + lcoi = model.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/kg")[0] + assert pytest.approx(lcoi, rel=1e-4) == 2.2103327773319883 + + with subtests.test("Value check on MOE"): + model = modify_tech_config(model, cases["MOE"]) + model.run() + lcoi = model.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/kg")[0] + assert pytest.approx(lcoi, rel=1e-4) == 1.1525394007265573 diff --git a/h2integrate/converters/iron/stinn/table1.txt b/h2integrate/converters/iron/stinn/table1.txt deleted file mode 100644 index 62cea5061..000000000 --- a/h2integrate/converters/iron/stinn/table1.txt +++ /dev/null @@ -1,43 +0,0 @@ - Capacity [kt/y] Capex [Year USD] Capex/ Capacity [year USD/ (kt/y)] Year Year CEI 2018 CEI Capex [2018 USD] Capex/ Capacity [2018 USD/ (kt/y)] Cell Count Temperature [C] Current Density [A/m^2] Current efficiency Operating potential [V] Specific energy [kWh/kg] Electrode area/ cell [m^2] Current / cell [kA] Power / cell [MW] Electrons per product Product molar mass [kg] Yearly productivity / cell [kt/y] Notes -Fe AHE "$1,077 " "$1,077 " 2018 607 607 "$1,077 " "$1,077 " 0.055845 Humbert -Fe MSE 7.41 "$10,947 " "$10,947 " 2018 607 607 "$10,947 " "$10,947 " 1 900 10000 0.9 1.79 0.635 30 300 0.537 3 0.055845 7.41 Humbert -Fe MOE 0.587 "$4,902 " "$4,902 " 2018 607 607 "$4,902 " "$4,902 " 1 1600 10000 0.9 0.82 3.67 30 300 0.246 2 0.055845 0.587 Humbert -Al 415 "$2,937,550,460 " "$7,070 " 2008 576 607 "$3,095,647,794 " "$7,451 " 517 1000 10000 0.95 4.18 16.5 30 300 1.254 3 0.027 0.80270793 1 line Rectifier - 415 "$2,920,930,656 " "$7,030 " 2008 576 607 "$3,078,133,521 " "$7,408 " 517 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 1 line Rectifier - 588 "$3,691,655,708 " "$6,275 " 2008 576 607 "$3,890,338,568 " "$6,613 " 732 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 1 line Rectifier - 588 "$3,753,428,433 " "$6,380 " 2008 576 607 "$3,955,435,866 " "$6,723 " 732 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 1 line Rectifier - 735 "$4,894,017,677 " "$6,655 " 2008 576 607 "$5,157,410,990 " "$7,013 " 915 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 1 line Rectifier - 735 "$4,360,860,229 " "$5,930 " 2008 576 607 "$4,595,559,304 " "$6,249 " 915 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 2 line Rectifier - 882 "$4,994,766,050 " "$5,660 " 2008 576 607 "$5,263,581,584 " "$5,965 " 1098 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 2 line Rectifier - 882 "$5,599,256,288 " "$6,345 " 2008 576 607 "$5,900,605,150 " "$6,686 " 1098 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 1 line Rectifier - 1177 "$6,712,636,117 " "$5,705 " 2008 576 607 "$7,073,906,463 " "$6,012 " 1465 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 2 line Rectifier - 1177 "$6,518,493,267 " "$5,540 " 2008 576 607 "$6,869,314,953 " "$5,838 " 1465 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 2 line Rectifier - 1691 "$8,676,861,694 " "$5,130 " 2008 576 607 "$9,143,845,570 " "$5,406 " 2105 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 2 line Rectifier - 500 "$3,250,000,000 " "$6,500 " 2001 394 607 "$5,006,979,695 " "$10,014 " 622 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 2 line Rectifier - 353 "$1,899,846,000 " "$5,382 " 2008 576 608 "$2,005,393,000 " "$5,681 " 439 1000 10000 0.95 4.18 30 300 1.25 3 0.027 0.85 1 line Rectifier -Mg 50 "$400,000,000 " "$8,000 " 2001 394 607 "$616,243,655 " "$12,325 " 41 750 6000 0.9 6 60 360 2.16 2 0.024 1.23 IG Cell - 109 "$359,700,000 " "$3,300 " 1979 239 607 "$913,547,699 " "$8,381 " 88 750 6000 0.9 6 60 360 2.16 2 0.024 1.23 IG Cell - 22 "$72,600,000 " "$3,300 " 1979 239 607 "$184,385,774 " "$8,381 " 18 750 6000 0.9 6 60 360 2.16 2 0.024 1.23 IG Cell - 4.5 "$14,850,000 " "$3,300 " 1979 239 607 "$37,715,272 " "$8,381 " 4 750 6000 0.9 6 60 360 2.16 2 0.024 1.23 IG Cell - 22.5 "$74,250,000 " "$3,300 " 1979 239 607 "$188,576,360 " "$8,381 " 18 750 6000 0.9 6 60 360 2.16 2 0.024 1.23 IG Cell -Na 50 "$82,500,000 " "$1,650 " 1979 239 607 "$209,529,289 " "$4,191 " 14 600 10000 0.85 5.7 60 600 3.42 1 0.023 3.67 Downs Cell -Zn 100 "$250,000,000 " "$2,500 " 2006 500 607 "$303,500,000 " "$3,035 " 766 50 300 0.85 3.5 50 15 0.053 2 0.065 0.13 - 100 "$60,000,000 " $600 1974 164.4 607 "$221,532,847 " "$2,215 " 766 50 300 0.85 3.5 50 15 0.053 2 0.065 0.13 - 120 "$508,500,000 " "$4,238 " 2011 590 607 "$523,151,695 " "$4,360 " 919 50 300 0.85 3.5 50 15 0.053 2 0.065 0.13 -Cu 1.1 "$2,600,000 " "$2,398 " 1980 261 607 "$6,046,743 " "$5,577 " 15 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW - 1.7 "$3,700,000 " "$2,239 " 1980 261 607 "$8,604,981 " "$5,207 " 23 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW - 5 "$12,400,000 " "$2,481 " 1980 261 607 "$28,838,314 " "$5,770 " 70 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW - 4.6 "$9,800,000 " "$2,140 " 1980 261 607 "$22,791,571 " "$4,977 " 64 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW - 15.1 "$26,000,000 " "$1,727 " 1980 261 607 "$60,467,433 " "$4,016 " 210 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW - 22.7 "$32,700,000 " "$1,441 " 1980 261 607 "$76,049,425 " "$3,351 " 316 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW - 21.2 "$33,500,000 " "$1,579 " 1980 261 607 "$77,909,962 " "$3,672 " 296 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW - 27 "$100,000,000 " "$3,703 " 1998 390 607 "$155,641,026 " "$5,764 " 377 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW - 1.3 "$3,000,000 " "$2,253 " 1980 390 607 "$4,669,231 " "$3,507 " 19 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW - 14.6 "$25,300,000 " "$1,728 " 1980 390 607 "$39,377,179 " "$2,689 " 204 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW - 50 "$268,500,000 " "$5,370 " 2014 580 607 "$280,999,138 " "$5,620 " 697 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW - 60 "$298,000,000 " "$4,966 " 2007 530 607 "$341,294,340 " "$5,688 " 837 40 300 0.8 3.5 30 9 0.032 2 0.064 0.07 SX-EW -Cl2 200 "$106,000,000 " $530 1980 261 607 "$246,521,073 " "$1,233 " 126 90 2700 0.96 3.79 55 149 0.563 2 0.071 1.59 diaphragm - 200 "$111,500,000 " $558 1980 261 607 "$259,312,261 " "$1,297 " 126 90 2700 0.96 3.79 55 149 0.563 2 0.071 1.59 membrane - 200 "$112,800,000 " $564 1980 261 607 "$262,335,632 " "$1,312 " 126 90 2700 0.96 3.79 55 149 0.563 2 0.071 1.59 membrane + inert anodes - 166 "$111,000,000 " $669 1990 358 607 "$188,203,911 " "$1,134 " 105 90 2700 0.96 3.79 55 149 0.563 2 0.071 1.59 - 880 "$194,868,534 " $221 1980 261 607 "$453,200,000 " $515 554 90 2700 0.96 3.79 55 149 0.563 2 0.071 1.59 From 8c79a30a9a9f84c9965965d842b3bc48435e5996 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Mon, 12 Jan 2026 18:25:38 -0700 Subject: [PATCH 17/29] Expanding unit tests --- .../converters/iron/humbert_ewin_perf.py | 8 +- .../converters/iron/test/test_humbert_ewin.py | 101 --------- .../iron/test/test_humbert_ewin_perf.py | 197 ++++++++++++++++++ .../iron/test/test_humbert_stinn_ewin_cost.py | 191 +++++++++++++++++ 4 files changed, 389 insertions(+), 108 deletions(-) delete mode 100644 h2integrate/converters/iron/test/test_humbert_ewin.py create mode 100644 h2integrate/converters/iron/test/test_humbert_ewin_perf.py create mode 100644 h2integrate/converters/iron/test/test_humbert_stinn_ewin_cost.py diff --git a/h2integrate/converters/iron/humbert_ewin_perf.py b/h2integrate/converters/iron/humbert_ewin_perf.py index 8983bd70c..4380b11b4 100644 --- a/h2integrate/converters/iron/humbert_ewin_perf.py +++ b/h2integrate/converters/iron/humbert_ewin_perf.py @@ -64,7 +64,7 @@ def setup(self): 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"] + self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] # Parse inputs elec_in = inputs["electricity_in"] @@ -73,12 +73,6 @@ def compute(self, inputs, outputs): 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 diff --git a/h2integrate/converters/iron/test/test_humbert_ewin.py b/h2integrate/converters/iron/test/test_humbert_ewin.py deleted file mode 100644 index f42be6776..000000000 --- a/h2integrate/converters/iron/test/test_humbert_ewin.py +++ /dev/null @@ -1,101 +0,0 @@ -import pytest -import openmdao.api as om -from pytest import fixture - -from h2integrate import EXAMPLE_DIR -from h2integrate.core.inputs.validation import load_driver_yaml -from h2integrate.converters.iron.humbert_ewin_perf import HumbertEwinPerformanceComponent - - -@fixture -def plant_config(): - plant_config = { - "plant": { - "plant_life": 30, - "simulation": { - "n_timesteps": 8760, - "dt": 3600, - }, - }, - "finance_parameters": { - "cost_adjustment_parameters": { - "cost_year_adjustment_inflation": 0.025, - "target_dollar_year": 2022, - } - }, - } - return plant_config - - -@fixture -def driver_config(): - driver_config = load_driver_yaml(EXAMPLE_DIR / "27_iron_electrowinning" / "driver_config.yaml") - return driver_config - - -@fixture -def tech_config(): - tech_config = { - "model_inputs": { - "shared_parameters": { - "electrolysis_type": "ahe", - }, - "performance_parameters": { - "ore_fe_wt_pct": 65.0, - "capacity_mw": 600.0, - }, - } - } - return tech_config - - -@fixture -def feedstocks_dict(): - feedstocks_dict = { - "electricity": { - "rated_capacity": 600000.0, # kW - "units": "kW", - "price": 0.05802, # $/kWh - }, - "iron_ore": { - "rated_capacity": 281426, # kg/h - "units": "kg/h", - "price": 27.5409, # USD/kg TODO: update - }, - } - return feedstocks_dict - - -def test_humbert_ewin_performance_component( - plant_config, driver_config, tech_config, feedstocks_dict, subtests -): - expected_sponge_iron_out = 1602439024.4 # kg/year - - prob = om.Problem() - - iron_ewin_perf = HumbertEwinPerformanceComponent( - plant_config=plant_config, tech_config=tech_config, driver_config={} - ) - - prob.model.add_subsystem("perf", iron_ewin_perf, promotes=["*"]) - prob.setup() - - for feedstock_name, feedstock_info in feedstocks_dict.items(): - prob.set_val( - f"perf.{feedstock_name}_in", - feedstock_info["rated_capacity"], - units=feedstock_info["units"], - ) - - prob.run_model() - - annual_sponge_iron = prob.get_val("perf.total_sponge_iron_produced", units="kg/year") - - with subtests.test("sponge_iron_out"): - assert ( - pytest.approx( - annual_sponge_iron, - rel=1e-3, - ) - == expected_sponge_iron_out - ) diff --git a/h2integrate/converters/iron/test/test_humbert_ewin_perf.py b/h2integrate/converters/iron/test/test_humbert_ewin_perf.py new file mode 100644 index 000000000..853f9a3dc --- /dev/null +++ b/h2integrate/converters/iron/test/test_humbert_ewin_perf.py @@ -0,0 +1,197 @@ +import pytest +import openmdao.api as om +from pytest import fixture + +from h2integrate import EXAMPLE_DIR +from h2integrate.core.inputs.validation import load_driver_yaml +from h2integrate.converters.iron.humbert_ewin_perf import HumbertEwinPerformanceComponent +from h2integrate.converters.iron.humbert_stinn_ewin_cost import HumbertStinnEwinCostComponent + + +@fixture +def plant_config(): + plant_config = { + "plant": { + "plant_life": 30, + "simulation": { + "n_timesteps": 8760, + "dt": 3600, + }, + }, + "finance_parameters": { + "cost_adjustment_parameters": { + "cost_year_adjustment_inflation": 0.025, + "target_dollar_year": 2022, + } + }, + } + return plant_config + + +@fixture +def driver_config(): + driver_config = load_driver_yaml(EXAMPLE_DIR / "27_iron_electrowinning" / "driver_config.yaml") + return driver_config + + +@fixture +def tech_config(): + tech_config = { + "model_inputs": { + "shared_parameters": { + "electrolysis_type": "ahe", + }, + "performance_parameters": { + "ore_fe_wt_pct": 65.0, + "capacity_mw": 600.0, + }, + } + } + return tech_config + + +@fixture +def feedstocks_dict(): + feedstocks_dict = { + "electricity": { + "rated_capacity": 600000.0, # kW + "units": "kW", + "price": 0.05802, # $/kWh + }, + "iron_ore": { + "rated_capacity": 237794.77, # kg/h + "units": "kg/h", + "price": 27.5409, # USD/kg TODO: update + }, + } + return feedstocks_dict + + +def setup_and_run(plant_config, driver_config, tech_config, feedstocks_dict): + prob = om.Problem() + + iron_ewin_perf = HumbertEwinPerformanceComponent( + plant_config=plant_config, tech_config=tech_config, driver_config={} + ) + + iron_ewin_cost = HumbertStinnEwinCostComponent( + plant_config=plant_config, tech_config=tech_config, driver_config={} + ) + + prob.model.add_subsystem("perf", iron_ewin_perf, promotes=["*"]) + prob.model.add_subsystem("cost", iron_ewin_cost, promotes=["*"]) + prob.setup() + + for feedstock_name, feedstock_info in feedstocks_dict.items(): + prob.set_val( + f"perf.{feedstock_name}_in", + feedstock_info["rated_capacity"], + units=feedstock_info["units"], + ) + + prob.run_model() + + elec_consumed = prob.get_val("perf.electricity_consumed", units="kW") + iron_out = prob.get_val("perf.total_sponge_iron_produced", units="kg/year") + iron_cap = prob.get_val("perf.output_capacity", units="kg/year") + + return elec_consumed, iron_out, iron_cap + + +def test_humbert_ewin_performance_component( + plant_config, driver_config, tech_config, feedstocks_dict, subtests +): + expected_elec_consumption_ahe = 506978.45 # kW + expected_elec_consumption_mse = 452725.57 # kW + expected_elec_consumption_moe = 567259.43 # kW + expected_sponge_iron_out_ahe = 1354003425.43 # kg/y + expected_sponge_iron_out_mse = 1354003425.43 # kg/y + expected_sponge_iron_out_moe = 1354003425.43 # kg/y + expected_output_capacity_ahe = 1602439024.39 # kg/y + expected_output_capacity_mse = 1794469102.08 # kg/y + expected_output_capacity_moe = 1432152588.56 # kg/y + + tech_config["model_inputs"]["shared_parameters"]["electrolysis_type"] = "ahe" + elec_consumed, iron_out, iron_cap = setup_and_run( + plant_config, driver_config, tech_config, feedstocks_dict + ) + with subtests.test("ahe_electricity"): + assert ( + pytest.approx( + elec_consumed, + rel=1e-3, + ) + == expected_elec_consumption_ahe + ) + with subtests.test("ahe_production"): + assert ( + pytest.approx( + iron_out, + rel=1e-3, + ) + == expected_sponge_iron_out_ahe + ) + with subtests.test("ahe_capacity"): + assert ( + pytest.approx( + iron_cap, + rel=1e-3, + ) + == expected_output_capacity_ahe + ) + tech_config["model_inputs"]["shared_parameters"]["electrolysis_type"] = "mse" + elec_consumed, iron_out, iron_cap = setup_and_run( + plant_config, driver_config, tech_config, feedstocks_dict + ) + with subtests.test("mse_electricity"): + assert ( + pytest.approx( + elec_consumed, + rel=1e-3, + ) + == expected_elec_consumption_mse + ) + with subtests.test("mse_production"): + assert ( + pytest.approx( + iron_out, + rel=1e-3, + ) + == expected_sponge_iron_out_mse + ) + with subtests.test("mse_capacity"): + assert ( + pytest.approx( + iron_cap, + rel=1e-3, + ) + == expected_output_capacity_mse + ) + tech_config["model_inputs"]["shared_parameters"]["electrolysis_type"] = "moe" + elec_consumed, iron_out, iron_cap = setup_and_run( + plant_config, driver_config, tech_config, feedstocks_dict + ) + with subtests.test("moe_electricity"): + assert ( + pytest.approx( + elec_consumed, + rel=1e-3, + ) + == expected_elec_consumption_moe + ) + with subtests.test("moe_production"): + assert ( + pytest.approx( + iron_out, + rel=1e-3, + ) + == expected_sponge_iron_out_moe + ) + with subtests.test("moe_capacity"): + assert ( + pytest.approx( + iron_cap, + rel=1e-3, + ) + == expected_output_capacity_moe + ) diff --git a/h2integrate/converters/iron/test/test_humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/test/test_humbert_stinn_ewin_cost.py new file mode 100644 index 000000000..7a0871c5d --- /dev/null +++ b/h2integrate/converters/iron/test/test_humbert_stinn_ewin_cost.py @@ -0,0 +1,191 @@ +import pytest +import openmdao.api as om +from pytest import fixture + +from h2integrate import EXAMPLE_DIR +from h2integrate.core.inputs.validation import load_driver_yaml +from h2integrate.converters.iron.humbert_ewin_perf import HumbertEwinPerformanceComponent +from h2integrate.converters.iron.humbert_stinn_ewin_cost import HumbertStinnEwinCostComponent + + +@fixture +def plant_config(): + plant_config = { + "plant": { + "plant_life": 30, + "simulation": { + "n_timesteps": 8760, + "dt": 3600, + }, + }, + "finance_parameters": { + "cost_adjustment_parameters": { + "cost_year_adjustment_inflation": 0.025, + "target_dollar_year": 2022, + } + }, + } + return plant_config + + +@fixture +def driver_config(): + driver_config = load_driver_yaml(EXAMPLE_DIR / "27_iron_electrowinning" / "driver_config.yaml") + return driver_config + + +@fixture +def tech_config(): + tech_config = { + "model_inputs": { + "shared_parameters": { + "electrolysis_type": "ahe", + }, + "performance_parameters": { + "ore_fe_wt_pct": 65.0, + "capacity_mw": 600.0, + }, + } + } + return tech_config + + +@fixture +def feedstocks_dict(): + feedstocks_dict = { + "electricity": { + "rated_capacity": 600000.0, # kW + "units": "kW", + "price": 0.05802, # $/kWh + }, + "iron_ore": { + "rated_capacity": 237794.77, # kg/h + "units": "kg/h", + "price": 27.5409, # USD/kg TODO: update + }, + } + return feedstocks_dict + + +def setup_and_run(plant_config, driver_config, tech_config, feedstocks_dict): + prob = om.Problem() + + iron_ewin_perf = HumbertEwinPerformanceComponent( + plant_config=plant_config, tech_config=tech_config, driver_config={} + ) + + iron_ewin_cost = HumbertStinnEwinCostComponent( + plant_config=plant_config, tech_config=tech_config, driver_config={} + ) + + prob.model.add_subsystem("perf", iron_ewin_perf, promotes=["*"]) + prob.model.add_subsystem("cost", iron_ewin_cost, promotes=["*"]) + prob.setup() + + for feedstock_name, feedstock_info in feedstocks_dict.items(): + prob.set_val( + f"perf.{feedstock_name}_in", + feedstock_info["rated_capacity"], + units=feedstock_info["units"], + ) + + prob.run_model() + + capex = prob.get_val("cost.CapEx", units="USD") + fopex = prob.get_val("cost.OpEx", units="USD/year") + vopex = prob.get_val("cost.VarOpEx", units="USD/year") + + return capex, fopex, vopex + + +def test_humbert_stinn_ewin_cost_component( + plant_config, driver_config, tech_config, feedstocks_dict, subtests +): + expected_capex_ahe = 6038571901.89 # USD + expected_fopex_ahe = 67050786.5 # USD/year + expected_vopex_ahe = 835954.89 # USD/year + expected_capex_mse = 19918313452.1 # USD + expected_fopex_mse = 51295503.77 # USD/year + expected_vopex_mse = 1220341.02 # USD/year + expected_capex_moe = 7307164315.34 # USD + expected_fopex_moe = 21761330.82 # USD/year + expected_vopex_moe = 3316122.05 # USD/year + + tech_config["model_inputs"]["shared_parameters"]["electrolysis_type"] = "ahe" + capex, fopex, vopex = setup_and_run(plant_config, driver_config, tech_config, feedstocks_dict) + with subtests.test("ahe_capex"): + assert ( + pytest.approx( + capex, + rel=1e-3, + ) + == expected_capex_ahe + ) + with subtests.test("ahe_fopex"): + assert ( + pytest.approx( + fopex, + rel=1e-3, + ) + == expected_fopex_ahe + ) + with subtests.test("ahe_vopex"): + assert ( + pytest.approx( + vopex, + rel=1e-3, + ) + == expected_vopex_ahe + ) + tech_config["model_inputs"]["shared_parameters"]["electrolysis_type"] = "mse" + capex, fopex, vopex = setup_and_run(plant_config, driver_config, tech_config, feedstocks_dict) + with subtests.test("mse_capex"): + assert ( + pytest.approx( + capex, + rel=1e-3, + ) + == expected_capex_mse + ) + with subtests.test("mse_fopex"): + assert ( + pytest.approx( + fopex, + rel=1e-3, + ) + == expected_fopex_mse + ) + with subtests.test("mse_vopex"): + assert ( + pytest.approx( + vopex, + rel=1e-3, + ) + == expected_vopex_mse + ) + tech_config["model_inputs"]["shared_parameters"]["electrolysis_type"] = "moe" + capex, fopex, vopex = setup_and_run(plant_config, driver_config, tech_config, feedstocks_dict) + with subtests.test("moe_capex"): + assert ( + pytest.approx( + capex, + rel=1e-3, + ) + == expected_capex_moe + ) + with subtests.test("moe_fopex"): + assert ( + pytest.approx( + fopex, + rel=1e-3, + ) + == expected_fopex_moe + ) + with subtests.test("moe_vopex"): + assert ( + pytest.approx( + vopex, + rel=1e-3, + ) + == expected_vopex_moe + ) From d30a44acd9ecb52658103dc1cd0f64c90b7d638e Mon Sep 17 00:00:00 2001 From: kbrunik Date: Tue, 13 Jan 2026 08:39:11 -0600 Subject: [PATCH 18/29] fix import --- h2integrate/converters/iron/humbert_stinn_ewin_cost.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index 05871a7e6..ef7b67ab5 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -1,9 +1,9 @@ import numpy as np from attrs import field, define -from h2integrate.core.utilities import CostModelBaseConfig, merge_shared_inputs +from h2integrate.core.utilities import merge_shared_inputs from h2integrate.core.validators import contains, must_equal -from h2integrate.core.model_baseclasses import CostModelBaseClass +from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig from h2integrate.converters.iron.stinn.cost_model import stinn_capex_calc, humbert_opex_calc From 3992372920a93aa643b9dc800b1150d981bb6127 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Tue, 13 Jan 2026 12:01:00 -0700 Subject: [PATCH 19/29] Docstrings begun --- CHANGELOG.md | 1 + .../27_iron_electrowinning.yaml | 2 +- .../converters/iron/humbert_ewin_perf.py | 52 ++++++++++++++++++- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3b6b93e6..7bfd31cb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Updates models for NumPy version 2.4.0 - Update test values for WOMBAT update to 0.13.0 - Added standlone iron DRI and steel EAF performance and cost models +- Added iron electrowinning model - Added capability to have transport models that require user input parameters - Add geologic hydrogen surface processing converter - Add baseclass for caching functionality diff --git a/examples/27_iron_electrowinning/27_iron_electrowinning.yaml b/examples/27_iron_electrowinning/27_iron_electrowinning.yaml index 33588912d..e600534b0 100644 --- a/examples/27_iron_electrowinning/27_iron_electrowinning.yaml +++ b/examples/27_iron_electrowinning/27_iron_electrowinning.yaml @@ -1,6 +1,6 @@ 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." +system_summary: "An iron plant using processed ore pellets for electrowinning." driver_config: "driver_config.yaml" technology_config: "tech_config.yaml" diff --git a/h2integrate/converters/iron/humbert_ewin_perf.py b/h2integrate/converters/iron/humbert_ewin_perf.py index 4380b11b4..f38022f87 100644 --- a/h2integrate/converters/iron/humbert_ewin_perf.py +++ b/h2integrate/converters/iron/humbert_ewin_perf.py @@ -1,3 +1,21 @@ +"""Iron electronwinning performance model based on Humbert et al. + +This module contains H2I performance configs and components for modeling iron electrowinning. It is +based of the work of Humbert et al. (doi.org/10.1007/s40831-024-00878-3) which reviews performance +and TEA literature for three different types of iron electrowinning: + - Aqueous Hydroxide Electrolysis (AHE) + - Molten Salt Electrolysis (MSE) + - Molten Oxide Electrolysis (MOE) + +This technology is selected in the tech_config as the performance_model. +"humbert_electrowinning_performance" + +Classes: + HumbertEwinConfig: Sets the required model_inputs fields. + HumbertEwinPerformanceComponent: Defines initialize(), setup(), and compute() methods. + +""" + import numpy as np import openmdao.api as om from attrs import field, define @@ -8,6 +26,18 @@ @define class HumbertEwinConfig(BaseConfig): + """Configuration class for the Humbert iron electrowinning performance model. + + Args: + electrolysis_type (str): The type of electrowinning being performed. Options: + "ahe": Aqueous Hydroxide Electrolysis (AHE) + "mse": Molten Salt Electrolysis (MSE) + "moe": Molten Oxide Electrolysis (MOE) + ore_fe_wt_pct (float): The iron content of the ore coming in, expressed as a percentage. + capacity_mw (float): The MW electrical capacity of the electrowinning plant. + + """ + electrolysis_type: str = field( kw_only=True, converter=(str.lower, str.strip), validator=contains(["ahe", "mse", "moe"]) ) # product selection @@ -16,8 +46,26 @@ class HumbertEwinConfig(BaseConfig): class HumbertEwinPerformanceComponent(om.ExplicitComponent): - """ - Humbert: doi.org/10.1007/s40831-024-00878-3 + """OpenMDAO component for the Humbert iron electrowinning performance model. + + Inputs: + electricity_in (array): Electric power input available in kW for each timestep. + iron_ore_in (array): Iron ore mass flow available in kg/h for each timestep. + ore_fe_wt_pct (float): The iron content of the ore coming in, expressed as a percentage. + spec_energy_cons_fe (float): The specific electrical energy consumption required to win + pure iron (Fe) from iron ore. These are currently calculated as averages between the + high and low stated values in Table 10 of Humbert et al., but this is exposed as an + OpenMDAO variable to probe the effect of specific energy consumption on iron cost. + capacity (float): The electrical capacity of the electrowinning plant in MW. + + Outputs: + electricity_consumed (array): Electric power consumption in kW for each timestep. + limiting_input (array): An array of integers indicating which input is the limiting factor + for iron production at each timestep: 0 = iron ore, 1 = electricity, 2 = capacity + sponge_iron_out (array): Sponge iron production in kg/h for each timestep. + total_sponge_iron_produced (float): Total annual sponge iron production in kg/y. + output_capacity (float): Maximum possible annual sponge iron production in kg/y. + """ def initialize(self): From 6845b1387f239312e7f16689e6bd007616632a9e Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Tue, 13 Jan 2026 18:11:42 -0700 Subject: [PATCH 20/29] Docstrings plus a little reorg --- .../converters/iron/humbert_ewin_perf.py | 35 +++--- .../iron/humbert_stinn_ewin_cost.py | 102 +++++++++++++++--- 2 files changed, 113 insertions(+), 24 deletions(-) diff --git a/h2integrate/converters/iron/humbert_ewin_perf.py b/h2integrate/converters/iron/humbert_ewin_perf.py index f38022f87..d22966ef5 100644 --- a/h2integrate/converters/iron/humbert_ewin_perf.py +++ b/h2integrate/converters/iron/humbert_ewin_perf.py @@ -1,13 +1,13 @@ """Iron electronwinning performance model based on Humbert et al. This module contains H2I performance configs and components for modeling iron electrowinning. It is -based of the work of Humbert et al. (doi.org/10.1007/s40831-024-00878-3) which reviews performance +based on the work of Humbert et al. (doi.org/10.1007/s40831-024-00878-3) which reviews performance and TEA literature for three different types of iron electrowinning: - Aqueous Hydroxide Electrolysis (AHE) - Molten Salt Electrolysis (MSE) - Molten Oxide Electrolysis (MOE) -This technology is selected in the tech_config as the performance_model. +This technology is selected in the tech_config as the performance_model "humbert_electrowinning_performance" Classes: @@ -81,22 +81,30 @@ def setup(self): ) ewin_type = self.config.electrolysis_type - # Lookup specific energy consumption from Humbert Table 10 + # Look up performance parameters for each electrolysis type from Humbert Table 10 if ewin_type == "ahe": - spec_energy_cons_lo = 2.781 - spec_energy_cons_hi = 3.779 + E_all_lo = 2.781 + E_all_hi = 3.779 + E_electrolysis_lo = 1.869 + E_electrolysis_hi = 2.72 elif ewin_type == "mse": - spec_energy_cons_lo = 2.720 - spec_energy_cons_hi = 3.138 + E_all_lo = 2.720 + E_all_hi = 3.138 + E_electrolysis_lo = 1.81 + E_electrolysis_hi = 2.08 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 + E_all_lo = 2.89 + E_all_hi = 4.45 + E_electrolysis_lo = 2.89 + E_electrolysis_hi = 4.45 + E_all = (E_all_lo + E_all_hi) / 2 # kWh/kg_Fe + E_electrolysis = (E_electrolysis_lo + E_electrolysis_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("spec_energy_all", val=E_all, units="kW*h/kg") + self.add_input("spec_energy_electrolysis", val=E_electrolysis, units="kW*h/kg") self.add_input("capacity", val=self.config.capacity_mw, units="MW") self.add_output( @@ -110,6 +118,7 @@ def setup(self): 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") + self.add_output("specific_energy_electrolysis", val=0.0, units="kW*h/kg") def compute(self, inputs, outputs): self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] @@ -118,7 +127,8 @@ def compute(self, inputs, outputs): 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"] + kwh_kg_fe = inputs["spec_energy_all"] + kwh_kg_electrolysis = inputs["spec_energy_electrolysis"] cap_kw = inputs["capacity"] * 1000 # Calculate max iron production for each input @@ -145,3 +155,4 @@ def compute(self, inputs, outputs): outputs["electricity_consumed"] = elec_consume outputs["total_sponge_iron_produced"] = np.sum(fe_prod) outputs["output_capacity"] = cap_kw / kwh_kg_fe * 8760 + outputs["specific_energy_electrolysis"] = kwh_kg_electrolysis diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index ef7b67ab5..1083bdd06 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -1,3 +1,21 @@ +"""Iron electronwinning cost model based on Humbert et al. and Stinn and Allanore + +This module contains H2I cost configs and components for modeling iron electrowinning. It is +based on the work of Humbert et al. (doi.org/10.1007/s40831-024-00878-3), which contains relevant +iron electrowinning performance and cost data, and Stinn & Allanore (doi.org/10.1149.2/2.F06202IF), +which presents an empirical capex model for electrowinning of many different metals based on many +physical parameters of the electrowinning process. The capex model developed by Stinn & Allanore is +imported from ./stinn/cost_model.py. This is combined with on + +This technology is selected in the tech_config as the cost_model +"humbert_stinn_electrowinning_cost" + +Classes: + HumbertEwinCostConfig: Sets the required model_inputs fields. + HumbertEwinCostComponent: Defines initialize(), setup(), and compute() methods. + +""" + import numpy as np from attrs import field, define @@ -9,6 +27,17 @@ @define class HumbertStinnEwinCostConfig(CostModelBaseConfig): + """Configuration class for the Humbert iron electrowinning performance model. + + Args: + electrolysis_type (str): The type of electrowinning being performed. Options: + "ahe": Aqueous Hydroxide Electrolysis (AHE) + "mse": Molten Salt Electrolysis (MSE) + "moe": Molten Oxide Electrolysis (MOE) + cost_year (int): The dollar year of costs output by the model. Defaults to 2018, the dollar + year in which data was given in the Stinn paper + """ + electrolysis_type: str = field( kw_only=True, converter=(str.lower, str.strip), validator=contains(["ahe", "mse", "moe"]) ) # product selection @@ -17,9 +46,53 @@ class HumbertStinnEwinCostConfig(CostModelBaseConfig): class HumbertStinnEwinCostComponent(CostModelBaseClass): - """ - Humbert: doi.org/10.1007/s40831-024-00878-3 - Stinn: doi.org/10.1149.2/2.F06202IF + """OpenMDAO component for the Humbert/Stinn iron electrowinning cost model. + + Default values for many inputs are set for 3 technology classes: + - Aqueous Hydroxide Electrolysis (AHE) + - Molten Salt Electrolysis (MSE) + - Molten Oxide Electrolysis (MOE) + All of these values come from the SI spreadsheet for the Humbert paper that can be downloaded + at doi.org/10.1007/s40831-024-00878-3 except for the default anode replacement interval. + These are exposed to OpenMDAO for potential future optimization/sensitivity analysis. + + Inputs: + output_capacity (float): + iron_ore_in (array): Iron ore mass flow available in kg/h for each timestep. + iron_transport_cost (float): + price_iron_ore (float) + electricity_in (array): Electric power input available in kW for each timestep. + price_electricity (float): + specific_energy_electrolysis (float): The specific electrical energy consumption required + to win pure iron (Fe) from iron ore - JUST the electrolysis step. + electrolysis_temp (float): Electrolysis temperature (°C). + electron_moles (float): Moles of electrons per mole of iron product. + current_density (float): Current density (A/m²). + electrode_area (float): Electrode area per cell (m²). + current_efficiency (float): Current efficiency (dimensionless). + cell_voltage (float): Cell operating voltage (V). + rectifier_lines (float): Number of rectifier lines. + positions (float): Labor rate (position-years/tonne). + NaOH_ratio (float): Ratio of NaOH consumed to Fe produced. + CaCl2_ratio (float): Ratio of CaCl2 consumed to Fe produced. + limestone_ratio (float): Ratio of limestone consumed to Fe produced. + anode_ratio (float): Ratio of annode mass to annual iron production. + anode_replacement_interval (float): Replacement interval of anodes (years) + + Outputs: + CapEx (float): Total capital cost of the electrowinning plant (USD). + OpEx (float): Yearly operating expenses in USD/year which do NOT depend on plant output. + VarOpEx (float): Yearly operating expenses in USD/year which DO depend on plant output. + processing_capex (float): Portion of the capex that is apportioned to preprocessing of ore. + electrolysis_capex (float): Portion of the capex that is apportioned to electrolysis. + rectifier_capex (float): Portion of the capex that is apportioned to rectifiers. + labor_opex (float): Portion of the opex that is apportioned to labor. + NaOH_opex (float): Portion of the opex that is apportioned to NaOH. + CaCl2_opex (float): Portion of the opex that is apportioned to CaCl2. + limestone_opex (float): Portion of the opex that is apportioned to limestone. + anode_opex (float): Portion of the opex that is apportioned to anodes. + ore_opex (float): Portion of the opex that is apportioned to ore. + elec_opex (float): Portion of the opex that is apportioned to electricity. """ def initialize(self): @@ -37,7 +110,8 @@ def setup(self): ewin_type = self.config.electrolysis_type - # Lookup specific inputs for electrowinning types from Humbert SI except where noted + # Lookup specific inputs for electrowinning types, mostly from the Humbert SI spreadsheet + # (noted where values did not come from this spreadsheet) if ewin_type == "ahe": # AHE - Capex T = 100 # Electrolysis temperature (°C) @@ -47,8 +121,6 @@ def setup(self): A = 250 # Electrode area per cell (m²) e = 0.66 # Current efficiency (dimensionless) N = 12 # Number of rectifier lines - # E_spec taken from Humbert Table 10 - average of Low and High estimates - E_spec = (1.869 + 2.72) / 2 # Specific energy of electrolysis (kWh/kg-Fe) # AHE - Opex positions = 739.2 / 2e6 # Labor rate (position-years/tonne) @@ -68,8 +140,6 @@ def setup(self): A = 250 # Electrode area per cell (m²) e = 0.66 # Current efficiency (dimensionless) N = 8 # Number of rectifier lines - # E_spec taken from Humbert Table 10 - average of Low and High estimates - E_spec = (1.81 + 2.08) / 2 # Specific energy of electrolysis (kWh/kg-Fe) # MSE - Opex positions = 499.2 / 2e6 # Labor rate (position-years/tonne) @@ -89,8 +159,6 @@ def setup(self): A = 30 # Electrode area per cell (m²) e = 0.95 # Current efficiency (dimensionless) N = 6 # Number of rectifier lines - # E_spec taken from Humbert Table 10 - average of Low and High estimates - E_spec = (2.89 + 4.45) / 2 # Specific energy of electrolysis (kWh/kg-Fe) # AHE - Opex positions = 230.4 / 2e6 # Labor rate (position-years/tonne) @@ -108,6 +176,7 @@ def setup(self): self.add_input("price_iron_ore", val=0.0, units="USD/Mg") self.add_input("electricity_in", val=0.0, shape=n_timesteps, units="kW") self.add_input("price_electricity", val=0.0, units="USD/kW/h") + self.add_input("specific_energy_electrolysis", val=0.0, units="kW*h/kg") # Set inputs for Stinn Capex model self.add_input("electrolysis_temp", val=T, units="C") @@ -117,7 +186,6 @@ def setup(self): self.add_input("current_efficiency", val=e, units=None) self.add_input("cell_voltage", val=V, units="V") self.add_input("rectifier_lines", val=N, units=None) - self.add_input("specific_energy", val=E_spec, units="kW*h/kg") # Set inputs for Humbert Opex model self.add_input("positions", val=positions, units="year/Mg") @@ -150,7 +218,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): e = inputs["current_efficiency"] V = inputs["cell_voltage"] N = inputs["rectifier_lines"] - E_spec = inputs["specific_energy"] + E_spec = inputs["specific_energy_electrolysis"] P = inputs["output_capacity"] p = P * 1000 / 8760 / 3600 # kg/s @@ -197,3 +265,13 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): ) outputs["OpEx"] = np.sum(opex_breakdown) outputs["VarOpEx"] = np.sum(opex_breakdown[1:]) + outputs["processing_capex"] = capex_breakdown[0] + outputs["electrolysis_capex"] = capex_breakdown[1] + outputs["rectifier_capex"] = capex_breakdown[2] + outputs["labor_opex"] = opex_breakdown[0] + outputs["NaOH_opex"] = opex_breakdown[1] + outputs["CaCl2_opex"] = opex_breakdown[2] + outputs["limestone_opex"] = opex_breakdown[3] + outputs["anode_opex"] = opex_breakdown[4] + outputs["ore_opex"] = opex_breakdown[5] + outputs["elec_opex"] = opex_breakdown[6] From 9229ed7818e6ef519ad355b3c79336136b5f1254 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Tue, 13 Jan 2026 18:37:44 -0700 Subject: [PATCH 21/29] Everything but the docs page --- docs/_toc.yml | 1 + docs/technology_models/iron_ewin.md | 3 +++ docs/user_guide/model_overview.md | 9 +++++++-- 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 docs/technology_models/iron_ewin.md diff --git a/docs/_toc.yml b/docs/_toc.yml index b4baf5171..4aeaf10a2 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -41,6 +41,7 @@ parts: - file: technology_models/geologic_hydrogen.md - file: technology_models/grid - file: technology_models/hydrogen_storage.md + - file: technology_models/iron_ewin.md - caption: Resource Models chapters: diff --git a/docs/technology_models/iron_ewin.md b/docs/technology_models/iron_ewin.md new file mode 100644 index 000000000..ab9afc3b2 --- /dev/null +++ b/docs/technology_models/iron_ewin.md @@ -0,0 +1,3 @@ +# Iron electrowinning models + +I'll write it tomorrow... diff --git a/docs/user_guide/model_overview.md b/docs/user_guide/model_overview.md index 03adfa715..f5fe73d6a 100644 --- a/docs/user_guide/model_overview.md +++ b/docs/user_guide/model_overview.md @@ -44,7 +44,7 @@ The inputs, outputs, and corresponding technology that are currently available i | `hopp` | electricity | N/A | | `electrolyzer` | hydrogen | electricity | | `geoh2` | hydrogen | rock type | -| `steel` | steel | hydrogen | +| `steel` | steel | iron ore | | `ammonia` | ammonia | nitrogen, hydrogen | | `doc` | co2 | electricity | | `oae` | co2 | electricity | @@ -225,13 +225,18 @@ Below summarizes the available performance, cost, and financial models for each + `'iron_mine_performance_martin'` - cost models: + `'iron_mine_cost_martin'` -- `iron_dri`: iron ore direct reduction +- `iron_dri`: direct reduced iron - performance models: + `'ng_dri_performance_rosner'` + `'h2_dri_performance_rosner'` - cost models: + `'ng_dri_cost_rosner'` + `'h2_dri_cost_rosner'` +- `iron_ewin`: iron electrowinning + - performance models: + + `'humbert_electrowinning_performance'` + - cost models: + + `'humbert_stinn_electrowinning_cost'` (transport-models)= ## Transport Models From bde4c2d9bc95ecaf126ed3cf4d2049683f0f73af Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Wed, 14 Jan 2026 11:34:15 -0700 Subject: [PATCH 22/29] Finished documentation --- docs/technology_models/iron_ewin.md | 44 ++++++++++++++++++- examples/27_iron_electrowinning/run_iron.py | 15 +++++++ .../iron/humbert_stinn_ewin_cost.py | 7 ++- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/docs/technology_models/iron_ewin.md b/docs/technology_models/iron_ewin.md index ab9afc3b2..e812ffbe6 100644 --- a/docs/technology_models/iron_ewin.md +++ b/docs/technology_models/iron_ewin.md @@ -1,3 +1,45 @@ # Iron electrowinning models -I'll write it tomorrow... +H2I contains models to simulate to separation of mostly pure iron from iron oxides. +The main input feedstock is iron ore, while the output commodity is "sponge iron", i.e. iron that is typically brittle ("spongey") and contains less carbon than most steel alloys. +This sponge iron can then be connected to an electric arc furnace (EAF) to produce steel. + +There are currently three iron electrowinning processes modeled in H2I: + - Aqueous Hydroxide Electrolysis (AHE) + - Molten Salt Electrolysis (MSE) + - Molten Oxide Electrolysis (MOE) + +In reality, the exact composition and structure of the resulting sponge iron will differ depending on the process and the conditions. +Currently, H2I models do not make these distinctions, as the technology is new and we are still building out the capability. +Instead, the models in their current form are based on two recent studies of electrowinning technology as a whole. + +The first study is by Humbert et al. (doi.org/10.1007/s40831-024-00878-3), who focus specifically on iron and the three technologies above. +These authors gather information on the specific energy required for electrolysis and associated pretreatments needed, which is applied in the `humbert_electrowinning_performance` performance model. +In their supporting information, they also model the full operational expenditures for each process, which is applied in the `humbert_stinn_electrowinning_cost` cost model. + +The second study is by Stinn & Allanore (doi.org/10.1149.2/2.F06202IF), who present a generalized capital cost model for electrowinning of many different metals. +These authors use both cost data and physical parameters from existing studies to fit the model to be applicable to any metal, including iron. +This model is applied in the `humbert_stinn_electrowinning_cost` cost model. + +To use this model, specify `"humbert_electrowinning_performance"` as the performance model and `"humbert_stinn_electrowinning_cost"` as the cost model. + +## Shared Parameters + +- `electrolysis_type`: The type of electrolysis used for electrowinning. Options are: + - `"ahe"`: Aqueous Hydroxide Electrolysis + - `"mse"`: Molten Salt Electrolysis + - `"moe"`: Molten Oxide Electrolysis + +# Performance Parameters + +- `ore_fe_wt_pct`: The percentage by weight of iron in the input ore. +- `capacity_mw`: The electrical capacity in MW of the electrowinning plant. + +## Cost Parameters + +None. The `cost_year` is automatically set to 2018 to match the Stinn/Allanore source values. Be sure to set `target_dollar_year` in the `finanace_parameters` to match your desired output dollar year in the finance calcluations. + +## Required Feedstocks + +- `iron_ore_in`: Iron ore to be used for electrowinning. +- `electricity_in`: Electricity used to reduce iron ore to sponge iron. diff --git a/examples/27_iron_electrowinning/run_iron.py b/examples/27_iron_electrowinning/run_iron.py index 21e95d181..732e911ff 100644 --- a/examples/27_iron_electrowinning/run_iron.py +++ b/examples/27_iron_electrowinning/run_iron.py @@ -1,3 +1,18 @@ +"""Comparing three different iron electrowinning technologies + +This script runs an end-to-end iron production system (including the mine) and compareds the +levelized cost of sponge_iron across three different iron electrowinning technologies to see +how their costs compare: + - Aqueous Hydroxide Electrolysis (AHE) + - Molten Salt Electrolysis (MSE) + - Molten Oxide Electrolysis (MOE) + +New users may find it helpful to look at the tech_config.yaml (particularly the iron_plant) to see +how the technologies were set up, as well as the plant_config.yaml (particularly the +technology_interconnections) to see how the technologies were connected. + +""" + from pathlib import Path from h2integrate.tools.run_cases import modify_tech_config, load_tech_config_cases diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index 1083bdd06..eef4c0b96 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -4,8 +4,11 @@ based on the work of Humbert et al. (doi.org/10.1007/s40831-024-00878-3), which contains relevant iron electrowinning performance and cost data, and Stinn & Allanore (doi.org/10.1149.2/2.F06202IF), which presents an empirical capex model for electrowinning of many different metals based on many -physical parameters of the electrowinning process. The capex model developed by Stinn & Allanore is -imported from ./stinn/cost_model.py. This is combined with on +physical parameters of the electrowinning process. + + + +The capex model developed by Stinn & Allanore is imported from ./stinn/cost_model.py This technology is selected in the tech_config as the cost_model "humbert_stinn_electrowinning_cost" From d3af3903ab5ca05d042976face706051b704dd8c Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Wed, 14 Jan 2026 11:41:53 -0700 Subject: [PATCH 23/29] Split off humbert cost_model --- .../converters/iron/humbert/cost_model.py | 46 +++++++++++++++++++ .../iron/humbert_stinn_ewin_cost.py | 5 +- 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 h2integrate/converters/iron/humbert/cost_model.py diff --git a/h2integrate/converters/iron/humbert/cost_model.py b/h2integrate/converters/iron/humbert/cost_model.py new file mode 100644 index 000000000..d3fb4e647 --- /dev/null +++ b/h2integrate/converters/iron/humbert/cost_model.py @@ -0,0 +1,46 @@ +""" +Calculations in Excel spreadsheet SI of Humbert doi.org/10.1149.2/2.F06202IF +""" + +import numpy as np + + +def humbert_opex_calc( + capacity, + positions, + NaOH_ratio, + CaCl2_ratio, + limestone_ratio, + anode_ratio, + anode_interval, + ore_in, + ore_price, + elec_in, + elec_price, +): + # Default costs - adjusted to 2018 to match Stinn via CPI + labor_rate = 55.90 # USD/person-hour + NaOH_cost = 415.179 # USD/tonne + CaCl2_cost = 207.59 # USD/tonne + limestone_cost = 0 + anode_cost = 1660.716 # USD/tonne + hours = 2000 # hours/position-year + + # All linear OpEx for now - TODO: apply scaling models + labor_opex = labor_rate * capacity * positions * hours # Labor OpEx USD/year + NaOH_varopex = NaOH_ratio * capacity * NaOH_cost # NaOH VarOpEx USD/year + CaCl2_varopex = CaCl2_ratio * capacity * CaCl2_cost # CaCl2 VarOpEx USD/year + limestone_varopex = limestone_ratio * capacity * limestone_cost # CaCl2 VarOpEx USD/year + anode_varopex = anode_ratio * capacity * anode_cost / anode_interval # Anode VarOpEx USD/year + ore_varopex = np.sum(ore_in * ore_price, keepdims=True) # Ore VarOpEx USD/year + elec_varopex = np.sum(elec_in * elec_price, keepdims=True) # Electricity VarOpEx USD/year + + return ( + labor_opex, + NaOH_varopex, + CaCl2_varopex, + limestone_varopex, + anode_varopex, + ore_varopex, + elec_varopex, + ) diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index eef4c0b96..bb1135883 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -6,7 +6,7 @@ which presents an empirical capex model for electrowinning of many different metals based on many physical parameters of the electrowinning process. - +The opex model developed by Humbert et al. is imported from ./humbert/cost_model.py The capex model developed by Stinn & Allanore is imported from ./stinn/cost_model.py @@ -25,7 +25,8 @@ from h2integrate.core.utilities import merge_shared_inputs from h2integrate.core.validators import contains, must_equal from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig -from h2integrate.converters.iron.stinn.cost_model import stinn_capex_calc, humbert_opex_calc +from h2integrate.converters.iron.stinn.cost_model import stinn_capex_calc +from h2integrate.converters.iron.humbert.cost_model import humbert_opex_calc @define From 4e51c3b7f8f387291a13330361090a520ef92887 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Wed, 14 Jan 2026 12:00:25 -0700 Subject: [PATCH 24/29] Config docstrings --- .../27_iron_electrowinning/driver_config.yaml | 2 +- .../27_iron_electrowinning/plant_config.yaml | 16 ++++++++-------- .../27_iron_electrowinning/tech_config.yaml | 18 +++++++++--------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/examples/27_iron_electrowinning/driver_config.yaml b/examples/27_iron_electrowinning/driver_config.yaml index d3b06a03e..c279b7f1b 100644 --- a/examples/27_iron_electrowinning/driver_config.yaml +++ b/examples/27_iron_electrowinning/driver_config.yaml @@ -1,5 +1,5 @@ name: "driver_config" -description: "This analysis runs a hybrid plant to match the first example in H2Integrate" +description: "Simply setting up an outputs folder, nothing fancy" general: folder_output: outputs diff --git a/examples/27_iron_electrowinning/plant_config.yaml b/examples/27_iron_electrowinning/plant_config.yaml index 422aaf2e8..5da27d018 100644 --- a/examples/27_iron_electrowinning/plant_config.yaml +++ b/examples/27_iron_electrowinning/plant_config.yaml @@ -1,5 +1,5 @@ name: "plant_config" -description: "This plant is located in MN, USA..." +description: "Configures an iron electrowinning system." site: latitude: 41.717 @@ -17,16 +17,16 @@ site: } ] - -# 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 + # Connect feedstocks to iron mine. + # Requires `electricity` and `crude ore`. + # Both are set up as generic feedstock components - see tech_config. ["grid_feedstock","iron_mine","electricity","cable"], ["mine_feedstock","iron_mine","crude_ore","pipe"], - # connect feedstocks to iron plant + # Connect feedstocks to iron electrowinning plant. + # Requires `electricity` and `iron_ore`. + # Electricity is set up as generic feedstock component, + # but iron_ore is set up as an output of an iron_mine. ["iron_mine","iron_plant","iron_ore","iron_transport"], ["ewin_grid_feedstock","iron_plant","electricity","cable"], ] diff --git a/examples/27_iron_electrowinning/tech_config.yaml b/examples/27_iron_electrowinning/tech_config.yaml index 30129173c..a7c18ddf0 100644 --- a/examples/27_iron_electrowinning/tech_config.yaml +++ b/examples/27_iron_electrowinning/tech_config.yaml @@ -1,8 +1,8 @@ name: "technology_config" -description: "This hybrid plant produces iron" +description: "Set up technology for an iron mine/iron electrowinning system" technologies: - grid_feedstock: #electricity feedstock for iron ore + grid_feedstock: #electricity feedstock for iron ore - comes from grid performance_model: model: "feedstock_performance" cost_model: @@ -12,14 +12,14 @@ technologies: feedstock_type: "electricity" units: "MW" performance_parameters: - rated_capacity: 30. # MW, need 27.913 MW per timestep for iron ore + rated_capacity: 30. cost_parameters: cost_year: 2022 price: 58.02 #USD/MW annual_cost: 0. start_up_cost: 0. - mine_feedstock: #iron ore feedstock + mine_feedstock: #crude iron ore feedstock - comes from the ground performance_model: model: "feedstock_performance" cost_model: @@ -29,14 +29,14 @@ technologies: feedstock_type: "crude_ore" units: "t/h" performance_parameters: - rated_capacity: 2000. # need 828.50385048 t/h + rated_capacity: 2000. cost_parameters: cost_year: 2022 price: 0.0 annual_cost: 0. start_up_cost: 0. - iron_mine: + iron_mine: # iron mine - turns crude_iron into iron_ore performance_model: model: "iron_mine_performance_martin" cost_model: @@ -47,7 +47,7 @@ technologies: taconite_pellet_type: "drg" max_ore_production_rate_tonnes_per_hr: 250 - iron_transport: + iron_transport: # ship iron_ore from iron_mine to iron_plant performance_model: model: "iron_transport_performance" cost_model: @@ -60,7 +60,7 @@ technologies: transport_year: 2022 cost_year: 2022 - ewin_grid_feedstock: #electricity feedstock for iron dri + ewin_grid_feedstock: #electricity feedstock for iron electrowinning performance_model: model: "feedstock_performance" cost_model: @@ -77,7 +77,7 @@ technologies: annual_cost: 0. start_up_cost: 0. - iron_plant: + iron_plant: # iron plant - turns iron_ore into sponge_iron performance_model: model: "humbert_electrowinning_performance" cost_model: From 674dac191b811059f8f1fa94762b3af894a7ec5c Mon Sep 17 00:00:00 2001 From: John Jasa Date: Fri, 16 Jan 2026 14:48:19 -0700 Subject: [PATCH 25/29] Refactoring iron ewinning calcs for clarity --- docs/technology_models/iron_ewin.md | 8 +- .../27_iron_electrowinning/plant_config.yaml | 12 -- examples/27_iron_electrowinning/run_iron.py | 35 +++-- .../27_iron_electrowinning/test_inputs.csv | 2 - examples/test/test_all_examples.py | 21 +-- .../converters/iron/humbert/cost_model.py | 46 ------- .../iron/humbert_stinn_ewin_cost.py | 126 ++++++++++++------ .../converters/iron/rosner/cost_coeffs.csv | 44 +++--- .../converters/iron/stinn/cost_model.py | 81 +---------- 9 files changed, 140 insertions(+), 235 deletions(-) delete mode 100644 examples/27_iron_electrowinning/test_inputs.csv delete mode 100644 h2integrate/converters/iron/humbert/cost_model.py diff --git a/docs/technology_models/iron_ewin.md b/docs/technology_models/iron_ewin.md index e812ffbe6..070fe48cd 100644 --- a/docs/technology_models/iron_ewin.md +++ b/docs/technology_models/iron_ewin.md @@ -2,7 +2,7 @@ H2I contains models to simulate to separation of mostly pure iron from iron oxides. The main input feedstock is iron ore, while the output commodity is "sponge iron", i.e. iron that is typically brittle ("spongey") and contains less carbon than most steel alloys. -This sponge iron can then be connected to an electric arc furnace (EAF) to produce steel. +This sponge iron can then be used in an electric arc furnace (EAF) to produce steel. There are currently three iron electrowinning processes modeled in H2I: - Aqueous Hydroxide Electrolysis (AHE) @@ -13,11 +13,11 @@ In reality, the exact composition and structure of the resulting sponge iron wil Currently, H2I models do not make these distinctions, as the technology is new and we are still building out the capability. Instead, the models in their current form are based on two recent studies of electrowinning technology as a whole. -The first study is by Humbert et al. (doi.org/10.1007/s40831-024-00878-3), who focus specifically on iron and the three technologies above. +The first study is by [Humbert et al.](doi.org/10.1007/s40831-024-00878-3), who focus specifically on iron and the three technologies above. These authors gather information on the specific energy required for electrolysis and associated pretreatments needed, which is applied in the `humbert_electrowinning_performance` performance model. In their supporting information, they also model the full operational expenditures for each process, which is applied in the `humbert_stinn_electrowinning_cost` cost model. -The second study is by Stinn & Allanore (doi.org/10.1149.2/2.F06202IF), who present a generalized capital cost model for electrowinning of many different metals. +The second study is by [Stinn & Allanore](doi.org/10.1149.2/2.F06202IF), who present a generalized capital cost model for electrowinning of many different metals. These authors use both cost data and physical parameters from existing studies to fit the model to be applicable to any metal, including iron. This model is applied in the `humbert_stinn_electrowinning_cost` cost model. @@ -37,7 +37,7 @@ To use this model, specify `"humbert_electrowinning_performance"` as the perform ## Cost Parameters -None. The `cost_year` is automatically set to 2018 to match the Stinn/Allanore source values. Be sure to set `target_dollar_year` in the `finanace_parameters` to match your desired output dollar year in the finance calcluations. +None. The `cost_year` is automatically set to 2018 to match the Stinn/Allanore source values. Be sure to set `target_dollar_year` in the `finanace_parameters` to match your desired output dollar year in the finance calculations. ## Required Feedstocks diff --git a/examples/27_iron_electrowinning/plant_config.yaml b/examples/27_iron_electrowinning/plant_config.yaml index 5da27d018..97a88c831 100644 --- a/examples/27_iron_electrowinning/plant_config.yaml +++ b/examples/27_iron_electrowinning/plant_config.yaml @@ -5,18 +5,6 @@ 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], - } - ] - technology_interconnections: [ # Connect feedstocks to iron mine. # Requires `electricity` and `crude ore`. diff --git a/examples/27_iron_electrowinning/run_iron.py b/examples/27_iron_electrowinning/run_iron.py index 732e911ff..3fd77f81b 100644 --- a/examples/27_iron_electrowinning/run_iron.py +++ b/examples/27_iron_electrowinning/run_iron.py @@ -1,6 +1,6 @@ """Comparing three different iron electrowinning technologies -This script runs an end-to-end iron production system (including the mine) and compareds the +This script runs an end-to-end iron production system (including the mine) and compares the levelized cost of sponge_iron across three different iron electrowinning technologies to see how their costs compare: - Aqueous Hydroxide Electrolysis (AHE) @@ -13,32 +13,31 @@ """ -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", -] +# Define the electrowinning types as a list +electrolysis_types = ["ahe", "mse", "moe"] lcois = [] -for casename in casenames: - model = modify_tech_config(model, cases[casename]) +for electrolysis_type in electrolysis_types: + # Set the technology config value directly + model.technology_config["technologies"]["iron_plant"]["model_inputs"]["shared_parameters"][ + "electrolysis_type" + ] = electrolysis_type + model.setup() # re-setup the model after changing config model.run() model.post_process() - lcois.append(float(model.model.get_val("finance_subgroup_sponge_iron.price_sponge_iron")[0])) + lcois.append( + float( + model.model.get_val("finance_subgroup_sponge_iron.price_sponge_iron", units="USD/kg")[0] + ) + ) # Compare the LCOIs from each electrowinning type -print(lcois) +print("Levelized Cost of Iron (LCOI) by Electrowinning Type:") +for electrolysis_type, lcoi in zip(electrolysis_types, lcois): + print(f" {electrolysis_type.upper()}: ${lcoi:,.2f} per kg of sponge iron") diff --git a/examples/27_iron_electrowinning/test_inputs.csv b/examples/27_iron_electrowinning/test_inputs.csv deleted file mode 100644 index 4104fc42c..000000000 --- a/examples/27_iron_electrowinning/test_inputs.csv +++ /dev/null @@ -1,2 +0,0 @@ -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 diff --git a/examples/test/test_all_examples.py b/examples/test/test_all_examples.py index 400a817a0..c1b59b37e 100644 --- a/examples/test/test_all_examples.py +++ b/examples/test/test_all_examples.py @@ -1488,30 +1488,33 @@ def test_21_iron_dri_eaf_example(subtests): def test_27_iron_electrowinning_example(subtests): - from h2integrate.tools.run_cases import modify_tech_config, load_tech_config_cases - os.chdir(EXAMPLE_DIR / "27_iron_electrowinning") model = H2IntegrateModel("27_iron_electrowinning.yaml") - # Load cases - case_file = Path("test_inputs.csv") - cases = load_tech_config_cases(case_file) - with subtests.test("Value check on AHE"): - model = modify_tech_config(model, cases["AHE"]) + model.technology_config["technologies"]["iron_plant"]["model_inputs"]["shared_parameters"][ + "electrolysis_type" + ] = "ahe" + model.setup() model.run() lcoi = model.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/kg")[0] assert pytest.approx(lcoi, rel=1e-4) == 1.057242764725443 with subtests.test("Value check on MSE"): - model = modify_tech_config(model, cases["MSE"]) + model.technology_config["technologies"]["iron_plant"]["model_inputs"]["shared_parameters"][ + "electrolysis_type" + ] = "mse" + model.setup() model.run() lcoi = model.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/kg")[0] assert pytest.approx(lcoi, rel=1e-4) == 2.2103327773319883 with subtests.test("Value check on MOE"): - model = modify_tech_config(model, cases["MOE"]) + model.technology_config["technologies"]["iron_plant"]["model_inputs"]["shared_parameters"][ + "electrolysis_type" + ] = "moe" + model.setup() model.run() lcoi = model.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/kg")[0] assert pytest.approx(lcoi, rel=1e-4) == 1.1525394007265573 diff --git a/h2integrate/converters/iron/humbert/cost_model.py b/h2integrate/converters/iron/humbert/cost_model.py deleted file mode 100644 index d3fb4e647..000000000 --- a/h2integrate/converters/iron/humbert/cost_model.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Calculations in Excel spreadsheet SI of Humbert doi.org/10.1149.2/2.F06202IF -""" - -import numpy as np - - -def humbert_opex_calc( - capacity, - positions, - NaOH_ratio, - CaCl2_ratio, - limestone_ratio, - anode_ratio, - anode_interval, - ore_in, - ore_price, - elec_in, - elec_price, -): - # Default costs - adjusted to 2018 to match Stinn via CPI - labor_rate = 55.90 # USD/person-hour - NaOH_cost = 415.179 # USD/tonne - CaCl2_cost = 207.59 # USD/tonne - limestone_cost = 0 - anode_cost = 1660.716 # USD/tonne - hours = 2000 # hours/position-year - - # All linear OpEx for now - TODO: apply scaling models - labor_opex = labor_rate * capacity * positions * hours # Labor OpEx USD/year - NaOH_varopex = NaOH_ratio * capacity * NaOH_cost # NaOH VarOpEx USD/year - CaCl2_varopex = CaCl2_ratio * capacity * CaCl2_cost # CaCl2 VarOpEx USD/year - limestone_varopex = limestone_ratio * capacity * limestone_cost # CaCl2 VarOpEx USD/year - anode_varopex = anode_ratio * capacity * anode_cost / anode_interval # Anode VarOpEx USD/year - ore_varopex = np.sum(ore_in * ore_price, keepdims=True) # Ore VarOpEx USD/year - elec_varopex = np.sum(elec_in * elec_price, keepdims=True) # Electricity VarOpEx USD/year - - return ( - labor_opex, - NaOH_varopex, - CaCl2_varopex, - limestone_varopex, - anode_varopex, - ore_varopex, - elec_varopex, - ) diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index bb1135883..7401cdcea 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -25,8 +25,6 @@ from h2integrate.core.utilities import merge_shared_inputs from h2integrate.core.validators import contains, must_equal from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig -from h2integrate.converters.iron.stinn.cost_model import stinn_capex_calc -from h2integrate.converters.iron.humbert.cost_model import humbert_opex_calc @define @@ -60,6 +58,10 @@ class HumbertStinnEwinCostComponent(CostModelBaseClass): at doi.org/10.1007/s40831-024-00878-3 except for the default anode replacement interval. These are exposed to OpenMDAO for potential future optimization/sensitivity analysis. + We calculate both CapEx and OpEx in this component. + CapEx is calculated using the Stinn & Allanore model. + OpEx is calculated using the Humbert et al. model. + Inputs: output_capacity (float): iron_ore_in (array): Iron ore mass flow available in kg/h for each timestep. @@ -80,7 +82,7 @@ class HumbertStinnEwinCostComponent(CostModelBaseClass): NaOH_ratio (float): Ratio of NaOH consumed to Fe produced. CaCl2_ratio (float): Ratio of CaCl2 consumed to Fe produced. limestone_ratio (float): Ratio of limestone consumed to Fe produced. - anode_ratio (float): Ratio of annode mass to annual iron production. + anode_ratio (float): Ratio of anode mass to annual iron production. anode_replacement_interval (float): Replacement interval of anodes (years) Outputs: @@ -131,7 +133,7 @@ def setup(self): NaOH_ratio = 25130.2 * 0.1 / 2e6 # Ratio of NaOH consumption to annual iron production CaCl2_ratio = 0 # Ratio of CaCl2 consumption to annual iron production limestone_ratio = 0 # Ratio of limestone consumption to annual iron production - anode_ratio = 0 # Ratio of annode mass to annual iron production + anode_ratio = 0 # Ratio of anode mass to annual iron production # Anode replacement interval not considered by Humbert, 3 years assumed here anode_replace_int = 3 # Replacement interval of anodes (years) @@ -150,7 +152,7 @@ def setup(self): NaOH_ratio = 0 # Ratio of NaOH consumption to annual iron production CaCl2_ratio = 23138 * 0.1 / 2e6 # Ratio of CaCl2 consumption to annual iron production limestone_ratio = 0 # Ratio of limestone consumption to annual iron production - anode_ratio = 1589.3 / 2e6 # Ratio of annode mass to annual iron production + anode_ratio = 1589.3 / 2e6 # Ratio of anode mass to annual iron production # Anode replacement interval not considered by Humbert, 3 years assumed here anode_replace_int = 3 # Replacement interval of anodes (years) @@ -169,7 +171,7 @@ def setup(self): NaOH_ratio = 0 # Ratio of NaOH consumption to annual iron production CaCl2_ratio = 0 # Ratio of CaCl2 consumption to annual iron production limestone_ratio = 0 # Ratio of limestone consumption to annual iron production - anode_ratio = 8365.6 / 2e6 # Ratio of annode mass to annual iron production + anode_ratio = 8365.6 / 2e6 # Ratio of anode mass to annual iron production # Anode replacement interval not considered by Humbert, 3 years assumed here anode_replace_int = 3 # Replacement interval of anodes (years) @@ -191,6 +193,11 @@ def setup(self): self.add_input("cell_voltage", val=V, units="V") self.add_input("rectifier_lines", val=N, units=None) + # Set outputs for Stinn Capex model + self.add_output("processing_capex", val=0.0, units="USD") + self.add_output("electrolysis_capex", val=0.0, units="USD") + self.add_output("rectifier_capex", val=0.0, units="USD") + # Set inputs for Humbert Opex model self.add_input("positions", val=positions, units="year/Mg") self.add_input("NaOH_ratio", val=NaOH_ratio, units=None) @@ -199,11 +206,6 @@ def setup(self): self.add_input("anode_ratio", val=anode_ratio, units=None) self.add_input("anode_replacement_interval", val=anode_replace_int, units="year") - # Set outputs for Stinn Capex model - self.add_output("processing_capex", val=0.0, units="USD") - self.add_output("electrolysis_capex", val=0.0, units="USD") - self.add_output("rectifier_capex", val=0.0, units="USD") - # Set outputs for Humbert Opex model self.add_output("labor_opex", val=0.0, units="USD/year") self.add_output("NaOH_opex", val=0.0, units="USD/year") @@ -214,7 +216,11 @@ def setup(self): self.add_output("elec_opex", val=0.0, units="USD/year") def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - # Parse inputs for Stinn Capex model + # Physical constants + F = 96485.3321 # Faraday constant: Electric charge per mole of electrons (C/mol) + M = 0.055845 # Fe molar mass (kg/mol) + + # Parse inputs for Stinn Capex model (doi.org/10.1149/2.F06202IF) T = inputs["electrolysis_temp"] z = inputs["electron_moles"] j = inputs["current_density"] @@ -233,11 +239,41 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): N_cell = P * 1e6 / P_cell # number of cells [-] Q = Q_cell * N_cell / 1e6 # total installed power [MW] - # Execute Stinn capex model - capex_breakdown = stinn_capex_calc(T, P, p, z, j, A, e, Q, V, N) - outputs["CapEx"] = np.sum(capex_breakdown[:3]) - - # Parse inputs for Humbert Opex model + # Stinn Capex model - Equation (7) from doi.org/10.1149/2.F06202IF + # Default coefficients + a1n = 51010 + a1d = -3.82e-03 + a1t = -631 + a2n = 5634000 + a2d = -7.1e-03 + a2t = 349 + a3n = 750000 + e1 = 0.8 + e2 = 0.9 + e3 = 0.15 + e4 = 0.5 + + # Alpha coefficients + a1 = a1n / (1 + np.exp(a1d * (T - a1t))) + a2 = a2n / (1 + np.exp(a2d * (T - a2t))) + a3 = a3n * Q + + # Pre-costs calculation + processing_capex = a1 * P**e1 + + # Electrolysis and product handling contribution to total cost + electrolysis_capex = a2 * ((p * z * F) / (j * A * e * M)) ** e2 + + # Power rectifying contribution + rectifier_capex = a3 * V**e3 * N**e4 + + # Capex outputs + outputs["CapEx"] = processing_capex + electrolysis_capex + rectifier_capex + outputs["processing_capex"] = processing_capex + outputs["electrolysis_capex"] = electrolysis_capex + outputs["rectifier_capex"] = rectifier_capex + + # Parse inputs for Humbert Opex model (doi.org/10.1007/s40831-024-00878-3) positions = inputs["positions"] NaOH_ratio = inputs["NaOH_ratio"] CaCl2_ratio = inputs["CaCl2_ratio"] @@ -253,29 +289,35 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Add ore transport cost TODO: turn iron_transport into proper transporter ore_price += ore_transport_cost - # Execute Humbert opex model - opex_breakdown = humbert_opex_calc( - P, - positions, - NaOH_ratio, - CaCl2_ratio, - limestone_ratio, - anode_ratio, - anode_interval, - ore_in, - ore_price, - elec_in, - elec_price, + # Humbert Opex model - from SI spreadsheet (doi.org/10.1007/s40831-024-00878-3) + # Default costs - adjusted to 2018 to match Stinn via CPI + labor_rate = 55.90 # USD/person-hour + NaOH_cost = 415.179 # USD/tonne + CaCl2_cost = 207.59 # USD/tonne + limestone_cost = 0 + anode_cost = 1660.716 # USD/tonne + hours = 2000 # hours/position-year + + # All linear OpEx for now - TODO: apply scaling models + labor_opex = labor_rate * P * positions * hours # Labor OpEx USD/year + NaOH_opex = NaOH_ratio * P * NaOH_cost # NaOH VarOpEx USD/year + CaCl2_opex = CaCl2_ratio * P * CaCl2_cost # CaCl2 VarOpEx USD/year + limestone_opex = limestone_ratio * P * limestone_cost # Limestone VarOpEx USD/year + anode_opex = anode_ratio * P * anode_cost / anode_interval # Anode VarOpEx USD/year + ore_opex = np.sum(ore_in * ore_price, keepdims=True) # Ore VarOpEx USD/year + elec_opex = np.sum(elec_in * elec_price, keepdims=True) # Electricity VarOpEx USD/year + + # Opex outputs + outputs["OpEx"] = ( + labor_opex + NaOH_opex + CaCl2_opex + limestone_opex + anode_opex + ore_opex + elec_opex + ) + outputs["VarOpEx"] = ( + NaOH_opex + CaCl2_opex + limestone_opex + anode_opex + ore_opex + elec_opex ) - outputs["OpEx"] = np.sum(opex_breakdown) - outputs["VarOpEx"] = np.sum(opex_breakdown[1:]) - outputs["processing_capex"] = capex_breakdown[0] - outputs["electrolysis_capex"] = capex_breakdown[1] - outputs["rectifier_capex"] = capex_breakdown[2] - outputs["labor_opex"] = opex_breakdown[0] - outputs["NaOH_opex"] = opex_breakdown[1] - outputs["CaCl2_opex"] = opex_breakdown[2] - outputs["limestone_opex"] = opex_breakdown[3] - outputs["anode_opex"] = opex_breakdown[4] - outputs["ore_opex"] = opex_breakdown[5] - outputs["elec_opex"] = opex_breakdown[6] + outputs["labor_opex"] = labor_opex + outputs["NaOH_opex"] = NaOH_opex + outputs["CaCl2_opex"] = CaCl2_opex + outputs["limestone_opex"] = limestone_opex + outputs["anode_opex"] = anode_opex + outputs["ore_opex"] = ore_opex + outputs["elec_opex"] = elec_opex diff --git a/h2integrate/converters/iron/rosner/cost_coeffs.csv b/h2integrate/converters/iron/rosner/cost_coeffs.csv index 30dbb8598..7b9420c9c 100644 --- a/h2integrate/converters/iron/rosner/cost_coeffs.csv +++ b/h2integrate/converters/iron/rosner/cost_coeffs.csv @@ -1,27 +1,27 @@ Name,Type,Coeff,Unit,h2_dri_eaf,h2_dri,ng_dri_eaf,ng_dri,h2_eaf,ng_eaf Dollar Year,all,-,-,2022.0,2022.0,2022.0,2022.0,2022.0,2022.0 -EAF & Casting,capital,lin,$,352191.5240169328,9.999999999999783e-11,348441.82271277835,9.999999999999996e-11,352191.5240169328,348441.82271277835 -Shaft Furnace,capital,lin,$,489.68060552071756,498.4011587515349,12706.35540748921,12706.35540748921,9.999999999999783e-11,9.999999999999996e-11 -Reformer,capital,lin,$,0.0,0.0,12585.824569411547,12585.824569411547,0.0,9.999999999999996e-11 -Recycle Compressor,capital,lin,$,0.0,0.0,956.7744259199391,956.7744259199391,0.0,9.999999999999996e-11 -Oxygen Supply,capital,lin,$,1715.21508561117,1364.556123425206,1204.989085989581,1024.5621894371002,393.8307342018522,201.3153857876692 -H2 Pre-heating,capital,lin,$,45.69122789208198,45.69122789208198,0.0,0.0,9.999999999999783e-11,0.0 -Cooling Tower,capital,lin,$,2513.0831352600603,2349.15226276371,1790.4037306002654,1774.6459078322403,195.45026173489327,428.90723393800783 -Piping,capital,lin,$,11815.72718570437,172.83401368816206,25651.756587058582,3917.34823234076,49970.89662834664,51294.534191987485 -Electrical & Instrumentation,capital,lin,$,7877.151460413984,115.2226758287703,17101.171070383258,2611.5654909389123,33313.93113452472,34196.35613320244 -"Buildings, Storage, Water Service",capital,lin,$,1097.818759618821,630.531300861788,1077.3955282629602,618.8012346570356,467.2874587570587,458.59429360590775 -Other Miscellaneous Cost,capital,lin,$,7877.151460413984,115.2226758287703,17101.171070383258,2611.5654909389123,32124.80020341839,34196.35613320244 -EAF & Casting,capital,exp,-,0.4559999999420573,1.3155100341401327e-15,0.45599999989917783,-3.9460856373682605e-16,0.4559999999420573,0.45599999989917783 -Shaft Furnace,capital,exp,-,0.8874110806752277,0.8862693772994416,0.654083508755392,0.654083508755392,1.3155100341401327e-15,-3.9460856373682605e-16 -Reformer,capital,exp,-,0.0,0.0,0.6504523630280986,0.6504523630280986,0.0,-3.9460856373682605e-16 -Recycle Compressor,capital,exp,-,0.0,0.0,0.7100000000012126,0.7100000000012126,0.0,-3.9460856373682605e-16 -Oxygen Supply,capital,exp,-,0.6457441946722564,0.634272491606804,0.6448555996459648,0.6370664161269146,0.6699999996017935,0.6699999991468175 -H2 Pre-heating,capital,exp,-,0.8656365854575331,0.8656365854575331,0.0,0.0,1.3155100341401327e-15,0.0 -Cooling Tower,capital,exp,-,0.6332532740097354,0.6286978709250873,0.6302820063981899,0.6308163319151914,0.6659797264431847,0.25999986981600937 -Piping,capital,exp,-,0.5998309612457874,0.8331608480295299,0.5641056316548689,0.6551154484929355,0.461960765339552,0.45807183935606205 -Electrical & Instrumentation,capital,exp,-,0.599830961215121,0.8331608479997342,0.5641056316058728,0.6551154484197809,0.4619607652382664,0.4580718393497376 -"Buildings, Storage, Water Service",capital,exp,-,0.7999999998942431,0.7999999999824277,0.7999999998654156,0.7999999999666229,0.7999999997752477,0.7999999997288553 -Other Miscellaneous Cost,capital,exp,-,0.599830961215121,0.8331608479997342,0.5641056316058728,0.6551154484197809,0.46389753648982157,0.4580718393497376 +EAF & Casting,capital,lin,$,352191.5240169347,1.0000000000000458e-10,348441.8227127845,9.999999999999286e-11,352191.5240169347,348441.8227127845 +Shaft Furnace,capital,lin,$,489.68060552073365,498.40115875154777,12706.355407490113,12706.355407490113,1.0000000000000458e-10,9.999999999999286e-11 +Reformer,capital,lin,$,0.0,0.0,12585.824569411949,12585.824569411949,0.0,9.999999999999286e-11 +Recycle Compressor,capital,lin,$,0.0,0.0,956.7744259199679,956.7744259199679,0.0,9.999999999999286e-11 +Oxygen Supply,capital,lin,$,1715.215085611161,1364.5561234251998,1204.9890859896068,1024.5621894371275,393.830734201856,201.31538578766953 +H2 Pre-heating,capital,lin,$,45.691227892080605,45.691227892080605,0.0,0.0,1.0000000000000458e-10,0.0 +Cooling Tower,capital,lin,$,2513.0831352601786,2349.1522627637264,1790.403730600302,1774.6459078323237,195.45026173489762,428.90723393800977 +Piping,capital,lin,$,11815.727185704474,172.83401368816038,25651.75658705922,3917.3482323408716,49970.896628346374,51294.53419198639 +Electrical & Instrumentation,capital,lin,$,7877.151460414222,115.2226758287705,17101.171070383716,2611.5654909389796,33313.93113452448,34196.35613320329 +"Buildings, Storage, Water Service",capital,lin,$,1097.818759618822,630.5313008617607,1077.395528263029,618.8012346570433,467.28745875704124,458.5942936059208 +Other Miscellaneous Cost,capital,lin,$,7877.151460414222,115.2226758287705,17101.171070383716,2611.5654909389796,32124.80020341787,34196.35613320329 +EAF & Casting,capital,exp,-,0.4559999999420569,-3.4203260887643445e-15,0.45599999989917644,5.129911328578738e-15,0.4559999999420569,0.45599999989917644 +Shaft Furnace,capital,exp,-,0.8874110806752251,0.8862693772994394,0.6540835087553862,0.6540835087553862,-3.4203260887643445e-15,5.129911328578738e-15 +Reformer,capital,exp,-,0.0,0.0,0.6504523630280962,0.6504523630280962,0.0,5.129911328578738e-15 +Recycle Compressor,capital,exp,-,0.0,0.0,0.7100000000012103,0.7100000000012103,0.0,5.129911328578738e-15 +Oxygen Supply,capital,exp,-,0.6457441946722567,0.6342724916068043,0.6448555996459632,0.6370664161269126,0.6699999996017925,0.6699999991468173 +H2 Pre-heating,capital,exp,-,0.8656365854575354,0.8656365854575354,0.0,0.0,-3.4203260887643445e-15,0.0 +Cooling Tower,capital,exp,-,0.6332532740097314,0.6286978709250868,0.6302820063981883,0.6308163319151877,0.6659797264431829,0.2599998698160089 +Piping,capital,exp,-,0.5998309612457867,0.8331608480295306,0.564105631654867,0.6551154484929332,0.4619607653395522,0.4580718393560637 +Electrical & Instrumentation,capital,exp,-,0.5998309612151187,0.8331608479997341,0.5641056316058707,0.6551154484197789,0.46196076523826685,0.45807183934973555 +"Buildings, Storage, Water Service",capital,exp,-,0.7999999998942431,0.799999999982431,0.7999999998654106,0.7999999999666216,0.7999999997752504,0.799999999728853 +Other Miscellaneous Cost,capital,exp,-,0.5998309612151187,0.8331608479997341,0.5641056316058707,0.6551154484197789,0.4638975364898228,0.45807183934973555 Preproduction,owner,lin,frac of TPC,0.02,0.02,0.02,0.02,0.02,0.02 Spare Parts,owner,lin,frac of TPC,0.005,0.005,0.005,0.005,0.005,0.005 "Initial Catalyst, Sorbent & Chemicals",owner,lin,frac of TPC,0.0,0.0,0.250835587,0.250835587,0.0,0.250835587 diff --git a/h2integrate/converters/iron/stinn/cost_model.py b/h2integrate/converters/iron/stinn/cost_model.py index 55629a24c..293da18c9 100644 --- a/h2integrate/converters/iron/stinn/cost_model.py +++ b/h2integrate/converters/iron/stinn/cost_model.py @@ -22,85 +22,6 @@ M = 0.055845 # Fe molar mass (kg/mol) -def stinn_capex_calc(T, P, p, z, j, A, e, Q, V, N): - """ - Equation (7) from Stinn: doi.org/10.1149.2/2.F06202IF - default values for coefficients defined as globals - """ - # Default coefficents - a1n = 51010 - a1d = -3.82e-03 - a1t = -631 - a2n = 5634000 - a2d = -7.1e-03 - a2t = 349 - a3n = 750000 - e1 = 0.8 - e2 = 0.9 - e3 = 0.15 - e4 = 0.5 - - # Alpha coefficients - a1 = a1n / (1 + np.exp(a1d * (T - a1t))) - a2 = a2n / (1 + np.exp(a2d * (T - a2t))) - a3 = a3n * Q - - # Pre-costs calculation - pre_costs = a1 * P**e1 - - # Electrolysis and product handling contribution to total cost - electrolysis_product_handling = a2 * ((p * z * F) / (j * A * e * M)) ** e2 - - # Power rectifying contribution - power_rectifying_contribution = a3 * V**e3 * N**e4 - - return pre_costs, electrolysis_product_handling, power_rectifying_contribution, a1, a2, a3 - - -def humbert_opex_calc( - capacity, - positions, - NaOH_ratio, - CaCl2_ratio, - limestone_ratio, - anode_ratio, - anode_interval, - ore_in, - ore_price, - elec_in, - elec_price, -): - """ - Calculations in Excel spreadsheet SI of Humbert doi.org/10.1149.2/2.F06202IF - """ - # Default costs - adjusted to 2018 to match Stinn via CPI - labor_rate = 55.90 # USD/person-hour - NaOH_cost = 415.179 # USD/tonne - CaCl2_cost = 207.59 # USD/tonne - limestone_cost = 0 - anode_cost = 1660.716 # USD/tonne - hours = 2000 # hours/position-year - - # All linear OpEx for now - TODO: apply scaling models - labor_opex = labor_rate * capacity * positions * hours # Labor OpEx USD/year - NaOH_varopex = NaOH_ratio * capacity * NaOH_cost # NaOH VarOpEx USD/year - CaCl2_varopex = CaCl2_ratio * capacity * CaCl2_cost # CaCl2 VarOpEx USD/year - limestone_varopex = limestone_ratio * capacity * limestone_cost # CaCl2 VarOpEx USD/year - anode_varopex = anode_ratio * capacity * anode_cost / anode_interval # Anode VarOpEx USD/year - ore_varopex = np.sum(ore_in * ore_price, keepdims=True) # Ore VarOpEx USD/year - elec_varopex = np.sum(elec_in * elec_price, keepdims=True) # Electricity VarOpEx USD/year - - return ( - labor_opex, - NaOH_varopex, - CaCl2_varopex, - limestone_varopex, - anode_varopex, - ore_varopex, - elec_varopex, - ) - - def plot_capex_calc( a1n, a1d, a1t, a2n, a2d, a2t, a3n, e1, e2, e3, e4, T, P, p, z, F, j, A, e, M, Q, V, N ): @@ -134,7 +55,7 @@ def main(config): config (object): Configuration object containing model inputs, including: cost_model (dict): Dictionary with the file path to cost coefficients. electrolysis_temp (float): Electrolysis temperature in degrees Celsius (°C). - intsalled capacity (float): Installed capacity in tonnes per year (t/y). + capacity (float): Installed capacity in tonnes per year (t/y). production_rate (float): Production rate in kilograms per second (kg/s). electron_moles (int): Moles of electrons per mole of product. faraday_const (float): Faraday constant in coulombs per mole (C/mol). From c34f4e546f7425522d947aa67bca384af54717dd Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Thu, 22 Jan 2026 10:57:06 -0700 Subject: [PATCH 26/29] Addressing reviews --- docs/technology_models/iron_ewin.md | 4 +- .../converters/iron/humbert_ewin_perf.py | 15 +- .../iron/humbert_stinn_ewin_cost.py | 5 +- .../converters/iron/rosner/cost_coeffs.csv | 44 ++-- .../converters/iron/stinn/cost_coeffs.csv | 12 -- .../converters/iron/stinn/cost_model.py | 193 ------------------ h2integrate/converters/iron/stinn/table1.csv | 43 ---- .../iron/test/test_humbert_ewin_perf.py | 26 +-- .../iron/test/test_humbert_stinn_ewin_cost.py | 20 +- h2integrate/tools/constants.py | 2 + 10 files changed, 51 insertions(+), 313 deletions(-) delete mode 100644 h2integrate/converters/iron/stinn/cost_coeffs.csv delete mode 100644 h2integrate/converters/iron/stinn/cost_model.py delete mode 100644 h2integrate/converters/iron/stinn/table1.csv diff --git a/docs/technology_models/iron_ewin.md b/docs/technology_models/iron_ewin.md index 070fe48cd..8e7a438d9 100644 --- a/docs/technology_models/iron_ewin.md +++ b/docs/technology_models/iron_ewin.md @@ -1,6 +1,6 @@ # Iron electrowinning models -H2I contains models to simulate to separation of mostly pure iron from iron oxides. +H2I contains iron electrowinning models to simulate the reduction of iron oxide to pure iron and removal of impurities. The main input feedstock is iron ore, while the output commodity is "sponge iron", i.e. iron that is typically brittle ("spongey") and contains less carbon than most steel alloys. This sponge iron can then be used in an electric arc furnace (EAF) to produce steel. @@ -22,6 +22,8 @@ These authors use both cost data and physical parameters from existing studies t This model is applied in the `humbert_stinn_electrowinning_cost` cost model. To use this model, specify `"humbert_electrowinning_performance"` as the performance model and `"humbert_stinn_electrowinning_cost"` as the cost model. +The performance model will Humbert et al.'s energy consumption data to consume electricity as a feedstock and feed this information to the cost model. +The cost model will calculate capex costs based on the Stinn correlations and opex costs based on the Humbert SI. ## Shared Parameters diff --git a/h2integrate/converters/iron/humbert_ewin_perf.py b/h2integrate/converters/iron/humbert_ewin_perf.py index d22966ef5..b5cf51c7e 100644 --- a/h2integrate/converters/iron/humbert_ewin_perf.py +++ b/h2integrate/converters/iron/humbert_ewin_perf.py @@ -51,7 +51,7 @@ class HumbertEwinPerformanceComponent(om.ExplicitComponent): Inputs: electricity_in (array): Electric power input available in kW for each timestep. iron_ore_in (array): Iron ore mass flow available in kg/h for each timestep. - ore_fe_wt_pct (float): The iron content of the ore coming in, expressed as a percentage. + ore_fe_concentration (float): The iron content of the ore coming in, given as a percentage. spec_energy_cons_fe (float): The specific electrical energy consumption required to win pure iron (Fe) from iron ore. These are currently calculated as averages between the high and low stated values in Table 10 of Humbert et al., but this is exposed as an @@ -102,7 +102,7 @@ def setup(self): 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("ore_fe_concentration", val=self.config.ore_fe_wt_pct, units="percent") self.add_input("spec_energy_all", val=E_all, units="kW*h/kg") self.add_input("spec_energy_electrolysis", val=E_electrolysis, units="kW*h/kg") self.add_input("capacity", val=self.config.capacity_mw, units="MW") @@ -126,7 +126,7 @@ def compute(self, inputs, outputs): # Parse inputs elec_in = inputs["electricity_in"] ore_in = inputs["iron_ore_in"] - pct_fe = inputs["ore_fe_wt_pct"] + pct_fe = inputs["ore_fe_concentration"] kwh_kg_fe = inputs["spec_energy_all"] kwh_kg_electrolysis = inputs["spec_energy_electrolysis"] cap_kw = inputs["capacity"] * 1000 @@ -135,15 +135,22 @@ def compute(self, inputs, outputs): fe_from_ore = ore_in * pct_fe / 100 fe_from_elec = elec_in / kwh_kg_fe - # Limiting iron production per hour by each input + # Limit iron production per hour by each input fe_prod = np.minimum.reduce([fe_from_ore, fe_from_elec]) + + # If production is limited by available ore at any timestep i, limiters[i] = 0 + # If limited by electricity, limiters[i] = 1 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)]) + + # If capacity limits production at any timestep i, cap_lim[i] = 1 + # Otherwise, cap_lim[i] = 0 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 + # At each timestep: 0 = iron ore, 1 = electricity, 2 = capacity limiters = np.maximum.reduce([cap_lim * 2, limiters]) outputs["limiting_input"] = limiters diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index 7401cdcea..91e92ba10 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -24,6 +24,7 @@ from h2integrate.core.utilities import merge_shared_inputs from h2integrate.core.validators import contains, must_equal +from h2integrate.tools.constants import FE_MW, faraday from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -217,8 +218,8 @@ def setup(self): def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Physical constants - F = 96485.3321 # Faraday constant: Electric charge per mole of electrons (C/mol) - M = 0.055845 # Fe molar mass (kg/mol) + F = faraday # Faraday constant: Electric charge per mole of electrons (C/mol) + M = FE_MW / 1000 # Fe molar mass (kg/mol) # Parse inputs for Stinn Capex model (doi.org/10.1149/2.F06202IF) T = inputs["electrolysis_temp"] diff --git a/h2integrate/converters/iron/rosner/cost_coeffs.csv b/h2integrate/converters/iron/rosner/cost_coeffs.csv index 7b9420c9c..30dbb8598 100644 --- a/h2integrate/converters/iron/rosner/cost_coeffs.csv +++ b/h2integrate/converters/iron/rosner/cost_coeffs.csv @@ -1,27 +1,27 @@ Name,Type,Coeff,Unit,h2_dri_eaf,h2_dri,ng_dri_eaf,ng_dri,h2_eaf,ng_eaf Dollar Year,all,-,-,2022.0,2022.0,2022.0,2022.0,2022.0,2022.0 -EAF & Casting,capital,lin,$,352191.5240169347,1.0000000000000458e-10,348441.8227127845,9.999999999999286e-11,352191.5240169347,348441.8227127845 -Shaft Furnace,capital,lin,$,489.68060552073365,498.40115875154777,12706.355407490113,12706.355407490113,1.0000000000000458e-10,9.999999999999286e-11 -Reformer,capital,lin,$,0.0,0.0,12585.824569411949,12585.824569411949,0.0,9.999999999999286e-11 -Recycle Compressor,capital,lin,$,0.0,0.0,956.7744259199679,956.7744259199679,0.0,9.999999999999286e-11 -Oxygen Supply,capital,lin,$,1715.215085611161,1364.5561234251998,1204.9890859896068,1024.5621894371275,393.830734201856,201.31538578766953 -H2 Pre-heating,capital,lin,$,45.691227892080605,45.691227892080605,0.0,0.0,1.0000000000000458e-10,0.0 -Cooling Tower,capital,lin,$,2513.0831352601786,2349.1522627637264,1790.403730600302,1774.6459078323237,195.45026173489762,428.90723393800977 -Piping,capital,lin,$,11815.727185704474,172.83401368816038,25651.75658705922,3917.3482323408716,49970.896628346374,51294.53419198639 -Electrical & Instrumentation,capital,lin,$,7877.151460414222,115.2226758287705,17101.171070383716,2611.5654909389796,33313.93113452448,34196.35613320329 -"Buildings, Storage, Water Service",capital,lin,$,1097.818759618822,630.5313008617607,1077.395528263029,618.8012346570433,467.28745875704124,458.5942936059208 -Other Miscellaneous Cost,capital,lin,$,7877.151460414222,115.2226758287705,17101.171070383716,2611.5654909389796,32124.80020341787,34196.35613320329 -EAF & Casting,capital,exp,-,0.4559999999420569,-3.4203260887643445e-15,0.45599999989917644,5.129911328578738e-15,0.4559999999420569,0.45599999989917644 -Shaft Furnace,capital,exp,-,0.8874110806752251,0.8862693772994394,0.6540835087553862,0.6540835087553862,-3.4203260887643445e-15,5.129911328578738e-15 -Reformer,capital,exp,-,0.0,0.0,0.6504523630280962,0.6504523630280962,0.0,5.129911328578738e-15 -Recycle Compressor,capital,exp,-,0.0,0.0,0.7100000000012103,0.7100000000012103,0.0,5.129911328578738e-15 -Oxygen Supply,capital,exp,-,0.6457441946722567,0.6342724916068043,0.6448555996459632,0.6370664161269126,0.6699999996017925,0.6699999991468173 -H2 Pre-heating,capital,exp,-,0.8656365854575354,0.8656365854575354,0.0,0.0,-3.4203260887643445e-15,0.0 -Cooling Tower,capital,exp,-,0.6332532740097314,0.6286978709250868,0.6302820063981883,0.6308163319151877,0.6659797264431829,0.2599998698160089 -Piping,capital,exp,-,0.5998309612457867,0.8331608480295306,0.564105631654867,0.6551154484929332,0.4619607653395522,0.4580718393560637 -Electrical & Instrumentation,capital,exp,-,0.5998309612151187,0.8331608479997341,0.5641056316058707,0.6551154484197789,0.46196076523826685,0.45807183934973555 -"Buildings, Storage, Water Service",capital,exp,-,0.7999999998942431,0.799999999982431,0.7999999998654106,0.7999999999666216,0.7999999997752504,0.799999999728853 -Other Miscellaneous Cost,capital,exp,-,0.5998309612151187,0.8331608479997341,0.5641056316058707,0.6551154484197789,0.4638975364898228,0.45807183934973555 +EAF & Casting,capital,lin,$,352191.5240169328,9.999999999999783e-11,348441.82271277835,9.999999999999996e-11,352191.5240169328,348441.82271277835 +Shaft Furnace,capital,lin,$,489.68060552071756,498.4011587515349,12706.35540748921,12706.35540748921,9.999999999999783e-11,9.999999999999996e-11 +Reformer,capital,lin,$,0.0,0.0,12585.824569411547,12585.824569411547,0.0,9.999999999999996e-11 +Recycle Compressor,capital,lin,$,0.0,0.0,956.7744259199391,956.7744259199391,0.0,9.999999999999996e-11 +Oxygen Supply,capital,lin,$,1715.21508561117,1364.556123425206,1204.989085989581,1024.5621894371002,393.8307342018522,201.3153857876692 +H2 Pre-heating,capital,lin,$,45.69122789208198,45.69122789208198,0.0,0.0,9.999999999999783e-11,0.0 +Cooling Tower,capital,lin,$,2513.0831352600603,2349.15226276371,1790.4037306002654,1774.6459078322403,195.45026173489327,428.90723393800783 +Piping,capital,lin,$,11815.72718570437,172.83401368816206,25651.756587058582,3917.34823234076,49970.89662834664,51294.534191987485 +Electrical & Instrumentation,capital,lin,$,7877.151460413984,115.2226758287703,17101.171070383258,2611.5654909389123,33313.93113452472,34196.35613320244 +"Buildings, Storage, Water Service",capital,lin,$,1097.818759618821,630.531300861788,1077.3955282629602,618.8012346570356,467.2874587570587,458.59429360590775 +Other Miscellaneous Cost,capital,lin,$,7877.151460413984,115.2226758287703,17101.171070383258,2611.5654909389123,32124.80020341839,34196.35613320244 +EAF & Casting,capital,exp,-,0.4559999999420573,1.3155100341401327e-15,0.45599999989917783,-3.9460856373682605e-16,0.4559999999420573,0.45599999989917783 +Shaft Furnace,capital,exp,-,0.8874110806752277,0.8862693772994416,0.654083508755392,0.654083508755392,1.3155100341401327e-15,-3.9460856373682605e-16 +Reformer,capital,exp,-,0.0,0.0,0.6504523630280986,0.6504523630280986,0.0,-3.9460856373682605e-16 +Recycle Compressor,capital,exp,-,0.0,0.0,0.7100000000012126,0.7100000000012126,0.0,-3.9460856373682605e-16 +Oxygen Supply,capital,exp,-,0.6457441946722564,0.634272491606804,0.6448555996459648,0.6370664161269146,0.6699999996017935,0.6699999991468175 +H2 Pre-heating,capital,exp,-,0.8656365854575331,0.8656365854575331,0.0,0.0,1.3155100341401327e-15,0.0 +Cooling Tower,capital,exp,-,0.6332532740097354,0.6286978709250873,0.6302820063981899,0.6308163319151914,0.6659797264431847,0.25999986981600937 +Piping,capital,exp,-,0.5998309612457874,0.8331608480295299,0.5641056316548689,0.6551154484929355,0.461960765339552,0.45807183935606205 +Electrical & Instrumentation,capital,exp,-,0.599830961215121,0.8331608479997342,0.5641056316058728,0.6551154484197809,0.4619607652382664,0.4580718393497376 +"Buildings, Storage, Water Service",capital,exp,-,0.7999999998942431,0.7999999999824277,0.7999999998654156,0.7999999999666229,0.7999999997752477,0.7999999997288553 +Other Miscellaneous Cost,capital,exp,-,0.599830961215121,0.8331608479997342,0.5641056316058728,0.6551154484197809,0.46389753648982157,0.4580718393497376 Preproduction,owner,lin,frac of TPC,0.02,0.02,0.02,0.02,0.02,0.02 Spare Parts,owner,lin,frac of TPC,0.005,0.005,0.005,0.005,0.005,0.005 "Initial Catalyst, Sorbent & Chemicals",owner,lin,frac of TPC,0.0,0.0,0.250835587,0.250835587,0.0,0.250835587 diff --git a/h2integrate/converters/iron/stinn/cost_coeffs.csv b/h2integrate/converters/iron/stinn/cost_coeffs.csv deleted file mode 100644 index 60ba31aee..000000000 --- a/h2integrate/converters/iron/stinn/cost_coeffs.csv +++ /dev/null @@ -1,12 +0,0 @@ -Name,Type,Coeff,Unit -alpha_1_numerator,capital,51010,- -alpha_1_denominator,capital,-3.82E-03,- -alpha_1_temp_offset,capital,631,- -alpha_2_numerator,capital,5634000,- -alpha_2_denominator,capital,-7.81E-03,- -alpha_2_temp_offset,capital,349,- -alpha_3,capital,750000,- -exp_1,capital,0.8, -exp_2,capital,0.9, -exp_3,capital,0.15, -exp_4,capital,0.5, diff --git a/h2integrate/converters/iron/stinn/cost_model.py b/h2integrate/converters/iron/stinn/cost_model.py deleted file mode 100644 index 293da18c9..000000000 --- a/h2integrate/converters/iron/stinn/cost_model.py +++ /dev/null @@ -1,193 +0,0 @@ -""" -Calculates the total direct capital cost (C) in 2018 US dollars. - -From: -Estimating the Capital Costs of Electrowinning Processes -Caspar Stinn and Antoine Allanore 2020 Electrochem. Soc. Interface 29 44 -https://iopscience.iop.org/article/10.1149/2.F06202IF -""" - -from pathlib import Path - -import numpy as np -import pandas as pd -import matplotlib.pyplot as plt - - -CD = Path(__file__).parent - -# Physical constants -faraday_const = 96485.3321 # Electric charge per mole of electrons (Faraday constant), C/mol -F = 96485.3321 # Faraday constant: Electric charge per mole of electrons (Faraday constant), C/mol -M = 0.055845 # Fe molar mass (kg/mol) - - -def plot_capex_calc( - a1n, a1d, a1t, a2n, a2d, a2t, a3n, e1, e2, e3, e4, T, P, p, z, F, j, A, e, M, Q, V, N -): - # Pre-costs calculation - a1 = a1n / (1 + np.exp(a1d * (T - a1t))) - a2 = a2n / (1 + np.exp(a2d * (T - a2t))) - a3 = a3n * Q - - pre_costs = a1 * P**e1 - - # Electrolysis and product handling contribution to total cost - electrolysis_product_handling = a2 * ((p * z * F) / (j * A * e * M)) ** e2 - - # Power rectifying contribution - power_rectifying_contribution = a3 * V**e3 * N**e4 - - return pre_costs, electrolysis_product_handling, power_rectifying_contribution, a1, a2, a3 - - -def main(config): - """ - Calculates the total direct capital cost of an electrowinning system in 2018 US dollars. - - The cost estimation is based on the methodology from: - "Estimating the Capital Costs of Electrowinning Processes" - by Caspar Stinn and Antoine Allanore (2020). - *Electrochem. Soc. Interface*, 29, 44. - DOI: https://iopscience.iop.org/article/10.1149/2.F06202IF - - Args: - config (object): Configuration object containing model inputs, including: - cost_model (dict): Dictionary with the file path to cost coefficients. - electrolysis_temp (float): Electrolysis temperature in degrees Celsius (°C). - capacity (float): Installed capacity in tonnes per year (t/y). - production_rate (float): Production rate in kilograms per second (kg/s). - electron_moles (int): Moles of electrons per mole of product. - faraday_const (float): Faraday constant in coulombs per mole (C/mol). - current_density (float): Current density in amperes per square meter (A/m²). - electrode_area (float): Electrode area in square meters (m²). - current_efficiency (float): Current efficiency (dimensionless, fraction). - molar_mass (float): Molar mass of the electrolysis product - in kilograms per mole (kg/mol). - installed_capacity (float): Installed power capacity in megawatts (MW). - cell_voltage (float): Cell operating voltage in volts (V). - rectifier_lines (int): Number of rectifier lines. - - Returns: - dict: A dictionary containing: - pre_costs (float): Pre-costs related to capacity and system preparation. - electrowinning_costs (float): Costs associated with electrolysis - and power rectification. - total_costs (float): Sum of pre-costs and electrowinning costs. - """ - # Load inputs - inputs_fp = CD / config.cost_model["inputs_fp"] - inputs_df = pd.read_csv(inputs_fp) - inputs_df = inputs_df.set_index("Metal") - - # Load coefficients - coeffs_fp = CD / config.cost_model["coeffs_fp"] - coeffs_df = pd.read_csv(coeffs_fp) - coeffs = coeffs_df.set_index("Name")["Coeff"].to_dict() - - # Extract coefficients - a1n = coeffs["alpha_1_numerator"] - a1d = coeffs["alpha_1_denominator"] - a1t = coeffs["alpha_1_temp_offset"] - a2n = coeffs["alpha_2_numerator"] - a2d = coeffs["alpha_2_denominator"] - a2t = coeffs["alpha_2_temp_offset"] - a3n = coeffs["alpha_3"] - e1 = coeffs["exp_1"] - e2 = coeffs["exp_2"] - e3 = coeffs["exp_3"] - e4 = coeffs["exp_4"] - - metal_dict = { - "Al": [0, 0, 0], - "Mg": [0, 0.5, 1], - "Na": [0, 1, 0], - "Zn": [0, 1, 1], - "Cu": [0, 0.5, 0], - "Cl2": [0, 0, 0.5], - } - - # Cycle through metals - for metal, color in metal_dict.items(): - metal_df = inputs_df.loc[[metal]] - cap = metal_df["Capacity [kt/y]"].values - capex_per_cap = metal_df["Capex/ Capacity [2018 USD/ (t/y)]"].values - plt.plot(cap, capex_per_cap, ".", color=color) - - T = metal_df["Temperature [C]"].values - P = cap * 1000 - p = P * 1000 / 8760 / 3600 - z = metal_df["Electrons per product"].values - f = faraday_const - j = metal_df["Current Density [A/m^2]"].values - A = metal_df["Electrode area/ cell [m^2]"].values - e = metal_df["Current efficiency"].values - M = metal_df["Product molar mass [kg]"].values - Q = metal_df["Power / cell [MW]"].values - V = metal_df["Operating potential [V]"].values - N = metal_df["Cell Count"].values - Q = Q * N - - F, E, R, a1, a2, a3 = plot_capex_calc( - a1n, a1d, a1t, a2n, a2d, a2t, a3n, e1, e2, e3, e4, T, P, p, z, f, j, A, e, M, Q, V, 1 - ) - - plt.plot(cap, (F + E + R) / P, "-", color=color) - - # Assign inputs from config - T = config.electrolysis_temp # Electrolysis temperature (°C) - P = config.capacity # installed capacity (t/y) - p = config.production_rate # Production rate (kg/s) - z = config.electron_moles # Moles of electrons per mole of product - f = faraday_const # Electric charge per mole of electrons (C/mol) - j = config.current_density # Current density (A/m²) - A = config.electrode_area # Electrode area (m²) - e = config.current_efficiency # Current efficiency (dimensionless) - M = config.molar_mass # Electrolysis product molar mass (kg/mol) - Q = config.installed_capacity # Installed power capacity (MW) - V = config.cell_voltage # Cell operating voltage (V) - N = config.rectifier_lines # Number of rectifier lines - - pre_costs, electrolysis_product_handling, power_rectifying_contribution, a1, a2, a3 = ( - plot_capex_calc( - a1n, a1d, a1t, a2n, a2d, a2t, a3, e1, e2, e3, e4, T, P, p, z, f, j, A, e, M, Q, V, N - ) - ) - - # Electrowinning costs - electrowinning_costs = electrolysis_product_handling + power_rectifying_contribution - - # Total costs - pre_costs + electrowinning_costs - - plt.xscale("log") - plt.show() - - # Return individual costs for modularity - return { - "pre_costs": pre_costs, - "electrowinning_costs": electrowinning_costs, - "total_costs": pre_costs + electrowinning_costs, - } - - -if __name__ == "__main__": - - class Config: - def __init__(self): - self.cost_model = {"coeffs_fp": "cost_coeffs.csv", "inputs_fp": "table1.csv"} - # Example values for each variable (replace with actual values) - self.electrolysis_temp = 1000 # Temperature in °C, example value - self.capacity = 1.5 # Installed capacity (t/y) - self.production_rate = 1.0 # Total production rate, kg/s - self.electron_moles = 3 # Moles of electrons per mole of product, example value - self.current_density = 5000 # Current density, A/m², example value - self.electrode_area = 30.0 # Electrode area, m², example value - self.current_efficiency = 0.95 # Current efficiency (dimensionless), example value - self.molar_mass = 0.018 # Electrolysis product molar mass, kg/mol (e.g., water) - self.installed_capacity = 500.0 # Installed power capacity, MW, example value - self.cell_voltage = 4.18 # Cell operating voltage, V, example value - self.rectifier_lines = 3 # Number of rectifier lines, example value - - results = main(Config()) - print(results) diff --git a/h2integrate/converters/iron/stinn/table1.csv b/h2integrate/converters/iron/stinn/table1.csv deleted file mode 100644 index 24c13ae84..000000000 --- a/h2integrate/converters/iron/stinn/table1.csv +++ /dev/null @@ -1,43 +0,0 @@ -Metal,Capacity [kt/y],Capex [Year USD],Capex/ Capacity [year USD/ (t/y)],Year,Year CEI,2018 CEI,Capex [2018 USD],Capex/ Capacity [2018 USD/ (t/y)],Cell Count,Temperature [C],Current Density [A/m^2],Current efficiency,Operating potential [V],Specific energy [kWh/kg],Electrode area/ cell [m^2],Current / cell [kA],Power / cell [MW],Electrons per product,Product molar mass [kg],Yearly productivity / cell [kt/y],Notes -Fe,100,71130000,711.3,2018,607,607,71130000,711.3,555.556,80,1000,0.9,3.2,4.64,30,30,0.1,3,0.06,0.18,Humbert Cost Kempler Operating Conditions -Fe,100,1094700000,10947,2018,607,607,1094700000,10947,13.495,900,6000,0.9,1.79,0.64,30,300,0.54,3,0.06,7.41,Humbert -Fe,100,101750000,1017.5,2018,607,607,101750000,1017.5,169.492,1600,10000,0.9,0.82,3.67,30,300,0.25,2,0.06,0.59,Humbert -Al,415,2937550460,7070,2008,576,607,3095647794,7451,517,1000,10000,0.95,4.18,16.5,30,300,1.25,3,0.03,0.8,1 line Rectifier -Al,415,2920930656,7030,2008,576,607,3078133521,7408,517,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,1 line Rectifier -Al,588,3691655708,6275,2008,576,607,3890338568,6613,732,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,1 line Rectifier -Al,588,3753428433,6380,2008,576,607,3955435866,6723,732,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,1 line Rectifier -Al,735,4894017677,6655,2008,576,607,5157410990,7013,915,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,1 line Rectifier -Al,735,4360860229,5930,2008,576,607,4595559304,6249,915,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,2 line Rectifier -Al,882,4994766050,5660,2008,576,607,5263581584,5965,1098,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,2 line Rectifier -Al,882,5599256288,6345,2008,576,607,5900605150,6686,1098,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,1 line Rectifier -Al,1177,6712636117,5705,2008,576,607,7073906463,6012,1465,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,2 line Rectifier -Al,1177,6518493267,5540,2008,576,607,6869314953,5838,1465,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,2 line Rectifier -Al,1691,8676861694,5130,2008,576,607,9143845570,5406,2105,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,2 line Rectifier -Al,500,3250000000,6500,2001,394,607,5006979695,10014,622,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,2 line Rectifier -Al,353,1899846000,5382,2008,576,608,2005393000,5681,439,1000,10000,0.95,4.18,,30,300,1.25,3,0.03,0.85,1 line Rectifier -Mg,50,400000000,8000,2001,394,607,616243655,12325,41,750,6000,0.9,6,,60,360,2.16,2,0.02,1.23,IG Cell -Mg,109,359700000,3300,1979,239,607,913547699,8381,88,750,6000,0.9,6,,60,360,2.16,2,0.02,1.23,IG Cell -Mg,22,72600000,3300,1979,239,607,184385774,8381,18,750,6000,0.9,6,,60,360,2.16,2,0.02,1.23,IG Cell -Mg,4.5,14850000,3300,1979,239,607,37715272,8381,4,750,6000,0.9,6,,60,360,2.16,2,0.02,1.23,IG Cell -Mg,22.5,74250000,3300,1979,239,607,188576360,8381,18,750,6000,0.9,6,,60,360,2.16,2,0.02,1.23,IG Cell -Na,50,82500000,1650,1979,239,607,209529289,4191,14,600,10000,0.85,5.7,,60,600,3.42,1,0.02,3.67,Downs Cell -Zn,100,250000000,2500,2006,500,607,303500000,3035,766,50,300,0.85,3.5,,50,15,0.05,2,0.07,0.13, -Zn,100,60000000,600,1974,164.4,607,221532847,2215,766,50,300,0.85,3.5,,50,15,0.05,2,0.07,0.13, -Zn,120,508500000,4238,2011,590,607,523151695,4360,919,50,300,0.85,3.5,,50,15,0.05,2,0.07,0.13, -Cu,1.1,2600000,2398,1980,261,607,6046743,5577,15,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW -Cu,1.7,3700000,2239,1980,261,607,8604981,5207,23,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW -Cu,5,12400000,2481,1980,261,607,28838314,5770,70,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW -Cu,4.6,9800000,2140,1980,261,607,22791571,4977,64,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW -Cu,15.1,26000000,1727,1980,261,607,60467433,4016,210,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW -Cu,22.7,32700000,1441,1980,261,607,76049425,3351,316,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW -Cu,21.2,33500000,1579,1980,261,607,77909962,3672,296,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW -Cu,27,100000000,3703,1998,390,607,155641026,5764,377,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW -Cu,1.3,3000000,2253,1980,390,607,4669231,3507,19,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW -Cu,14.6,25300000,1728,1980,390,607,39377179,2689,204,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW -Cu,50,268500000,5370,2014,580,607,280999138,5620,697,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW -Cu,60,298000000,4966,2007,530,607,341294340,5688,837,40,300,0.8,3.5,,30,9,0.03,2,0.06,0.07,SX-EW -Cl2,200,106000000,530,1980,261,607,246521073,1233,126,90,2700,0.96,3.79,,55,149,0.56,2,0.07,1.59,diaphragm -Cl2,200,111500000,558,1980,261,607,259312261,1297,126,90,2700,0.96,3.79,,55,149,0.56,2,0.07,1.59,membrane -Cl2,200,112800000,564,1980,261,607,262335632,1312,126,90,2700,0.96,3.79,,55,149,0.56,2,0.07,1.59,membrane + inert anodes -Cl2,166,111000000,669,1990,358,607,188203911,1134,105,90,2700,0.96,3.79,,55,149,0.56,2,0.07,1.59, -Cl2,880,194868534,221,1980,261,607,453200000,515,554,90,2700,0.96,3.79,,55,149,0.56,2,0.07,1.59, diff --git a/h2integrate/converters/iron/test/test_humbert_ewin_perf.py b/h2integrate/converters/iron/test/test_humbert_ewin_perf.py index 853f9a3dc..c293554c3 100644 --- a/h2integrate/converters/iron/test/test_humbert_ewin_perf.py +++ b/h2integrate/converters/iron/test/test_humbert_ewin_perf.py @@ -2,8 +2,6 @@ import openmdao.api as om from pytest import fixture -from h2integrate import EXAMPLE_DIR -from h2integrate.core.inputs.validation import load_driver_yaml from h2integrate.converters.iron.humbert_ewin_perf import HumbertEwinPerformanceComponent from h2integrate.converters.iron.humbert_stinn_ewin_cost import HumbertStinnEwinCostComponent @@ -28,12 +26,6 @@ def plant_config(): return plant_config -@fixture -def driver_config(): - driver_config = load_driver_yaml(EXAMPLE_DIR / "27_iron_electrowinning" / "driver_config.yaml") - return driver_config - - @fixture def tech_config(): tech_config = { @@ -67,7 +59,7 @@ def feedstocks_dict(): return feedstocks_dict -def setup_and_run(plant_config, driver_config, tech_config, feedstocks_dict): +def setup_and_run(plant_config, tech_config, feedstocks_dict): prob = om.Problem() iron_ewin_perf = HumbertEwinPerformanceComponent( @@ -98,9 +90,7 @@ def setup_and_run(plant_config, driver_config, tech_config, feedstocks_dict): return elec_consumed, iron_out, iron_cap -def test_humbert_ewin_performance_component( - plant_config, driver_config, tech_config, feedstocks_dict, subtests -): +def test_humbert_ewin_performance_component(plant_config, tech_config, feedstocks_dict, subtests): expected_elec_consumption_ahe = 506978.45 # kW expected_elec_consumption_mse = 452725.57 # kW expected_elec_consumption_moe = 567259.43 # kW @@ -112,9 +102,7 @@ def test_humbert_ewin_performance_component( expected_output_capacity_moe = 1432152588.56 # kg/y tech_config["model_inputs"]["shared_parameters"]["electrolysis_type"] = "ahe" - elec_consumed, iron_out, iron_cap = setup_and_run( - plant_config, driver_config, tech_config, feedstocks_dict - ) + elec_consumed, iron_out, iron_cap = setup_and_run(plant_config, tech_config, feedstocks_dict) with subtests.test("ahe_electricity"): assert ( pytest.approx( @@ -140,9 +128,7 @@ def test_humbert_ewin_performance_component( == expected_output_capacity_ahe ) tech_config["model_inputs"]["shared_parameters"]["electrolysis_type"] = "mse" - elec_consumed, iron_out, iron_cap = setup_and_run( - plant_config, driver_config, tech_config, feedstocks_dict - ) + elec_consumed, iron_out, iron_cap = setup_and_run(plant_config, tech_config, feedstocks_dict) with subtests.test("mse_electricity"): assert ( pytest.approx( @@ -168,9 +154,7 @@ def test_humbert_ewin_performance_component( == expected_output_capacity_mse ) tech_config["model_inputs"]["shared_parameters"]["electrolysis_type"] = "moe" - elec_consumed, iron_out, iron_cap = setup_and_run( - plant_config, driver_config, tech_config, feedstocks_dict - ) + elec_consumed, iron_out, iron_cap = setup_and_run(plant_config, tech_config, feedstocks_dict) with subtests.test("moe_electricity"): assert ( pytest.approx( diff --git a/h2integrate/converters/iron/test/test_humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/test/test_humbert_stinn_ewin_cost.py index 7a0871c5d..f4d060781 100644 --- a/h2integrate/converters/iron/test/test_humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/test/test_humbert_stinn_ewin_cost.py @@ -2,8 +2,6 @@ import openmdao.api as om from pytest import fixture -from h2integrate import EXAMPLE_DIR -from h2integrate.core.inputs.validation import load_driver_yaml from h2integrate.converters.iron.humbert_ewin_perf import HumbertEwinPerformanceComponent from h2integrate.converters.iron.humbert_stinn_ewin_cost import HumbertStinnEwinCostComponent @@ -28,12 +26,6 @@ def plant_config(): return plant_config -@fixture -def driver_config(): - driver_config = load_driver_yaml(EXAMPLE_DIR / "27_iron_electrowinning" / "driver_config.yaml") - return driver_config - - @fixture def tech_config(): tech_config = { @@ -67,7 +59,7 @@ def feedstocks_dict(): return feedstocks_dict -def setup_and_run(plant_config, driver_config, tech_config, feedstocks_dict): +def setup_and_run(plant_config, tech_config, feedstocks_dict): prob = om.Problem() iron_ewin_perf = HumbertEwinPerformanceComponent( @@ -98,9 +90,7 @@ def setup_and_run(plant_config, driver_config, tech_config, feedstocks_dict): return capex, fopex, vopex -def test_humbert_stinn_ewin_cost_component( - plant_config, driver_config, tech_config, feedstocks_dict, subtests -): +def test_humbert_stinn_ewin_cost_component(plant_config, tech_config, feedstocks_dict, subtests): expected_capex_ahe = 6038571901.89 # USD expected_fopex_ahe = 67050786.5 # USD/year expected_vopex_ahe = 835954.89 # USD/year @@ -112,7 +102,7 @@ def test_humbert_stinn_ewin_cost_component( expected_vopex_moe = 3316122.05 # USD/year tech_config["model_inputs"]["shared_parameters"]["electrolysis_type"] = "ahe" - capex, fopex, vopex = setup_and_run(plant_config, driver_config, tech_config, feedstocks_dict) + capex, fopex, vopex = setup_and_run(plant_config, tech_config, feedstocks_dict) with subtests.test("ahe_capex"): assert ( pytest.approx( @@ -138,7 +128,7 @@ def test_humbert_stinn_ewin_cost_component( == expected_vopex_ahe ) tech_config["model_inputs"]["shared_parameters"]["electrolysis_type"] = "mse" - capex, fopex, vopex = setup_and_run(plant_config, driver_config, tech_config, feedstocks_dict) + capex, fopex, vopex = setup_and_run(plant_config, tech_config, feedstocks_dict) with subtests.test("mse_capex"): assert ( pytest.approx( @@ -164,7 +154,7 @@ def test_humbert_stinn_ewin_cost_component( == expected_vopex_mse ) tech_config["model_inputs"]["shared_parameters"]["electrolysis_type"] = "moe" - capex, fopex, vopex = setup_and_run(plant_config, driver_config, tech_config, feedstocks_dict) + capex, fopex, vopex = setup_and_run(plant_config, tech_config, feedstocks_dict) with subtests.test("moe_capex"): assert ( pytest.approx( diff --git a/h2integrate/tools/constants.py b/h2integrate/tools/constants.py index 6b48d8a39..4e67a8853 100644 --- a/h2integrate/tools/constants.py +++ b/h2integrate/tools/constants.py @@ -2,3 +2,5 @@ N_MW = 14.007 # Molecular weight of Nitrogen in g/mol O2_MW = 31.998 # Molecular weight of Oxygen in g/mol AR_MW = 39.948 # Molecular weight of Argon in g/mol +FE_MW = 55.845 # Molecular weight of Iron in g/mol +faraday = 96485.3321 # Faraday constant: Electric charge per mole of electrons (C/mol) From a7e62c7b35466e3d3c6b6e4b2765c3d793f2d645 Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Fri, 23 Jan 2026 19:01:09 -0700 Subject: [PATCH 27/29] site -> sites --- examples/27_iron_electrowinning/plant_config.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/27_iron_electrowinning/plant_config.yaml b/examples/27_iron_electrowinning/plant_config.yaml index 97a88c831..8f36db502 100644 --- a/examples/27_iron_electrowinning/plant_config.yaml +++ b/examples/27_iron_electrowinning/plant_config.yaml @@ -1,9 +1,10 @@ name: "plant_config" description: "Configures an iron electrowinning system." -site: - latitude: 41.717 - longitude: -88.398 +sites: + ewin_site: + latitude: 41.717 + longitude: -88.398 technology_interconnections: [ # Connect feedstocks to iron mine. From f4b24f59739126c4c401e269ca98d711b6f00cda Mon Sep 17 00:00:00 2001 From: jmartin4 Date: Mon, 26 Jan 2026 16:58:58 -0700 Subject: [PATCH 28/29] Updating test values --- examples/test/test_all_examples.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/test/test_all_examples.py b/examples/test/test_all_examples.py index 187832fd2..cf5fe4231 100644 --- a/examples/test/test_all_examples.py +++ b/examples/test/test_all_examples.py @@ -1651,7 +1651,7 @@ def test_27_iron_electrowinning_example(subtests): model.setup() model.run() lcoi = model.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/kg")[0] - assert pytest.approx(lcoi, rel=1e-4) == 1.057242764725443 + assert pytest.approx(lcoi, rel=1e-4) == 2.187928233525775 with subtests.test("Value check on MSE"): model.technology_config["technologies"]["iron_plant"]["model_inputs"]["shared_parameters"][ @@ -1660,7 +1660,7 @@ def test_27_iron_electrowinning_example(subtests): model.setup() model.run() lcoi = model.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/kg")[0] - assert pytest.approx(lcoi, rel=1e-4) == 2.2103327773319883 + assert pytest.approx(lcoi, rel=1e-4) == 3.3410182461323226 with subtests.test("Value check on MOE"): model.technology_config["technologies"]["iron_plant"]["model_inputs"]["shared_parameters"][ @@ -1669,7 +1669,9 @@ def test_27_iron_electrowinning_example(subtests): model.setup() model.run() lcoi = model.model.get_val("finance_subgroup_sponge_iron.LCOS", units="USD/kg")[0] - assert pytest.approx(lcoi, rel=1e-4) == 1.1525394007265573 + assert pytest.approx(lcoi, rel=1e-4) == 2.2832248695268893 + + def test_sweeping_different_resource_sites_doe(subtests): os.chdir(EXAMPLE_DIR / "27_site_doe_diff") import pandas as pd From 98bb8c5930babe75d81f98c17196af765dd83859 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Mon, 26 Jan 2026 19:24:00 -0700 Subject: [PATCH 29/29] Added autodoc to the electrowinning docs --- docs/technology_models/iron_ewin.md | 51 +++++++++++-------- .../converters/iron/humbert_ewin_perf.py | 7 ++- .../iron/humbert_stinn_ewin_cost.py | 26 ++++++---- 3 files changed, 52 insertions(+), 32 deletions(-) diff --git a/docs/technology_models/iron_ewin.md b/docs/technology_models/iron_ewin.md index 8e7a438d9..f368b43c3 100644 --- a/docs/technology_models/iron_ewin.md +++ b/docs/technology_models/iron_ewin.md @@ -25,23 +25,34 @@ To use this model, specify `"humbert_electrowinning_performance"` as the perform The performance model will Humbert et al.'s energy consumption data to consume electricity as a feedstock and feed this information to the cost model. The cost model will calculate capex costs based on the Stinn correlations and opex costs based on the Humbert SI. -## Shared Parameters - -- `electrolysis_type`: The type of electrolysis used for electrowinning. Options are: - - `"ahe"`: Aqueous Hydroxide Electrolysis - - `"mse"`: Molten Salt Electrolysis - - `"moe"`: Molten Oxide Electrolysis - -# Performance Parameters - -- `ore_fe_wt_pct`: The percentage by weight of iron in the input ore. -- `capacity_mw`: The electrical capacity in MW of the electrowinning plant. - -## Cost Parameters - -None. The `cost_year` is automatically set to 2018 to match the Stinn/Allanore source values. Be sure to set `target_dollar_year` in the `finanace_parameters` to match your desired output dollar year in the finance calculations. - -## Required Feedstocks - -- `iron_ore_in`: Iron ore to be used for electrowinning. -- `electricity_in`: Electricity used to reduce iron ore to sponge iron. +## Performance Model + +```{eval-rst} +.. autoclass:: h2integrate.converters.iron.humbert_ewin_perf.HumbertEwinConfig + :members: + :undoc-members: + :show-inheritance: +``` + +```{eval-rst} +.. autoclass:: h2integrate.converters.iron.humbert_ewin_perf.HumbertEwinPerformanceComponent + :members: + :undoc-members: + :show-inheritance: +``` + +## Cost Model + +```{eval-rst} +.. autoclass:: h2integrate.converters.iron.humbert_stinn_ewin_cost.HumbertStinnEwinCostConfig + :members: + :undoc-members: + :show-inheritance: +``` + +```{eval-rst} +.. autoclass:: h2integrate.converters.iron.humbert_stinn_ewin_cost.HumbertStinnEwinCostComponent + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/h2integrate/converters/iron/humbert_ewin_perf.py b/h2integrate/converters/iron/humbert_ewin_perf.py index b5cf51c7e..6aa0cc818 100644 --- a/h2integrate/converters/iron/humbert_ewin_perf.py +++ b/h2integrate/converters/iron/humbert_ewin_perf.py @@ -48,7 +48,9 @@ class HumbertEwinConfig(BaseConfig): class HumbertEwinPerformanceComponent(om.ExplicitComponent): """OpenMDAO component for the Humbert iron electrowinning performance model. - Inputs: + Attributes: + OpenMDAO Inputs: + electricity_in (array): Electric power input available in kW for each timestep. iron_ore_in (array): Iron ore mass flow available in kg/h for each timestep. ore_fe_concentration (float): The iron content of the ore coming in, given as a percentage. @@ -58,7 +60,8 @@ class HumbertEwinPerformanceComponent(om.ExplicitComponent): OpenMDAO variable to probe the effect of specific energy consumption on iron cost. capacity (float): The electrical capacity of the electrowinning plant in MW. - Outputs: + OpenMDAO Outputs: + electricity_consumed (array): Electric power consumption in kW for each timestep. limiting_input (array): An array of integers indicating which input is the limiting factor for iron production at each timestep: 0 = iron ore, 1 = electricity, 2 = capacity diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index 91e92ba10..89614b69f 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -52,9 +52,11 @@ class HumbertStinnEwinCostComponent(CostModelBaseClass): """OpenMDAO component for the Humbert/Stinn iron electrowinning cost model. Default values for many inputs are set for 3 technology classes: - - Aqueous Hydroxide Electrolysis (AHE) - - Molten Salt Electrolysis (MSE) - - Molten Oxide Electrolysis (MOE) + + - Aqueous Hydroxide Electrolysis (AHE) + - Molten Salt Electrolysis (MSE) + - Molten Oxide Electrolysis (MOE) + All of these values come from the SI spreadsheet for the Humbert paper that can be downloaded at doi.org/10.1007/s40831-024-00878-3 except for the default anode replacement interval. These are exposed to OpenMDAO for potential future optimization/sensitivity analysis. @@ -63,13 +65,15 @@ class HumbertStinnEwinCostComponent(CostModelBaseClass): CapEx is calculated using the Stinn & Allanore model. OpEx is calculated using the Humbert et al. model. - Inputs: - output_capacity (float): + Attributes: + OpenMDAO Inputs: + + output_capacity (float): Maximum annual iron production capacity in kg/year. iron_ore_in (array): Iron ore mass flow available in kg/h for each timestep. - iron_transport_cost (float): - price_iron_ore (float) + iron_transport_cost (float): Cost to transport iron ore in USD/kg. + price_iron_ore (float): Price of iron ore in USD/kg. electricity_in (array): Electric power input available in kW for each timestep. - price_electricity (float): + price_electricity (float): Price of electricity in USD/kWh. specific_energy_electrolysis (float): The specific electrical energy consumption required to win pure iron (Fe) from iron ore - JUST the electrolysis step. electrolysis_temp (float): Electrolysis temperature (°C). @@ -84,9 +88,10 @@ class HumbertStinnEwinCostComponent(CostModelBaseClass): CaCl2_ratio (float): Ratio of CaCl2 consumed to Fe produced. limestone_ratio (float): Ratio of limestone consumed to Fe produced. anode_ratio (float): Ratio of anode mass to annual iron production. - anode_replacement_interval (float): Replacement interval of anodes (years) + anode_replacement_interval (float): Replacement interval of anodes (years). + + OpenMDAO Outputs: - Outputs: CapEx (float): Total capital cost of the electrowinning plant (USD). OpEx (float): Yearly operating expenses in USD/year which do NOT depend on plant output. VarOpEx (float): Yearly operating expenses in USD/year which DO depend on plant output. @@ -100,6 +105,7 @@ class HumbertStinnEwinCostComponent(CostModelBaseClass): anode_opex (float): Portion of the opex that is apportioned to anodes. ore_opex (float): Portion of the opex that is apportioned to ore. elec_opex (float): Portion of the opex that is apportioned to electricity. + """ def initialize(self):