diff --git a/greenheart/simulation/technologies/ammonia/__init__.py b/greenheart/simulation/technologies/ammonia/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/greenheart/simulation/technologies/ammonia/ammonia.py b/greenheart/simulation/technologies/ammonia/ammonia.py new file mode 100644 index 000000000..0f32e43ca --- /dev/null +++ b/greenheart/simulation/technologies/ammonia/ammonia.py @@ -0,0 +1,545 @@ +""" +Author: Abhineet Gupta and Kaitlin Brunik +Created: 02/22/2024 +Institution: National Renewable Energy Lab +Description: This file outputs +Costs are in 2018 USD + +Sources: + - [1] +""" + +from typing import Dict, Union +import ProFAST + +import pandas as pd +from attrs import define, Factory + + +@define +class Feedstocks: + """ + Represents the costs and consumption rates of various feedstocks and resources + used in ammonia production. + + Attributes: + electricity_cost (float): Cost per MWh of electricity. + hydrogen_cost (float): Cost per kg of hydrogen. + cooling_water_cost (float): Cost per gallon of cooling water. + iron_based_catalyst_cost (float): Cost per kg of iron-based catalyst. + oxygen_cost (float): Cost per kg of oxygen. + electricity_consumption (float): Electricity consumption in MWh per kg of + ammonia production, default is 0.1207 / 1000. + hydrogen_consumption (float): Hydrogen consumption in kg per kg of ammonia + production, default is 0.197284403. + cooling_water_consumption (float): Cooling water consumption in gallons per + kg of ammonia production, default is 0.049236824. + iron_based_catalyst_consumption (float): Iron-based catalyst consumption in kg + per kg of ammonia production, default is 0.000091295354067341. + oxygen_byproduct (float): Oxygen byproduct in kg per kg of ammonia production, + default is 0.29405077250145. + """ + + electricity_cost: float + hydrogen_cost: float + cooling_water_cost: float + iron_based_catalyst_cost: float + oxygen_cost: float + electricity_consumption: float = 0.1207 / 1000 + hydrogen_consumption = 0.197284403 + cooling_water_consumption = 0.049236824 + iron_based_catalyst_consumption = 0.000091295354067341 + oxygen_byproduct = 0.29405077250145 + + +@define +class AmmoniaCostModelConfig: + """ + Configuration inputs for the ammonia cost model, including plant capacity and + feedstock details. + + Attributes: + plant_capacity_kgpy (float): Annual production capacity of the plant in kg. + plant_capacity_factor (float): The ratio of actual production to maximum + possible production over a year. + feedstocks (Feedstocks): An instance of the `Feedstocks` class detailing the + costs and consumption rates of resources used in production. + """ + + plant_capacity_kgpy: float + plant_capacity_factor: float + feedstocks: Feedstocks + + +@define +class AmmoniaCosts: + """ + Base dataclass for calculated costs related to ammonia production, including + capital expenditures (CapEx) and operating expenditures (OpEx). + + Attributes: + capex_air_separation_crygenic (float): Capital cost for air separation. + capex_haber_bosch (float): Capital cost for the Haber-Bosch process. + capex_boiler (float): Capital cost for boilers. + capex_cooling_tower (float): Capital cost for cooling towers. + capex_direct (float): Direct capital costs. + capex_depreciable_nonequipment (float): Depreciable non-equipment capital costs. + land_cost (float): Cost of land. + labor_cost (float): Annual labor cost. + general_administration_cost (float): Annual general and administrative cost. + property_tax_insurance (float): Annual property tax and insurance cost. + maintenance_cost (float): Annual maintenance cost. + total_fixed_operating_cost (float): Total annual fixed operating cost. + H2_cost_in_startup_year (float): Hydrogen cost in the startup year. + energy_cost_in_startup_year (float): Energy cost in the startup year. + non_energy_cost_in_startup_year (float): Non-energy cost in the startup year. + variable_cost_in_startup_year (float): Variable cost in the startup year. + credits_byproduct (float): Credits from byproducts. + """ + + # CapEx + capex_air_separation_crygenic: float + capex_haber_bosch: float + capex_boiler: float + capex_cooling_tower: float + capex_direct: float + capex_depreciable_nonequipment: float + land_cost: float + + # Fixed OpEx + labor_cost: float + general_administration_cost: float + property_tax_insurance: float + maintenance_cost: float + total_fixed_operating_cost: float + + # Feedstock and Byproduct costs + H2_cost_in_startup_year: float + energy_cost_in_startup_year: float + non_energy_cost_in_startup_year: float + variable_cost_in_startup_year: float + credits_byproduct: float + + +@define +class AmmoniaCostModelOutputs(AmmoniaCosts): + """ + Outputs from the ammonia cost model, extending `AmmoniaCosts` with total capital + expenditure calculations. + + Attributes: + capex_total (float): The total capital expenditure for the ammonia plant. + """ + + # CapEx + capex_total: float + + +def run_ammonia_model( + plant_capacity_kgpy: float, plant_capacity_factor: float +) -> float: + """ + Calculates the annual ammonia production in kilograms based on the plant's + capacity and its capacity factor. + + Args: + plant_capacity_kgpy (float): The plant's annual capacity in kilograms per year. + plant_capacity_factor (float): The capacity factor of the plant, a ratio of + its actual output over a period of time to its potential output if it + were possible for it to operate at full capacity continuously over the + same period. + + Returns: + float: The calculated annual ammonia production in kilograms per year. + """ + ammonia_production_kgpy = plant_capacity_kgpy * plant_capacity_factor + + return ammonia_production_kgpy + + +def run_ammonia_cost_model(config: AmmoniaCostModelConfig) -> AmmoniaCostModelOutputs: + """ + Calculates the various costs associated with ammonia production, including + capital expenditures (CapEx), operating expenditures (OpEx), and credits from + byproducts, based on the provided configuration settings. + + Args: + config (AmmoniaCostModelConfig): Configuration object containing all necessary + parameters for the cost calculation, including plant capacity, capacity + factor, and feedstock costs. + + Returns: + AmmoniaCostModelOutputs: Object containing detailed breakdowns of calculated + costs, including total capital expenditure, operating costs, and credits + from byproducts. + """ + feedstocks = config.feedstocks + + model_year_CEPCI = 596.2 # TODO: what year + equation_year_CEPCI = 541.7 # TODO: what year + + # scale with respect to a baseline plant (What is this?) + scaling_ratio = config.plant_capacity_kgpy / (365.0 * 1266638.4) + + # -------------------------------CapEx Costs------------------------------ + scaling_factor_equipment = 0.6 + + capex_scale_factor = scaling_ratio**scaling_factor_equipment + capex_air_separation_crygenic = ( + model_year_CEPCI / equation_year_CEPCI * 22506100 * capex_scale_factor + ) + capex_haber_bosch = ( + model_year_CEPCI / equation_year_CEPCI * 18642800 * capex_scale_factor + ) + capex_boiler = model_year_CEPCI / equation_year_CEPCI * 7069100 * capex_scale_factor + capex_cooling_tower = ( + model_year_CEPCI / equation_year_CEPCI * 4799200 * capex_scale_factor + ) + capex_direct = ( + capex_air_separation_crygenic + + capex_haber_bosch + + capex_boiler + + capex_cooling_tower + ) + capex_depreciable_nonequipment = ( + capex_direct * 0.42 + 4112701.84103543 * scaling_ratio + ) + capex_total = capex_direct + capex_depreciable_nonequipment + land_cost = capex_depreciable_nonequipment # TODO: determine if this is the right method or the one in Fixed O&M costs + + # -------------------------------Fixed O&M Costs------------------------------ + scaling_factor_labor = 0.25 + labor_cost = 57 * 50 * 2080 * scaling_ratio**scaling_factor_labor + general_administration_cost = labor_cost * 0.2 + property_tax_insurance = capex_total * 0.02 + maintenance_cost = capex_direct * 0.005 * scaling_ratio**scaling_factor_equipment + land_cost = 2500000 * capex_scale_factor + total_fixed_operating_cost = ( + land_cost + + labor_cost + + general_administration_cost + + property_tax_insurance + + maintenance_cost + ) + + # -------------------------------Feedstock Costs------------------------------ + H2_cost_in_startup_year = ( + feedstocks.hydrogen_cost + * feedstocks.hydrogen_consumption + * config.plant_capacity_kgpy + * config.plant_capacity_factor + ) + energy_cost_in_startup_year = ( + feedstocks.electricity_cost + * feedstocks.electricity_consumption + * config.plant_capacity_kgpy + * config.plant_capacity_factor + ) + non_energy_cost_in_startup_year = ( + ( + (feedstocks.cooling_water_cost * feedstocks.cooling_water_consumption) + + ( + feedstocks.iron_based_catalyst_cost + * feedstocks.iron_based_catalyst_consumption + ) + ) + * config.plant_capacity_kgpy + * config.plant_capacity_factor + ) + variable_cost_in_startup_year = ( + energy_cost_in_startup_year + non_energy_cost_in_startup_year + ) + # -------------------------------Byproduct Costs------------------------------ + credits_byproduct = ( + feedstocks.oxygen_cost + * feedstocks.oxygen_byproduct + * config.plant_capacity_kgpy + * config.plant_capacity_factor + ) + + return AmmoniaCostModelOutputs( + # Capex + capex_air_separation_crygenic=capex_air_separation_crygenic, + capex_haber_bosch=capex_haber_bosch, + capex_boiler=capex_boiler, + capex_cooling_tower=capex_cooling_tower, + capex_direct=capex_direct, + capex_depreciable_nonequipment=capex_depreciable_nonequipment, + capex_total=capex_total, + land_cost=land_cost, + # Fixed OpEx + labor_cost=labor_cost, + general_administration_cost=general_administration_cost, + property_tax_insurance=property_tax_insurance, + maintenance_cost=maintenance_cost, + total_fixed_operating_cost=total_fixed_operating_cost, + # Feedstock & Byproducts + H2_cost_in_startup_year=H2_cost_in_startup_year, + energy_cost_in_startup_year=energy_cost_in_startup_year, + non_energy_cost_in_startup_year=non_energy_cost_in_startup_year, + variable_cost_in_startup_year=variable_cost_in_startup_year, + credits_byproduct=credits_byproduct, + ) + + +@define +class AmmoniaFinanceModelConfig: + """ + Configuration for the financial model of an ammonia production plant, including + operational parameters, cost inputs, and financial assumptions. + + Attributes: + plant_life (int): Expected operational life of the plant in years. + plant_capacity_kgpy (float): Annual production capacity of the plant in kilograms. + plant_capacity_factor (float): The fraction of the year that the plant operates + at full capacity. + grid_prices (Dict[str, float]): Electricity prices per kWh, indexed by year. + feedstocks (Feedstocks): Instance of `Feedstocks` detailing costs and consumption + rates of inputs. + costs (Union[AmmoniaCosts, AmmoniaCostModelOutputs]): Pre-calculated capital and + operating costs for the plant. + financial_assumptions (Dict[str, float]): Key financial metrics and assumptions + for the model, such as discount rate and inflation rate. Default is an + empty dict but should be populated with relevant values. + install_years (int): Number of years over which the plant is installed and + ramped up to full production, default is 3 years. + gen_inflation (float): General inflation rate, default is 0.0. + """ + + plant_life: int + plant_capacity_kgpy: float + plant_capacity_factor: float + grid_prices: Dict[str, float] + feedstocks: Feedstocks + costs: Union[AmmoniaCosts, AmmoniaCostModelOutputs] + financial_assumptions: Dict[str, float] = Factory(dict) + install_years: int = 3 + gen_inflation: float = 0.0 + + +@define +class AmmoniaFinanceModelOutputs: + """ + Outputs from the financial model of an ammonia production plant, providing detailed + financial analysis and projections. + + Attributes: + sol (dict): Solution to the financial model, containing key performance indicators + like Net Present Value (NPV), Internal Rate of Return (IRR), and payback + period. + summary (dict): Summary of the financial analysis, providing a high-level overview + of the plant's financial viability. + price_breakdown (pd.DataFrame): Detailed breakdown of costs contributing to the + production price of ammonia. + ammonia_price_breakdown (dict): Breakdown of the ammonia production cost into + component costs, showing the contribution of each cost element to the + overall production cost. + """ + + sol: dict + summary: dict + price_breakdown: pd.DataFrame + + +def run_ammonia_finance_model( + config: AmmoniaFinanceModelConfig, +) -> AmmoniaFinanceModelOutputs: + """ + Executes the financial analysis for an ammonia production plant based on the + provided configuration settings. This analysis includes calculating the Net + Present Value (NPV), Internal Rate of Return (IRR), payback period, and + providing a detailed cost breakdown for producing ammonia. + + This function leverages the configuration specified in `AmmoniaFinanceModelConfig`, + including plant operational parameters, grid prices, feedstock costs, pre-calculated + CapEx and OpEx, and financial assumptions to evaluate the financial performance of + the ammonia production facility. + + Args: + config (AmmoniaFinanceModelConfig): Configuration object containing all the + necessary parameters for the financial analysis, including assumptions + and pre-calculated cost inputs. + + Returns: + AmmoniaFinanceModelOutputs: An object containing the results of the financial + analysis. This includes a solution dictionary with key financial metrics, + a summary of the financial viability, a price breakdown of ammonia + production costs, and a detailed breakdown of how each cost component + contributes to the overall cost of ammonia. + """ + feedstocks = config.feedstocks + costs = config.costs + + # Set up ProFAST + pf = ProFAST.ProFAST("blank") + + # apply all params passed through from config + for param, val in config.financial_assumptions.items(): + pf.set_params(param, val) + + analysis_start = int(list(config.grid_prices.keys())[0]) - config.install_years + + # Fill these in - can have most of them as 0 also + pf.set_params( + "commodity", + { + "name": "Ammonia", + "unit": "kg", + "initial price": 1000, + "escalation": config.gen_inflation, + }, + ) + pf.set_params("capacity", config.plant_capacity_kgpy / 365) # units/day + pf.set_params( + "maintenance", + {"value": 0, "escalation": config.gen_inflation}, + ) + pf.set_params("analysis start year", analysis_start) + pf.set_params("operating life", config.plant_life) + pf.set_params("installation months", 12 * config.install_years) + pf.set_params( + "installation cost", + { + "value": costs.total_fixed_operating_cost, + "depr type": "Straight line", + "depr period": 4, + "depreciable": False, + }, + ) + pf.set_params("non depr assets", costs.land_cost) + pf.set_params( + "end of proj sale non depr assets", + costs.land_cost * (1 + config.gen_inflation) ** config.plant_life, + ) + pf.set_params("demand rampup", 0) + pf.set_params("long term utilization", config.plant_capacity_factor) + pf.set_params("credit card fees", 0) + pf.set_params("sales tax", 0) + pf.set_params( + "license and permit", {"value": 00, "escalation": config.gen_inflation} + ) + pf.set_params("rent", {"value": 0, "escalation": config.gen_inflation}) + pf.set_params("property tax and insurance", 0) + pf.set_params("admin expense", 0) + pf.set_params("sell undepreciated cap", True) + pf.set_params("tax losses monetized", True) + pf.set_params("general inflation rate", config.gen_inflation) + pf.set_params("debt type", "Revolving debt") + pf.set_params("cash onhand", 1) + + # ----------------------------------- Add capital items to ProFAST ---------------- + pf.add_capital_item( + name="Air Separation by Cryogenic", + cost=costs.capex_air_separation_crygenic, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Haber Bosch", + cost=costs.capex_haber_bosch, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Boiler and Steam Turbine", + cost=costs.capex_boiler, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Cooling Tower", + cost=costs.capex_cooling_tower, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Depreciable Nonequipment", + cost=costs.capex_depreciable_nonequipment, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + + # -------------------------------------- Add fixed costs-------------------------------- + pf.add_fixed_cost( + name="Labor Cost", + usage=1, + unit="$/year", + cost=costs.labor_cost, + escalation=config.gen_inflation, + ) + pf.add_fixed_cost( + name="Maintenance Cost", + usage=1, + unit="$/year", + cost=costs.maintenance_cost, + escalation=config.gen_inflation, + ) + pf.add_fixed_cost( + name="Administrative Expense", + usage=1, + unit="$/year", + cost=costs.general_administration_cost, + escalation=config.gen_inflation, + ) + pf.add_fixed_cost( + name="Property tax and insurance", + usage=1, + unit="$/year", + cost=costs.property_tax_insurance, + escalation=0.0, + ) + + # ---------------------- Add feedstocks, note the various cost options------------------- + pf.add_feedstock( + name="Hydrogen", + usage=feedstocks.hydrogen_consumption, + unit="kilogram of hydrogen per kilogram of ammonia", + cost=feedstocks.hydrogen_cost, + escalation=config.gen_inflation, + ) + + pf.add_feedstock( + name="Electricity", + usage=feedstocks.electricity_consumption, + unit="MWh per kilogram of ammonia", + cost=config.grid_prices, + escalation=config.gen_inflation, + ) + pf.add_feedstock( + name="Cooling water", + usage=feedstocks.cooling_water_consumption, + unit="Gallon per kilogram of ammonia", + cost=feedstocks.cooling_water_cost, + escalation=config.gen_inflation, + ) + pf.add_feedstock( + name="Iron based catalyst", + usage=feedstocks.iron_based_catalyst_consumption, + unit="kilogram of catalyst per kilogram of ammonia", + cost=feedstocks.iron_based_catalyst_cost, + escalation=config.gen_inflation, + ) + pf.add_coproduct( + name="Oxygen byproduct", + usage=feedstocks.oxygen_byproduct, + unit="kilogram of oxygen per kilogram of ammonia", + cost=feedstocks.oxygen_cost, + escalation=config.gen_inflation, + ) + + # ------------------------------ Set up outputs --------------------------- + + sol = pf.solve_price() + summary = pf.get_summary_vals() + price_breakdown = pf.get_cost_breakdown() + + return AmmoniaFinanceModelOutputs( + sol=sol, + summary=summary, + price_breakdown=price_breakdown, + ) diff --git a/greenheart/simulation/technologies/steel.py b/greenheart/simulation/technologies/steel.py new file mode 100644 index 000000000..e2c2c0623 --- /dev/null +++ b/greenheart/simulation/technologies/steel.py @@ -0,0 +1,766 @@ +from typing import Dict, Union + +import ProFAST +import pandas as pd +from attrs import define, Factory + + +@define +class Feedstocks: + """ + Represents the consumption rates and costs of various feedstocks used in steel + production. + + Attributes: + natural_gas_prices (Dict[str, float]): + Natural gas costs, indexed by year ($/GJ). + excess_oxygen (float): Excess oxygen produced (kgO2), default = 395. + lime_unitcost (float): Cost per metric tonne of lime ($/metric tonne). + carbon_unitcost (float): Cost per metric tonne of carbon ($/metric tonne). + electricity_cost (float): + Electricity cost per metric tonne of steel production ($/metric tonne). + iron_ore_pellet_unitcost (float): + Cost per metric tonne of iron ore ($/metric tonne). + oxygen_market_price (float): + Market price per kg of oxygen ($/kgO2). + raw_water_unitcost (float): + Cost per metric tonne of raw water ($/metric tonne). + iron_ore_consumption (float): + Iron ore consumption per metric tonne of steel production (metric tonnes). + raw_water_consumption (float): + Raw water consumption per metric tonne of steel production (metric tonnes). + lime_consumption (float): + Lime consumption per metric tonne of steel production (metric tonnes). + carbon_consumption (float): + Carbon consumption per metric tonne of steel production (metric tonnes). + hydrogen_consumption (float): + Hydrogen consumption per metric tonne of steel production (metric tonnes). + natural_gas_consumption (float): + Natural gas consumption per metric tonne of steel production (GJ-LHV). + electricity_consumption (float): + Electricity consumption per metric tonne of steel production (MWh). + slag_disposal_unitcost (float): + Cost per metric tonne of slag disposal ($/metric tonne). + slag_production (float): + Slag production per metric tonne of steel production (metric tonnes). + maintenance_materials_unitcost (float): + Cost per metric tonne of annual steel slab production at real capacity + factor ($/metric tonne). + """ + + natural_gas_prices: Dict[str, float] + excess_oxygen: float = 395 + lime_unitcost: float = 122.1 + carbon_unitcost: float = 236.97 + electricity_cost: float = 48.92 + iron_ore_pellet_unitcost: float = 207.35 + oxygen_market_price: float = 0.03 + raw_water_unitcost: float = 0.59289 + iron_ore_consumption: float = 1.62927 + raw_water_consumption: float = 0.80367 + lime_consumption: float = 0.01812 + carbon_consumption: float = 0.0538 + hydrogen_consumption: float = 0.06596 + natural_gas_consumption: float = 0.71657 + electricity_consumption: float = 0.5502 + slag_disposal_unitcost: float = 37.63 + slag_production: float = 0.17433 + maintenance_materials_unitcost: float = 7.72 + + +@define +class SteelCostModelConfig: + """ + Configuration for the steel cost model, including operational parameters and + feedstock costs. + + Attributes: + operational_year (int): The year of operation for cost estimation. + plant_capacity_mtpy (float): Plant capacity in metric tons per year. + lcoh (float): Levelized cost of hydrogen ($/kg). + feedstocks (Feedstocks): + An instance of the Feedstocks class containing feedstock consumption + rates and costs. + o2_heat_integration (bool): + Indicates whether oxygen and heat integration is used, affecting preheating + CapEx, cooling CapEx, and oxygen sales. Default is True. + co2_fuel_emissions (float): + CO2 emissions from fuel per metric tonne of steel production. + co2_carbon_emissions (float): + CO2 emissions from carbon per metric tonne of steel production. + surface_water_discharge (float): + Surface water discharge per metric tonne of steel production. + """ + + operational_year: int + plant_capacity_mtpy: float + lcoh: float + feedstocks: Feedstocks + o2_heat_integration: bool = True + co2_fuel_emissions: float = 0.03929 + co2_carbon_emissions: float = 0.17466 + surface_water_discharge: float = 0.42113 + + +@define +class SteelCosts: + """ + Base dataclass for calculated steel costs. + + Attributes: + capex_eaf_casting (float): + Capital expenditure for electric arc furnace and casting. + capex_shaft_furnace (float): Capital expenditure for shaft furnace. + capex_oxygen_supply (float): Capital expenditure for oxygen supply. + capex_h2_preheating (float): Capital expenditure for hydrogen preheating. + capex_cooling_tower (float): Capital expenditure for cooling tower. + capex_piping (float): Capital expenditure for piping. + capex_elec_instr (float): + Capital expenditure for electrical and instrumentation. + capex_buildings_storage_water (float): + Capital expenditure for buildings, storage, and water service. + capex_misc (float): + Capital expenditure for miscellaneous items. + labor_cost_annual_operation (float): Annual operating labor cost. + labor_cost_maintenance (float): Maintenance labor cost. + labor_cost_admin_support (float): Administrative and support labor cost. + property_tax_insurance (float): Cost for property tax and insurance. + land_cost (float): Cost of land. + installation_cost (float): Cost of installation. + + Note: + These represent the minimum set of required cost data for + `run_steel_finance_model`, as well as base data for `SteelCostModelOutputs`. + """ + + capex_eaf_casting: float + capex_shaft_furnace: float + capex_oxygen_supply: float + capex_h2_preheating: float + capex_cooling_tower: float + capex_piping: float + capex_elec_instr: float + capex_buildings_storage_water: float + capex_misc: float + labor_cost_annual_operation: float + labor_cost_maintenance: float + labor_cost_admin_support: float + property_tax_insurance: float + land_cost: float + installation_cost: float + + +@define +class SteelCostModelOutputs(SteelCosts): + """ + Outputs of the steel cost model, extending the SteelCosts data with total + cost calculations and specific cost components related to the operation and + installation of a steel production plant. + + Attributes: + total_plant_cost (float): + The total capital expenditure (CapEx) for the steel plant. + total_fixed_operating_cost (float): + The total annual operating expenditure (OpEx), including labor, + maintenance, administrative support, and property tax/insurance. + labor_cost_fivemonth (float): + Cost of labor for the first five months of operation, often used in startup + cost calculations. + maintenance_materials_onemonth (float): + Cost of maintenance materials for one month of operation. + non_fuel_consumables_onemonth (float): + Cost of non-fuel consumables for one month of operation. + waste_disposal_onemonth (float): + Cost of waste disposal for one month of operation. + monthly_energy_cost (float): + Cost of energy (electricity, natural gas, etc.) for one month of operation. + spare_parts_cost (float): + Cost of spare parts as part of the initial investment. + misc_owners_costs (float): + Miscellaneous costs incurred by the owner, including but not limited to, + initial supply stock, safety equipment, and initial training programs. + """ + + total_plant_cost: float + total_fixed_operating_cost: float + labor_cost_fivemonth: float + maintenance_materials_onemonth: float + non_fuel_consumables_onemonth: float + waste_disposal_onemonth: float + monthly_energy_cost: float + spare_parts_cost: float + misc_owners_costs: float + + +def run_steel_model(plant_capacity_mtpy: float, plant_capacity_factor: float) -> float: + """ + Calculates the annual steel production in metric tons based on plant capacity and + capacity factor. + + Args: + plant_capacity_mtpy (float): + The plant's annual capacity in metric tons per year. + plant_capacity_factor (float): + The capacity factor of the plant. + + Returns: + float: The calculated annual steel production in metric tons per year. + """ + steel_production_mtpy = plant_capacity_mtpy * plant_capacity_factor + + return steel_production_mtpy + + +def run_steel_cost_model(config: SteelCostModelConfig) -> SteelCostModelOutputs: + """ + Calculates the capital expenditure (CapEx) and operating expenditure (OpEx) for + a steel manufacturing plant based on the provided configuration. + + Args: + config (SteelCostModelConfig): + Configuration object containing all necessary parameters for the cost + model, including plant capacity, feedstock costs, and integration options + for oxygen and heat. + + Returns: + SteelCostModelOutputs: An object containing detailed breakdowns of capital and + operating costs, as well as total plant cost and other financial metrics. + + Note: + The calculation includes various cost components such as electric arc furnace + (EAF) casting, shaft furnace, oxygen supply, hydrogen preheating, cooling tower, + and more, adjusted based on the Chemical Engineering Plant Cost Index (CEPCI). + """ + feedstocks = config.feedstocks + + model_year_CEPCI = 596.2 + equation_year_CEPCI = 708.8 + + capex_eaf_casting = ( + model_year_CEPCI + / equation_year_CEPCI + * 352191.5237 + * config.plant_capacity_mtpy**0.456 + ) + capex_shaft_furnace = ( + model_year_CEPCI + / equation_year_CEPCI + * 489.68061 + * config.plant_capacity_mtpy**0.88741 + ) + capex_oxygen_supply = ( + model_year_CEPCI + / equation_year_CEPCI + * 1715.21508 + * config.plant_capacity_mtpy**0.64574 + ) + if config.o2_heat_integration: + capex_h2_preheating = ( + model_year_CEPCI + / equation_year_CEPCI + * (1 - 0.4) + * (45.69123 * config.plant_capacity_mtpy**0.86564) + ) # Optimistic ballpark estimate of 60% reduction in preheating + capex_cooling_tower = ( + model_year_CEPCI + / equation_year_CEPCI + * (1 - 0.3) + * (2513.08314 * config.plant_capacity_mtpy**0.63325) + ) # Optimistic ballpark estimate of 30% reduction in cooling + else: + capex_h2_preheating = ( + model_year_CEPCI + / equation_year_CEPCI + * 45.69123 + * config.plant_capacity_mtpy**0.86564 + ) + capex_cooling_tower = ( + model_year_CEPCI + / equation_year_CEPCI + * 2513.08314 + * config.plant_capacity_mtpy**0.63325 + ) + capex_piping = ( + model_year_CEPCI + / equation_year_CEPCI + * 11815.72718 + * config.plant_capacity_mtpy**0.59983 + ) + capex_elec_instr = ( + model_year_CEPCI + / equation_year_CEPCI + * 7877.15146 + * config.plant_capacity_mtpy**0.59983 + ) + capex_buildings_storage_water = ( + model_year_CEPCI + / equation_year_CEPCI + * 1097.81876 + * config.plant_capacity_mtpy**0.8 + ) + capex_misc = ( + model_year_CEPCI + / equation_year_CEPCI + * 7877.1546 + * config.plant_capacity_mtpy**0.59983 + ) + + total_plant_cost = ( + capex_eaf_casting + + capex_shaft_furnace + + capex_oxygen_supply + + capex_h2_preheating + + capex_cooling_tower + + capex_piping + + capex_elec_instr + + capex_buildings_storage_water + + capex_misc + ) + + # -------------------------------Fixed O&M Costs------------------------------ + + labor_cost_annual_operation = ( + 69375996.9 + * ((config.plant_capacity_mtpy / 365 * 1000) ** 0.25242) + / ((1162077 / 365 * 1000) ** 0.25242) + ) + labor_cost_maintenance = 0.00863 * total_plant_cost + labor_cost_admin_support = 0.25 * ( + labor_cost_annual_operation + labor_cost_maintenance + ) + + property_tax_insurance = 0.02 * total_plant_cost + + total_fixed_operating_cost = ( + labor_cost_annual_operation + + labor_cost_maintenance + + labor_cost_admin_support + + property_tax_insurance + ) + + # ---------------------- Owner's (Installation) Costs -------------------------- + labor_cost_fivemonth = ( + 5 + / 12 + * ( + labor_cost_annual_operation + + labor_cost_maintenance + + labor_cost_admin_support + ) + ) + + maintenance_materials_onemonth = ( + feedstocks.maintenance_materials_unitcost * config.plant_capacity_mtpy / 12 + ) + non_fuel_consumables_onemonth = ( + config.plant_capacity_mtpy + * ( + feedstocks.raw_water_consumption * feedstocks.raw_water_unitcost + + feedstocks.lime_consumption * feedstocks.lime_unitcost + + feedstocks.carbon_consumption * feedstocks.carbon_unitcost + + feedstocks.iron_ore_consumption * feedstocks.iron_ore_pellet_unitcost + ) + / 12 + ) + + waste_disposal_onemonth = ( + config.plant_capacity_mtpy + * feedstocks.slag_disposal_unitcost + * feedstocks.slag_production + / 12 + ) + + monthly_energy_cost = ( + config.plant_capacity_mtpy + * ( + feedstocks.hydrogen_consumption * config.lcoh * 1000 + + feedstocks.natural_gas_consumption + * feedstocks.natural_gas_prices[str(config.operational_year)] + + feedstocks.electricity_consumption * feedstocks.electricity_cost + ) + / 12 + ) + two_percent_tpc = 0.02 * total_plant_cost + + fuel_consumables_60day_supply_cost = ( + config.plant_capacity_mtpy + * ( + feedstocks.raw_water_consumption * feedstocks.raw_water_unitcost + + feedstocks.lime_consumption * feedstocks.lime_unitcost + + feedstocks.carbon_consumption * feedstocks.carbon_unitcost + + feedstocks.iron_ore_consumption * feedstocks.iron_ore_pellet_unitcost + ) + / 365 + * 60 + ) + + spare_parts_cost = 0.005 * total_plant_cost + land_cost = 0.775 * config.plant_capacity_mtpy + misc_owners_costs = 0.15 * total_plant_cost + + installation_cost = ( + labor_cost_fivemonth + + two_percent_tpc + + fuel_consumables_60day_supply_cost + + spare_parts_cost + + misc_owners_costs + ) + + return SteelCostModelOutputs( + # CapEx + capex_eaf_casting=capex_eaf_casting, + capex_shaft_furnace=capex_shaft_furnace, + capex_oxygen_supply=capex_oxygen_supply, + capex_h2_preheating=capex_h2_preheating, + capex_cooling_tower=capex_cooling_tower, + capex_piping=capex_piping, + capex_elec_instr=capex_elec_instr, + capex_buildings_storage_water=capex_buildings_storage_water, + capex_misc=capex_misc, + total_plant_cost=total_plant_cost, + # Fixed OpEx + labor_cost_annual_operation=labor_cost_annual_operation, + labor_cost_maintenance=labor_cost_maintenance, + labor_cost_admin_support=labor_cost_admin_support, + property_tax_insurance=property_tax_insurance, + total_fixed_operating_cost=total_fixed_operating_cost, + # Owner's Installation costs + labor_cost_fivemonth=labor_cost_fivemonth, + maintenance_materials_onemonth=maintenance_materials_onemonth, + non_fuel_consumables_onemonth=non_fuel_consumables_onemonth, + waste_disposal_onemonth=waste_disposal_onemonth, + monthly_energy_cost=monthly_energy_cost, + spare_parts_cost=spare_parts_cost, + land_cost=land_cost, + misc_owners_costs=misc_owners_costs, + installation_cost=installation_cost, + ) + + +@define +class SteelFinanceModelConfig: + """ + Configuration for the steel finance model, including plant characteristics, financial assumptions, and cost inputs. + + Attributes: + plant_life (int): The operational lifetime of the plant in years. + plant_capacity_mtpy (float): Plant capacity in metric tons per year. + plant_capacity_factor (float): + The fraction of the year the plant operates at full capacity. + steel_production_mtpy (float): Annual steel production in metric tons. + lcoh (float): Levelized cost of hydrogen. + grid_prices (Dict[str, float]): Electricity prices per unit. + feedstocks (Feedstocks): + The feedstocks required for steel production, including types and costs. + costs (Union[SteelCosts, SteelCostModelOutputs]): + Calculated CapEx and OpEx costs. + o2_heat_integration (bool): Indicates if oxygen and heat integration is used. + financial_assumptions (Dict[str, float]): + Financial assumptions for model calculations. + install_years (int): The number of years over which the plant is installed. + gen_inflation (float): General inflation rate. + """ + + plant_life: int + plant_capacity_mtpy: float + plant_capacity_factor: float + steel_production_mtpy: float + lcoh: float + grid_prices: Dict[str, float] + feedstocks: Feedstocks + costs: Union[SteelCosts, SteelCostModelOutputs] + o2_heat_integration: bool = True + financial_assumptions: Dict[str, float] = Factory(dict) + install_years: int = 3 + gen_inflation: float = 0.00 + + +@define +class SteelFinanceModelOutputs: + """ + Represents the outputs of the steel finance model, encapsulating the results of financial analysis for steel production. + + Attributes: + sol (dict): + A dictionary containing the solution to the financial model, including key + financial indicators such as NPV (Net Present Value), IRR (Internal Rate of + Return), and breakeven price. + summary (dict): + A summary of key results from the financial analysis, providing a + high-level overview of financial metrics and performance indicators. + price_breakdown (pd.DataFrame): + A Pandas DataFrame detailing the cost breakdown for producing steel, + including both capital and operating expenses, as well as the impact of + various cost factors on the overall price of steel. + """ + + sol: dict + summary: dict + price_breakdown: pd.DataFrame + + +def run_steel_finance_model( + config: SteelFinanceModelConfig, +) -> SteelFinanceModelOutputs: + """ + Executes the financial model for steel production, calculating the breakeven price + of steel and other financial metrics based on the provided configuration and cost + models. + + This function integrates various cost components, including capital expenditures + (CapEx), operating expenses (OpEx), and owner's costs. It leverages the ProFAST + financial analysis software framework. + + Args: + config (SteelFinanceModelConfig): + Configuration object containing all necessary parameters and assumptions + for the financial model, including plant characteristics, cost inputs, + financial assumptions, and grid prices. + + Returns: + SteelFinanceModelOutputs: + Object containing detailed financial analysis results, including solution + metrics, summary values, price breakdown, and steel price breakdown per + tonne. This output is instrumental in assessing the financial performance + and breakeven price for the steel production facility. + """ + + feedstocks = config.feedstocks + costs = config.costs + + # Set up ProFAST + pf = ProFAST.ProFAST("blank") + + # apply all params passed through from config + for param, val in config.financial_assumptions.items(): + print(f"setting {param}: {val}") + pf.set_params(param, val) + + analysis_start = int(list(config.grid_prices.keys())[0]) - config.install_years + + # Fill these in - can have most of them as 0 also + pf.set_params( + "commodity", + { + "name": "Steel", + "unit": "metric tonnes", + "initial price": 1000, + "escalation": config.gen_inflation, + }, + ) + pf.set_params("capacity", config.plant_capacity_mtpy / 365) # units/day + pf.set_params("maintenance", {"value": 0, "escalation": config.gen_inflation}) + pf.set_params("analysis start year", analysis_start) + pf.set_params("operating life", config.plant_life) + pf.set_params("installation months", 12 * config.install_years) + pf.set_params( + "installation cost", + { + "value": costs.installation_cost, + "depr type": "Straight line", + "depr period": 4, + "depreciable": False, + }, + ) + pf.set_params("non depr assets", costs.land_cost) + pf.set_params( + "end of proj sale non depr assets", + costs.land_cost * (1 + config.gen_inflation) ** config.plant_life, + ) + pf.set_params("demand rampup", 5.3) + pf.set_params("long term utilization", config.plant_capacity_factor) + pf.set_params("credit card fees", 0) + pf.set_params("sales tax", 0) + pf.set_params( + "license and permit", {"value": 00, "escalation": config.gen_inflation} + ) + pf.set_params("rent", {"value": 0, "escalation": config.gen_inflation}) + pf.set_params("property tax and insurance", 0) + pf.set_params("admin expense", 0) + pf.set_params("sell undepreciated cap", True) + pf.set_params("tax losses monetized", True) + pf.set_params("general inflation rate", config.gen_inflation) + pf.set_params("debt type", "Revolving debt") + pf.set_params("cash onhand", 1) + + # ----------------------------------- Add capital items to ProFAST ---------------- + pf.add_capital_item( + name="EAF & Casting", + cost=costs.capex_eaf_casting, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Shaft Furnace", + cost=costs.capex_shaft_furnace, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Oxygen Supply", + cost=costs.capex_oxygen_supply, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="H2 Pre-heating", + cost=costs.capex_h2_preheating, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Cooling Tower", + cost=costs.capex_cooling_tower, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Piping", + cost=costs.capex_piping, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Electrical & Instrumentation", + cost=costs.capex_elec_instr, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Buildings, Storage, Water Service", + cost=costs.capex_buildings_storage_water, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Other Miscellaneous Costs", + cost=costs.capex_misc, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + + # -------------------------------------- Add fixed costs-------------------------------- + pf.add_fixed_cost( + name="Annual Operating Labor Cost", + usage=1, + unit="$/year", + cost=costs.labor_cost_annual_operation, + escalation=config.gen_inflation, + ) + pf.add_fixed_cost( + name="Maintenance Labor Cost", + usage=1, + unit="$/year", + cost=costs.labor_cost_maintenance, + escalation=config.gen_inflation, + ) + pf.add_fixed_cost( + name="Administrative & Support Labor Cost", + usage=1, + unit="$/year", + cost=costs.labor_cost_admin_support, + escalation=config.gen_inflation, + ) + pf.add_fixed_cost( + name="Property tax and insurance", + usage=1, + unit="$/year", + cost=costs.property_tax_insurance, + escalation=0.0, + ) + # Putting property tax and insurance here to zero out depcreciation/escalation. Could instead put it in set_params if + # we think that is more accurate + + # ---------------------- Add feedstocks, note the various cost options------------------- + pf.add_feedstock( + name="Maintenance Materials", + usage=1.0, + unit="Units per metric tonne of steel", + cost=feedstocks.maintenance_materials_unitcost, + escalation=config.gen_inflation, + ) + pf.add_feedstock( + name="Raw Water Withdrawal", + usage=feedstocks.raw_water_consumption, + unit="metric tonnes of water per metric tonne of steel", + cost=feedstocks.raw_water_unitcost, + escalation=config.gen_inflation, + ) + pf.add_feedstock( + name="Lime", + usage=feedstocks.lime_consumption, + unit="metric tonnes of lime per metric tonne of steel", + cost=feedstocks.lime_unitcost, + escalation=config.gen_inflation, + ) + pf.add_feedstock( + name="Carbon", + usage=feedstocks.carbon_consumption, + unit="metric tonnes of carbon per metric tonne of steel", + cost=feedstocks.carbon_unitcost, + escalation=config.gen_inflation, + ) + pf.add_feedstock( + name="Iron Ore", + usage=feedstocks.iron_ore_consumption, + unit="metric tonnes of iron ore per metric tonne of steel", + cost=feedstocks.iron_ore_pellet_unitcost, + escalation=config.gen_inflation, + ) + pf.add_feedstock( + name="Hydrogen", + usage=feedstocks.hydrogen_consumption, + unit="metric tonnes of hydrogen per metric tonne of steel", + cost=config.lcoh * 1000, + escalation=config.gen_inflation, + ) + pf.add_feedstock( + name="Natural Gas", + usage=feedstocks.natural_gas_consumption, + unit="GJ-LHV per metric tonne of steel", + cost=feedstocks.natural_gas_prices, + escalation=config.gen_inflation, + ) + pf.add_feedstock( + name="Electricity", + usage=feedstocks.electricity_consumption, + unit="MWh per metric tonne of steel", + cost=config.grid_prices, + escalation=config.gen_inflation, + ) + pf.add_feedstock( + name="Slag Disposal", + usage=feedstocks.slag_production, + unit="metric tonnes of slag per metric tonne of steel", + cost=feedstocks.slag_disposal_unitcost, + escalation=config.gen_inflation, + ) + + pf.add_coproduct( + name="Oxygen sales", + usage=feedstocks.excess_oxygen, + unit="kg O2 per metric tonne of steel", + cost=feedstocks.oxygen_market_price, + escalation=config.gen_inflation, + ) + + # ------------------------------ Set up outputs --------------------------- + + sol = pf.solve_price() + summary = pf.get_summary_vals() + price_breakdown = pf.get_cost_breakdown() + + return SteelFinanceModelOutputs( + sol=sol, + summary=summary, + price_breakdown=price_breakdown, + ) diff --git a/tests/greenheart/test_ammonia.py b/tests/greenheart/test_ammonia.py new file mode 100644 index 000000000..83be7af0e --- /dev/null +++ b/tests/greenheart/test_ammonia.py @@ -0,0 +1,112 @@ +from pytest import approx + +from greenheart.simulation.technologies.ammonia import ammonia + + +def test_run_ammonia_model(): + capacity = 100.0 + capacity_factor = 0.9 + + ammonia_production_kgpy = ammonia.run_ammonia_model(capacity, capacity_factor) + + assert ammonia_production_kgpy == 90 + + +def test_ammonia_cost_model(subtests): + config = ammonia.AmmoniaCostModelConfig( + plant_capacity_kgpy=362560672.27155423, + plant_capacity_factor=0.9, + feedstocks=ammonia.Feedstocks( + electricity_cost=89.42320514456621, + hydrogen_cost=4.2986685034417045, + cooling_water_cost=0.00291, + iron_based_catalyst_cost=23.19977341, + oxygen_cost=0, + ), + ) + + res: ammonia.AmmoniaCostModelOutputs = ammonia.run_ammonia_cost_model(config) + + with subtests.test("Total CapEx"): + assert res.capex_total == approx(74839480.74961768) + with subtests.test("Fixed OpEx"): + assert res.total_fixed_operating_cost == approx(10569658.376900101) + with subtests.test("Variable costs"): + assert res.variable_cost_in_startup_year == approx(4259805.969069265) + with subtests.test("Land costs"): + assert res.land_cost == approx(2160733.0556864925) + + +def test_ammonia_finance_model(): + # Parameter -> Hydrogen/Steel/Ammonia + 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, + } + + cost_config = ammonia.AmmoniaCostModelConfig( + plant_capacity_kgpy=362560672.27155423, + plant_capacity_factor=0.9, + feedstocks=ammonia.Feedstocks( + electricity_cost=89.42320514456621, + hydrogen_cost=4.2986685034417045, + cooling_water_cost=0.00291, + iron_based_catalyst_cost=23.19977341, + oxygen_cost=0, + ), + ) + + costs: ammonia.AmmoniaCostModelOutputs = ammonia.run_ammonia_cost_model(cost_config) + + plant_capacity_kgpy = 362560672.27155423 + plant_capacity_factor = 0.9 + + config = ammonia.AmmoniaFinanceModelConfig( + plant_life=30, + plant_capacity_kgpy=plant_capacity_kgpy, + plant_capacity_factor=plant_capacity_factor, + feedstocks=cost_config.feedstocks, + grid_prices={ + "2035": 89.42320514456621, + "2036": 89.97947569251141, + "2037": 90.53574624045662, + "2038": 91.09201678840184, + "2039": 91.64828733634704, + "2040": 92.20455788429224, + "2041": 89.87291235917809, + "2042": 87.54126683406393, + "2043": 85.20962130894978, + "2044": 82.87797578383562, + "2045": 80.54633025872147, + "2046": 81.38632144593608, + "2047": 82.22631263315068, + "2048": 83.0663038203653, + "2049": 83.90629500757991, + "2050": 84.74628619479452, + "2051": 84.74628619479452, + "2052": 84.74628619479452, + "2053": 84.74628619479452, + "2054": 84.74628619479452, + "2055": 84.74628619479452, + "2056": 84.74628619479452, + "2057": 84.74628619479452, + "2058": 84.74628619479452, + "2059": 84.74628619479452, + "2060": 84.74628619479452, + "2061": 84.74628619479452, + "2062": 84.74628619479452, + "2063": 84.74628619479452, + "2064": 84.74628619479452, + }, + financial_assumptions=financial_assumptions, + costs=costs, + ) + + lcoa_expected = 0.9322866176899477 + + res: ammonia.AmmoniaFinanceModelOutputs = ammonia.run_ammonia_finance_model(config) + + assert res.sol.get("price") == lcoa_expected diff --git a/tests/greenheart/test_steel.py b/tests/greenheart/test_steel.py new file mode 100644 index 000000000..3b36f898a --- /dev/null +++ b/tests/greenheart/test_steel.py @@ -0,0 +1,139 @@ +from pytest import approx, fixture + +from greenheart.simulation.technologies import steel + +ng_prices_dict = { + "2035": 3.76232, + "2036": 3.776032, + "2037": 3.812906, + "2038": 3.9107960000000004, + "2039": 3.865776, + "2040": 3.9617400000000003, + "2041": 4.027136, + "2042": 4.017166, + "2043": 3.9715339999999997, + "2044": 3.924314, + "2045": 3.903287, + "2046": 3.878192, + "2047": 3.845413, + "2048": 3.813366, + "2049": 3.77735, + "2050": 3.766164, + "2051": 3.766164, + "2052": 3.766164, + "2053": 3.766164, + "2054": 3.766164, + "2055": 3.766164, + "2056": 3.766164, + "2057": 3.766164, + "2058": 3.766164, + "2059": 3.766164, + "2060": 3.766164, + "2061": 3.766164, + "2062": 3.766164, + "2063": 3.766164, + "2064": 3.766164, +} +grid_prices_dict = { + "2035": 89.42320514456621, + "2036": 89.97947569251141, + "2037": 90.53574624045662, + "2038": 91.09201678840184, + "2039": 91.64828733634704, + "2040": 92.20455788429224, + "2041": 89.87291235917809, + "2042": 87.54126683406393, + "2043": 85.20962130894978, + "2044": 82.87797578383562, + "2045": 80.54633025872147, + "2046": 81.38632144593608, + "2047": 82.22631263315068, + "2048": 83.0663038203653, + "2049": 83.90629500757991, + "2050": 84.74628619479452, + "2051": 84.74628619479452, + "2052": 84.74628619479452, + "2053": 84.74628619479452, + "2054": 84.74628619479452, + "2055": 84.74628619479452, + "2056": 84.74628619479452, + "2057": 84.74628619479452, + "2058": 84.74628619479452, + "2059": 84.74628619479452, + "2060": 84.74628619479452, + "2061": 84.74628619479452, + "2062": 84.74628619479452, + "2063": 84.74628619479452, + "2064": 84.74628619479452, +} + + +@fixture +def cost_config(): + config = steel.SteelCostModelConfig( + operational_year=2035, + plant_capacity_mtpy=1084408.2137715619, + lcoh=4.2986685034417045, + feedstocks=steel.Feedstocks( + natural_gas_prices=ng_prices_dict, oxygen_market_price=0 + ), + o2_heat_integration=False, + ) + return config + + +def test_run_steel_model(): + capacity = 100.0 + capacity_factor = 0.9 + + steel_production_mtpy = steel.run_steel_model(capacity, capacity_factor) + + assert steel_production_mtpy == 90.0 + + +def test_steel_cost_model(subtests, cost_config): + + res: steel.SteelCostModelOutputs = steel.run_steel_cost_model(cost_config) + + with subtests.test("CapEx"): + assert res.total_plant_cost == approx(451513562.41513157) + with subtests.test("Fixed OpEx"): + assert res.total_fixed_operating_cost == approx(99119892.8431614) + with subtests.test("Installation"): + assert res.installation_cost == approx(179525207.856775) + + +def test_steel_finance_model(cost_config): + # Parameter -> Hydrogen/Steel/Ammonia + 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, + } + + costs: steel.SteelCostModelOutputs = steel.run_steel_cost_model(cost_config) + + plant_capacity_factor = 0.9 + steel_production_mtpy = steel.run_steel_model( + cost_config.plant_capacity_mtpy, plant_capacity_factor + ) + + config = steel.SteelFinanceModelConfig( + plant_life=30, + plant_capacity_mtpy=cost_config.plant_capacity_mtpy, + plant_capacity_factor=plant_capacity_factor, + steel_production_mtpy=steel_production_mtpy, + lcoh=cost_config.lcoh, + feedstocks=cost_config.feedstocks, + grid_prices=grid_prices_dict, + financial_assumptions=financial_assumptions, + costs=costs, + ) + + lcos_expected = 961.2866791076059 + + res: steel.SteelFinanceModelOutputs = steel.run_steel_finance_model(config) + + assert res.sol.get("price") == lcos_expected