From 763c5302fb19f5f051ec0da544b17a614dfcfadb Mon Sep 17 00:00:00 2001 From: John Jasa Date: Fri, 17 Oct 2025 12:44:50 -0600 Subject: [PATCH 01/30] Removing unneeded files and tests --- .../hydrogen/eco_tools_pem_electrolyzer.py | 5 +- h2integrate/tools/eco/__init__.py | 3 - h2integrate/tools/eco/electrolysis.py | 282 -- h2integrate/tools/eco/finance.py | 1844 --------- h2integrate/tools/eco/utilities.py | 3679 ----------------- tests/h2integrate/test_finance.py | 248 -- .../h2integrate/test_h2integrate_utilities.py | 43 - tests/h2integrate/test_steel.py | 298 -- 8 files changed, 4 insertions(+), 6398 deletions(-) delete mode 100644 h2integrate/tools/eco/__init__.py delete mode 100644 h2integrate/tools/eco/electrolysis.py delete mode 100644 h2integrate/tools/eco/finance.py delete mode 100644 h2integrate/tools/eco/utilities.py delete mode 100644 tests/h2integrate/test_finance.py delete mode 100644 tests/h2integrate/test_h2integrate_utilities.py delete mode 100644 tests/h2integrate/test_steel.py diff --git a/h2integrate/converters/hydrogen/eco_tools_pem_electrolyzer.py b/h2integrate/converters/hydrogen/eco_tools_pem_electrolyzer.py index de64729f0..eedb6340a 100644 --- a/h2integrate/converters/hydrogen/eco_tools_pem_electrolyzer.py +++ b/h2integrate/converters/hydrogen/eco_tools_pem_electrolyzer.py @@ -2,11 +2,14 @@ from h2integrate.core.utilities import BaseConfig, merge_shared_inputs from h2integrate.core.validators import gt_zero, contains -from h2integrate.tools.eco.utilities import ceildiv from h2integrate.converters.hydrogen.electrolyzer_baseclass import ElectrolyzerPerformanceBaseClass from h2integrate.simulation.technologies.hydrogen.electrolysis.run_h2_PEM import run_h2_PEM +def ceildiv(a, b): + return -(a // -b) + + @define class ECOElectrolyzerPerformanceModelConfig(BaseConfig): """ diff --git a/h2integrate/tools/eco/__init__.py b/h2integrate/tools/eco/__init__.py deleted file mode 100644 index 854517ca9..000000000 --- a/h2integrate/tools/eco/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .electrolysis import * -from .finance import * -from .utilities import * diff --git a/h2integrate/tools/eco/electrolysis.py b/h2integrate/tools/eco/electrolysis.py deleted file mode 100644 index e53b408b2..000000000 --- a/h2integrate/tools/eco/electrolysis.py +++ /dev/null @@ -1,282 +0,0 @@ -import warnings -from pathlib import Path - -import numpy as np -import pandas as pd -import matplotlib.pyplot as plt -from matplotlib import ticker - -from h2integrate.tools.eco.utilities import ceildiv -from h2integrate.simulation.technologies.hydrogen.electrolysis.run_h2_PEM import run_h2_PEM -from h2integrate.simulation.technologies.hydrogen.electrolysis.pem_mass_and_footprint import ( - mass as run_electrolyzer_mass, - footprint as run_electrolyzer_footprint, -) - - -def run_electrolyzer_physics( - hopp_results, - h2integrate_config, - wind_resource, - design_scenario, - show_plots=False, - save_plots=False, - output_dir="./output/", - verbose=False, -): - if isinstance(output_dir, str): - output_dir = Path(output_dir).resolve() - electrolyzer_size_mw = h2integrate_config["electrolyzer"]["rating"] - electrolyzer_capex_kw = h2integrate_config["electrolyzer"]["electrolyzer_capex"] - - # IF GRID CONNECTED - if h2integrate_config["project_parameters"]["grid_connection"]: - # NOTE: if grid-connected, it assumes that hydrogen demand is input and there is not - # multi-cluster control strategies. This capability exists at the cluster level, not at the - # system level. - if h2integrate_config["electrolyzer"]["sizing"]["hydrogen_dmd"] is not None: - grid_connection_scenario = "grid-only" - hydrogen_production_capacity_required_kgphr = h2integrate_config["electrolyzer"][ - "sizing" - ]["hydrogen_dmd"] - energy_to_electrolyzer_kw = [] - else: - grid_connection_scenario = "off-grid" - hydrogen_production_capacity_required_kgphr = [] - energy_to_electrolyzer_kw = np.ones(8760) * electrolyzer_size_mw * 1e3 - # IF NOT GRID CONNECTED - else: - hydrogen_production_capacity_required_kgphr = [] - grid_connection_scenario = "off-grid" - energy_to_electrolyzer_kw = np.asarray( - hopp_results["combined_hybrid_power_production_hopp"] - ) - - n_pem_clusters = int( - ceildiv( - round(electrolyzer_size_mw, 1), h2integrate_config["electrolyzer"]["cluster_rating_MW"] - ) - ) - - electrolyzer_real_capacity_kW = ( - n_pem_clusters * h2integrate_config["electrolyzer"]["cluster_rating_MW"] * 1e3 - ) - - if np.abs(electrolyzer_real_capacity_kW - (electrolyzer_size_mw * 1e3)) > 1.0: - electrolyzer_real_capacity_mw = electrolyzer_real_capacity_kW / 1e3 - cluster_cap_mw = h2integrate_config["electrolyzer"]["cluster_rating_MW"] - msg = ( - f"setting electrolyzer capacity to {electrolyzer_real_capacity_mw} MW. " - f"Input value of {electrolyzer_size_mw:.2f} MW is not a " - f"multiple of cluster capacity ({cluster_cap_mw} MW)" - ) - warnings.warn(msg, UserWarning) - ## run using greensteel model - pem_param_dict = { - "eol_eff_percent_loss": h2integrate_config["electrolyzer"]["eol_eff_percent_loss"], - "uptime_hours_until_eol": h2integrate_config["electrolyzer"]["uptime_hours_until_eol"], - "include_degradation_penalty": h2integrate_config["electrolyzer"][ - "include_degradation_penalty" - ], - "turndown_ratio": h2integrate_config["electrolyzer"]["turndown_ratio"], - } - - if "water_usage_gal_pr_kg" in h2integrate_config["electrolyzer"]: - pem_param_dict.update( - {"water_usage_gal_pr_kg": h2integrate_config["electrolyzer"]["water_usage_gal_pr_kg"]} - ) - if "curve_coeff" in h2integrate_config["electrolyzer"]: - pem_param_dict.update({"curve_coeff": h2integrate_config["electrolyzer"]["curve_coeff"]}) - - if "time_between_replacement" in h2integrate_config["electrolyzer"]: - msg = ( - "`time_between_replacement` as an input is deprecated. It is now calculated internally" - " and is output in electrolyzer_physics_results['H2_Results']['Time Until Replacement" - " [hrs]']." - ) - warnings.warn(msg) - - H2_Results, h2_ts, h2_tot, power_to_electrolyzer_kw = run_h2_PEM( - electrical_generation_timeseries=energy_to_electrolyzer_kw, - electrolyzer_size=electrolyzer_size_mw, - useful_life=h2integrate_config["project_parameters"][ - "project_lifetime" - ], # EG: should be in years for full plant life - only used in financial model - n_pem_clusters=n_pem_clusters, - pem_control_type=h2integrate_config["electrolyzer"]["pem_control_type"], - electrolyzer_direct_cost_kw=electrolyzer_capex_kw, - user_defined_pem_param_dictionary=pem_param_dict, - grid_connection_scenario=grid_connection_scenario, # if not offgrid, assumes steady h2 demand in kgphr for full year # noqa: E501 - hydrogen_production_capacity_required_kgphr=hydrogen_production_capacity_required_kgphr, - debug_mode=False, - verbose=verbose, - ) - - # calculate mass and foorprint of system - mass_kg = run_electrolyzer_mass(electrolyzer_size_mw) - footprint_m2 = run_electrolyzer_footprint(electrolyzer_size_mw) - - # store results for return - H2_Results.update({"system capacity [kW]": electrolyzer_real_capacity_kW}) - electrolyzer_physics_results = { - "H2_Results": H2_Results, - "capacity_factor": H2_Results["Life: Capacity Factor"], - "equipment_mass_kg": mass_kg, - "equipment_footprint_m2": footprint_m2, - "power_to_electrolyzer_kw": power_to_electrolyzer_kw, - } - - if verbose: - print("\nElectrolyzer Physics:") # 61837444.34555772 145297297.29729727 - print( - "H2 Produced Annually (metric tons): ", - H2_Results["Life: Annual H2 production [kg/year]"] * 1e-3, - ) - print( - "Max H2 hourly (metric tons): ", - max(H2_Results["Hydrogen Hourly Production [kg/hr]"]) * 1e-3, - ) - print( - "Max H2 daily (metric tons): ", - max( - np.convolve( - H2_Results["Hydrogen Hourly Production [kg/hr]"], - np.ones(24), - mode="valid", - ) - ) - * 1e-3, - ) - - prodrate = 1.0 / round(H2_Results["Rated BOL: Efficiency [kWh/kg]"], 2) # kg/kWh - roughest = power_to_electrolyzer_kw * prodrate - print("Energy to electrolyzer (kWh): ", sum(power_to_electrolyzer_kw)) - print( - "Energy per kg (kWh/kg): ", - H2_Results["Sim: Total Input Power [kWh]"] / H2_Results["Sim: Total H2 Produced [kg]"], - ) - print("Max hourly based on est kg/kWh (kg): ", max(roughest)) - print( - "Max daily rough est (metric tons): ", - max(np.convolve(roughest, np.ones(24), mode="valid")) * 1e-3, - ) - print( - "Electrolyzer Life Average Capacity Factor: ", - H2_Results["Life: Capacity Factor"], - ) - - if save_plots or show_plots: - N = 24 * 7 * 4 - fig, ax = plt.subplots(3, 2, sharex=True, sharey="row") - - wind_speed = [W[2] for W in wind_resource._data["data"]] - - # plt.title("4-week running average") - pad = 5 - ax[0, 0].annotate( - "Hourly", - xy=(0.5, 1), - xytext=(0, pad), - xycoords="axes fraction", - textcoords="offset points", - size="large", - ha="center", - va="baseline", - ) - ax[0, 1].annotate( - "4-week running average", - xy=(0.5, 1), - xytext=(0, pad), - xycoords="axes fraction", - textcoords="offset points", - size="large", - ha="center", - va="baseline", - ) - - ax[0, 0].plot(wind_speed) - convolved_wind_speed = np.convolve(wind_speed, np.ones(N) / (N), mode="valid") - ave_x = range(N, len(convolved_wind_speed) + N) - - ax[0, 1].plot(ave_x, convolved_wind_speed) - ax[0, 0].set(ylabel="Wind\n(m/s)", ylim=[0, 30], xlim=[0, len(wind_speed)]) - tick_spacing = 10 - ax[0, 0].yaxis.set_major_locator(ticker.MultipleLocator(tick_spacing)) - - y = h2integrate_config["electrolyzer"]["rating"] - ax[1, 0].plot(energy_to_electrolyzer_kw * 1e-3) - ax[1, 0].axhline(y=y, color="r", linestyle="--", label="Nameplate Capacity") - - convolved_energy_to_electrolyzer = np.convolve( - energy_to_electrolyzer_kw * 1e-3, np.ones(N) / (N), mode="valid" - ) - - ax[1, 1].plot( - ave_x, - convolved_energy_to_electrolyzer, - ) - ax[1, 1].axhline(y=y, color="r", linestyle="--", label="Nameplate Capacity") - ax[1, 0].set(ylabel="Electrolyzer \nPower (MW)", ylim=[0, 500], xlim=[0, len(wind_speed)]) - # ax[1].legend(frameon=False, loc="best") - tick_spacing = 200 - ax[1, 0].yaxis.set_major_locator(ticker.MultipleLocator(tick_spacing)) - ax[1, 0].text(1000, y + 0.1 * tick_spacing, "Electrolyzer Rating", color="r") - - ax[2, 0].plot( - electrolyzer_physics_results["H2_Results"]["Hydrogen Hourly Production [kg/hr]"] * 1e-3 - ) - convolved_hydrogen_production = np.convolve( - electrolyzer_physics_results["H2_Results"]["Hydrogen Hourly Production [kg/hr]"] * 1e-3, - np.ones(N) / (N), - mode="valid", - ) - ax[2, 1].plot( - ave_x, - convolved_hydrogen_production, - ) - tick_spacing = 2 - ax[2, 0].set( - xlabel="Hour", - ylabel="Hydrogen\n(metric tons/hr)", - # ylim=[0, 7000], - xlim=[0, len(H2_Results["Hydrogen Hourly Production [kg/hr]"])], - ) - ax[2, 0].yaxis.set_major_locator(ticker.MultipleLocator(tick_spacing)) - - ax[2, 1].set( - xlabel="Hour", - # ylim=[0, 7000], - xlim=[ - 4 * 7 * 24 - 1, - len(H2_Results["Hydrogen Hourly Production [kg/hr]"] + 4 * 7 * 24 + 2), - ], - ) - ax[2, 1].yaxis.set_major_locator(ticker.MultipleLocator(tick_spacing)) - - plt.tight_layout() - if save_plots: - savepaths = [ - output_dir / "figures/production/", - output_dir / "data/", - ] - for savepath in savepaths: - if not savepath.exists(): - savepath.mkdir(parents=True) - plt.savefig( - savepaths[0] / f"production_overview_{design_scenario['id']}.png", - transparent=True, - ) - pd.DataFrame.from_dict( - data={ - "Hydrogen Hourly Production [kg/hr]": H2_Results[ - "Hydrogen Hourly Production [kg/hr]" - ], - "Hourly Water Consumption [kg/hr]": electrolyzer_physics_results["H2_Results"][ - "Water Hourly Consumption [kg/hr]" - ], - } - ).to_csv(savepaths[1] / f"h2_flow_{design_scenario['id']}.csv") - if show_plots: - plt.show() - - return electrolyzer_physics_results diff --git a/h2integrate/tools/eco/finance.py b/h2integrate/tools/eco/finance.py deleted file mode 100644 index ccd89018e..000000000 --- a/h2integrate/tools/eco/finance.py +++ /dev/null @@ -1,1844 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -import numpy as np -import pandas as pd -import ProFAST # system financial model -import numpy_financial as npf -from attrs import field, define -from ORBIT import ProjectManager -from hopp.simulation import HoppInterface - - -def adjust_dollar_year(init_cost, init_dollar_year, adj_cost_year, costing_general_inflation): - """Adjust cost based on inflation. - - Args: - init_cost (dict, float, int, list, np.ndarrray): cost of item ($) - init_dollar_year (int): dollar-year of init_cost - adj_cost_year (int): dollar-year to adjust cost to - costing_general_inflation (float): inflation rate (%) - - Returns: - same type as init_cost: cost in dollar-year of adj_cost_year - """ - periods = adj_cost_year - init_dollar_year - if isinstance(init_cost, (float, int)): - adj_cost = -npf.fv(costing_general_inflation, periods, 0.0, init_cost) - elif isinstance(init_cost, dict): - adj_cost = {} - for key, val in init_cost.items(): - new_val = -npf.fv(costing_general_inflation, periods, 0.0, val) - adj_cost.update({key: new_val}) - elif isinstance(init_cost, (list, np.ndarray)): - adj_cost = np.zeros(len(init_cost)) - for i in range(len(init_cost)): - adj_cost[i] = -npf.fv(costing_general_inflation, periods, 0.0, init_cost[i]) - if isinstance(init_cost, list): - adj_cost = list(adj_cost) - - return adj_cost - - -@define -class WindCostConfig: - """ - Represents the inputs to the wind cost models - - Attributes: - design_scenario (Dict[str, str]): - Definition of plant subsystem locations (e.g. onshore platform, offshore, none, etc) - hopp_config (Dict[str, float]): - Configuration parameters for HOPP - h2integrate_config (Dict[str, float]): - Configuration parameters for H2Integrate - orbit_config (Dict[str, float], optional): - Required input structure for ORBIT - turbine_config (Dict[str, float], optional): - Configuration parameters specific to turbine - orbit_hybrid_electrical_export_config (Dict[str, float], optional): - Configuration parameters for hybrid electrical export in ORBIT, required if using a - different substation size for the hybrid plant than for the wind plant alone - weather (Union[list, tuple, numpy.ndarray], optional): - Array-like of wind speeds for ORBIT to use in determining installation time and costs - """ - - design_scenario: dict[str, str] - hopp_config: dict[str, float] - h2integrate_config: dict[str, float] - orbit_config: dict[str, float] | None = field(default={}) - turbine_config: dict[str, float] | None = field(default={}) - orbit_hybrid_electrical_export_config: dict[str, float] | None = field(default={}) - weather: list | tuple | np.ndarray | None = field(default=None) - hopp_interface: HoppInterface | None = field(default=None) - - -@define -class WindCostOutputs: - """ - Represents the outputs to the wind cost models. - - Attributes: - total_wind_cost_no_export (float): - Total wind cost without export system costs - total_used_export_system_costs (float): - Total used export system costs - annual_operating_cost_wind (float): - Annual operating cost for wind - installation_time (float, optional): - Estimated installation time in months (default: 0.0) - orbit_project (dict, optional): - Details of the ORBIT project (default: None) - """ - - total_wind_cost_no_export: float - annual_operating_cost_wind: float - installation_time: float = field(default=0.0) - total_used_export_system_costs: float | None = field(default=0.0) - orbit_project: dict | ProjectManager | None = field(default=None) - - -def run_wind_cost_model(wind_cost_inputs: WindCostConfig, verbose=False) -> WindCostOutputs: - if wind_cost_inputs.design_scenario["wind_location"] == "offshore": - # if per kw - project, orbit_hybrid_electrical_export_project = run_orbit( - wind_cost_inputs.orbit_config, - verbose=verbose, - weather=wind_cost_inputs.weather, - orbit_hybrid_electrical_export_config=wind_cost_inputs.orbit_hybrid_electrical_export_config, - ) - - ( - total_wind_cost_no_export, - total_used_export_system_costs, - ) = breakout_export_costs_from_orbit_results( - project, - wind_cost_inputs.h2integrate_config, - wind_cost_inputs.design_scenario, - ) - - if orbit_hybrid_electrical_export_project is not None: - ( - _, - total_used_export_system_costs, - ) = breakout_export_costs_from_orbit_results( - orbit_hybrid_electrical_export_project, - wind_cost_inputs.h2integrate_config, - wind_cost_inputs.design_scenario, - ) - - # WIND ONLY Total O&M expenses including fixed, variable, and capacity-based, $/year - # use values from hybrid substation if a hybrid plant - if orbit_hybrid_electrical_export_project is None: - annual_operating_cost_wind = ( - max(project.monthly_opex.values()) * 12 - ) # np.average(hopp_results["hybrid_plant"].wind.om_total_expense) - - else: - annual_operating_cost_wind = ( - max(orbit_hybrid_electrical_export_project.monthly_opex.values()) * 12 - ) - - if "installation_time" in wind_cost_inputs.h2integrate_config["project_parameters"]: - installation_time = wind_cost_inputs.h2integrate_config["project_parameters"][ - "installation_time" - ] - else: - installation_time = (project.installation_time / (365 * 24)) * (12.0 / 1.0) - - # if total amount - # TODO - return WindCostOutputs( - total_wind_cost_no_export=total_wind_cost_no_export, - total_used_export_system_costs=total_used_export_system_costs, - annual_operating_cost_wind=annual_operating_cost_wind, - installation_time=installation_time, - orbit_project=project, - ) - elif wind_cost_inputs.design_scenario["wind_location"] == "onshore": - total_wind_cost_no_export = ( - wind_cost_inputs.hopp_config["config"]["cost_info"]["wind_installed_cost_mw"] - * wind_cost_inputs.hopp_config["technologies"]["wind"]["num_turbines"] - * wind_cost_inputs.turbine_config["turbine_rating"] - ) - - annual_operating_cost_wind = wind_cost_inputs.hopp_interface.system.wind.om_total_expense[0] - - if "installation_time" in wind_cost_inputs.h2integrate_config["project_parameters"]: - installation_time = wind_cost_inputs.h2integrate_config["project_parameters"][ - "installation_time" - ] - else: - installation_time = 0 - - return WindCostOutputs( - total_wind_cost_no_export=total_wind_cost_no_export, - annual_operating_cost_wind=annual_operating_cost_wind, - installation_time=installation_time, - ) - else: - raise ValueError( - "Wind design location must either be 'onshore' or 'offshore', but currently " - f"'wind_location' is set to {wind_cost_inputs.design_scenario['wind_location']}." - ) - - -# Function to run orbit from provided inputs - this is just for wind costs -def run_orbit(orbit_config, verbose=False, weather=None, orbit_hybrid_electrical_export_config={}): - # set up ORBIT - project = ProjectManager(orbit_config, weather=weather) - - # run ORBIT - project.run(availability=orbit_config["installation_availability"]) - - # run ORBIT for hybrid substation if applicable - if orbit_hybrid_electrical_export_config == {}: - hybrid_substation_project = None - else: - hybrid_substation_project = ProjectManager( - orbit_hybrid_electrical_export_config, weather=weather - ) - hybrid_substation_project.run(availability=orbit_config["installation_availability"]) - - # print results if desired - if verbose: - print(f"Installation CapEx: {project.installation_capex/1e6:.0f} M") - print(f"System CapEx: {project.system_capex/1e6:.0f} M") - print(f"Turbine CapEx: {project.turbine_capex/1e6:.0f} M") - print(f"Soft CapEx: {project.soft_capex/1e6:.0f} M") - print(f"Total CapEx: {project.total_capex/1e6:.0f} M") - print(f"Annual OpEx Rate: {max(project.monthly_opex.values())*12:.0f} ") - print(f"\nInstallation Time: {project.installation_time:.0f} h") - print("\nN Substations: ", (project.phases["ElectricalDesign"].num_substations)) - print("N cables: ", (project.phases["ElectricalDesign"].num_cables)) - print("\n") - - # cable cost breakdown - print("Cable specific costs") - print( - "Export cable installation CAPEX: %.2f M USD" - % (project.phases["ExportCableInstallation"].installation_capex * 1e-6) - ) - print("\n") - - return project, hybrid_substation_project - - -def adjust_orbit_costs(orbit_project, h2integrate_config): - if ("expected_plant_cost" in h2integrate_config["finance_parameters"]["wind"]) and ( - h2integrate_config["finance_parameters"]["wind"]["expected_plant_cost"] != "none" - ): - wind_capex_multiplier = ( - h2integrate_config["finance_parameters"]["wind"]["expected_plant_cost"] * 1e9 - ) / orbit_project.total_capex - else: - wind_capex_multiplier = 1.0 - - wind_total_capex = orbit_project.total_capex * wind_capex_multiplier - wind_capex_breakdown = orbit_project.capex_breakdown - for key in wind_capex_breakdown.keys(): - wind_capex_breakdown[key] *= wind_capex_multiplier - - return wind_total_capex, wind_capex_breakdown, wind_capex_multiplier - - -def breakout_export_costs_from_orbit_results(orbit_project, h2integrate_config, design_scenario): - # adjust wind capex to meet expectations - wind_total_capex, wind_capex_breakdown, wind_capex_multiplier = adjust_orbit_costs( - orbit_project=orbit_project, h2integrate_config=h2integrate_config - ) - - # onshore substation cost not included in ORBIT costs by default, so add it separately - total_wind_installed_costs_with_export = wind_total_capex - - # breakout export system costs - array_cable_equipment_cost = wind_capex_breakdown["Array System"] - array_cable_installation_cost = wind_capex_breakdown["Array System Installation"] - total_array_cable_system_capex = array_cable_equipment_cost + array_cable_installation_cost - - export_cable_equipment_cost = wind_capex_breakdown[ - "Export System" - ] # this should include the onshore substation - export_cable_installation_cost = wind_capex_breakdown["Export System Installation"] - substation_equipment_cost = wind_capex_breakdown["Offshore Substation"] - substation_installation_cost = wind_capex_breakdown["Offshore Substation Installation"] - total_export_cable_system_capex = export_cable_equipment_cost + export_cable_installation_cost - - total_offshore_substation_capex = substation_equipment_cost + substation_installation_cost - - total_electrical_export_system_cost = ( - total_array_cable_system_capex - + total_offshore_substation_capex - + total_export_cable_system_capex - ) - - ## adjust wind cost to remove export - if design_scenario["transportation"] == "hvdc+pipeline": - unused_export_system_cost = 0.0 - elif ( - design_scenario["electrolyzer_location"] == "turbine" - and design_scenario["h2_storage_location"] == "turbine" - ): - unused_export_system_cost = ( - total_array_cable_system_capex - + total_export_cable_system_capex - + total_offshore_substation_capex - ) - elif ( - design_scenario["electrolyzer_location"] == "turbine" - and design_scenario["h2_storage_location"] == "platform" - ): - unused_export_system_cost = total_export_cable_system_capex # TODO check assumptions here - elif ( - design_scenario["electrolyzer_location"] == "platform" - and design_scenario["h2_storage_location"] == "platform" - ): - unused_export_system_cost = total_export_cable_system_capex # TODO check assumptions here - elif ( - design_scenario["electrolyzer_location"] == "platform" - or design_scenario["electrolyzer_location"] == "turbine" - ) and design_scenario["h2_storage_location"] == "onshore": - unused_export_system_cost = total_export_cable_system_capex # TODO check assumptions here - else: - unused_export_system_cost = 0.0 - - total_used_export_system_costs = total_electrical_export_system_cost - unused_export_system_cost - - total_wind_cost_no_export = ( - total_wind_installed_costs_with_export - total_used_export_system_costs - ) - - return total_wind_cost_no_export, total_used_export_system_costs - - -def run_capex( - hopp_results, - wind_cost_results, - electrolyzer_cost_results, - h2_pipe_array_results, - h2_transport_compressor_results, - h2_transport_pipe_results, - h2_storage_results, - hopp_config, - h2integrate_config, - design_scenario, - desal_results, - platform_results, - verbose=False, -): - # total_wind_cost_no_export, total_used_export_system_costs = breakout_export_costs_from_orbit_results(orbit_project, h2integrate_config, design_scenario) # noqa: E501 - - # if orbit_hybrid_electrical_export_project is not None: - # _, total_used_export_system_costs = breakout_export_costs_from_orbit_results(orbit_hybrid_electrical_export_project, h2integrate_config, design_scenario) # noqa: E501 - - # wave capex - if hopp_config["site"]["wave"]: - cost_dict = hopp_results["hybrid_plant"].wave.mhk_costs.cost_outputs - - wcapex = ( - cost_dict["structural_assembly_cost_modeled"] - + cost_dict["power_takeoff_system_cost_modeled"] - + cost_dict["mooring_found_substruc_cost_modeled"] - ) - wbos = ( - cost_dict["development_cost_modeled"] - + cost_dict["eng_and_mgmt_cost_modeled"] - + cost_dict["plant_commissioning_cost_modeled"] - + cost_dict["site_access_port_staging_cost_modeled"] - + cost_dict["assembly_and_install_cost_modeled"] - + cost_dict["other_infrastructure_cost_modeled"] - ) - welec_infrastruc_costs = ( - cost_dict["array_cable_system_cost_modeled"] - + cost_dict["export_cable_system_cost_modeled"] - + cost_dict["other_elec_infra_cost_modeled"] - ) # +\ - # cost_dict['onshore_substation_cost_modeled']+\ - # cost_dict['offshore_substation_cost_modeled'] - # financial = cost_dict['project_contingency']+\ - # cost_dict['insurance_during_construction']+\ - # cost_dict['reserve_accounts'] - wave_capex = wcapex + wbos + welec_infrastruc_costs - else: - wave_capex = 0.0 - - # solar capex - if "pv" in hopp_config["technologies"].keys(): - solar_capex = hopp_results["hybrid_plant"].pv.total_installed_cost - else: - solar_capex = 0.0 - - # battery capex - if "battery" in hopp_config["technologies"].keys(): - battery_capex = hopp_results["hybrid_plant"].battery.total_installed_cost - else: - battery_capex = 0.0 - - # TODO bos capex - # bos_capex = hopp_results["hybrid_plant"].bos.total_installed_cost - - ## desal capex - if desal_results is not None: - desal_capex = desal_results["desal_capex_usd"] - else: - desal_capex = 0.0 - - ## electrolyzer capex - electrolyzer_total_capital_cost = electrolyzer_cost_results["electrolyzer_total_capital_cost"] - - if ( - design_scenario["electrolyzer_location"] == "platform" - or design_scenario["h2_storage_location"] == "platform" - or hopp_config["site"]["solar"] - ): - platform_costs = platform_results["capex"] - else: - platform_costs = 0.0 - - # h2 transport - h2_transport_compressor_capex = h2_transport_compressor_results["compressor_capex"] - h2_transport_pipe_capex = h2_transport_pipe_results["total capital cost [$]"][0] - - ## h2 storage - if h2integrate_config["h2_storage"]["type"] == "none": - h2_storage_capex = 0.0 - elif ( - h2integrate_config["h2_storage"]["type"] == "pipe" - ): # ug pipe storage model includes compression - h2_storage_capex = h2_storage_results["storage_capex"] - elif ( - h2integrate_config["h2_storage"]["type"] == "turbine" - ): # ug pipe storage model includes compression - h2_storage_capex = h2_storage_results["storage_capex"] - elif ( - h2integrate_config["h2_storage"]["type"] == "pressure_vessel" - ): # pressure vessel storage model includes compression - h2_storage_capex = h2_storage_results["storage_capex"] - elif ( - h2integrate_config["h2_storage"]["type"] == "salt_cavern" - ): # salt cavern storage model includes compression - h2_storage_capex = h2_storage_results["storage_capex"] - elif ( - h2integrate_config["h2_storage"]["type"] == "lined_rock_cavern" - ): # lined rock cavern storage model includes compression - h2_storage_capex = h2_storage_results["storage_capex"] - else: - msg = ( - f'the storage type you have indicated ({h2integrate_config["h2_storage"]["type"]}) ' - 'has not been implemented.' - ) - raise NotImplementedError(msg) - - # store capex component breakdown - capex_breakdown = { - "wind": wind_cost_results.total_wind_cost_no_export, - "wave": wave_capex, - "solar": solar_capex, - "battery": battery_capex, - "platform": platform_costs, - "electrical_export_system": wind_cost_results.total_used_export_system_costs, - "desal": desal_capex, - "electrolyzer": electrolyzer_total_capital_cost, - "h2_pipe_array": h2_pipe_array_results["capex"], - "h2_transport_compressor": h2_transport_compressor_capex, - "h2_transport_pipeline": h2_transport_pipe_capex, - "h2_storage": h2_storage_capex, - } - - # discount capex to appropriate year for unified costing - for key in capex_breakdown.keys(): - if key == "h2_storage": - # if design_scenario["h2_storage_location"] == "turbine" and h2integrate_config["h2_storage"]["type"] == "turbine": # noqa: E501 - # cost_year = h2integrate_config["finance_parameters"]["discount_years"][key][ - # design_scenario["h2_storage_location"] - # ] - # else: - cost_year = h2integrate_config["finance_parameters"]["discount_years"][key][ - h2integrate_config["h2_storage"]["type"] - ] - else: - cost_year = h2integrate_config["finance_parameters"]["discount_years"][key] - - capex_breakdown[key] = adjust_dollar_year( - capex_breakdown[key], - cost_year, - h2integrate_config["project_parameters"]["cost_year"], - h2integrate_config["finance_parameters"]["costing_general_inflation"], - ) - - total_system_installed_cost = sum(capex_breakdown[key] for key in capex_breakdown.keys()) - - if verbose: - print("\nCAPEX Breakdown") - for key in capex_breakdown.keys(): - print(key, "%.2f" % (capex_breakdown[key] * 1e-6), " M") - - print( - "\nTotal system CAPEX: ", - "$%.2f" % (total_system_installed_cost * 1e-9), - " B", - ) - - return total_system_installed_cost, capex_breakdown - - -def run_fixed_opex( - hopp_results, - wind_cost_results, - electrolyzer_cost_results, - h2_pipe_array_results, - h2_transport_compressor_results, - h2_transport_pipe_results, - h2_storage_results, - hopp_config, - h2integrate_config, - desal_results, - platform_results, - verbose=False, - total_export_system_cost=0, -): - # WIND ONLY Total O&M expenses including fixed, variable, and capacity-based, $/year - # use values from hybrid substation if a hybrid plant - # if orbit_hybrid_electrical_export_project is None: - - # wave opex - if hopp_config["site"]["wave"]: - cost_dict = hopp_results["hybrid_plant"].wave.mhk_costs.cost_outputs - wave_opex = cost_dict["maintenance_cost"] + cost_dict["operations_cost"] - else: - wave_opex = 0.0 - - # solar opex - if "pv" in hopp_config["technologies"].keys(): - solar_opex = hopp_results["hybrid_plant"].pv.om_total_expense[0] - if solar_opex < 0.1: - raise (RuntimeWarning(f"Solar OPEX returned as {solar_opex}")) - else: - solar_opex = 0.0 - - # battery opex - if "battery" in hopp_config["technologies"].keys(): - battery_opex = hopp_results["hybrid_plant"].battery.om_total_expense[0] - if battery_opex < 0.1: - raise (RuntimeWarning(f"Battery OPEX returned as {battery_opex}")) - else: - battery_opex = 0.0 - - # H2 OPEX - platform_operating_costs = platform_results["opex"] # TODO update this - - annual_operating_cost_h2 = electrolyzer_cost_results["electrolyzer_OM_cost_annual"] - - h2_transport_compressor_opex = h2_transport_compressor_results["compressor_opex"] # annual - - h2_transport_pipeline_opex = h2_transport_pipe_results["annual operating cost [$]"][0] # annual - - storage_opex = h2_storage_results["storage_opex"] - # desal OPEX - if desal_results is not None: - desal_opex = desal_results["desal_opex_usd_per_year"] - else: - desal_opex = 0.0 - annual_operating_cost_desal = desal_opex - - # store opex component breakdown - opex_breakdown_annual = { - "wind_and_electrical": wind_cost_results.annual_operating_cost_wind, - "platform": platform_operating_costs, - # "electrical_export_system": total_export_om_cost, - "wave": wave_opex, - "solar": solar_opex, - "battery": battery_opex, - "desal": annual_operating_cost_desal, - "electrolyzer": annual_operating_cost_h2, - "h2_pipe_array": h2_pipe_array_results["opex"], - "h2_transport_compressor": h2_transport_compressor_opex, - "h2_transport_pipeline": h2_transport_pipeline_opex, - "h2_storage": storage_opex, - } - - # discount opex to appropriate year for unified costing - for key in opex_breakdown_annual.keys(): - if key == "h2_storage": - cost_year = h2integrate_config["finance_parameters"]["discount_years"][key][ - h2integrate_config["h2_storage"]["type"] - ] - else: - cost_year = h2integrate_config["finance_parameters"]["discount_years"][key] - - opex_breakdown_annual[key] = adjust_dollar_year( - opex_breakdown_annual[key], - cost_year, - h2integrate_config["project_parameters"]["cost_year"], - h2integrate_config["finance_parameters"]["costing_general_inflation"], - ) - - # Calculate the total annual OPEX of the installed system - total_annual_operating_costs = sum(opex_breakdown_annual.values()) - - if verbose: - print("\nAnnual OPEX Breakdown") - for key in opex_breakdown_annual.keys(): - print(key, "%.2f" % (opex_breakdown_annual[key] * 1e-6), " M") - - print( - "\nTotal Annual OPEX: ", - "$%.2f" % (total_annual_operating_costs * 1e-6), - " M", - ) - print(opex_breakdown_annual) - return total_annual_operating_costs, opex_breakdown_annual - - -def run_variable_opex( - electrolyzer_cost_results, - h2integrate_config, -): - """calculate variable O&M in $/kg-H2. - - Args: - electrolyzer_cost_results (dict): output of - h2integrate.tools.eco.electrolysis.run_electrolyzer_cost - h2integrate_config (:obj:`h2integrate_simulation.H2IntegrateSimulationConfig`): H2Integrate - simulation config. - - Returns: - dict: dictionary of components and corresponding variable O&M in $/kg-H2 for - adjusted for inflation so cost is in dollar-year corresponding to - `h2integrate_config["project_parameters"]["cost_year"]` - """ - electrolyzer_vom = electrolyzer_cost_results["electrolyzer_variable_OM_annual"] - - vopex_breakdown_annual = {"electrolyzer": electrolyzer_vom} - - for key in vopex_breakdown_annual.keys(): - cost_year = h2integrate_config["finance_parameters"]["discount_years"][key] - vopex_breakdown_annual[key] = adjust_dollar_year( - vopex_breakdown_annual[key], - cost_year, - h2integrate_config["project_parameters"]["cost_year"], - h2integrate_config["finance_parameters"]["costing_general_inflation"], - ) - return vopex_breakdown_annual - - -def calc_financial_parameter_weighted_average_by_capex( - parameter_name: str, h2integrate_config: dict, capex_breakdown: dict -) -> float: - """Allows the user to provide individual financial parameters for each technology in the system. - The values given will be weighted by their CAPEX values to determine the final - weighted-average parameter value to be supplied to the financial model. If only one - technology has a unique parameter value, a "general" parameter value in the dictionary and - that will be used for all technologies not specified individually. - - Args: - parameter_name (str): The name of the parameter to be weighted by capex. The name should - correspond to the name in the h2integrate config - h2integrate_config (dict): Dictionary form of the h2integrate config - capex_breakdown (dict): Output from `run_capex`, a dictionary of all capital items for - the financial model - - Returns: - parameter_value (float): if the parameter in the h2integrate config is given as a - dictionary, then the weighted average by capex parameter value is returnd. Otherwise no - averaging is done and the value of the parameter in the h2integrate_config is returned. - """ - - if type(h2integrate_config["finance_parameters"][parameter_name]) is not dict: - # if only one value is given for the parameter, use that value - parameter_value = h2integrate_config["finance_parameters"][parameter_name] - - else: - # assign capex amounts as weights - weights = np.array(list(capex_breakdown.values())) - - # initialize value array - values = np.zeros_like(weights) - - # assign values - for i, key in enumerate(capex_breakdown.keys()): - if key in h2integrate_config["finance_parameters"][parameter_name].keys(): - values[i] = h2integrate_config["finance_parameters"][parameter_name][key] - elif capex_breakdown[key] == 0.0: - values[i] = 0.0 - else: - values[i] = h2integrate_config["finance_parameters"][parameter_name]["general"] - - # calcuated weighted average parameter value - parameter_value = np.average(values, weights=weights) - return parameter_value - - -def run_profast_lcoe( - h2integrate_config, - wind_cost_results, - capex_breakdown, - opex_breakdown, - hopp_results, - incentive_option, - design_scenario, - verbose=False, - show_plots=False, - save_plots=False, - output_dir="./output/", -): - if isinstance(output_dir, str): - output_dir = Path(output_dir).resolve() - gen_inflation = h2integrate_config["finance_parameters"]["inflation_rate"] - - # initialize dictionary of weights for averaging financial parameters - finance_param_weights = {} - - if ( - design_scenario["h2_storage_location"] == "onshore" - or design_scenario["electrolyzer_location"] == "onshore" - ): - if "land_cost" in h2integrate_config["finance_parameters"]: - land_cost = h2integrate_config["finance_parameters"]["land_cost"] - else: - land_cost = 1e6 # TODO should model this - else: - land_cost = 0.0 - - pf = ProFAST.ProFAST() - pf.set_params( - "commodity", - { - "name": "electricity", - "unit": "kWh", - "initial price": 100, - "escalation": gen_inflation, - }, - ) - pf.set_params( - "capacity", - np.sum(hopp_results["combined_hybrid_power_production_hopp"]) / 365.0, - ) # kWh/day - pf.set_params("maintenance", {"value": 0, "escalation": gen_inflation}) - pf.set_params( - "analysis start year", - h2integrate_config["project_parameters"]["financial_analysis_start_year"], - ) - pf.set_params("operating life", h2integrate_config["project_parameters"]["project_lifetime"]) - pf.set_params( - "installation months", h2integrate_config["project_parameters"]["installation_time"] - ) - pf.set_params( - "installation cost", - { - "value": 0, - "depr type": "Straight line", - "depr period": 4, - "depreciable": False, - }, - ) - if land_cost > 0: - pf.set_params("non depr assets", land_cost) - pf.set_params( - "end of proj sale non depr assets", - land_cost - * (1 + gen_inflation) ** h2integrate_config["project_parameters"]["project_lifetime"], - ) - pf.set_params("demand rampup", 0) - pf.set_params("long term utilization", 1) - pf.set_params("credit card fees", 0) - pf.set_params("sales tax", h2integrate_config["finance_parameters"]["sales_tax_rate"]) - pf.set_params("license and permit", {"value": 00, "escalation": gen_inflation}) - pf.set_params("rent", {"value": 0, "escalation": gen_inflation}) - pf.set_params( - "property tax and insurance", - h2integrate_config["finance_parameters"]["property_tax"] - + h2integrate_config["finance_parameters"]["property_insurance"], - ) - pf.set_params( - "admin expense", - h2integrate_config["finance_parameters"]["administrative_expense_percent_of_sales"], - ) - pf.set_params( - "total income tax rate", - h2integrate_config["finance_parameters"]["total_income_tax_rate"], - ) - pf.set_params( - "capital gains tax rate", - h2integrate_config["finance_parameters"]["capital_gains_tax_rate"], - ) - pf.set_params("sell undepreciated cap", True) - pf.set_params("tax losses monetized", True) - pf.set_params("general inflation rate", gen_inflation) - - pf.set_params("debt type", h2integrate_config["finance_parameters"]["debt_type"]) - pf.set_params("loan period if used", h2integrate_config["finance_parameters"]["loan_period"]) - - pf.set_params("cash onhand", h2integrate_config["finance_parameters"]["cash_onhand_months"]) - - # ----------------------------------- Add capital items to ProFAST ---------------- - if "wind" in capex_breakdown.keys(): - pf.add_capital_item( - name="Wind system", - cost=capex_breakdown["wind"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"]["depreciation_period"], - refurb=[0], - ) - finance_param_weights["wind"] = capex_breakdown["wind"] - if "wave" in capex_breakdown.keys(): - pf.add_capital_item( - name="Wave system", - cost=capex_breakdown["wave"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"]["depreciation_period"], - refurb=[0], - ) - finance_param_weights["wave"] = capex_breakdown["wave"] - if "solar" in capex_breakdown.keys(): - pf.add_capital_item( - name="Solar PV system", - cost=capex_breakdown["solar"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"]["depreciation_period"], - refurb=[0], - ) - finance_param_weights["solar"] = capex_breakdown["solar"] - if "battery" in capex_breakdown.keys(): - pf.add_capital_item( - name="Battery system", - cost=capex_breakdown["battery"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"]["depreciation_period"], - refurb=[0], - ) - finance_param_weights["battery"] = capex_breakdown["battery"] - if design_scenario["transportation"] == "hvdc+pipeline" or not ( - design_scenario["electrolyzer_location"] == "turbine" - and design_scenario["h2_storage_location"] == "turbine" - ): - pf.add_capital_item( - name="Electrical export system", - cost=capex_breakdown["electrical_export_system"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"]["depreciation_period"], - refurb=[0], - ) - finance_param_weights["electrical_export_system"] = capex_breakdown[ - "electrical_export_system" - ] - # -------------------------------------- Add fixed costs-------------------------------- - pf.add_fixed_cost( - name="Wind and electrical fixed O&M cost", - usage=1.0, - unit="$/year", - cost=opex_breakdown["wind_and_electrical"], - escalation=gen_inflation, - ) - - if "wave" in opex_breakdown.keys(): - pf.add_fixed_cost( - name="Wave O&M cost", - usage=1.0, - unit="$/year", - cost=opex_breakdown["wave"], - escalation=gen_inflation, - ) - - if "solar" in opex_breakdown.keys(): - pf.add_fixed_cost( - name="Solar O&M cost", - usage=1.0, - unit="$/year", - cost=opex_breakdown["solar"], - escalation=gen_inflation, - ) - - if "battery" in opex_breakdown.keys(): - pf.add_fixed_cost( - name="Battery O&M cost", - usage=1.0, - unit="$/year", - cost=opex_breakdown["battery"], - escalation=gen_inflation, - ) - - # ------------------------------------- add incentives ----------------------------------- - """ - Note: ptc units must be given to ProFAST in terms of dollars per unit of the primary commodity - being produced - - Note: full tech-nutral (wind) tax credits are no longer available if constructions starts after - Jan. 1 2034 (Jan 1. 2033 for h2 ptc) - """ - - # catch incentive option and add relevant incentives - incentive_dict = h2integrate_config["policy_parameters"][f"option{incentive_option}"] - # add electricity_ptc ($/kW) - # adjust from 1992 dollars to start year - wind_ptc_in_dollars_per_kw = -npf.fv( - h2integrate_config["finance_parameters"]["costing_general_inflation"], - h2integrate_config["project_parameters"]["financial_analysis_start_year"] - + round(wind_cost_results.installation_time / 12) - - 1992, - 0, - incentive_dict["electricity_ptc"], - ) # given in 1992 dollars but adjust for inflation - - pf.add_incentive( - name="Electricity PTC", - value=wind_ptc_in_dollars_per_kw, - decay=-gen_inflation, - sunset_years=10, - tax_credit=True, - ) # TODO check decay - - # ----------------------- Add weight-averaged parameters ----------------------- - - equity_discount_rate = calc_financial_parameter_weighted_average_by_capex( - parameter_name="discount_rate", - h2integrate_config=h2integrate_config, - capex_breakdown=finance_param_weights, - ) - pf.set_params( - "leverage after tax nominal discount rate", - equity_discount_rate, - ) - - debt_interest_rate = calc_financial_parameter_weighted_average_by_capex( - parameter_name="debt_interest_rate", - h2integrate_config=h2integrate_config, - capex_breakdown=finance_param_weights, - ) - pf.set_params( - "debt interest rate", - debt_interest_rate, - ) - - if "debt_equity_split" in h2integrate_config["finance_parameters"].keys(): - debt_equity_split = calc_financial_parameter_weighted_average_by_capex( - parameter_name="debt_equity_split", - h2integrate_config=h2integrate_config, - capex_breakdown=finance_param_weights, - ) - pf.set_params( - "debt equity ratio of initial financing", - (debt_equity_split / (100 - debt_equity_split)), - ) - elif "debt_equity_ratio" in h2integrate_config["finance_parameters"].keys(): - debt_equity_ratio = calc_financial_parameter_weighted_average_by_capex( - parameter_name="debt_equity_ratio", - h2integrate_config=h2integrate_config, - capex_breakdown=finance_param_weights, - ) - pf.set_params( - "debt equity ratio of initial financing", - debt_equity_ratio, - ) - else: - msg = ( - "missing value in `finance_parameters`. " - "Requires either `debt_equity_ratio` or `debt_equity_split`" - ) - raise ValueError(msg) - - # ---------------------- Run ProFAST ------------------------------------------- - sol = pf.solve_price() - - lcoe = sol["price"] - - if verbose: - print("\nProFAST LCOE: ", "%.2f" % (lcoe * 1e3), "$/MWh") - - # -------------------------- Plots --------------------------------------------- - if show_plots or save_plots: - savepath = output_dir / "figures/wind_only" - if not savepath.exists(): - savepath.mkdir(parents=True) - pf.plot_costs_yearly( - per_kg=False, - scale="M", - remove_zeros=True, - remove_depreciation=False, - fileout=savepath / f'annual_cash_flow_wind_only_{design_scenario["id"]}.png', - show_plot=show_plots, - ) - pf.plot_costs_yearly2( - per_kg=False, - scale="M", - remove_zeros=True, - remove_depreciation=False, - fileout=savepath / f'annual_cash_flow_wind_only_{design_scenario["id"]}.html', - show_plot=show_plots, - ) - pf.plot_capital_expenses( - fileout=savepath / f'capital_expense_only_{design_scenario["id"]}.png', - show_plot=show_plots, - ) - pf.plot_cashflow( - fileout=savepath / f'cash_flow_wind_only_{design_scenario["id"]}.png', - show_plot=show_plots, - ) - pf.plot_costs( - fileout=savepath / f'cost_breakdown_{design_scenario["id"]}.png', - show_plot=show_plots, - ) - - return lcoe, pf, sol - - -def run_profast_grid_only( - h2integrate_config, - wind_cost_results, - electrolyzer_performance_results, - capex_breakdown, - opex_breakdown_total, - hopp_results, - design_scenario, - total_accessory_power_renewable_kw, - total_accessory_power_grid_kw, - verbose=False, - show_plots=False, - save_plots=False, - output_dir="./output/", -): - vopex_breakdown = opex_breakdown_total["variable_om"] - fopex_breakdown = opex_breakdown_total["fixed_om"] - - if isinstance(output_dir, str): - output_dir = Path(output_dir).resolve() - gen_inflation = h2integrate_config["finance_parameters"]["inflation_rate"] - - # initialize dictionary of weights for averaging financial parameters - finance_param_weights = {} - - if ( - design_scenario["h2_storage_location"] == "onshore" - or design_scenario["electrolyzer_location"] == "onshore" - ): - if "land_cost" in h2integrate_config["finance_parameters"]: - land_cost = h2integrate_config["finance_parameters"]["land_cost"] - else: - land_cost = 1e6 # TODO should model this - else: - land_cost = 0.0 - - pf = ProFAST.ProFAST() - pf.set_params( - "commodity", - { - "name": "Hydrogen", - "unit": "kg", - "initial price": 100, - "escalation": gen_inflation, - }, - ) - pf.set_params( - "capacity", - electrolyzer_performance_results.rated_capacity_kg_pr_day, - ) # kg/day - pf.set_params("maintenance", {"value": 0, "escalation": gen_inflation}) - # TODO: update analysis start year below (ESG) - pf.set_params( - "analysis start year", - h2integrate_config["project_parameters"]["financial_analysis_start_year"], - ) - pf.set_params("operating life", h2integrate_config["project_parameters"]["project_lifetime"]) - pf.set_params( - "installation cost", - { - "value": 0, - "depr type": "Straight line", - "depr period": 4, - "depreciable": False, - }, - ) - if land_cost > 0: - pf.set_params("non depr assets", land_cost) - pf.set_params( - "end of proj sale non depr assets", - land_cost - * (1 + gen_inflation) ** h2integrate_config["project_parameters"]["project_lifetime"], - ) - pf.set_params("demand rampup", 0) - pf.set_params("long term utilization", electrolyzer_performance_results.long_term_utilization) - pf.set_params("credit card fees", 0) - pf.set_params("sales tax", h2integrate_config["finance_parameters"]["sales_tax_rate"]) - pf.set_params("license and permit", {"value": 00, "escalation": gen_inflation}) - pf.set_params("rent", {"value": 0, "escalation": gen_inflation}) - pf.set_params( - "property tax and insurance", - h2integrate_config["finance_parameters"]["property_tax"] - + h2integrate_config["finance_parameters"]["property_insurance"], - ) - pf.set_params( - "admin expense", - h2integrate_config["finance_parameters"]["administrative_expense_percent_of_sales"], - ) - pf.set_params( - "total income tax rate", - h2integrate_config["finance_parameters"]["total_income_tax_rate"], - ) - pf.set_params( - "capital gains tax rate", - h2integrate_config["finance_parameters"]["capital_gains_tax_rate"], - ) - pf.set_params("sell undepreciated cap", True) - pf.set_params("tax losses monetized", True) - pf.set_params("general inflation rate", gen_inflation) - - pf.set_params("debt type", h2integrate_config["finance_parameters"]["debt_type"]) - pf.set_params("loan period if used", h2integrate_config["finance_parameters"]["loan_period"]) - - pf.set_params("cash onhand", h2integrate_config["finance_parameters"]["cash_onhand_months"]) - - # ----------------------------------- Add capital items to ProFAST ---------------- - - pf.add_capital_item( - name="Electrolysis system", - cost=capex_breakdown["electrolyzer"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"]["depreciation_period_electrolyzer"], - refurb=electrolyzer_performance_results.refurb_cost_percent, - ) - finance_param_weights["electrolyzer"] = capex_breakdown["electrolyzer"] - pf.add_capital_item( - name="Hydrogen storage system", - cost=capex_breakdown["h2_storage"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"]["depreciation_period_electrolyzer"], - refurb=[0], - ) - finance_param_weights["h2_storage"] = capex_breakdown["h2_storage"] - # -------------------------------------- Add fixed costs-------------------------------- - pf.add_fixed_cost( - name="Electrolyzer fixed O&M cost", - usage=1.0, - unit="$/year", - cost=fopex_breakdown["electrolyzer"], - escalation=gen_inflation, - ) - pf.add_fixed_cost( - name="Hydrogen storage fixed O&M cost", - usage=1.0, - unit="$/year", - cost=fopex_breakdown["h2_storage"], - escalation=gen_inflation, - ) - - # ---------------------- Add feedstocks, note the various cost options------------------- - pf.add_feedstock( - name="Water", - usage=electrolyzer_performance_results.water_usage_gal_pr_kg, - unit="gal", - cost="US Average", - escalation=gen_inflation, - ) - pf.add_feedstock( - name="Electrolyzer Variable O&M", - usage=1.0, - unit="$/kg", - cost=vopex_breakdown["electrolyzer"], - escalation=gen_inflation, - ) - - # if h2integrate_config["project_parameters"]["grid_connection"]: - - energy_purchase = ( - 365 * 24 * h2integrate_config["electrolyzer"]["rating"] * 1e3 - + sum(total_accessory_power_renewable_kw) - + sum(total_accessory_power_grid_kw) - ) - - pf.add_fixed_cost( - name="Electricity from grid", - usage=1.0, - unit="$/year", - cost=energy_purchase * h2integrate_config["project_parameters"]["ppa_price"], - escalation=gen_inflation, - ) - - # ----------------------- Add weight-averaged parameters ----------------------- - - equity_discount_rate = calc_financial_parameter_weighted_average_by_capex( - parameter_name="discount_rate", - h2integrate_config=h2integrate_config, - capex_breakdown=finance_param_weights, - ) - - pf.set_params( - "leverage after tax nominal discount rate", - equity_discount_rate, - ) - - debt_interest_rate = calc_financial_parameter_weighted_average_by_capex( - parameter_name="debt_interest_rate", - h2integrate_config=h2integrate_config, - capex_breakdown=finance_param_weights, - ) - pf.set_params( - "debt interest rate", - debt_interest_rate, - ) - - if "debt_equity_split" in h2integrate_config["finance_parameters"].keys(): - debt_equity_split = calc_financial_parameter_weighted_average_by_capex( - parameter_name="debt_equity_split", - h2integrate_config=h2integrate_config, - capex_breakdown=finance_param_weights, - ) - pf.set_params( - "debt equity ratio of initial financing", - (debt_equity_split / (100 - debt_equity_split)), - ) - elif "debt_equity_ratio" in h2integrate_config["finance_parameters"].keys(): - debt_equity_ratio = calc_financial_parameter_weighted_average_by_capex( - parameter_name="debt_equity_ratio", - h2integrate_config=h2integrate_config, - capex_breakdown=finance_param_weights, - ) - pf.set_params( - "debt equity ratio of initial financing", - debt_equity_ratio, - ) - else: - msg = ( - "missing value in `finance_parameters`. " - "Requires either `debt_equity_ratio` or `debt_equity_split`" - ) - raise ValueError(msg) - - # ----------------------- Run ProFAST ----------------------------------------- - - sol = pf.solve_price() - - lcoh = sol["price"] - if verbose: - print(f"\nLCOH grid only: {lcoh:.2f} $/kg") - print(f'ProFAST grid only NPV: {sol["NPV"]:.2f}') - print(f'ProFAST grid only IRR: {max(sol["irr"]):.5f}') - print(f'ProFAST grid only LCO: {sol["lco"]:.2f} $/kg') - print(f'ProFAST grid only Profit Index: {sol["profit index"]:.2f}') - print(f'ProFAST grid only payback period: {sol["investor payback period"]}') - - # ----------------------- Plots ----------------------------------------------- - if save_plots or show_plots: - savepaths = [ - output_dir / "figures/capex", - output_dir / "figures/annual_cash_flow", - output_dir / "figures/lcoh_breakdown", - output_dir / "data", - ] - for savepath in savepaths: - if not savepath.exists(): - savepath.mkdir(parents=True) - - pf.plot_capital_expenses( - fileout=savepaths[0] / f"capital_expense_grid_only_{design_scenario['id']}.pdf", - show_plot=show_plots, - ) - pf.plot_cashflow( - fileout=savepaths[1] / f"cash_flow_grid_only_{design_scenario['id']}.png", - show_plot=show_plots, - ) - - pd.DataFrame.from_dict(data=pf.cash_flow_out, orient="index").to_csv( - savepaths[3] / f"cash_flow_grid_only_{design_scenario['id']}.csv" - ) - - pf.plot_costs( - savepaths[2] / f"lcoh_grid_only_{design_scenario['id']}", - show_plot=show_plots, - ) - return lcoh, pf, sol - - -def run_profast_full_plant_model( - h2integrate_config, - wind_cost_results, - electrolyzer_performance_results, - capex_breakdown, - opex_breakdown_total, - hopp_results, - incentive_option, - design_scenario, - total_accessory_power_renewable_kw, - total_accessory_power_grid_kw, - verbose=False, - show_plots=False, - save_plots=False, - output_dir="./output/", -): - vopex_breakdown = opex_breakdown_total["variable_om"] - fopex_breakdown = opex_breakdown_total["fixed_om"] - - if isinstance(output_dir, str): - output_dir = Path(output_dir).resolve() - gen_inflation = h2integrate_config["finance_parameters"]["inflation_rate"] - - if "financial_analysis_start_year" not in h2integrate_config["finance_parameters"]: - financial_analysis_start_year = h2integrate_config["project_parameters"][ - "financial_analysis_start_year" - ] - else: - financial_analysis_start_year = h2integrate_config["finance_parameters"][ - "financial_analysis_start_year" - ] - - if "installation_time" not in h2integrate_config["project_parameters"]: - installation_period_months = wind_cost_results.installation_time - else: - installation_period_months = h2integrate_config["project_parameters"]["installation_time"] - - # initialize dictionary of weights for averaging financial parameters - finance_param_weights = {} - - if ( - design_scenario["h2_storage_location"] == "onshore" - or design_scenario["electrolyzer_location"] == "onshore" - ): - if "land_cost" in h2integrate_config["finance_parameters"]: - land_cost = h2integrate_config["finance_parameters"]["land_cost"] - else: - land_cost = 1e6 # TODO should model this - else: - land_cost = 0.0 - - pf = ProFAST.ProFAST() - pf.set_params( - "commodity", - { - "name": "Hydrogen", - "unit": "kg", - "initial price": 100, - "escalation": gen_inflation, - }, - ) - pf.set_params( - "capacity", - electrolyzer_performance_results.rated_capacity_kg_pr_day, - ) # kg/day - pf.set_params("maintenance", {"value": 0, "escalation": gen_inflation}) - pf.set_params( - "analysis start year", - financial_analysis_start_year, - ) - pf.set_params("operating life", h2integrate_config["project_parameters"]["project_lifetime"]) - pf.set_params( - "installation months", - installation_period_months, - ) - pf.set_params( - "installation cost", - { - "value": 0, - "depr type": "Straight line", - "depr period": 4, - "depreciable": False, - }, - ) - if land_cost > 0: - pf.set_params("non depr assets", land_cost) - pf.set_params( - "end of proj sale non depr assets", - land_cost - * (1 + gen_inflation) ** h2integrate_config["project_parameters"]["project_lifetime"], - ) - pf.set_params("demand rampup", 0) - pf.set_params("long term utilization", electrolyzer_performance_results.long_term_utilization) - pf.set_params("credit card fees", 0) - pf.set_params("sales tax", h2integrate_config["finance_parameters"]["sales_tax_rate"]) - pf.set_params("license and permit", {"value": 00, "escalation": gen_inflation}) - pf.set_params("rent", {"value": 0, "escalation": gen_inflation}) - # TODO how to handle property tax and insurance for fully offshore? - pf.set_params( - "property tax and insurance", - h2integrate_config["finance_parameters"]["property_tax"] - + h2integrate_config["finance_parameters"]["property_insurance"], - ) - pf.set_params( - "admin expense", - h2integrate_config["finance_parameters"]["administrative_expense_percent_of_sales"], - ) - pf.set_params( - "total income tax rate", - h2integrate_config["finance_parameters"]["total_income_tax_rate"], - ) - pf.set_params( - "capital gains tax rate", - h2integrate_config["finance_parameters"]["capital_gains_tax_rate"], - ) - pf.set_params("sell undepreciated cap", True) - pf.set_params("tax losses monetized", True) - pf.set_params("general inflation rate", gen_inflation) - - pf.set_params("debt type", h2integrate_config["finance_parameters"]["debt_type"]) - pf.set_params("loan period if used", h2integrate_config["finance_parameters"]["loan_period"]) - pf.set_params("cash onhand", h2integrate_config["finance_parameters"]["cash_onhand_months"]) - - # ----------------------------------- Add capital and fixed items to ProFAST ---------------- - if "wind" in capex_breakdown.keys(): - pf.add_capital_item( - name="Wind system", - cost=capex_breakdown["wind"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"]["depreciation_period"], - refurb=[0], - ) - finance_param_weights["wind"] = capex_breakdown["wind"] - - if "wave" in capex_breakdown.keys(): - pf.add_capital_item( - name="Wave system", - cost=capex_breakdown["wave"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"]["depreciation_period"], - refurb=[0], - ) - finance_param_weights["wave"] = capex_breakdown["wave"] - - if "solar" in capex_breakdown.keys(): - pf.add_capital_item( - name="Solar PV system", - cost=capex_breakdown["solar"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"]["depreciation_period"], - refurb=[0], - ) - finance_param_weights["solar"] = capex_breakdown["solar"] - - if "battery" in capex_breakdown.keys(): - pf.add_capital_item( - name="Battery system", - cost=capex_breakdown["battery"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"]["depreciation_period"], - refurb=[0], - ) - finance_param_weights["battery"] = capex_breakdown["battery"] - - if "platform" in capex_breakdown.keys() and capex_breakdown["platform"] > 0: - pf.add_capital_item( - name="Equipment platform", - cost=capex_breakdown["platform"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"]["depreciation_period"], - refurb=[0], - ) - finance_param_weights["platform"] = capex_breakdown["platform"] - - pf.add_fixed_cost( - name="Equipment platform O&M cost", - usage=1.0, - unit="$/year", - cost=fopex_breakdown["platform"], - escalation=gen_inflation, - ) - - pf.add_fixed_cost( - name="Wind and electrical export fixed O&M cost", - usage=1.0, - unit="$/year", - cost=fopex_breakdown["wind_and_electrical"], - escalation=gen_inflation, - ) - if "wave" in fopex_breakdown.keys(): - pf.add_fixed_cost( - name="Wave O&M cost", - usage=1.0, - unit="$/year", - cost=fopex_breakdown["wave"], - escalation=gen_inflation, - ) - - if "solar" in fopex_breakdown.keys(): - pf.add_fixed_cost( - name="Solar O&M cost", - usage=1.0, - unit="$/year", - cost=fopex_breakdown["solar"], - escalation=gen_inflation, - ) - - if "battery" in fopex_breakdown.keys(): - pf.add_fixed_cost( - name="Battery O&M cost", - usage=1.0, - unit="$/year", - cost=fopex_breakdown["battery"], - escalation=gen_inflation, - ) - - if design_scenario["transportation"] == "hvdc+pipeline" or not ( - design_scenario["electrolyzer_location"] == "turbine" - and design_scenario["h2_storage_location"] == "turbine" - ): - pf.add_capital_item( - name="Electrical export system", - cost=capex_breakdown["electrical_export_system"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"]["depreciation_period"], - refurb=[0], - ) - finance_param_weights["electrical_export_system"] = capex_breakdown[ - "electrical_export_system" - ] - # TODO assess if this makes sense (electrical export O&M included in wind O&M) - - pf.add_capital_item( - name="Electrolysis system", - cost=capex_breakdown["electrolyzer"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"]["depreciation_period_electrolyzer"], - refurb=electrolyzer_performance_results.refurb_cost_percent, - ) - finance_param_weights["electrolyzer"] = capex_breakdown["electrolyzer"] - pf.add_fixed_cost( - name="Electrolysis system fixed O&M cost", - usage=1.0, - unit="$/year", - cost=fopex_breakdown["electrolyzer"], - escalation=gen_inflation, - ) - - pf.add_feedstock( - name="Electrolyzer Variable O&M", - usage=1.0, - unit="$/kg", - cost=vopex_breakdown["electrolyzer"], - escalation=gen_inflation, - ) - - if design_scenario["electrolyzer_location"] == "turbine": - pf.add_capital_item( - name="H2 pipe array system", - cost=capex_breakdown["h2_pipe_array"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"][ - "depreciation_period_electrolyzer" - ], - refurb=[0], - ) - finance_param_weights["h2_pipe_array"] = capex_breakdown["h2_pipe_array"] - pf.add_fixed_cost( - name="H2 pipe array fixed O&M cost", - usage=1.0, - unit="$/year", - cost=fopex_breakdown["h2_pipe_array"], - escalation=gen_inflation, - ) - - if ( - ( - design_scenario["h2_storage_location"] == "onshore" - and design_scenario["electrolyzer_location"] != "onshore" - ) - or ( - design_scenario["h2_storage_location"] != "onshore" - and design_scenario["electrolyzer_location"] == "onshore" - ) - or (design_scenario["transportation"] == "hvdc+pipeline") - ): - pf.add_capital_item( - name="H2 transport compressor system", - cost=capex_breakdown["h2_transport_compressor"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"][ - "depreciation_period_electrolyzer" - ], - refurb=[0], - ) - finance_param_weights["h2_transport_compressor"] = capex_breakdown[ - "h2_transport_compressor" - ] - pf.add_capital_item( - name="H2 transport pipeline system", - cost=capex_breakdown["h2_transport_pipeline"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"][ - "depreciation_period_electrolyzer" - ], - refurb=[0], - ) - finance_param_weights["h2_transport_pipeline"] = capex_breakdown["h2_transport_pipeline"] - - pf.add_fixed_cost( - name="H2 transport compression fixed O&M cost", - usage=1.0, - unit="$/year", - cost=fopex_breakdown["h2_transport_compressor"], - escalation=gen_inflation, - ) - pf.add_fixed_cost( - name="H2 transport pipeline fixed O&M cost", - usage=1.0, - unit="$/year", - cost=fopex_breakdown["h2_transport_pipeline"], - escalation=gen_inflation, - ) - - if h2integrate_config["h2_storage"]["type"] != "none": - pf.add_capital_item( - name="Hydrogen storage system", - cost=capex_breakdown["h2_storage"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"][ - "depreciation_period_electrolyzer" - ], - refurb=[0], - ) - finance_param_weights["h2_storage"] = capex_breakdown["h2_storage"] - pf.add_fixed_cost( - name="Hydrogen storage fixed O&M cost", - usage=1.0, - unit="$/year", - cost=fopex_breakdown["h2_storage"], - escalation=gen_inflation, - ) - - # ---------------------- Add feedstocks, note the various cost options------------------- - if design_scenario["electrolyzer_location"] == "onshore": - pf.add_feedstock( - name="Water", - usage=electrolyzer_performance_results.water_usage_gal_pr_kg, - unit="gal", - cost="US Average", - escalation=gen_inflation, - ) - else: - pf.add_capital_item( - name="Desal system", - cost=capex_breakdown["desal"], - depr_type=h2integrate_config["finance_parameters"]["depreciation_method"], - depr_period=h2integrate_config["finance_parameters"][ - "depreciation_period_electrolyzer" - ], - refurb=[0], - ) - finance_param_weights["desal"] = capex_breakdown["desal"] - pf.add_fixed_cost( - name="Desal fixed O&M cost", - usage=1.0, - unit="$/year", - cost=fopex_breakdown["desal"], - escalation=gen_inflation, - ) - - if ( - h2integrate_config["project_parameters"]["grid_connection"] - or sum(total_accessory_power_grid_kw) > 0 - ): - energy_purchase = sum(total_accessory_power_grid_kw) # * 365 * 24 - - if h2integrate_config["project_parameters"]["grid_connection"]: - annual_energy_shortfall = np.sum(hopp_results["energy_shortfall_hopp"]) - energy_purchase += annual_energy_shortfall - - pf.add_fixed_cost( - name="Electricity from grid", - usage=1.0, - unit="$/year", - cost=energy_purchase * h2integrate_config["project_parameters"]["ppa_price"], - escalation=gen_inflation, - ) - - # ------------------------------------- add incentives ----------------------------------- - """ - Note: units must be given to ProFAST in terms of dollars per unit of the primary commodity being - produced - - Note: full tech-nutral (wind) tax credits are no longer available if constructions starts after - Jan. 1 2034 (Jan 1. 2033 for h2 ptc) - """ - - # catch incentive option and add relevant incentives - incentive_dict = h2integrate_config["policy_parameters"][f"option{incentive_option}"] - - # add wind_itc (% of wind capex) - electricity_itc_value_percent_wind_capex = incentive_dict["electricity_itc"] - electricity_itc_value_dollars = electricity_itc_value_percent_wind_capex * ( - capex_breakdown["wind"] + capex_breakdown["electrical_export_system"] - ) - pf.set_params( - "one time cap inct", - { - "value": electricity_itc_value_dollars, - "depr type": h2integrate_config["finance_parameters"]["depreciation_method"], - "depr period": h2integrate_config["finance_parameters"]["depreciation_period"], - "depreciable": True, - }, - ) - - # add h2_storage_itc (% of h2 storage capex) - itc_value_percent_h2_store_capex = incentive_dict["h2_storage_itc"] - electricity_itc_value_dollars_h2_store = ( - itc_value_percent_h2_store_capex * (capex_breakdown["h2_storage"]) - ) - pf.set_params( - "one time cap inct", - { - "value": electricity_itc_value_dollars_h2_store, - "depr type": h2integrate_config["finance_parameters"]["depreciation_method"], - "depr period": h2integrate_config["finance_parameters"]["depreciation_period"], - "depreciable": True, - }, - ) - - # add electricity_ptc ($/kW) - # adjust from 1992 dollars to start year - electricity_ptc_in_dollars_per_kw = -npf.fv( - h2integrate_config["finance_parameters"]["costing_general_inflation"], - h2integrate_config["project_parameters"]["financial_analysis_start_year"] - + round(wind_cost_results.installation_time / 12) - - 1992, - 0, - incentive_dict["electricity_ptc"], - ) # given in 1992 dollars but adjust for inflation - kw_per_kg_h2 = sum(hopp_results["combined_hybrid_power_production_hopp"]) / np.mean( - electrolyzer_performance_results.electrolyzer_annual_h2_production_kg - ) - electricity_ptc_in_dollars_per_kg_h2 = electricity_ptc_in_dollars_per_kw * kw_per_kg_h2 - pf.add_incentive( - name="Electricity PTC", - value=electricity_ptc_in_dollars_per_kg_h2, - decay=-gen_inflation, - sunset_years=10, - tax_credit=True, - ) # TODO check decay - - # add h2_ptc ($/kg) - h2_ptc_inflation_adjusted = -npf.fv( - h2integrate_config["finance_parameters"][ - "costing_general_inflation" - ], # use ATB year (cost inflation 2.5%) costing_general_inflation - h2integrate_config["project_parameters"]["financial_analysis_start_year"] - + round(wind_cost_results.installation_time / 12) - - 2022, - 0, - incentive_dict["h2_ptc"], - ) - pf.add_incentive( - name="H2 PTC", - value=h2_ptc_inflation_adjusted, - decay=-gen_inflation, # correct inflation - sunset_years=10, - tax_credit=True, - ) # TODO check decay - - # ----------------------- Add weight-averaged parameters ----------------------- - - equity_discount_rate = calc_financial_parameter_weighted_average_by_capex( - parameter_name="discount_rate", - h2integrate_config=h2integrate_config, - capex_breakdown=finance_param_weights, - ) - pf.set_params( - "leverage after tax nominal discount rate", - equity_discount_rate, - ) - - debt_interest_rate = calc_financial_parameter_weighted_average_by_capex( - parameter_name="debt_interest_rate", - h2integrate_config=h2integrate_config, - capex_breakdown=finance_param_weights, - ) - pf.set_params( - "debt interest rate", - debt_interest_rate, - ) - - if "debt_equity_split" in h2integrate_config["finance_parameters"].keys(): - debt_equity_split = calc_financial_parameter_weighted_average_by_capex( - parameter_name="debt_equity_split", - h2integrate_config=h2integrate_config, - capex_breakdown=finance_param_weights, - ) - pf.set_params( - "debt equity ratio of initial financing", - (debt_equity_split / (100 - debt_equity_split)), - ) - elif "debt_equity_ratio" in h2integrate_config["finance_parameters"].keys(): - debt_equity_ratio = calc_financial_parameter_weighted_average_by_capex( - parameter_name="debt_equity_ratio", - h2integrate_config=h2integrate_config, - capex_breakdown=finance_param_weights, - ) - pf.set_params( - "debt equity ratio of initial financing", - debt_equity_ratio, - ) - else: - msg = ( - "missing value in `finance_parameters`. " - "Requires either `debt_equity_ratio` or `debt_equity_split`" - ) - raise ValueError(msg) - - # ------------------------------------ solve and post-process ----------------------------- - - sol = pf.solve_price() - - df = pf.cash_flow_out - - lcoh = sol["price"] - - if verbose: - print(f"\nProFAST LCOH: {lcoh:.2f} $/kg") - print(f'ProFAST NPV: {sol["NPV"]:.2f}') - print(f'ProFAST IRR: {max(sol["irr"]):.5f}') - print(f'ProFAST LCO: {sol["lco"]:.2f} $/kg') - print(f'ProFAST Profit Index: {sol["profit index"]:.2f}') - print(f'ProFAST payback period: {sol["investor payback period"]}') - - MIRR = npf.mirr( - df["Investor cash flow"], - debt_interest_rate, - equity_discount_rate, - ) # TODO probably ignore MIRR - NPV = npf.npv( - h2integrate_config["finance_parameters"]["inflation_rate"], - df["Investor cash flow"], - ) - ROI = np.sum(df["Investor cash flow"]) / abs( - np.sum(df["Investor cash flow"][df["Investor cash flow"] < 0]) - ) # ROI is not a good way of thinking about the value of the project - - # TODO project level IRR - capex and operating cash flow - - # note: hurdle rate typically 20% IRR before investing in it due to typically optimistic - # assumptions - - # note: negative retained earnings (keeping debt, paying down equity) - to get around it, - # do another line for retained earnings and watch dividends paid by the project - # (net income/equity should stay positive this way) - - print("Investor NPV: ", np.round(NPV * 1e-6, 2), "M USD") - print("Investor MIRR: ", np.round(MIRR, 5), "") - print("Investor ROI: ", np.round(ROI, 5), "") - - if save_plots or show_plots: - savepaths = [ - output_dir / "figures/capex", - output_dir / "figures/annual_cash_flow", - output_dir / "figures/lcoh_breakdown", - output_dir / "data", - ] - for savepath in savepaths: - if not savepath.exists(): - savepath.mkdir(parents=True) - - pf.plot_capital_expenses( - fileout=savepaths[0] / f"capital_expense_{design_scenario['id']}.pdf", - show_plot=show_plots, - ) - pf.plot_cashflow( - fileout=savepaths[1] / f"cash_flow_{design_scenario['id']}.png", - show_plot=show_plots, - ) - - pd.DataFrame.from_dict(data=pf.cash_flow_out).to_csv( - savepaths[3] / f"cash_flow_{design_scenario['id']}.csv" - ) - - pf.plot_costs( - savepaths[2] / f"lcoh_{design_scenario['id']}", - show_plot=show_plots, - ) - - return lcoh, pf, sol diff --git a/h2integrate/tools/eco/utilities.py b/h2integrate/tools/eco/utilities.py deleted file mode 100644 index 016e54a40..000000000 --- a/h2integrate/tools/eco/utilities.py +++ /dev/null @@ -1,3679 +0,0 @@ -from __future__ import annotations - -import copy -import warnings -from pathlib import Path - -import numpy as np -import ORBIT as orbit -import pandas as pd -import numpy_financial as npf -import matplotlib.pyplot as plt -import matplotlib.ticker as ticker -import matplotlib.patches as patches -from hopp.utilities import load_yaml -from hopp.simulation import HoppInterface -from hopp.tools.dispatch import plot_tools -from hopp.simulation.technologies.resource.greet_data import GREETData -from hopp.simulation.technologies.resource.cambium_data import CambiumData - -from h2integrate.tools.h2integrate_sim_file_utils import load_dill_pickle - -from .finance import adjust_orbit_costs - - -""" -This function returns the ceiling of a/b (rounded to the nearest greater integer). -The function was copied from https://stackoverflow.com/a/17511341/5128616 -""" - - -def ceildiv(a, b): - return -(a // -b) - - -def convert_relative_to_absolute_path(config_filepath, resource_filepath): - if resource_filepath == "": - return "" - else: - abs_config_filepath = Path(config_filepath).absolute().parent - return abs_config_filepath / resource_filepath - - -# Function to load inputs -def get_inputs( - filename_hopp_config, - filename_h2integrate_config, - filename_orbit_config, - filename_turbine_config, - filename_floris_config=None, - verbose=False, - show_plots=False, - save_plots=False, -): - ############### load turbine inputs from yaml - - # load turbine inputs - turbine_config = load_yaml(filename_turbine_config) - - # load hopp inputs - hopp_config = load_yaml(filename_hopp_config) - - # load eco inputs - h2integrate_config = load_yaml(filename_h2integrate_config) - - # convert relative filepath to absolute for HOPP ingestion - hopp_config["site"]["solar_resource_file"] = convert_relative_to_absolute_path( - filename_hopp_config, hopp_config["site"]["solar_resource_file"] - ) - hopp_config["site"]["wind_resource_file"] = convert_relative_to_absolute_path( - filename_hopp_config, hopp_config["site"]["wind_resource_file"] - ) - hopp_config["site"]["wave_resource_file"] = convert_relative_to_absolute_path( - filename_hopp_config, hopp_config["site"]["wave_resource_file"] - ) - hopp_config["site"]["grid_resource_file"] = convert_relative_to_absolute_path( - filename_hopp_config, hopp_config["site"]["grid_resource_file"] - ) - - ################ load plant inputs from yaml - if filename_orbit_config is not None: - orbit_config = orbit.load_config(filename_orbit_config) - - # print plant inputs if desired - if verbose: - print("\nPlant configuration:") - for key in orbit_config.keys(): - print(key, ": ", orbit_config[key]) - - # check that orbit and hopp inputs are compatible - if ( - orbit_config["plant"]["capacity"] - != hopp_config["technologies"]["wind"]["num_turbines"] - * hopp_config["technologies"]["wind"]["turbine_rating_kw"] - * 1e-3 - ): - raise (ValueError("Provided ORBIT and HOPP wind plant capacities do not match")) - - # update floris_config file with correct input from other files - # load floris inputs - if ( - hopp_config["technologies"]["wind"]["model_name"] == "floris" - ): # TODO replace elements of the file - if filename_floris_config is None: - raise (ValueError("floris input file must be specified.")) - else: - floris_config = load_yaml(filename_floris_config) - floris_config.update({"farm": {"turbine_type": turbine_config}}) - else: - floris_config = None - - # print turbine inputs if desired - if verbose: - print("\nTurbine configuration:") - for key in turbine_config.keys(): - print(key, ": ", turbine_config[key]) - - ############## provide custom layout for ORBIT and FLORIS if desired - if filename_orbit_config is not None: - if orbit_config["plant"]["layout"] == "custom": - # generate ORBIT config from floris layout - for i, x in enumerate(floris_config["farm"]["layout_x"]): - floris_config["farm"]["layout_x"][i] = x + 400 - - layout_config, layout_data_location = convert_layout_from_floris_for_orbit( - floris_config["farm"]["layout_x"], - floris_config["farm"]["layout_y"], - save_config=True, - ) - - # update orbit_config with custom layout - # orbit_config = orbit.core.library.extract_library_data( - # orbit_config, additional_keys=layout_config - # ) - orbit_config["array_system_design"]["location_data"] = layout_data_location - - # if hybrid plant, adjust hybrid plant capacity to include all technologies - total_hybrid_plant_capacity_mw = 0.0 - for tech in hopp_config["technologies"].keys(): - if tech == "grid": - continue - elif tech == "wind": - total_hybrid_plant_capacity_mw += ( - hopp_config["technologies"][tech]["num_turbines"] - * hopp_config["technologies"][tech]["turbine_rating_kw"] - * 1e-3 - ) - elif tech == "pv": - total_hybrid_plant_capacity_mw += ( - hopp_config["technologies"][tech]["system_capacity_kw"] * 1e-3 - ) - elif tech == "wave": - total_hybrid_plant_capacity_mw += ( - hopp_config["technologies"][tech]["num_devices"] - * hopp_config["technologies"][tech]["device_rating_kw"] - * 1e-3 - ) - - # initialize dict for hybrid plant - if filename_orbit_config is not None: - if total_hybrid_plant_capacity_mw != orbit_config["plant"]["capacity"]: - orbit_hybrid_electrical_export_config = copy.deepcopy(orbit_config) - orbit_hybrid_electrical_export_config["plant"]["capacity"] = ( - total_hybrid_plant_capacity_mw - ) - # allow orbit to set num_turbines later based on the new hybrid capacity and - # turbinerating - orbit_hybrid_electrical_export_config["plant"].pop("num_turbines") - else: - orbit_hybrid_electrical_export_config = {} - - if verbose: - print(f"Total hybrid plant rating calculated: {total_hybrid_plant_capacity_mw} MW") - - if filename_orbit_config is None: - orbit_config = None - orbit_hybrid_electrical_export_config = {} - - ############## return all inputs - - return ( - hopp_config, - h2integrate_config, - orbit_config, - turbine_config, - floris_config, - orbit_hybrid_electrical_export_config, - ) - - -def convert_layout_from_floris_for_orbit(turbine_x, turbine_y, save_config=False): - turbine_x_km = (np.array(turbine_x) * 1e-3).tolist() - turbine_y_km = (np.array(turbine_y) * 1e-3).tolist() - - # initialize dict with data for turbines - turbine_dict = { - "id": list(range(0, len(turbine_x))), - "substation_id": ["OSS"] * len(turbine_x), - "name": list(range(0, len(turbine_x))), - "longitude": turbine_x_km, - "latitude": turbine_y_km, - "string": [0] * len(turbine_x), # can be left empty - "order": [0] * len(turbine_x), # can be left empty - "cable_length": [0] * len(turbine_x), - "bury_speed": [0] * len(turbine_x), - } - string_counter = -1 - order_counter = 0 - for i in range(0, len(turbine_x)): - if turbine_x[i] - 400 == 0: - string_counter += 1 - order_counter = 0 - - turbine_dict["order"][i] = order_counter - turbine_dict["string"][i] = string_counter - - order_counter += 1 - - # initialize dict with substation information - substation_dict = { - "id": "OSS", - "substation_id": "OSS", - "name": "OSS", - "longitude": np.min(turbine_x_km) - 200 * 1e-3, - "latitude": np.average(turbine_y_km), - "string": "", # can be left empty - "order": "", # can be left empty - "cable_length": "", - "bury_speed": "", - } - - # combine turbine and substation dicts - for key in turbine_dict.keys(): - # turbine_dict[key].append(substation_dict[key]) - turbine_dict[key].insert(0, substation_dict[key]) - - # add location data - file_name = "osw_cable_layout" - save_location = Path("./input/project/plant/").resolve() - # turbine_dict["array_system_design"]["location_data"] = data_location - if save_config: - if not save_location.exists(): - save_location.mkdir(parents=True) - # create pandas data frame - df = pd.DataFrame.from_dict(turbine_dict) - - # df.drop("index") - df.set_index("id") - - # save to csv - df.to_csv(save_location / f"{file_name}.csv", index=False) - - return turbine_dict, file_name - - -def visualize_plant( - hopp_config, - h2integrate_config, - turbine_config, - wind_cost_outputs, - hopp_results, - platform_results, - desal_results, - h2_storage_results, - electrolyzer_physics_results, - design_scenario, - colors, - plant_design_number, - show_plots=False, - save_plots=False, - output_dir="./output/", -): - if isinstance(output_dir, str): - output_dir = Path(output_dir).resolve() - # save plant sizing to dict - component_areas = {} - - plt.rcParams.update({"font.size": 7}) - - if hopp_config["technologies"]["wind"]["model_name"] != "floris": - msg = ( - f"`visualize_plant()` only works with the 'floris' wind model, `model_name`" - f" {hopp_config['technologies']['wind']['model_name']} has been specified" - ) - raise NotImplementedError(msg) - - # set colors - turbine_rotor_color = colors[0] - turbine_tower_color = colors[1] - pipe_color = colors[2] - cable_color = colors[8] - electrolyzer_color = colors[4] - desal_color = colors[9] - h2_storage_color = colors[6] - substation_color = colors[7] - equipment_platform_color = colors[1] - compressor_color = colors[0] - if hopp_config["site"]["solar"]: - solar_color = colors[2] - if hopp_config["site"]["wave"]: - wave_color = colors[8] - battery_color = colors[8] - - # set hatches - solar_hatch = "//" - wave_hatch = "\\\\" - battery_hatch = "+" - electrolyzer_hatch = "///" - desalinator_hatch = "xxxx" - - # Views - # offshore plant, onshore plant, offshore platform, offshore turbine - - # get plant location - - # get shore location - - # get cable/pipe locations - if design_scenario["wind_location"] == "offshore": - # ORBIT gives coordinates in km, convert to m for (val / 1e3) - - cable_array_points = ( - wind_cost_outputs.orbit_project.phases["ArraySystemDesign"].coordinates * 1e3 - ) - pipe_array_points = ( - wind_cost_outputs.orbit_project.phases["ArraySystemDesign"].coordinates * 1e3 - ) - - # get turbine tower base diameter - tower_base_diameter = wind_cost_outputs.orbit_project.config["turbine"]["tower"][ - "section_diameters" - ][0] # in m - tower_base_radius = tower_base_diameter / 2.0 - - # get turbine locations - turbine_x = ( - wind_cost_outputs.orbit_project.phases["ArraySystemDesign"].turbines_x.flatten() * 1e3 - ) - turbine_x = turbine_x[~np.isnan(turbine_x)] - turbine_y = ( - wind_cost_outputs.orbit_project.phases["ArraySystemDesign"].turbines_y.flatten() * 1e3 - ) - turbine_y = turbine_y[~np.isnan(turbine_y)] - - # get offshore substation location and dimensions (treated as center) - substation_x = wind_cost_outputs.orbit_project.phases["ArraySystemDesign"].oss_x * 1e3 - substation_y = wind_cost_outputs.orbit_project.phases["ArraySystemDesign"].oss_y * 1e3 - - # [m] just based on a large substation - # (https://www.windpowerengineering.com/making-modern-offshore-substation/) - # since the dimensions are not available in ORBIT - substation_side_length = 20 - - # get equipment platform location and dimensions - equipment_platform_area = platform_results["toparea_m2"] - equipment_platform_side_length = np.sqrt(equipment_platform_area) - - # [m] (treated as center) - equipment_platform_x = ( - substation_x - substation_side_length - equipment_platform_side_length / 2 - ) - equipment_platform_y = substation_y - - # get platform equipment dimensions - if design_scenario["electrolyzer_location"] == "turbine": - # equipment_footprint_m2 - desal_equipment_area = desal_results["per_turb_equipment_footprint_m2"] - elif design_scenario["electrolyzer_location"] == "platform": - desal_equipment_area = desal_results["equipment_footprint_m2"] - else: - desal_equipment_area = 0 - - desal_equipment_side = np.sqrt(desal_equipment_area) - - # get pipe points - np.array([substation_x - 1000, substation_x]) - np.array([substation_y, substation_y]) - - # get cable points - - else: - turbine_x = np.array( - hopp_config["technologies"]["wind"]["floris_config"]["farm"]["layout_x"] - ) - turbine_y = np.array( - hopp_config["technologies"]["wind"]["floris_config"]["farm"]["layout_y"] - ) - cable_array_points = [] - - # wind farm area - turbine_length_x = np.max(turbine_x) - np.min(turbine_x) - turbine_length_y = np.max(turbine_y) - np.min(turbine_y) - turbine_area = turbine_length_x * turbine_length_y - - # compressor side # not sized - compressor_area = 25 - compressor_side = np.sqrt(compressor_area) - - # get turbine rotor diameter - rotor_diameter = turbine_config["rotor_diameter"] # in m - rotor_radius = rotor_diameter / 2.0 - - # set onshore substation dimensions - onshore_substation_x_side_length = 127.25 # [m] based on 1 acre area https://www.power-technology.com/features/making-space-for-power-how-much-land-must-renewables-use/ - onshore_substation_y_side_length = 31.8 # [m] based on 1 acre area https://www.power-technology.com/features/making-space-for-power-how-much-land-must-renewables-use/ - onshore_substation_area = onshore_substation_x_side_length * onshore_substation_y_side_length - - if h2integrate_config["h2_storage"]["type"] == "pressure_vessel": - h2_storage_area = h2_storage_results["tank_footprint_m2"] - h2_storage_side = np.sqrt(h2_storage_area) - else: - h2_storage_side = 0 - h2_storage_area = 0 - - electrolyzer_area = electrolyzer_physics_results["equipment_footprint_m2"] - if design_scenario["electrolyzer_location"] == "turbine": - electrolyzer_area /= hopp_config["technologies"]["wind"]["num_turbines"] - - electrolyzer_side = np.sqrt(electrolyzer_area) - - # set onshore origin - onshorex = 50 - onshorey = 50 - - wind_buffer = np.min(turbine_x) - (onshorey + 3 * rotor_diameter + electrolyzer_side) - if "pv" in hopp_config["technologies"].keys(): - wind_buffer -= np.sqrt(hopp_results["hybrid_plant"].pv.footprint_area) - if "battery" in hopp_config["technologies"].keys(): - wind_buffer -= np.sqrt(hopp_results["hybrid_plant"].battery.footprint_area) - if wind_buffer < 50: - onshorey += wind_buffer - 50 - - if design_scenario["wind_location"] == "offshore": - origin_x = substation_x - origin_y = substation_y - else: - origin_x = 0.0 - origin_y = 0.0 - - ## create figure - if design_scenario["wind_location"] == "offshore": - fig, ax = plt.subplots(2, 2, figsize=(10, 6)) - ax_index_plant = (0, 0) - ax_index_detail = (1, 0) - ax_index_wind_plant = (0, 1) - ax_index_turbine_detail = (1, 1) - else: - fig, ax = plt.subplots(1, 2, figsize=(10, 6)) - ax_index_plant = 0 - ax_index_wind_plant = 0 - ax_index_detail = 1 - ax_index_turbine_detail = False - - # plot the stuff - - # onshore plant | offshore plant - # platform/substation | turbine - - ## add turbines - def add_turbines(ax, turbine_x, turbine_y, radius, color): - i = 0 - for x, y in zip(turbine_x, turbine_y): - if i == 0: - rlabel = "Wind turbine rotor" - i += 1 - else: - rlabel = None - turbine_patch = patches.Circle( - (x, y), - radius=radius, - color=color, - fill=False, - label=rlabel, - zorder=10, - ) - ax.add_patch(turbine_patch) - - add_turbines(ax[ax_index_wind_plant], turbine_x, turbine_y, rotor_radius, turbine_rotor_color) - component_areas["turbine_area_m2"] = turbine_area - # turbine_patch01_tower = patches.Circle((x, y), radius=tower_base_radius, color=turbine_tower_color, fill=False, label=tlabel, zorder=10) # noqa: E501 - # ax[0, 1].add_patch(turbine_patch01_tower) - if design_scenario["wind_location"] == "onshore": - add_turbines(ax[ax_index_detail], turbine_x, turbine_y, rotor_radius, turbine_rotor_color) - - if ax_index_turbine_detail: - # turbine_patch11_rotor = patches.Circle((turbine_x[0], turbine_y[0]), radius=rotor_radius, color=turbine_rotor_color, fill=False, label=None, zorder=10) # noqa: E501 - tlabel = "Wind turbine tower" - turbine_patch11_tower = patches.Circle( - (turbine_x[0], turbine_y[0]), - radius=tower_base_radius, - color=turbine_tower_color, - fill=False, - label=tlabel, - zorder=10, - ) - # ax[1, 1].add_patch(turbine_patch11_rotor) - ax[ax_index_turbine_detail].add_patch(turbine_patch11_tower) - - # add pipe array - if design_scenario["transportation"] == "hvdc+pipeline" or ( - design_scenario["h2_storage_location"] != "turbine" - and design_scenario["electrolyzer_location"] == "turbine" - ): - i = 0 - for point_string in pipe_array_points: - if i == 0: - label = "Array pipes" - i += 1 - else: - label = None - ax[0, 1].plot( - point_string[:, 0], - point_string[:, 1] - substation_side_length / 2, - ":", - color=pipe_color, - zorder=0, - linewidth=1, - label=label, - ) - ax[1, 0].plot( - point_string[:, 0], - point_string[:, 1] - substation_side_length / 2, - ":", - color=pipe_color, - zorder=0, - linewidth=1, - label=label, - ) - ax[1, 1].plot( - point_string[:, 0], - point_string[:, 1] - substation_side_length / 2, - ":", - color=pipe_color, - zorder=0, - linewidth=1, - label=label, - ) - - ## add cables - if (len(cable_array_points) > 1) and ( - design_scenario["h2_storage_location"] != "turbine" - or design_scenario["transportation"] == "hvdc+pipeline" - ): - i = 0 - for point_string in cable_array_points: - if i == 0: - label = "Array cables" - i += 1 - else: - label = None - ax[0, 1].plot( - point_string[:, 0], - point_string[:, 1] + substation_side_length / 2, - "-", - color=cable_color, - zorder=0, - linewidth=1, - label=label, - ) - ax[1, 0].plot( - point_string[:, 0], - point_string[:, 1] + substation_side_length / 2, - "-", - color=cable_color, - zorder=0, - linewidth=1, - label=label, - ) - ax[1, 1].plot( - point_string[:, 0], - point_string[:, 1] + substation_side_length / 2, - "-", - color=cable_color, - zorder=0, - linewidth=1, - label=label, - ) - - ## add offshore substation - if design_scenario["wind_location"] == "offshore" and ( - design_scenario["h2_storage_location"] != "turbine" - or design_scenario["transportation"] == "hvdc+pipeline" - ): - substation_patch01 = patches.Rectangle( - ( - substation_x - substation_side_length, - substation_y - substation_side_length / 2, - ), - substation_side_length, - substation_side_length, - fill=True, - color=substation_color, - label="Substation*", - zorder=11, - ) - substation_patch10 = patches.Rectangle( - ( - substation_x - substation_side_length, - substation_y - substation_side_length / 2, - ), - substation_side_length, - substation_side_length, - fill=True, - color=substation_color, - label="Substation*", - zorder=11, - ) - ax[0, 1].add_patch(substation_patch01) - ax[1, 0].add_patch(substation_patch10) - - component_areas["offshore_substation_area_m2"] = substation_side_length**2 - - ## add equipment platform - if design_scenario["wind_location"] == "offshore" and ( - design_scenario["h2_storage_location"] == "platform" - or design_scenario["electrolyzer_location"] == "platform" - ): # or design_scenario["transportation"] == "pipeline": - equipment_platform_patch01 = patches.Rectangle( - ( - equipment_platform_x - equipment_platform_side_length / 2, - equipment_platform_y - equipment_platform_side_length / 2, - ), - equipment_platform_side_length, - equipment_platform_side_length, - color=equipment_platform_color, - fill=True, - label="Equipment platform", - zorder=1, - ) - equipment_platform_patch10 = patches.Rectangle( - ( - equipment_platform_x - equipment_platform_side_length / 2, - equipment_platform_y - equipment_platform_side_length / 2, - ), - equipment_platform_side_length, - equipment_platform_side_length, - color=equipment_platform_color, - fill=True, - label="Equipment platform", - zorder=1, - ) - ax[0, 1].add_patch(equipment_platform_patch01) - ax[1, 0].add_patch(equipment_platform_patch10) - - component_areas["equipment_platform_area_m2"] = equipment_platform_area - - ## add hvdc cable - if ( - design_scenario["transportation"] == "hvdc" - or design_scenario["transportation"] == "hvdc+pipeline" - ): - ax[0, 0].plot( - [onshorex + onshore_substation_x_side_length, 10000], - [ - onshorey - onshore_substation_y_side_length, - onshorey - onshore_substation_y_side_length, - ], - "--", - color=cable_color, - label="HVDC cable", - ) - ax[0, 1].plot( - [-50000, substation_x], - [substation_y - 100, substation_y - 100], - "--", - color=cable_color, - label="HVDC cable", - zorder=0, - ) - ax[1, 0].plot( - [-5000, substation_x], - [substation_y - 2, substation_y - 2], - "--", - color=cable_color, - label="HVDC cable", - zorder=0, - ) - - ## add onshore substation - if ( - design_scenario["transportation"] == "hvdc" - or design_scenario["transportation"] == "hvdc+pipeline" - ): - onshore_substation_patch00 = patches.Rectangle( - ( - onshorex + 0.2 * onshore_substation_y_side_length, - onshorey - onshore_substation_y_side_length * 1.2, - ), - onshore_substation_x_side_length, - onshore_substation_y_side_length, - fill=True, - color=substation_color, - label="Substation*", - zorder=11, - ) - ax[0, 0].add_patch(onshore_substation_patch00) - - component_areas["onshore_substation_area_m2"] = onshore_substation_area - - ## add transport pipeline - if design_scenario["transportation"] == "colocated": - # add hydrogen pipeline to end use - linetype = "-." - label = "Pipeline to storage/end-use" - linewidth = 1.0 - - ax[ax_index_plant].plot( - [onshorex, -10000], - [onshorey, onshorey], - linetype, - color=pipe_color, - label=label, - linewidth=linewidth, - zorder=0, - ) - - ax[ax_index_detail].plot( - [onshorex, -10000], - [onshorey, onshorey], - linetype, - color=pipe_color, - label=label, - linewidth=linewidth, - zorder=0, - ) - if ( - design_scenario["transportation"] == "pipeline" - or design_scenario["transportation"] == "hvdc+pipeline" - or ( - design_scenario["transportation"] == "hvdc" - and design_scenario["h2_storage_location"] == "platform" - ) - ): - linetype = "-." - label = "Transport pipeline" - linewidth = 1.0 - - ax[ax_index_plant].plot( - [onshorex, 1000], - [onshorey + 2, onshorey + 2], - linetype, - color=pipe_color, - label=label, - linewidth=linewidth, - zorder=0, - ) - - if design_scenario["wind_location"] == "offshore": - ax[ax_index_wind_plant].plot( - [-5000, substation_x], - [substation_y + 100, substation_y + 100], - linetype, - linewidth=linewidth, - color=pipe_color, - label=label, - zorder=0, - ) - ax[ax_index_detail].plot( - [-5000, substation_x], - [substation_y + 2, substation_y + 2], - linetype, - linewidth=linewidth, - color=pipe_color, - label=label, - zorder=0, - ) - - if ( - design_scenario["transportation"] == "hvdc" - or design_scenario["transportation"] == "hvdc+pipeline" - ) and design_scenario["h2_storage_location"] == "platform": - h2cx = onshorex - compressor_side - h2cy = onshorey - compressor_side + 2 - h2cax = ax[ax_index_plant] - else: - h2cx = substation_x - substation_side_length - h2cy = substation_y - h2cax = ax[ax_index_detail] - - if design_scenario["wind_location"] == "onshore": - compressor_patch01 = patches.Rectangle( - (origin_x, origin_y), - compressor_side, - compressor_side, - color=compressor_color, - fill=None, - label="Transport compressor*", - hatch="+++", - zorder=20, - ) - ax[ax_index_plant].add_patch(compressor_patch01) - - compressor_patch10 = patches.Rectangle( - (h2cx, h2cy), - compressor_side, - compressor_side, - color=compressor_color, - fill=None, - label="Transport compressor*", - hatch="+++", - zorder=20, - ) - h2cax.add_patch(compressor_patch10) - - component_areas["compressor_area_m2"] = compressor_area - - ## add plant components - if design_scenario["electrolyzer_location"] == "onshore": - electrolyzer_x = onshorex - electrolyzer_y = onshorey - electrolyzer_patch = patches.Rectangle( - (electrolyzer_x, electrolyzer_y), - electrolyzer_side, - electrolyzer_side, - color=electrolyzer_color, - fill=None, - label="H$_2$ Electrolyzer", - zorder=20, - hatch=electrolyzer_hatch, - ) - ax[ax_index_plant].add_patch(electrolyzer_patch) - component_areas["electrolyzer_area_m2"] = electrolyzer_area - - if design_scenario["wind_location"] == "onshore": - electrolyzer_patch = patches.Rectangle( - (onshorex - h2_storage_side, onshorey + 4), - electrolyzer_side, - electrolyzer_side, - color=electrolyzer_color, - fill=None, - label="H$_2$ Electrolyzer", - zorder=20, - hatch=electrolyzer_hatch, - ) - ax[ax_index_detail].add_patch(electrolyzer_patch) - - elif design_scenario["electrolyzer_location"] == "platform": - dx = equipment_platform_x - equipment_platform_side_length / 2 - dy = equipment_platform_y - equipment_platform_side_length / 2 - e_side_y = equipment_platform_side_length - e_side_x = electrolyzer_area / e_side_y - d_side_y = equipment_platform_side_length - d_side_x = desal_equipment_area / d_side_y - electrolyzer_x = dx + d_side_x - electrolyzer_y = dy - - electrolyzer_patch = patches.Rectangle( - (electrolyzer_x, electrolyzer_y), - e_side_x, - e_side_y, - color=electrolyzer_color, - fill=None, - zorder=20, - label="H$_2$ Electrolyzer", - hatch=electrolyzer_hatch, - ) - ax[ax_index_detail].add_patch(electrolyzer_patch) - desal_patch = patches.Rectangle( - (dx, dy), - d_side_x, - d_side_y, - color=desal_color, - zorder=21, - fill=None, - label="Desalinator", - hatch=desalinator_hatch, - ) - ax[ax_index_detail].add_patch(desal_patch) - component_areas["desalination_area_m2"] = desal_equipment_area - - elif design_scenario["electrolyzer_location"] == "turbine": - electrolyzer_patch11 = patches.Rectangle( - (turbine_x[0], turbine_y[0] + tower_base_radius), - electrolyzer_side, - electrolyzer_side, - color=electrolyzer_color, - fill=None, - zorder=20, - label="H$_2$ Electrolyzer", - hatch=electrolyzer_hatch, - ) - ax[ax_index_turbine_detail].add_patch(electrolyzer_patch11) - desal_patch11 = patches.Rectangle( - (turbine_x[0] - desal_equipment_side, turbine_y[0] + tower_base_radius), - desal_equipment_side, - desal_equipment_side, - color=desal_color, - zorder=21, - fill=None, - label="Desalinator", - hatch=desalinator_hatch, - ) - ax[ax_index_turbine_detail].add_patch(desal_patch11) - component_areas["desalination_area_m2"] = desal_equipment_area - i = 0 - for x, y in zip(turbine_x, turbine_y): - if i == 0: - elabel = "H$_2$ Electrolyzer" - dlabel = "Desalinator" - else: - elabel = None - dlabel = None - electrolyzer_patch01 = patches.Rectangle( - (x, y + tower_base_radius), - electrolyzer_side, - electrolyzer_side, - color=electrolyzer_color, - fill=None, - zorder=20, - label=elabel, - hatch=electrolyzer_hatch, - ) - desal_patch01 = patches.Rectangle( - (x - desal_equipment_side, y + tower_base_radius), - desal_equipment_side, - desal_equipment_side, - color=desal_color, - zorder=21, - fill=None, - label=dlabel, - hatch=desalinator_hatch, - ) - ax[ax_index_wind_plant].add_patch(electrolyzer_patch01) - ax[ax_index_wind_plant].add_patch(desal_patch01) - i += 1 - - h2_storage_hatch = "\\\\\\" - if design_scenario["h2_storage_location"] == "onshore" and ( - h2integrate_config["h2_storage"]["type"] != "none" - ): - h2_storage_patch = patches.Rectangle( - (onshorex - h2_storage_side, onshorey - h2_storage_side - 2), - h2_storage_side, - h2_storage_side, - color=h2_storage_color, - fill=None, - label="H$_2$ storage", - hatch=h2_storage_hatch, - ) - ax[ax_index_plant].add_patch(h2_storage_patch) - component_areas["h2_storage_area_m2"] = h2_storage_area - - if design_scenario["wind_location"] == "onshore": - h2_storage_patch = patches.Rectangle( - (onshorex - h2_storage_side, onshorey - h2_storage_side - 2), - h2_storage_side, - h2_storage_side, - color=h2_storage_color, - fill=None, - label="H$_2$ storage", - hatch=h2_storage_hatch, - ) - ax[ax_index_detail].add_patch(h2_storage_patch) - component_areas["h2_storage_area_m2"] = h2_storage_area - elif design_scenario["h2_storage_location"] == "platform" and ( - h2integrate_config["h2_storage"]["type"] != "none" - ): - s_side_y = equipment_platform_side_length - s_side_x = h2_storage_area / s_side_y - sx = equipment_platform_x - equipment_platform_side_length / 2 - sy = equipment_platform_y - equipment_platform_side_length / 2 - if design_scenario["electrolyzer_location"] == "platform": - sx += equipment_platform_side_length - s_side_x - - h2_storage_patch = patches.Rectangle( - (sx, sy), - s_side_x, - s_side_y, - color=h2_storage_color, - fill=None, - label="H$_2$ storage", - hatch=h2_storage_hatch, - ) - ax[ax_index_detail].add_patch(h2_storage_patch) - component_areas["h2_storage_area_m2"] = h2_storage_area - - elif design_scenario["h2_storage_location"] == "turbine": - if h2integrate_config["h2_storage"]["type"] == "turbine": - h2_storage_patch = patches.Circle( - (turbine_x[0], turbine_y[0]), - radius=tower_base_diameter / 2, - color=h2_storage_color, - fill=None, - label="H$_2$ storage", - hatch=h2_storage_hatch, - ) - ax[ax_index_turbine_detail].add_patch(h2_storage_patch) - component_areas["h2_storage_area_m2"] = h2_storage_area - i = 0 - for x, y in zip(turbine_x, turbine_y): - if i == 0: - slabel = "H$_2$ storage" - else: - slabel = None - h2_storage_patch = patches.Circle( - (x, y), - radius=tower_base_diameter / 2, - color=h2_storage_color, - fill=None, - label=None, - hatch=h2_storage_hatch, - ) - ax[ax_index_wind_plant].add_patch(h2_storage_patch) - elif h2integrate_config["h2_storage"]["type"] == "pressure_vessel": - h2_storage_side = np.sqrt(h2_storage_area / h2integrate_config["plant"]["num_turbines"]) - h2_storage_patch = patches.Rectangle( - ( - turbine_x[0] - h2_storage_side - desal_equipment_side, - turbine_y[0] + tower_base_radius, - ), - width=h2_storage_side, - height=h2_storage_side, - color=h2_storage_color, - fill=None, - label="H$_2$ storage", - hatch=h2_storage_hatch, - ) - ax[ax_index_turbine_detail].add_patch(h2_storage_patch) - component_areas["h2_storage_area_m2"] = h2_storage_area - for i in range(zip(turbine_x, turbine_y)): - if i == 0: - slabel = "H$_2$ storage" - else: - slabel = None - h2_storage_patch = patches.Rectangle( - ( - turbine_x[i] - h2_storage_side - desal_equipment_side, - turbine_y[i] + tower_base_radius, - ), - width=h2_storage_side, - height=h2_storage_side, - color=h2_storage_color, - fill=None, - label=slabel, - hatch=h2_storage_hatch, - ) - ax[ax_index_wind_plant].add_patch(h2_storage_patch) - - ## add battery - if "battery" in hopp_config["technologies"].keys(): - component_areas["battery_area_m2"] = hopp_results["hybrid_plant"].battery.footprint_area - if design_scenario["battery_location"] == "onshore": - battery_side_y = np.sqrt(hopp_results["hybrid_plant"].battery.footprint_area) - battery_side_x = battery_side_y - - batteryx = electrolyzer_x - - batteryy = electrolyzer_y + electrolyzer_side + 10 - - battery_patch = patches.Rectangle( - (batteryx, batteryy), - battery_side_x, - battery_side_y, - color=battery_color, - fill=None, - label="Battery array", - hatch=battery_hatch, - ) - ax[ax_index_plant].add_patch(battery_patch) - - if design_scenario["wind_location"] == "onshore": - battery_patch = patches.Rectangle( - (batteryx, batteryy), - battery_side_x, - battery_side_y, - color=battery_color, - fill=None, - label="Battery array", - hatch=battery_hatch, - ) - ax[ax_index_detail].add_patch(battery_patch) - - elif design_scenario["battery_location"] == "platform": - battery_side_y = equipment_platform_side_length - battery_side_x = hopp_results["hybrid_plant"].battery.footprint_area / battery_side_y - - batteryx = equipment_platform_x - equipment_platform_side_length / 2 - batteryy = equipment_platform_y - equipment_platform_side_length / 2 - - battery_patch = patches.Rectangle( - (batteryx, batteryy), - battery_side_x, - battery_side_y, - color=battery_color, - fill=None, - label="Battery array", - hatch=battery_hatch, - ) - ax[ax_index_detail].add_patch(battery_patch) - - else: - battery_side_y = 0.0 - battery_side_x = 0.0 - - ## add solar - if hopp_config["site"]["solar"]: - component_areas["pv_area_m2"] = hopp_results["hybrid_plant"].pv.footprint_area - if design_scenario["pv_location"] == "offshore": - solar_side_y = equipment_platform_side_length - solar_side_x = hopp_results["hybrid_plant"].pv.footprint_area / solar_side_y - - solarx = equipment_platform_x - equipment_platform_side_length / 2 - solary = equipment_platform_y - equipment_platform_side_length / 2 - - solar_patch = patches.Rectangle( - (solarx, solary), - solar_side_x, - solar_side_y, - color=solar_color, - fill=None, - label="Solar array", - hatch=solar_hatch, - ) - ax[ax_index_detail].add_patch(solar_patch) - else: - solar_side_y = np.sqrt(hopp_results["hybrid_plant"].pv.footprint_area) - solar_side_x = hopp_results["hybrid_plant"].pv.footprint_area / solar_side_y - - solarx = electrolyzer_x - - solary = electrolyzer_y + electrolyzer_side + 10 - - if "battery" in hopp_config["technologies"].keys(): - solary += battery_side_y + 10 - - solar_patch = patches.Rectangle( - (solarx, solary), - solar_side_x, - solar_side_y, - color=solar_color, - fill=None, - label="Solar array", - hatch=solar_hatch, - ) - - ax[ax_index_plant].add_patch(solar_patch) - - if design_scenario["wind_location"] != "offshore": - solar_patch = patches.Rectangle( - (solarx, solary), - solar_side_x, - solar_side_y, - color=solar_color, - fill=None, - label="Solar array", - hatch=solar_hatch, - ) - - ax[ax_index_detail].add_patch(solar_patch) - - else: - solar_side_x = 0.0 - solar_side_y = 0.0 - - ## add wave - if hopp_config["site"]["wave"]: - # get wave generation area geometry - num_devices = hopp_config["technologies"]["wave"]["num_devices"] - distance_to_shore = ( - hopp_config["technologies"]["wave"]["cost_inputs"]["distance_to_shore"] * 1e3 - ) - number_rows = hopp_config["technologies"]["wave"]["cost_inputs"]["number_rows"] - device_spacing = hopp_config["technologies"]["wave"]["cost_inputs"]["device_spacing"] - row_spacing = hopp_config["technologies"]["wave"]["cost_inputs"]["row_spacing"] - - # calculate wave generation area dimenstions - wave_side_y = device_spacing * np.ceil(num_devices / number_rows) - wave_side_x = row_spacing * (number_rows) - wave_area = wave_side_x * wave_side_y - component_areas["wave_area_m2"] = wave_area - - # generate wave generation patch - wavex = substation_x - wave_side_x - wavey = substation_y + distance_to_shore - wave_patch = patches.Rectangle( - (wavex, wavey), - wave_side_x, - wave_side_y, - color=wave_color, - fill=None, - label="Wave array", - hatch=wave_hatch, - zorder=1, - ) - ax[ax_index_wind_plant].add_patch(wave_patch) - - # add electrical transmission for wave - wave_export_cable_coords_x = [substation_x, substation_x] - wave_export_cable_coords_y = [substation_y, substation_y + distance_to_shore] - - ax[ax_index_wind_plant].plot( - wave_export_cable_coords_x, - wave_export_cable_coords_y, - cable_color, - zorder=0, - ) - ax[ax_index_detail].plot( - wave_export_cable_coords_x, - wave_export_cable_coords_y, - cable_color, - zorder=0, - ) - - if design_scenario["wind_location"] == "offshore": - allpoints = cable_array_points.flatten() - else: - allpoints = turbine_x - - allpoints = allpoints[~np.isnan(allpoints)] - - if design_scenario["wind_location"] == "offshore": - roundto = -2 - ax[ax_index_plant].set( - xlim=[ - round(np.min(onshorex - 100), ndigits=roundto), - round( - np.max( - [ - onshorex, - onshore_substation_x_side_length, - electrolyzer_side, - solar_side_x, - ] - ) - * 1.8, - ndigits=roundto, - ), - ], - ylim=[ - round(np.min(onshorey - 100), ndigits=roundto), - round( - np.max(onshorey + battery_side_y + electrolyzer_side + solar_side_y + 100) - * 1.9, - ndigits=roundto, - ), - ], - ) - ax[ax_index_plant].set(aspect="equal") - - roundto = -3 - point_range_x = np.max(allpoints) - np.min(allpoints) - point_range_y = np.max(turbine_y) - np.min(turbine_y) - ax[ax_index_wind_plant].set( - xlim=[ - round(np.min(allpoints) - 0.5 * point_range_x, ndigits=roundto), - round(np.max(allpoints) + 0.5 * point_range_x, ndigits=roundto), - ], - ylim=[ - round(np.min(turbine_y) - 0.3 * point_range_y, ndigits=roundto), - round(np.max(turbine_y) + 0.3 * point_range_y, ndigits=roundto), - ], - ) - # ax[ax_index_wind_plant].autoscale() - ax[ax_index_wind_plant].set(aspect="equal") - # ax[ax_index_wind_plant].xaxis.set_major_locator(ticker.\ - # MultipleLocator(np.round(point_range_x*0.5, decimals=-3))) - # ax[ax_index_wind_plant].yaxis.set_major_locator(ticker.\ - # MultipleLocator(np.round(point_range_y*0.5, device_spacing=-3))) - - else: - roundto = -3 - point_range_x = np.max(allpoints) - np.min(allpoints) - point_range_y = np.max(turbine_y) - onshorey - ax[ax_index_plant].set( - xlim=[ - round(np.min(allpoints) - 0.7 * point_range_x, ndigits=roundto), - round(np.max(allpoints + 0.7 * point_range_x), ndigits=roundto), - ], - ylim=[ - round(np.min(onshorey) - 0.2 * point_range_y, ndigits=roundto), - round(np.max(turbine_y) + 1.0 * point_range_y, ndigits=roundto), - ], - ) - # ax[ax_index_plant].autoscale() - ax[ax_index_plant].set(aspect="equal") - # ax[ax_index_plant].xaxis.set_major_locator(ticker.MultipleLocator(2000)) - # ax[ax_index_plant].yaxis.set_major_locator(ticker.MultipleLocator(1000)) - - if design_scenario["wind_location"] == "offshore": - roundto = -2 - ax[ax_index_detail].set( - xlim=[ - round(origin_x - 400, ndigits=roundto), - round(origin_x + 100, ndigits=roundto), - ], - ylim=[ - round(origin_y - 200, ndigits=roundto), - round(origin_y + 200, ndigits=roundto), - ], - ) - ax[ax_index_detail].set(aspect="equal") - else: - roundto = -2 - - if "pv" in hopp_config["technologies"].keys(): - xmax = round( - np.max([onshorex, electrolyzer_side, battery_side_x, solar_side_x]) * 1.1, - ndigits=roundto, - ) - ymax = round( - onshorey + (solar_side_y + electrolyzer_side + battery_side_y) * 1.15, - ndigits=roundto, - ) - else: - xmax = round(np.max([onshorex]) * 1.1, ndigits=roundto) - ymax = round( - onshorey + (electrolyzer_side + battery_side_y + solar_side_y) * 1.1, - ndigits=roundto, - ) - ax[ax_index_detail].set( - xlim=[ - round(onshorex - 10, ndigits=roundto), - xmax, - ], - ylim=[ - round(onshorey - 200, ndigits=roundto), - ymax, - ], - ) - ax[ax_index_detail].set(aspect="equal") - - if design_scenario["wind_location"] == "offshore": - tower_buffer0 = 10 - tower_buffer1 = 10 - roundto = -1 - ax[ax_index_turbine_detail].set( - xlim=[ - round( - turbine_x[0] - tower_base_radius - tower_buffer0 - 50, - ndigits=roundto, - ), - round( - turbine_x[0] + tower_base_radius + 3 * tower_buffer1, - ndigits=roundto, - ), - ], - ylim=[ - round( - turbine_y[0] - tower_base_radius - 2 * tower_buffer0, - ndigits=roundto, - ), - round( - turbine_y[0] + tower_base_radius + 4 * tower_buffer1, - ndigits=roundto, - ), - ], - ) - ax[ax_index_turbine_detail].set(aspect="equal") - ax[ax_index_turbine_detail].xaxis.set_major_locator(ticker.MultipleLocator(10)) - ax[ax_index_turbine_detail].yaxis.set_major_locator(ticker.MultipleLocator(10)) - # ax[0,1].legend(frameon=False) - # ax[0,1].axis('off') - - if design_scenario["wind_location"] == "offshore": - labels = [ - "(a) Onshore plant", - "(b) Offshore plant", - "(c) Equipment platform and substation", - "(d) NW-most wind turbine", - ] - else: - labels = ["(a) Full plant", "(b) Non-wind plant detail"] - for axi, label in zip(ax.flatten(), labels): - axi.legend(frameon=False, ncol=2) # , ncol=2, loc="best") - axi.set(xlabel="Easting (m)", ylabel="Northing (m)") - axi.set_title(label, loc="left") - # axi.spines[['right', 'top']].set_visible(False) - - ## save the plot - plt.tight_layout() - savepaths = [ - output_dir / "figures/layout", - output_dir / "data", - ] - if save_plots: - for savepath in savepaths: - if not savepath.exists(): - savepath.mkdir(parents=True) - plt.savefig(savepaths[0] / f"plant_layout_{plant_design_number}.png", transparent=True) - - df = pd.DataFrame([component_areas]) - df.to_csv( - savepaths[1] / "fcomponent_areas_layout_{plant_design_number}.csv", - index=False, - ) - - if show_plots: - plt.show() - else: - plt.close() - - -def save_energy_flows( - hybrid_plant: HoppInterface.system, - electrolyzer_physics_results, - solver_results, - hours, - h2_storage_results, - simulation_length=8760, - output_dir="./output/", -): - if isinstance(output_dir, str): - output_dir = Path(output_dir).resolve() - - output = {} - if hybrid_plant.pv: - solar_plant_power = np.array(hybrid_plant.pv.generation_profile[0:simulation_length]) - output.update({"pv generation [kW]": solar_plant_power}) - if hybrid_plant.wind: - wind_plant_power = np.array(hybrid_plant.wind.generation_profile[0:simulation_length]) - output.update({"wind generation [kW]": wind_plant_power}) - if hybrid_plant.wave: - wave_plant_power = np.array(hybrid_plant.wave.generation_profile[0:simulation_length]) - output.update({"wave generation [kW]": wave_plant_power}) - if hybrid_plant.battery: - battery_power_out_mw = hybrid_plant.battery.outputs.P - output.update( - {"battery discharge [kW]": [(int(p > 0)) * p for p in battery_power_out_mw]} - ) # convert from MW to kW and extract only discharging - output.update( - {"battery charge [kW]": [-(int(p < 0)) * p for p in battery_power_out_mw]} - ) # convert from MW to kW and extract only charging - output.update({"battery state of charge [%]": hybrid_plant.battery.outputs.dispatch_SOC}) - total_generation_hourly = hybrid_plant.grid._system_model.Outputs.system_pre_interconnect_kwac[ - 0:simulation_length - ] - output.update({"total generation hourly [kW]": total_generation_hourly}) - output.update( - { - "total generation curtailed hourly [kW]": hybrid_plant.grid.generation_curtailed[ - 0:simulation_length - ] - } - ) - output.update({"total accessory power required [kW]": solver_results[0]}) - output.update({"grid energy usage hourly [kW]": solver_results[1]}) - output.update({"desal energy hourly [kW]": [solver_results[2]] * simulation_length}) - output.update( - { - "electrolyzer energy hourly [kW]": electrolyzer_physics_results[ - "power_to_electrolyzer_kw" - ] - } - ) - output.update({"electrolyzer bop energy hourly [kW]": solver_results[5]}) - output.update( - {"transport compressor energy hourly [kW]": [solver_results[3]] * simulation_length} - ) - output.update({"storage energy hourly [kW]": [solver_results[4]] * simulation_length}) - output.update( - { - "h2 production hourly [kg]": electrolyzer_physics_results["H2_Results"][ - "Hydrogen Hourly Production [kg/hr]" - ] - } - ) - if "hydrogen_storage_soc" in h2_storage_results: - output.update({"hydrogen storage SOC [kg]": h2_storage_results["hydrogen_storage_soc"]}) - if "hydrogen_demand_kgphr" in h2_storage_results: - output.update({"hydrogen demand [kg/h]": h2_storage_results["hydrogen_demand_kgphr"]}) - - df = pd.DataFrame.from_dict(output) - - filepath = output_dir / "data/production" - - if not filepath.exists(): - filepath.mkdir(parents=True) - - df.to_csv(filepath / "energy_flows.csv") - - return output - - -def calculate_lca( - wind_annual_energy_kwh, - solar_pv_annual_energy_kwh, - energy_shortfall_hopp, - h2_annual_prod_kg, - energy_to_electrolyzer_kwh, - hopp_config, - h2integrate_config, - total_accessory_power_renewable_kw, - total_accessory_power_grid_kw, - plant_design_scenario_number, - incentive_option_number, -): - """ - Function to perform Life Cycle Assessment (LCA) of the simulated system. - Calculates Scope 1, 2, and 3 average emissions over the lifetime of the plant in kg CO2e per - unit mass of product produced. - CO2e or carbon dioxide equivalent is a metric for the global warming potential of different - greenhouse gases (GHGs) by converting their emissions to the equivalent amount of CO2. - Leverages ANL's GREET model to determine emission intensity (EI), efficiency, feedstock - consumption, and energy consumption values of various processes - Leverages NREL's Cambium API to determine future grid generation mixes and emissions intensities - of grid electricity consumption - - Args: - wind_annual_energy_kwh (float): Annual energy from wind power (kWh) - solar_pv_annual_energy_kwh (float): Annual energy from solar pv power (kWh) - energy_shortfall_hopp: Total electricity to electrolyzer & peripherals from grid power (kWh) - h2_annual_prod_kg: Lifetime average annual H2 production accounting for electrolyzer - degradation (kg H2/year) - energy_to_electrolyzer_kwh: Total electricity to electrolyzer from grid power (kWh) - hopp_config (dict): HOPP configuration inputs based on input files - h2integrate_config (H2IntegrateSimulationConfig): all inputs to the h2integrate simulation - total_accessory_power_renewable_kw (numpy.ndarray): Total electricity to electrolysis - peripherals from renewable power (kWh) with shape = (8760,) - total_accessory_power_grid_kw (numpy.ndarray): Total electricity to electrolysis - peripherals from grid power (kWh) with shape = (8760,) - plant_design_scenario_number (int): plant design scenario number - incentive_option_number (int): incentive option number - - Returns: - lca_df (pandas.DataFrame): Pandas DataFrame containing average emissions intensities over - lifetime of plant and other relevant data - """ - # TODO: - # confirm site lat/long is proper for where electricity use will be - # (set from iron_pre or iron_win?) - - # Load relevant config and results data from HOPP and H2Integrate: - site_latitude = hopp_config["site"]["data"]["lat"] - site_longitude = hopp_config["site"]["data"]["lon"] - project_lifetime = h2integrate_config["project_parameters"][ - "project_lifetime" - ] # system lifetime (years) - plant_design_scenario = h2integrate_config["plant_design"][ - f"scenario{plant_design_scenario_number}" - ] # plant design scenario number - tax_incentive_option = h2integrate_config["policy_parameters"][ - f"option{incentive_option_number}" - ] # tax incentive option number - - # battery_annual_energy_kwh = hopp_results["annual_energies"][ - # "battery" - # ] # annual energy from battery (kWh) - # battery_system_capacity_kwh = hopp_results["hybrid_plant"].battery.system_capacity_kwh - # # battery rated capacity (kWh) - wind_turbine_rating_MW = ( - hopp_config["technologies"]["wind"]["turbine_rating_kw"] / 1000 - ) # wind turbine rating (MW) - wind_model = hopp_config["technologies"]["wind"]["model_name"] # wind model used in analysis - - # Determine renewable technologies in system and define renewables_case string for output file - renewable_technologies_modeled = [ - tech for tech in hopp_config["technologies"] if tech != "grid" - ] - if len(renewable_technologies_modeled) > 1: - renewables_case = "+".join(renewable_technologies_modeled) - elif len(renewable_technologies_modeled) == 1: - renewables_case = str(renewable_technologies_modeled[0]) - else: - renewables_case = "No-ren" - - # Determine grid case and define grid_case string for output file - # NOTE: original LCA project code calculations were created with functionality for a - # hybrid-grid case, however this functionality was removed during prior HOPP refactors - # NOTE: In future, update logic below to include 'hybrid-grid' case. Possibly look at - # input config yamls and technologies present for this logic?(pending modular framework): - # if only grid present -> grid-only? - # if any renewables + grid present -> hybrid-grid? - # if only renewables present -> off-grid? - if h2integrate_config["project_parameters"]["grid_connection"]: - if h2integrate_config["electrolyzer"]["sizing"]["hydrogen_dmd"] is not None: - grid_case = "grid-only" - else: - grid_case = "off-grid" - else: - grid_case = "off-grid" - - # Capture electrolyzer configuration variables / strings for output files - if h2integrate_config["electrolyzer"]["include_degradation_penalty"]: - electrolyzer_degradation = "True" - else: - electrolyzer_degradation = "False" - if plant_design_scenario["transportation"] == "colocated": - electrolyzer_centralization = "Centralized" - else: - electrolyzer_centralization = "Distributed" - electrolyzer_optimized = h2integrate_config["electrolyzer"]["pem_control_type"] - electrolyzer_type = h2integrate_config["lca_config"]["electrolyzer_type"] - number_of_electrolyzer_clusters = int( - ceildiv( - h2integrate_config["electrolyzer"]["rating"], - h2integrate_config["electrolyzer"]["cluster_rating_MW"], - ) - ) - - # Calculate average annual and lifetime h2 production - h2_lifetime_prod_kg = ( - h2_annual_prod_kg * project_lifetime - ) # Lifetime H2 production accounting for electrolyzer degradation (kg H2) - - # Calculate energy to electrolyzer and peripherals when hybrid-grid case - if grid_case == "hybrid-grid": - # Total electricity to electrolyzer and peripherals from grid power (kWh) - energy_shortfall_hopp.shape = ( - project_lifetime, - 8760, - ) # Reshaped to be annual power (project_lifetime, 8760) - annual_energy_to_electrolysis_from_grid = np.mean( - energy_shortfall_hopp, axis=0 - ) # Lifetime Average Annual electricity to electrolyzer and peripherals from grid power - # shape = (8760,) - - # Calculate energy to electrolyzer and peripherals when grid-only case - if grid_case == "grid-only": - energy_to_peripherals = ( - total_accessory_power_renewable_kw + total_accessory_power_grid_kw - ) # Total electricity to peripherals from grid power (kWh) - annual_energy_to_electrolysis_from_grid = ( - energy_to_electrolyzer_kwh + energy_to_peripherals - ) # Average Annual electricity to electrolyzer and peripherals from grid power - # shape = (8760,) - - # Create dataframe for electrolyzer + peripherals grid power profiles if grid connected - if grid_case in ("grid-only", "hybrid-grid"): - electrolyzer_grid_profile_data_dict = { - "Energy to electrolysis from grid (kWh)": annual_energy_to_electrolysis_from_grid - } - electrolyzer_grid_profile_df = pd.DataFrame(data=electrolyzer_grid_profile_data_dict) - electrolyzer_grid_profile_df = electrolyzer_grid_profile_df.reset_index().rename( - columns={"index": "Interval"} - ) - electrolyzer_grid_profile_df["Interval"] = electrolyzer_grid_profile_df["Interval"] + 1 - electrolyzer_grid_profile_df = electrolyzer_grid_profile_df.set_index("Interval") - - # Instantiate lists that define technologies / processes and LCA scopes - # used to dynamically define key value pairs in dictionaries to store data - processes = [ - "electrolysis", - "smr", - "smr_ccs", - "atr", - "atr_ccs", - "NH3_electrolysis", - "NH3_smr", - "NH3_smr_ccs", - "NH3_atr", - "NH3_atr_ccs", - "steel_electrolysis", - "steel_smr", - "steel_smr_ccs", - "steel_atr", - "steel_atr_ccs", - "ng_dri", - "ng_dri_eaf", - "h2_electrolysis_dri", - "h2_electrolysis_dri_eaf", - ] - - scopes = ["Scope3", "Scope2", "Scope1", "Total"] - - # Instantiate dictionary of numpy objects (np.nan -> converts to np.float when assigned value) - # to hold EI values per cambium year - EI_values = { - f"{process}_{scope}_EI": globals().get(f"{process}_{scope}_EI", np.nan) - for process in processes - for scope in scopes - } - - # Instantiate dictionary of lists to hold EI time series (ts) data for all cambium years - # EI_values for each cambium year are appended to corresponding lists - ts_EI_data = {f"{process}_{scope}_EI": [] for process in processes for scope in scopes} - - ## GREET Data - # Define conversions - g_to_kg = 0.001 # 1 g = 0.001 kg - MT_to_kg = 1000 # 1 metric ton = 1000 kg - kWh_to_MWh = 0.001 # 1 kWh = 0.001 MWh - MWh_to_kWh = 1000 # 1 MWh = 1000 kWh - gal_H2O_to_MT = 0.00378541 # 1 US gallon of H2O = 0.00378541 metric tons - - # Instantiate GreetData class object, parse greet if not already parsed - # return class object and load data dictionary - greet_data = GREETData(greet_year=2023) - greet_data_dict = greet_data.data - - # ------------------------------------------------------------------------------ - # Natural Gas - # ------------------------------------------------------------------------------ - NG_combust_EI = greet_data_dict[ - "NG_combust_EI" - ] # GHG Emissions Intensity of Natural Gas combustion in a utility / industrial large boiler - # (g CO2e/MJ Natural Gas combusted) - NG_supply_EI = greet_data_dict[ - "NG_supply_EI" - ] # GHG Emissions Intensity of supplying Natural Gas to processes as a feedstock / process fuel - # (g CO2e/MJ Natural Gas consumed) - - # ------------------------------------------------------------------------------ - # Water - # ------------------------------------------------------------------------------ - if h2integrate_config["lca_config"]["feedstock_water_type"] == "desal": - H2O_supply_EI = greet_data_dict[ - "desal_H2O_supply_EI" - ] # GHG Emissions Intensity of RO desalination and supply of that water to processes - # (kg CO2e/gal H2O). - elif h2integrate_config["lca_config"]["feedstock_water_type"] == "ground": - H2O_supply_EI = greet_data_dict[ - "ground_H2O_supply_EI" - ] # GHG Emissions Intensity of ground water and supply of that water to processes - # (kg CO2e/gal H2O). - elif h2integrate_config["lca_config"]["feedstock_water_type"] == "surface": - H2O_supply_EI = greet_data_dict[ - "surface_H2O_supply_EI" - ] # GHG Emissions Intensity of surface water and supply of that water to processes - # (kg CO2e/gal H2O). - # ------------------------------------------------------------------------------ - # Lime - # ------------------------------------------------------------------------------ - lime_supply_EI = greet_data_dict[ - "lime_supply_EI" - ] # GHG Emissions Intensity of supplying Lime to processes accounting for limestone mining, - # lime production, lime processing, and lime transportation assuming 20 miles via Diesel engines - # (kg CO2e/kg lime) - # ------------------------------------------------------------------------------ - # Carbon Coke - # ------------------------------------------------------------------------------ - coke_supply_EI = greet_data_dict[ - "coke_supply_EI" - ] # GHG Emissions Intensity of supplying Coke to processes accounting for combustion - # and non-combustion emissions of coke production - # (kg CO2e/kg Coke) - # ------------------------------------------------------------------------------ - # Renewable infrastructure embedded emission intensities - # ------------------------------------------------------------------------------ - # NOTE: HOPP/H2Integrate version at time of dev can only model PEM electrolysis - if electrolyzer_type == "pem": - # ely_stack_capex_EI = greet_data_dict[ - # "pem_ely_stack_capex_EI" - # ] # PEM electrolyzer CAPEX emissions (kg CO2e/kg H2) - ely_stack_and_BoP_capex_EI = greet_data_dict[ - "pem_ely_stack_and_BoP_capex_EI" - ] # PEM electrolyzer stack CAPEX + Balance of Plant emissions (kg CO2e/kg H2) - elif electrolyzer_type == "alkaline": - # ely_stack_capex_EI = greet_data_dict[ - # "alk_ely_stack_capex_EI" - # ] # Alkaline electrolyzer CAPEX emissions (kg CO2e/kg H2) - ely_stack_and_BoP_capex_EI = greet_data_dict[ - "alk_ely_stack_and_BoP_capex_EI" - ] # Alkaline electrolyzer stack CAPEX + Balance of Plant emissions (kg CO2e/kg H2) - elif electrolyzer_type == "soec": - # ely_stack_capex_EI = greet_data_dict[ - # "soec_ely_stack_capex_EI" - # ] # SOEC electrolyzer CAPEX emissions (kg CO2e/kg H2) - ely_stack_and_BoP_capex_EI = greet_data_dict[ - "soec_ely_stack_and_BoP_capex_EI" - ] # SOEC electrolyzer stack CAPEX + Balance of Plant emissions (kg CO2e/kg H2) - wind_capex_EI = greet_data_dict["wind_capex_EI"] # Wind CAPEX emissions (g CO2e/kWh) - solar_pv_capex_EI = greet_data_dict[ - "solar_pv_capex_EI" - ] # Solar PV CAPEX emissions (g CO2e/kWh) - battery_EI = greet_data_dict["battery_LFP_EI"] # LFP Battery embodied emissions (g CO2e/kWh) - nuclear_BWR_capex_EI = greet_data_dict[ - "nuclear_BWR_capex_EI" - ] # Nuclear Boiling Water Reactor (BWR) CAPEX emissions (g CO2e/kWh) - nuclear_PWR_capex_EI = greet_data_dict[ - "nuclear_PWR_capex_EI" - ] # Nuclear Pressurized Water Reactor (PWR) CAPEX emissions (g CO2e/kWh) - coal_capex_EI = greet_data_dict["coal_capex_EI"] # Coal CAPEX emissions (g CO2e/kWh) - gas_capex_EI = greet_data_dict[ - "gas_capex_EI" - ] # Natural Gas Combined Cycle (NGCC) CAPEX emissions (g CO2e/kWh) - hydro_capex_EI = greet_data_dict["hydro_capex_EI"] # Hydro CAPEX emissions (g CO2e/kWh) - bio_capex_EI = greet_data_dict["bio_capex_EI"] # Biomass CAPEX emissions (g CO2e/kWh) - # geothermal_egs_capex_EI = greet_data_dict[ - # "geothermal_egs_capex_EI" - # ] # Geothermal EGS CAPEX emissions (g CO2e/kWh) - geothermal_binary_capex_EI = greet_data_dict[ - "geothermal_binary_capex_EI" - ] # Geothermal Binary CAPEX emissions (g CO2e/kWh) - geothermal_flash_capex_EI = greet_data_dict[ - "geothermal_flash_capex_EI" - ] # Geothermal Flash CAPEX emissions (g CO2e/kWh) - - # ------------------------------------------------------------------------------ - # Steam methane reforming (SMR) and Autothermal Reforming (ATR) - # Incumbent H2 production processes - # ------------------------------------------------------------------------------ - smr_HEX_eff = greet_data_dict["smr_HEX_eff"] # SMR Heat exchange efficiency (%) - # SMR without CCS - smr_steam_prod = greet_data_dict[ - "smr_steam_prod" - ] # Steam exported for SMR w/out CCS (MJ/kg H2) - smr_NG_consume = greet_data_dict[ - "smr_NG_consume" - ] # Natural gas consumption for SMR w/out CCS accounting for efficiency, NG as feed and - # process fuel for SMR and steam production (MJ-LHV/kg H2) - smr_electricity_consume = greet_data_dict[ - "smr_electricity_consume" - ] # Electricity consumption for SMR w/out CCS accounting for efficiency, electricity - # as a process fuel (kWh/kg H2) - # SMR with CCS - smr_ccs_steam_prod = greet_data_dict[ - "smr_ccs_steam_prod" - ] # Steam exported for SMR with CCS (MJ/kg H2) - smr_ccs_perc_capture = greet_data_dict["smr_ccs_perc_capture"] # CCS rate for SMR (%) - smr_ccs_NG_consume = greet_data_dict[ - "smr_ccs_NG_consume" - ] # Natural gas consumption for SMR with CCS accounting for efficiency, NG as feed and process - # fuel for SMR and steam production (MJ-LHV/kg H2) - smr_ccs_electricity_consume = greet_data_dict[ - "smr_ccs_electricity_consume" - ] # SMR via NG w/ CCS WTG Total Energy consumption (kWh/kg H2) - # ATR without CCS - atr_NG_consume = greet_data_dict[ - "atr_NG_consume" - ] # Natural gas consumption for ATR w/out CCS accounting for efficiency, NG as feed and - # process fuel for SMR and steam production (MJ-LHV/kg H2) - atr_electricity_consume = greet_data_dict[ - "atr_electricity_consume" - ] # Electricity consumption for ATR w/out CCS accounting for efficiency, electricity as a - # process fuel (kWh/kg H2) - # ATR with CCS - atr_ccs_perc_capture = greet_data_dict["atr_ccs_perc_capture"] # CCS rate for ATR (%) - atr_ccs_NG_consume = greet_data_dict[ - "atr_ccs_NG_consume" - ] # Natural gas consumption for ATR with CCS accounting for efficiency, NG as feed and - # process fuel for SMR and steam production (MJ-LHV/kg H2) - atr_ccs_electricity_consume = greet_data_dict[ - "atr_ccs_electricity_consume" - ] # Electricity consumption for ATR with CCS accounting for efficiency, electricity as a - # process fuel (kWh/kg H2) - - # ------------------------------------------------------------------------------ - # Hydrogen production via water electrolysis - # ------------------------------------------------------------------------------ - if electrolyzer_type == "pem": - ely_H2O_consume = greet_data_dict[ - "pem_ely_H2O_consume" - ] # H2O consumption for H2 production in PEM electrolyzer (gal H20/kg H2) - elif electrolyzer_type == "alkaline": - ely_H2O_consume = greet_data_dict[ - "alk_ely_H2O_consume" - ] # H2O consumption for H2 production in Alkaline electrolyzer (gal H20/kg H2) - elif electrolyzer_type == "soec": - ely_H2O_consume = greet_data_dict[ - "soec_ely_H2O_consume" - ] # H2O consumption for H2 production in High Temp SOEC electrolyzer (gal H20/kg H2) - # ------------------------------------------------------------------------------ - # Ammonia (NH3) - # ------------------------------------------------------------------------------ - NH3_NG_consume = greet_data_dict[ - "NH3_NG_consume" - ] # Natural gas consumption for combustion in the Haber-Bosch process / Boiler for Ammonia - # production (MJ/metric ton NH3) - NH3_H2_consume = greet_data_dict[ - "NH3_H2_consume" - ] # Gaseous Hydrogen consumption for Ammonia production, based on chemical balance and is - # applicable for all NH3 production pathways (kg H2/kg NH3) - NH3_electricity_consume = greet_data_dict[ - "NH3_electricity_consume" - ] # Total Electrical Energy consumption for Ammonia production (kWh/kg NH3) - - # ------------------------------------------------------------------------------ - # Steel - # ------------------------------------------------------------------------------ - # Values agnostic of DRI-EAF config - # NOTE: in future if accounting for different iron ore mining, pelletizing processes, - # and production processes, then add if statement to check h2integrate_config for - # iron production type (DRI, electrowinning, etc) - # iron_ore_mining_EI_per_MT_steel = greet_data_dict[ - # "DRI_iron_ore_mining_EI_per_MT_steel" - # ] # GHG Emissions Intensity of Iron ore mining for use in DRI-EAF Steel production - # # (kg CO2e/metric ton steel produced) - iron_ore_mining_EI_per_MT_ore = greet_data_dict[ - "DRI_iron_ore_mining_EI_per_MT_ore" - ] # GHG Emissions Intensity of Iron ore mining for use in DRI-EAF Steel production - # (kg CO2e/metric ton iron ore) - # iron_ore_pelletizing_EI_per_MT_steel = greet_data_dict[ - # "DRI_iron_ore_pelletizing_EI_per_MT_steel" - # ] # GHG Emissions Intensity of Iron ore pelletizing for use in DRI-EAF Steel production - # # (kg CO2e/metric ton steel produced) - iron_ore_pelletizing_EI_per_MT_ore = greet_data_dict[ - "DRI_iron_ore_pelletizing_EI_per_MT_ore" - ] # GHG Emissions Intensity of Iron ore pelletizing for use in DRI-EAF Steel production - # (kg CO2e/metric ton iron ore) - - # NOTE: in future if accounting for different steel productin processes (DRI-EAF vs XYZ), - # then add if statement to check h2integrate_config for steel production process and - # update HOPP > greet_data.py with specific variables for each process - steel_H2O_consume = greet_data_dict[ - "steel_H2O_consume" - ] # Total H2O consumption for DRI-EAF Steel production w/ 83% H2 and 0% scrap, accounts for - # water used in iron ore mining, pelletizing, DRI, and EAF - # (metric ton H2O/metric ton steel production) - steel_H2_consume = greet_data_dict[ - "steel_H2_consume" - ] # Hydrogen consumption for DRI-EAF Steel production w/ 83% H2 regardless of scrap - # (metric tons H2/metric ton steel production) - steel_NG_consume = greet_data_dict[ - "steel_NG_consume" - ] # Natural gas consumption for DRI-EAF Steel production accounting for DRI with 83% H2, - # and EAF + LRF (GJ/metric ton steel) - steel_electricity_consume = greet_data_dict[ - "steel_electricity_consume" - ] # Total Electrical Energy consumption for DRI-EAF Steel production accounting for - # DRI with 83% H2 and EAF + LRF (MWh/metric ton steel production) - steel_iron_ore_consume = greet_data_dict[ - "steel_iron_ore_consume" - ] # Iron ore consumption for DRI-EAF Steel production - # (metric ton iron ore/metric ton steel production) - steel_lime_consume = greet_data_dict[ - "steel_lime_consume" - ] # Lime consumption for DRI-EAF Steel production - # (metric ton lime/metric ton steel production) - - ## Load in Iron model outputs - # Read iron_performance.performances_df from pkl - iron_performance_fn = "{}/iron_performance/{:.3f}_{:.3f}_{:d}.pkl".format( - h2integrate_config["iron_out_fn"], - site_latitude, - site_longitude, - hopp_config["site"]["data"]["year"], - ) - iron_performance = load_dill_pickle(iron_performance_fn) - iron_performance = iron_performance.performances_df - # Instantiate objects to hold iron performance values - ng_dri_steel_prod = np.nan - ng_dri_pigiron_prod = np.nan - ng_dri_iron_ore_consume = np.nan - ng_dri_NG_consume = np.nan - ng_dri_electricity_consume = np.nan - ng_dri_H2O_consume = np.nan - ng_dri_eaf_steel_prod = np.nan - ng_dri_eaf_pigiron_prod = np.nan - ng_dri_eaf_iron_ore_consume = np.nan - ng_dri_eaf_lime_consume = np.nan - ng_dri_eaf_coke_consume = np.nan - ng_dri_eaf_NG_consume = np.nan - ng_dri_eaf_electricity_consume = np.nan - ng_dri_eaf_H2O_consume = np.nan - h2_dri_steel_prod = np.nan - h2_dri_pigiron_prod = np.nan - h2_dri_H2_consume = np.nan - h2_dri_iron_ore_consume = np.nan - h2_dri_NG_consume = np.nan - h2_dri_electricity_consume = np.nan - h2_dri_H2O_consume = np.nan - h2_dri_eaf_steel_prod = np.nan - h2_dri_eaf_pigiron_prod = np.nan - h2_dri_eaf_H2_consume = np.nan - h2_dri_eaf_iron_ore_consume = np.nan - h2_dri_eaf_lime_consume = np.nan - h2_dri_eaf_coke_consume = np.nan - h2_dri_eaf_NG_consume = np.nan - h2_dri_eaf_electricity_consume = np.nan - h2_dri_eaf_H2O_consume = np.nan - # Pull iron_performance values - if iron_performance["Product"].values[0] == "ng_dri": - # Note to Dakota from Jonathan - the denominator has been corrected, - # we're now getting performance per unit pig iron, not per unit steel - # Leave this code in though, I want to be able to build an option to - # calculate per unit steel instead of per unit iron - ng_dri_steel_prod = iron_performance.loc[ - iron_performance["Name"] == "Steel Production", "Model" - ].item() - # metric tonnes steel per year - ng_dri_pigiron_prod = iron_performance.loc[ - iron_performance["Name"] == "Pig Iron Production", "Model" - ].item() - # metric tonnes pig iron per year - capacity_denominator = h2integrate_config["iron_win"]["performance"]["capacity_denominator"] - if capacity_denominator == "iron": - steel_to_pigiron_ratio = 1 - elif capacity_denominator == "steel": - steel_to_pigiron_ratio = ng_dri_steel_prod / ng_dri_pigiron_prod - # conversion from MT steel to MT pig iron in denominator of units - ng_dri_iron_ore_consume = ( - iron_performance.loc[iron_performance["Name"] == "Iron Ore", "Model"].item() - * steel_to_pigiron_ratio - ) - # metric tonnes ore / pellet consumed per metric tonne pig iron produced - ng_dri_NG_consume = ( - iron_performance.loc[iron_performance["Name"] == "Natural Gas", "Model"].item() - * steel_to_pigiron_ratio - ) - # GJ-LHV NG consumed per metric tonne pig iron produced - ng_dri_electricity_consume = ( - iron_performance.loc[iron_performance["Name"] == "Electricity", "Model"].item() - * steel_to_pigiron_ratio - ) - # MWh electricity consumed per metric tonne pig iron produced - ng_dri_H2O_consume = ( - iron_performance.loc[iron_performance["Name"] == "Raw Water Withdrawal", "Model"].item() - * steel_to_pigiron_ratio - ) - # metric tonne H2O consumed per metric tonne pig iron produced - if iron_performance["Product"].values[0] == "ng_dri_eaf": - ng_dri_eaf_steel_prod = iron_performance.loc[ - iron_performance["Name"] == "Steel Production", "Model" - ].item() - # metric tonnes steel per year - ng_dri_eaf_pigiron_prod = iron_performance.loc[ - iron_performance["Name"] == "Pig Iron Production", "Model" - ].item() - # metric tonnes pig iron per year - capacity_denominator = h2integrate_config["iron_win"]["performance"]["capacity_denominator"] - if capacity_denominator == "iron": - steel_to_pigiron_ratio = 1 - elif capacity_denominator == "steel": - steel_to_pigiron_ratio = ng_dri_eaf_steel_prod / ng_dri_eaf_pigiron_prod - # conversion from MT steel to MT pig iron in denominator of units - ng_dri_eaf_iron_ore_consume = ( - iron_performance.loc[iron_performance["Name"] == "Iron Ore", "Model"].item() - * steel_to_pigiron_ratio - ) - # metric tonnes ore / pellet consumed per metric tonne pig iron produced - ng_dri_eaf_NG_consume = ( - iron_performance.loc[iron_performance["Name"] == "Natural Gas", "Model"].item() - * steel_to_pigiron_ratio - ) - # GJ-LHV NG consumed per metric tonne pig iron produced - ng_dri_eaf_electricity_consume = ( - iron_performance.loc[iron_performance["Name"] == "Electricity", "Model"].item() - * steel_to_pigiron_ratio - ) - # MWh electricity consumed per metric tonne pig iron produced - ( - iron_performance.loc[iron_performance["Name"] == "Carbon (Coke)", "Model"].item() - * steel_to_pigiron_ratio - ) - # metric tonnes carbon coke consumed per metric tonne pig iron produced - ng_dri_eaf_lime_consume = ( - iron_performance.loc[iron_performance["Name"] == "Lime", "Model"].item() - * steel_to_pigiron_ratio - ) - # metric tonnes carbon lime consumed per metric tonne pig iron produced - ng_dri_eaf_H2O_consume = ( - iron_performance.loc[iron_performance["Name"] == "Raw Water Withdrawal", "Model"].item() - * steel_to_pigiron_ratio - ) - # metric tonne H2O consumed per metric tonne pig iron produced - if iron_performance["Product"].values[0] == "h2_dri": - h2_dri_steel_prod = iron_performance.loc[ - iron_performance["Name"] == "Steel Production", "Model" - ].item() - # metric tonnes steel per year - h2_dri_pigiron_prod = iron_performance.loc[ - iron_performance["Name"] == "Pig Iron Production", "Model" - ].item() - # metric tonnes pig iron per year - capacity_denominator = h2integrate_config["iron_win"]["performance"]["capacity_denominator"] - if capacity_denominator == "iron": - steel_to_pigiron_ratio = 1 - elif capacity_denominator == "steel": - steel_to_pigiron_ratio = h2_dri_steel_prod / h2_dri_pigiron_prod - # conversion from MT steel to MT pig iron in denominator of units - h2_dri_iron_ore_consume = ( - iron_performance.loc[iron_performance["Name"] == "Iron Ore", "Model"].item() - * steel_to_pigiron_ratio - ) - # metric tonnes ore / pellet consumed per metric tonne pig iron produced - h2_dri_H2_consume = ( - iron_performance.loc[iron_performance["Name"] == "Hydrogen", "Model"].item() - * steel_to_pigiron_ratio - ) - # metric tonne H2 consumed per metric tonne pig iron produced - h2_dri_NG_consume = ( - iron_performance.loc[iron_performance["Name"] == "Natural Gas", "Model"].item() - * steel_to_pigiron_ratio - ) - # GJ-LHV NG consumed per metric tonne pig iron produced - h2_dri_electricity_consume = ( - iron_performance.loc[iron_performance["Name"] == "Electricity", "Model"].item() - * steel_to_pigiron_ratio - ) - # MWh electricity consumed per metric tonne pig iron produced - h2_dri_H2O_consume = ( - iron_performance.loc[iron_performance["Name"] == "Raw Water Withdrawal", "Model"].item() - * steel_to_pigiron_ratio - ) - # metric tonne H2O consume per metric tonne pig iron produced - if iron_performance["Product"].values[0] == "h2_dri_eaf": - h2_dri_eaf_steel_prod = iron_performance.loc[ - iron_performance["Name"] == "Steel Production", "Model" - ].item() - # metric tonnes steel per year - h2_dri_eaf_pigiron_prod = iron_performance.loc[ - iron_performance["Name"] == "Pig Iron Production", "Model" - ].item() - # metric tonnes pig iron per year - capacity_denominator = h2integrate_config["iron_win"]["performance"]["capacity_denominator"] - if capacity_denominator == "iron": - steel_to_pigiron_ratio = 1 - elif capacity_denominator == "steel": - steel_to_pigiron_ratio = h2_dri_eaf_steel_prod / h2_dri_eaf_pigiron_prod - # conversion from MT steel to MT pig iron in denominator of units - h2_dri_eaf_iron_ore_consume = ( - iron_performance.loc[iron_performance["Name"] == "Iron Ore", "Model"].item() - * steel_to_pigiron_ratio - ) - # metric tonnes ore / pellet consumed per metric tonne pig iron produced - h2_dri_eaf_H2_consume = ( - iron_performance.loc[iron_performance["Name"] == "Hydrogen", "Model"].item() - * steel_to_pigiron_ratio - ) - # metric tonne H2 consumed per metric tonne pig iron produced - h2_dri_eaf_NG_consume = ( - iron_performance.loc[iron_performance["Name"] == "Natural Gas", "Model"].item() - * steel_to_pigiron_ratio - ) - # GJ-LHV NG consumed per metric tonne pig iron produced - h2_dri_eaf_electricity_consume = ( - iron_performance.loc[iron_performance["Name"] == "Electricity", "Model"].item() - * steel_to_pigiron_ratio - ) - # MWh electricity consumed per metric tonne pig iron produced - ( - iron_performance.loc[iron_performance["Name"] == "Carbon (Coke)", "Model"].item() - * steel_to_pigiron_ratio - ) - # metric tonnes carbon coke consumed per metric tonne pig iron produced - h2_dri_eaf_lime_consume = ( - iron_performance.loc[iron_performance["Name"] == "Lime", "Model"].item() - * steel_to_pigiron_ratio - ) - # metric tonnes carbon lime consumed per metric tonne pig iron produced - h2_dri_eaf_H2O_consume = ( - iron_performance.loc[iron_performance["Name"] == "Raw Water Withdrawal", "Model"].item() - * steel_to_pigiron_ratio - ) - # metric tonne H2O consume per metric tonne pig iron produced - - ## Cambium - # Define cambium_year - # NOTE: at time of dev hopp logic for LCOH = atb_year + 2yr + install_period(3yrs) = 5 years - cambium_year = h2integrate_config["project_parameters"]["financial_analysis_start_year"] + 3 - # Pull / download cambium data files - cambium_data = CambiumData( - lat=site_latitude, - lon=site_longitude, - year=cambium_year, - project_uuid=h2integrate_config["lca_config"]["cambium"]["project_uuid"], - scenario=h2integrate_config["lca_config"]["cambium"]["scenario"], - location_type=h2integrate_config["lca_config"]["cambium"]["location_type"], - time_type=h2integrate_config["lca_config"]["cambium"]["time_type"], - ) - - # Read in Cambium data file for each year available - # NOTE: Additional LRMER values for CO2, CH4, and NO2 are available through the cambium call - # that are not used in this analysis - for resource_file in cambium_data.resource_files: - # Read in csv file to a dataframe, update column names and indexes - cambium_data_df = pd.read_csv( - resource_file, - index_col=None, - header=0, - usecols=[ - "lrmer_co2e_c", - "lrmer_co2e_p", - "lrmer_co2e", - "generation", - "battery_MWh", - "biomass_MWh", - "beccs_MWh", - "canada_MWh", - "coal_MWh", - "coal-ccs_MWh", - "csp_MWh", - "distpv_MWh", - "gas-cc_MWh", - "gas-cc-ccs_MWh", - "gas-ct_MWh", - "geothermal_MWh", - "hydro_MWh", - "nuclear_MWh", - "o-g-s_MWh", - "phs_MWh", - "upv_MWh", - "wind-ons_MWh", - "wind-ofs_MWh", - ], - ) - cambium_data_df = cambium_data_df.reset_index().rename( - columns={ - "index": "Interval", - "lrmer_co2e_c": "LRMER CO2 equiv. combustion (kg-CO2e/MWh)", - "lrmer_co2e_p": "LRMER CO2 equiv. precombustion (kg-CO2e/MWh)", - "lrmer_co2e": "LRMER CO2 equiv. total (kg-CO2e/MWh)", - } - ) - cambium_data_df["Interval"] = cambium_data_df["Interval"] + 1 - cambium_data_df = cambium_data_df.set_index("Interval") - - if grid_case in ("grid-only", "hybrid-grid"): - # Calculate consumption and emissions factor for electrolysis powered by the grid - combined_data_df = pd.concat([electrolyzer_grid_profile_df, cambium_data_df], axis=1) - electrolysis_grid_electricity_consume = combined_data_df[ - "Energy to electrolysis from grid (kWh)" - ].sum() # Total energy to the electrolyzer from the grid (kWh) - electrolysis_scope3_grid_emissions = ( - (combined_data_df["Energy to electrolysis from grid (kWh)"] / 1000) - * combined_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"] - ).sum() # Scope 3 Electrolysis Emissions from grid electricity consumption (kg CO2e) - electrolysis_scope2_grid_emissions = ( - (combined_data_df["Energy to electrolysis from grid (kWh)"] / 1000) - * combined_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"] - ).sum() # Scope 2 Electrolysis Emissions from grid electricity consumption (kg CO2e) - - # Calculate annual percentages of nuclear, geothermal, hydropower, wind, solar, battery, - # and fossil fuel power in cambium grid mix (%) - generation_annual_total_MWh = cambium_data_df["generation"].sum() - generation_annual_nuclear_fraction = ( - cambium_data_df["nuclear_MWh"].sum() / generation_annual_total_MWh - ) - generation_annual_coal_oil_fraction = ( - cambium_data_df["coal_MWh"].sum() - + cambium_data_df["coal-ccs_MWh"].sum() - + cambium_data_df["o-g-s_MWh"].sum() - ) / generation_annual_total_MWh - generation_annual_gas_fraction = ( - cambium_data_df["gas-cc_MWh"].sum() - + cambium_data_df["gas-cc-ccs_MWh"].sum() - + cambium_data_df["gas-ct_MWh"].sum() - ) / generation_annual_total_MWh - generation_annual_bio_fraction = ( - cambium_data_df["biomass_MWh"].sum() + cambium_data_df["beccs_MWh"].sum() - ) / generation_annual_total_MWh - generation_annual_geothermal_fraction = ( - cambium_data_df["geothermal_MWh"].sum() / generation_annual_total_MWh - ) - generation_annual_hydro_fraction = ( - cambium_data_df["hydro_MWh"].sum() + cambium_data_df["phs_MWh"].sum() - ) / generation_annual_total_MWh - generation_annual_wind_fraction = ( - cambium_data_df["wind-ons_MWh"].sum() + cambium_data_df["wind-ofs_MWh"].sum() - ) / generation_annual_total_MWh - generation_annual_solar_fraction = ( - cambium_data_df["upv_MWh"].sum() - + cambium_data_df["distpv_MWh"].sum() - + cambium_data_df["csp_MWh"].sum() - ) / generation_annual_total_MWh - generation_annual_battery_fraction = ( - cambium_data_df["battery_MWh"].sum() - ) / generation_annual_total_MWh - nuclear_PWR_fraction = 0.655 # % of grid nuclear power from PWR, calculated from USNRC data - # based on type and rated capacity - nuclear_BWR_fraction = 0.345 # % of grid nuclear power from BWR, calculated from USNRC data - # based on type and rated capacity - # https://www.nrc.gov/reactors/operating/list-power-reactor-units.html - geothermal_binary_fraction = 0.28 # % of grid geothermal power from binary, - # average from EIA data and NREL Geothermal prospector - geothermal_flash_fraction = 0.72 # % of grid geothermal power from flash, - # average from EIA data and NREL Geothermal prospector - # https://www.eia.gov/todayinenergy/detail.php?id=44576# - - # Calculate Grid Imbedded Emissions Intensity for cambium grid mix of power sources - # (kg CO2e/kwh) - grid_capex_EI = ( - (generation_annual_nuclear_fraction * nuclear_PWR_fraction * nuclear_PWR_capex_EI) - + (generation_annual_nuclear_fraction * nuclear_BWR_fraction * nuclear_BWR_capex_EI) - + (generation_annual_coal_oil_fraction * coal_capex_EI) - + (generation_annual_gas_fraction * gas_capex_EI) - + (generation_annual_bio_fraction * bio_capex_EI) - + ( - generation_annual_geothermal_fraction - * geothermal_binary_fraction - * geothermal_binary_capex_EI - ) - + ( - generation_annual_geothermal_fraction - * geothermal_flash_fraction - * geothermal_flash_capex_EI - ) - + (generation_annual_hydro_fraction * hydro_capex_EI) - + (generation_annual_wind_fraction * wind_capex_EI) - + (generation_annual_solar_fraction * solar_pv_capex_EI) - + (generation_annual_battery_fraction * battery_EI) * g_to_kg - ) - - # NOTE: current config assumes SMR, ATR, NH3, and Steel processes are always grid powered - # electricity needed for these processes does not come from renewables - # NOTE: this is reflective of the current state of modeling these systems in the code - # at time of dev and should be updated to allow renewables in the future - if "hybrid-grid" in grid_case: - ## H2 production via electrolysis - # Calculate grid-connected electrolysis emissions (kg CO2e/kg H2) - # future cases should reflect targeted electrolyzer electricity usage - EI_values["electrolysis_Scope3_EI"] = ( - ely_stack_and_BoP_capex_EI - + (ely_H2O_consume * H2O_supply_EI) - + ( - ( - electrolysis_scope3_grid_emissions - + (wind_capex_EI * g_to_kg * wind_annual_energy_kwh) - + (solar_pv_capex_EI * g_to_kg * solar_pv_annual_energy_kwh) - + (grid_capex_EI * electrolysis_grid_electricity_consume) - ) - / h2_annual_prod_kg - ) - ) - EI_values["electrolysis_Scope2_EI"] = ( - electrolysis_scope2_grid_emissions / h2_annual_prod_kg - ) - EI_values["electrolysis_Scope1_EI"] = 0 - EI_values["electrolysis_Total_EI"] = ( - EI_values["electrolysis_Scope1_EI"] - + EI_values["electrolysis_Scope2_EI"] - + EI_values["electrolysis_Scope3_EI"] - ) - - # Calculate ammonia emissions via hybrid grid electrolysis (kg CO2e/kg NH3) - EI_values["NH3_electrolysis_Scope3_EI"] = ( - (NH3_H2_consume * EI_values["electrolysis_Total_EI"]) - + (NH3_NG_consume * NG_supply_EI * g_to_kg / MT_to_kg) - + ( - NH3_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (NH3_electricity_consume * grid_capex_EI) - ) - EI_values["NH3_electrolysis_Scope2_EI"] = ( - NH3_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["NH3_electrolysis_Scope1_EI"] = ( - NH3_NG_consume * NG_combust_EI * g_to_kg / MT_to_kg - ) - EI_values["NH3_electrolysis_Total_EI"] = ( - EI_values["NH3_electrolysis_Scope1_EI"] - + EI_values["NH3_electrolysis_Scope2_EI"] - + EI_values["NH3_electrolysis_Scope3_EI"] - ) - - # Calculate steel emissions via hybrid grid electrolysis (kg CO2e/metric ton steel) - EI_values["steel_electrolysis_Scope3_EI"] = ( - (steel_H2_consume * MT_to_kg * EI_values["electrolysis_Total_EI"]) - + (steel_lime_consume * lime_supply_EI * MT_to_kg) - + (steel_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (steel_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (steel_NG_consume * NG_supply_EI) - + (steel_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - steel_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (steel_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["steel_electrolysis_Scope2_EI"] = ( - steel_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["steel_electrolysis_Scope1_EI"] = steel_NG_consume * NG_combust_EI - EI_values["steel_electrolysis_Total_EI"] = ( - EI_values["steel_electrolysis_Scope1_EI"] - + EI_values["steel_electrolysis_Scope2_EI"] - + EI_values["steel_electrolysis_Scope3_EI"] - ) - - # Calculate H2 DRI emissions via hybrid grid electrolysis - # (kg CO2e/metric tonne pig iron) - EI_values["h2_electrolysis_dri_Scope3_EI"] = ( - (h2_dri_H2_consume * MT_to_kg * EI_values["electrolysis_Total_EI"]) - + (h2_dri_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (h2_dri_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (h2_dri_NG_consume * NG_supply_EI) - + (h2_dri_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - h2_dri_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (h2_dri_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["h2_electrolysis_dri_Scope2_EI"] = ( - h2_dri_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["h2_electrolysis_dri_Scope1_EI"] = h2_dri_NG_consume * NG_combust_EI - EI_values["h2_electrolysis_dri_Total_EI"] = ( - EI_values["h2_electrolysis_dri_Scope1_EI"] - + EI_values["h2_electrolysis_dri_Scope2_EI"] - + EI_values["h2_electrolysis_dri_Scope3_EI"] - ) - - # Calculate H2 DRI EAF emissions via hybrid grid electrolysis - # (kg CO2e/metric tonne pig iron) - EI_values["h2_electrolysis_dri_eaf_Scope3_EI"] = ( - (h2_dri_eaf_H2_consume * MT_to_kg * EI_values["electrolysis_Total_EI"]) - + (h2_dri_eaf_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (h2_dri_eaf_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (h2_dri_eaf_lime_consume * MT_to_kg * lime_supply_EI) - + (h2_dri_eaf_coke_consume * MT_to_kg * coke_supply_EI) - + (h2_dri_eaf_NG_consume * NG_supply_EI) - + (h2_dri_eaf_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - h2_dri_eaf_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (h2_dri_eaf_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["h2_electrolysis_dri_eaf_Scope2_EI"] = ( - h2_dri_eaf_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["h2_electrolysis_dri_eaf_Scope1_EI"] = h2_dri_eaf_NG_consume * NG_combust_EI - EI_values["h2_electrolysis_dri_eaf_Total_EI"] = ( - EI_values["h2_electrolysis_dri_eaf_Scope1_EI"] - + EI_values["h2_electrolysis_dri_eaf_Scope2_EI"] - + EI_values["h2_electrolysis_dri_eaf_Scope3_EI"] - ) - - # Calculate Natural Gas (NG) DRI emissions - # (kg CO2e/metric tonne pig iron) - EI_values["ng_dri_Scope3_EI"] = ( - (ng_dri_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (ng_dri_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (ng_dri_NG_consume * NG_supply_EI) - + (ng_dri_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - ng_dri_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (ng_dri_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["ng_dri_Scope2_EI"] = ( - ng_dri_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["ng_dri_Scope1_EI"] = ng_dri_NG_consume * NG_combust_EI - EI_values["ng_dri_Total_EI"] = ( - EI_values["ng_dri_Scope1_EI"] - + EI_values["ng_dri_Scope2_EI"] - + EI_values["ng_dri_Scope3_EI"] - ) - - # Calculate Natural Gas (NG) DRI EAF emissions - # (kg CO2e/metric tonne pig iron) - EI_values["ng_dri_eaf_Scope3_EI"] = ( - (ng_dri_eaf_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (ng_dri_eaf_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (ng_dri_eaf_lime_consume * MT_to_kg * lime_supply_EI) - + (ng_dri_eaf_coke_consume * MT_to_kg * coke_supply_EI) - + (ng_dri_eaf_NG_consume * NG_supply_EI) - + (ng_dri_eaf_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - ng_dri_eaf_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (ng_dri_eaf_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["ng_dri_eaf_Scope2_EI"] = ( - ng_dri_eaf_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["ng_dri_eaf_Scope1_EI"] = ng_dri_eaf_NG_consume * NG_combust_EI - EI_values["ng_dri_eaf_Total_EI"] = ( - EI_values["ng_dri_eaf_Scope1_EI"] - + EI_values["ng_dri_eaf_Scope2_EI"] - + EI_values["ng_dri_eaf_Scope3_EI"] - ) - - if "grid-only" in grid_case: - ## H2 production via electrolysis - # Calculate grid-connected electrolysis emissions (kg CO2e/kg H2) - EI_values["electrolysis_Scope3_EI"] = ( - ely_stack_and_BoP_capex_EI - + (ely_H2O_consume * H2O_supply_EI) - + ( - ( - electrolysis_scope3_grid_emissions - + (grid_capex_EI * electrolysis_grid_electricity_consume) - ) - / h2_annual_prod_kg - ) - ) - EI_values["electrolysis_Scope2_EI"] = ( - electrolysis_scope2_grid_emissions / h2_annual_prod_kg - ) - EI_values["electrolysis_Scope1_EI"] = 0 - EI_values["electrolysis_Total_EI"] = ( - EI_values["electrolysis_Scope1_EI"] - + EI_values["electrolysis_Scope2_EI"] - + EI_values["electrolysis_Scope3_EI"] - ) - - # Calculate ammonia emissions via grid only electrolysis (kg CO2e/kg NH3) - EI_values["NH3_electrolysis_Scope3_EI"] = ( - (NH3_H2_consume * EI_values["electrolysis_Total_EI"]) - + (NH3_NG_consume * NG_supply_EI * g_to_kg / MT_to_kg) - + ( - NH3_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (NH3_electricity_consume * grid_capex_EI) - ) - EI_values["NH3_electrolysis_Scope2_EI"] = ( - NH3_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["NH3_electrolysis_Scope1_EI"] = ( - NH3_NG_consume * NG_combust_EI * g_to_kg / MT_to_kg - ) - EI_values["NH3_electrolysis_Total_EI"] = ( - EI_values["NH3_electrolysis_Scope1_EI"] - + EI_values["NH3_electrolysis_Scope2_EI"] - + EI_values["NH3_electrolysis_Scope3_EI"] - ) - - # Calculate steel emissions via grid only electrolysis (kg CO2e/metric ton steel) - EI_values["steel_electrolysis_Scope3_EI"] = ( - (steel_H2_consume * MT_to_kg * EI_values["electrolysis_Total_EI"]) - + (steel_lime_consume * lime_supply_EI * MT_to_kg) - + (steel_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (steel_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (steel_NG_consume * NG_supply_EI) - + (steel_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - steel_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (steel_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["steel_electrolysis_Scope2_EI"] = ( - steel_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["steel_electrolysis_Scope1_EI"] = steel_NG_consume * NG_combust_EI - EI_values["steel_electrolysis_Total_EI"] = ( - EI_values["steel_electrolysis_Scope1_EI"] - + EI_values["steel_electrolysis_Scope2_EI"] - + EI_values["steel_electrolysis_Scope3_EI"] - ) - - # Calculate H2 DRI emissions via grid only electrolysis - # (kg CO2e/metric tonne pig iron) - EI_values["h2_electrolysis_dri_Scope3_EI"] = ( - (h2_dri_H2_consume * MT_to_kg * EI_values["electrolysis_Total_EI"]) - + (h2_dri_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (h2_dri_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (h2_dri_NG_consume * NG_supply_EI) - + (h2_dri_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - h2_dri_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (h2_dri_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["h2_electrolysis_dri_Scope2_EI"] = ( - h2_dri_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["h2_electrolysis_dri_Scope1_EI"] = h2_dri_NG_consume * NG_combust_EI - EI_values["h2_electrolysis_dri_Total_EI"] = ( - EI_values["h2_electrolysis_dri_Scope1_EI"] - + EI_values["h2_electrolysis_dri_Scope2_EI"] - + EI_values["h2_electrolysis_dri_Scope3_EI"] - ) - - # Calculate H2 DRI EAF emissions via grid only electrolysis - # (kg CO2e/metric tonne pig iron) - EI_values["h2_electrolysis_dri_eaf_Scope3_EI"] = ( - (h2_dri_eaf_H2_consume * MT_to_kg * EI_values["electrolysis_Total_EI"]) - + (h2_dri_eaf_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (h2_dri_eaf_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (h2_dri_eaf_lime_consume * MT_to_kg * lime_supply_EI) - + (h2_dri_eaf_coke_consume * MT_to_kg * coke_supply_EI) - + (h2_dri_eaf_NG_consume * NG_supply_EI) - + (h2_dri_eaf_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - h2_dri_eaf_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (h2_dri_eaf_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["h2_electrolysis_dri_eaf_Scope2_EI"] = ( - h2_dri_eaf_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["h2_electrolysis_dri_eaf_Scope1_EI"] = h2_dri_eaf_NG_consume * NG_combust_EI - EI_values["h2_electrolysis_dri_eaf_Total_EI"] = ( - EI_values["h2_electrolysis_dri_eaf_Scope1_EI"] - + EI_values["h2_electrolysis_dri_eaf_Scope2_EI"] - + EI_values["h2_electrolysis_dri_eaf_Scope3_EI"] - ) - - ## H2 production via SMR - # Calculate SMR emissions. SMR and SMR + CCS are always grid-connected (kg CO2e/kg H2) - EI_values["smr_Scope3_EI"] = ( - (NG_supply_EI * g_to_kg * (smr_NG_consume - smr_steam_prod / smr_HEX_eff)) - + ( - smr_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (smr_electricity_consume * grid_capex_EI) - ) - EI_values["smr_Scope2_EI"] = ( - smr_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["smr_Scope1_EI"] = ( - NG_combust_EI * g_to_kg * (smr_NG_consume - smr_steam_prod / smr_HEX_eff) - ) - EI_values["smr_Total_EI"] = ( - EI_values["smr_Scope1_EI"] + EI_values["smr_Scope2_EI"] + EI_values["smr_Scope3_EI"] - ) - - # Calculate ammonia emissions via SMR process (kg CO2e/kg NH3) - EI_values["NH3_smr_Scope3_EI"] = ( - (NH3_H2_consume * EI_values["smr_Total_EI"]) - + (NH3_NG_consume * NG_supply_EI * g_to_kg / MT_to_kg) - + ( - NH3_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (NH3_electricity_consume * grid_capex_EI) - ) - EI_values["NH3_smr_Scope2_EI"] = ( - NH3_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["NH3_smr_Scope1_EI"] = NH3_NG_consume * NG_combust_EI * g_to_kg / MT_to_kg - EI_values["NH3_smr_Total_EI"] = ( - EI_values["NH3_smr_Scope1_EI"] - + EI_values["NH3_smr_Scope2_EI"] - + EI_values["NH3_smr_Scope3_EI"] - ) - - # Calculate steel emissions via SMR process (kg CO2e/metric ton steel) - EI_values["steel_smr_Scope3_EI"] = ( - (steel_H2_consume * MT_to_kg * EI_values["smr_Total_EI"]) - + (steel_lime_consume * lime_supply_EI * MT_to_kg) - + (steel_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (steel_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (steel_NG_consume * NG_supply_EI) - + (steel_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - steel_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (steel_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["steel_smr_Scope2_EI"] = ( - steel_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["steel_smr_Scope1_EI"] = steel_NG_consume * NG_combust_EI - EI_values["steel_smr_Total_EI"] = ( - EI_values["steel_smr_Scope1_EI"] - + EI_values["steel_smr_Scope2_EI"] - + EI_values["steel_smr_Scope3_EI"] - ) - - # Calculate SMR + CCS emissions (kg CO2e/kg H2) - EI_values["smr_ccs_Scope3_EI"] = ( - (NG_supply_EI * g_to_kg * (smr_ccs_NG_consume - smr_ccs_steam_prod / smr_HEX_eff)) - + ( - smr_ccs_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (smr_ccs_electricity_consume * grid_capex_EI) - ) - EI_values["smr_ccs_Scope2_EI"] = ( - smr_ccs_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["smr_ccs_Scope1_EI"] = ( - (1 - smr_ccs_perc_capture) - * NG_combust_EI - * g_to_kg - * (smr_ccs_NG_consume - smr_ccs_steam_prod / smr_HEX_eff) - ) - EI_values["smr_ccs_Total_EI"] = ( - EI_values["smr_ccs_Scope1_EI"] - + EI_values["smr_ccs_Scope2_EI"] - + EI_values["smr_ccs_Scope3_EI"] - ) - - # Calculate ammonia emissions via SMR with CCS process (kg CO2e/kg NH3) - EI_values["NH3_smr_ccs_Scope3_EI"] = ( - (NH3_H2_consume * EI_values["smr_ccs_Total_EI"]) - + (NH3_NG_consume * NG_supply_EI * g_to_kg / MT_to_kg) - + ( - NH3_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (NH3_electricity_consume * grid_capex_EI) - ) - EI_values["NH3_smr_ccs_Scope2_EI"] = ( - NH3_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["NH3_smr_ccs_Scope1_EI"] = NH3_NG_consume * NG_combust_EI * g_to_kg / MT_to_kg - EI_values["NH3_smr_ccs_Total_EI"] = ( - EI_values["NH3_smr_ccs_Scope1_EI"] - + EI_values["NH3_smr_ccs_Scope2_EI"] - + EI_values["NH3_smr_ccs_Scope3_EI"] - ) - - # Calculate steel emissions via SMR with CCS process (kg CO2e/metric ton steel) - EI_values["steel_smr_ccs_Scope3_EI"] = ( - (steel_H2_consume * MT_to_kg * EI_values["smr_ccs_Total_EI"]) - + (steel_lime_consume * lime_supply_EI * MT_to_kg) - + (steel_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (steel_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (steel_NG_consume * NG_supply_EI) - + (steel_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - steel_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (steel_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["steel_smr_ccs_Scope2_EI"] = ( - steel_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["steel_smr_ccs_Scope1_EI"] = steel_NG_consume * NG_combust_EI - EI_values["steel_smr_ccs_Total_EI"] = ( - EI_values["steel_smr_ccs_Scope1_EI"] - + EI_values["steel_smr_ccs_Scope2_EI"] - + EI_values["steel_smr_ccs_Scope3_EI"] - ) - - ## H2 production via ATR - # Calculate ATR emissions. ATR and ATR + CCS are always grid-connected (kg CO2e/kg H2) - EI_values["atr_Scope3_EI"] = ( - (NG_supply_EI * g_to_kg * atr_NG_consume) - + ( - atr_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (atr_electricity_consume * grid_capex_EI) - ) - EI_values["atr_Scope2_EI"] = ( - atr_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["atr_Scope1_EI"] = NG_combust_EI * g_to_kg * atr_NG_consume - EI_values["atr_Total_EI"] = ( - EI_values["atr_Scope1_EI"] + EI_values["atr_Scope2_EI"] + EI_values["atr_Scope3_EI"] - ) - - # Calculate ammonia emissions via ATR process (kg CO2e/kg NH3) - EI_values["NH3_atr_Scope3_EI"] = ( - (NH3_H2_consume * EI_values["atr_Total_EI"]) - + (NH3_NG_consume * NG_supply_EI * g_to_kg / MT_to_kg) - + ( - NH3_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (NH3_electricity_consume * grid_capex_EI) - ) - EI_values["NH3_atr_Scope2_EI"] = ( - NH3_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["NH3_atr_Scope1_EI"] = NH3_NG_consume * NG_combust_EI * g_to_kg / MT_to_kg - EI_values["NH3_atr_Total_EI"] = ( - EI_values["NH3_atr_Scope1_EI"] - + EI_values["NH3_atr_Scope2_EI"] - + EI_values["NH3_atr_Scope3_EI"] - ) - - # Calculate steel emissions via ATR process (kg CO2e/metric ton steel) - EI_values["steel_atr_Scope3_EI"] = ( - (steel_H2_consume * MT_to_kg * EI_values["atr_Total_EI"]) - + (steel_lime_consume * lime_supply_EI * MT_to_kg) - + (steel_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (steel_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (steel_NG_consume * NG_supply_EI) - + (steel_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - steel_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (steel_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["steel_atr_Scope2_EI"] = ( - steel_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["steel_atr_Scope1_EI"] = steel_NG_consume * NG_combust_EI - EI_values["steel_atr_Total_EI"] = ( - EI_values["steel_atr_Scope1_EI"] - + EI_values["steel_atr_Scope2_EI"] - + EI_values["steel_atr_Scope3_EI"] - ) - - # Calculate ATR + CCS emissions (kg CO2e/kg H2) - EI_values["atr_ccs_Scope3_EI"] = ( - (NG_supply_EI * g_to_kg * atr_ccs_NG_consume) - + ( - atr_ccs_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (atr_ccs_electricity_consume * grid_capex_EI) - ) - EI_values["atr_ccs_Scope2_EI"] = ( - atr_ccs_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["atr_ccs_Scope1_EI"] = ( - (1 - atr_ccs_perc_capture) * NG_combust_EI * g_to_kg * atr_ccs_NG_consume - ) - EI_values["atr_ccs_Total_EI"] = ( - EI_values["atr_ccs_Scope1_EI"] - + EI_values["atr_ccs_Scope2_EI"] - + EI_values["atr_ccs_Scope3_EI"] - ) - - # Calculate ammonia emissions via ATR with CCS process (kg CO2e/kg NH3) - EI_values["NH3_atr_ccs_Scope3_EI"] = ( - (NH3_H2_consume * EI_values["atr_ccs_Total_EI"]) - + (NH3_NG_consume * NG_supply_EI * g_to_kg / MT_to_kg) - + ( - NH3_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (NH3_electricity_consume * grid_capex_EI) - ) - EI_values["NH3_atr_ccs_Scope2_EI"] = ( - NH3_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["NH3_atr_ccs_Scope1_EI"] = NH3_NG_consume * NG_combust_EI * g_to_kg / MT_to_kg - EI_values["NH3_atr_ccs_Total_EI"] = ( - EI_values["NH3_atr_ccs_Scope1_EI"] - + EI_values["NH3_atr_ccs_Scope2_EI"] - + EI_values["NH3_atr_ccs_Scope3_EI"] - ) - - # Calculate steel emissions via ATR with CCS process (kg CO2e/metric ton steel) - EI_values["steel_atr_ccs_Scope3_EI"] = ( - (steel_H2_consume * MT_to_kg * EI_values["atr_ccs_Total_EI"]) - + (steel_lime_consume * lime_supply_EI * MT_to_kg) - + (steel_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (steel_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (steel_NG_consume * NG_supply_EI) - + (steel_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - steel_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (steel_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["steel_atr_ccs_Scope2_EI"] = ( - steel_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["steel_atr_ccs_Scope1_EI"] = steel_NG_consume * NG_combust_EI - EI_values["steel_atr_ccs_Total_EI"] = ( - EI_values["steel_atr_ccs_Scope1_EI"] - + EI_values["steel_atr_ccs_Scope2_EI"] - + EI_values["steel_atr_ccs_Scope3_EI"] - ) - - # Calculate Natural Gas (NG) DRI emissions (kg CO2e/metric tonne pig iron) - EI_values["ng_dri_Scope3_EI"] = ( - (ng_dri_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (ng_dri_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (ng_dri_NG_consume * NG_supply_EI) - + (ng_dri_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - ng_dri_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (ng_dri_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["ng_dri_Scope2_EI"] = ( - ng_dri_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["ng_dri_Scope1_EI"] = ng_dri_NG_consume * NG_combust_EI - EI_values["ng_dri_Total_EI"] = ( - EI_values["ng_dri_Scope1_EI"] - + EI_values["ng_dri_Scope2_EI"] - + EI_values["ng_dri_Scope3_EI"] - ) - - # Calculate Natural Gas (NG) DRI EAF emissions (kg CO2e/metric tonne pig iron) - EI_values["ng_dri_eaf_Scope3_EI"] = ( - (ng_dri_eaf_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (ng_dri_eaf_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (ng_dri_eaf_lime_consume * MT_to_kg * lime_supply_EI) - + (ng_dri_eaf_coke_consume * MT_to_kg * coke_supply_EI) - + (ng_dri_eaf_NG_consume * NG_supply_EI) - + (ng_dri_eaf_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - ng_dri_eaf_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (ng_dri_eaf_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["ng_dri_eaf_Scope2_EI"] = ( - ng_dri_eaf_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["ng_dri_eaf_Scope1_EI"] = ng_dri_eaf_NG_consume * NG_combust_EI - EI_values["ng_dri_eaf_Total_EI"] = ( - EI_values["ng_dri_eaf_Scope1_EI"] - + EI_values["ng_dri_eaf_Scope2_EI"] - + EI_values["ng_dri_eaf_Scope3_EI"] - ) - - if "off-grid" in grid_case: - ## H2 production via electrolysis - # Calculate renewable only electrolysis emissions (kg CO2e/kg H2) - EI_values["electrolysis_Scope3_EI"] = ( - ely_stack_and_BoP_capex_EI - + (ely_H2O_consume * H2O_supply_EI) - + ( - ( - (wind_capex_EI * g_to_kg * wind_annual_energy_kwh) - + (solar_pv_capex_EI * g_to_kg * solar_pv_annual_energy_kwh) - ) - / h2_annual_prod_kg - ) - ) - EI_values["electrolysis_Scope2_EI"] = 0 - EI_values["electrolysis_Scope1_EI"] = 0 - EI_values["electrolysis_Total_EI"] = ( - EI_values["electrolysis_Scope1_EI"] - + EI_values["electrolysis_Scope2_EI"] - + EI_values["electrolysis_Scope3_EI"] - ) - - # Calculate ammonia emissions via renewable electrolysis (kg CO2e/kg NH3) - EI_values["NH3_electrolysis_Scope3_EI"] = ( - (NH3_H2_consume * EI_values["electrolysis_Total_EI"]) - + (NH3_NG_consume * NG_supply_EI * g_to_kg / MT_to_kg) - + ( - NH3_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (NH3_electricity_consume * grid_capex_EI) - ) - EI_values["NH3_electrolysis_Scope2_EI"] = ( - NH3_electricity_consume - * kWh_to_MWh - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["NH3_electrolysis_Scope1_EI"] = ( - NH3_NG_consume * NG_combust_EI * g_to_kg / MT_to_kg - ) - EI_values["NH3_electrolysis_Total_EI"] = ( - EI_values["NH3_electrolysis_Scope1_EI"] - + EI_values["NH3_electrolysis_Scope2_EI"] - + EI_values["NH3_electrolysis_Scope3_EI"] - ) - - # Calculate steel emissions via renewable electrolysis (kg CO2e/metric ton steel) - EI_values["steel_electrolysis_Scope3_EI"] = ( - (steel_H2_consume * MT_to_kg * EI_values["electrolysis_Total_EI"]) - + (steel_lime_consume * lime_supply_EI * MT_to_kg) - + (steel_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (steel_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (steel_NG_consume * NG_supply_EI) - + (steel_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - steel_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (steel_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["steel_electrolysis_Scope2_EI"] = ( - steel_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["steel_electrolysis_Scope1_EI"] = steel_NG_consume * NG_combust_EI - EI_values["steel_electrolysis_Total_EI"] = ( - EI_values["steel_electrolysis_Scope1_EI"] - + EI_values["steel_electrolysis_Scope2_EI"] - + EI_values["steel_electrolysis_Scope3_EI"] - ) - - # Calculate H2 DRI emissions via off grid electrolysis (kg CO2e/metric tonne pig iron) - EI_values["h2_electrolysis_dri_Scope3_EI"] = ( - (h2_dri_H2_consume * MT_to_kg * EI_values["electrolysis_Total_EI"]) - + (h2_dri_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (h2_dri_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (h2_dri_NG_consume * NG_supply_EI) - + (h2_dri_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - h2_dri_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (h2_dri_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["h2_electrolysis_dri_Scope2_EI"] = ( - h2_dri_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["h2_electrolysis_dri_Scope1_EI"] = h2_dri_NG_consume * NG_combust_EI - EI_values["h2_electrolysis_dri_Total_EI"] = ( - EI_values["h2_electrolysis_dri_Scope1_EI"] - + EI_values["h2_electrolysis_dri_Scope2_EI"] - + EI_values["h2_electrolysis_dri_Scope3_EI"] - ) - - # Calculate H2 DRI EAF emissions via off grid electrolysis (kg CO2e/tonne pig iron) - EI_values["h2_electrolysis_dri_eaf_Scope3_EI"] = ( - (h2_dri_eaf_H2_consume * MT_to_kg * EI_values["electrolysis_Total_EI"]) - + (h2_dri_eaf_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (h2_dri_eaf_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (h2_dri_eaf_lime_consume * MT_to_kg * lime_supply_EI) - + (h2_dri_eaf_coke_consume * MT_to_kg * coke_supply_EI) - + (h2_dri_eaf_NG_consume * NG_supply_EI) - + (h2_dri_eaf_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - h2_dri_eaf_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (h2_dri_eaf_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["h2_electrolysis_dri_eaf_Scope2_EI"] = ( - h2_dri_eaf_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["h2_electrolysis_dri_eaf_Scope1_EI"] = h2_dri_eaf_NG_consume * NG_combust_EI - EI_values["h2_electrolysis_dri_eaf_Total_EI"] = ( - EI_values["h2_electrolysis_dri_eaf_Scope1_EI"] - + EI_values["h2_electrolysis_dri_eaf_Scope2_EI"] - + EI_values["h2_electrolysis_dri_eaf_Scope3_EI"] - ) - - # Calculate Natural Gas (NG) DRI emissions (kg CO2e/metric tonne pig iron) - EI_values["ng_dri_Scope3_EI"] = ( - (ng_dri_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (ng_dri_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (ng_dri_NG_consume * NG_supply_EI) - + (ng_dri_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - ng_dri_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (ng_dri_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["ng_dri_Scope2_EI"] = ( - ng_dri_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["ng_dri_Scope1_EI"] = ng_dri_NG_consume * NG_combust_EI - EI_values["ng_dri_Total_EI"] = ( - EI_values["ng_dri_Scope1_EI"] - + EI_values["ng_dri_Scope2_EI"] - + EI_values["ng_dri_Scope3_EI"] - ) - - # Calculate Natural Gas (NG) DRI EAF emissions (kg CO2e/metric tonne pig iron) - EI_values["ng_dri_eaf_Scope3_EI"] = ( - (ng_dri_eaf_iron_ore_consume * iron_ore_mining_EI_per_MT_ore) - + (ng_dri_eaf_iron_ore_consume * iron_ore_pelletizing_EI_per_MT_ore) - + (ng_dri_eaf_lime_consume * MT_to_kg * lime_supply_EI) - + (ng_dri_eaf_coke_consume * MT_to_kg * coke_supply_EI) - + (ng_dri_eaf_NG_consume * NG_supply_EI) - + (ng_dri_eaf_H2O_consume * (H2O_supply_EI / gal_H2O_to_MT)) - + ( - ng_dri_eaf_electricity_consume - * cambium_data_df["LRMER CO2 equiv. precombustion (kg-CO2e/MWh)"].mean() - ) - + (ng_dri_eaf_electricity_consume * MWh_to_kWh * grid_capex_EI) - ) - EI_values["ng_dri_eaf_Scope2_EI"] = ( - ng_dri_eaf_electricity_consume - * cambium_data_df["LRMER CO2 equiv. combustion (kg-CO2e/MWh)"].mean() - ) - EI_values["ng_dri_eaf_Scope1_EI"] = ng_dri_eaf_NG_consume * NG_combust_EI - EI_values["ng_dri_eaf_Total_EI"] = ( - EI_values["ng_dri_eaf_Scope1_EI"] - + EI_values["ng_dri_eaf_Scope2_EI"] - + EI_values["ng_dri_eaf_Scope3_EI"] - ) - - # Append emission intensity values for each year to lists in the ts_EI_data dictionary - for key in ts_EI_data: - ts_EI_data[key].append(EI_values[key]) - - ## Interpolation of emission intensities for years not captured by cambium - # (cambium 2023 offers 2025-2050 in 5 year increments) - # Define end of life based on cambium_year and project lifetime - endoflife_year = cambium_year + project_lifetime - - # Instantiate dictionary of lists to hold full EI time series (ts) data - # including interpolated data for years when cambium data is not available - ts_EI_data_interpolated = { - f"{process}_{scope}_EI": [] for process in processes for scope in scopes - } - - # Loop through years between cambium_year and endoflife_year, interpolate values - # Check if the defined cambium_year is less than the earliest data year available - # from the cambium API, flag and warn users - if cambium_year < min(cambium_data.cambium_years): - cambium_year_warning_message = """Warning, the earliest year available for cambium data is - {min_cambium_year}! For all years less than {min_cambium_year}, LCA calculations will use - Cambium data from {min_cambium_year}. Thus, calculated emission intensity values for these - years may be understated.""".format(min_cambium_year=min(cambium_data.cambium_years)) - print("****************** WARNING ******************") - warnings.warn(cambium_year_warning_message) - cambium_warning_flag = True - else: - cambium_warning_flag = False - for year in range(cambium_year, endoflife_year): - # if year < the minimum cambium_year (currently 2025 in Cambium 2023) - # use data from the minimum year - if year < min(cambium_data.cambium_years): - for key in ts_EI_data_interpolated: - ts_EI_data_interpolated[key].append(ts_EI_data[key][0]) - - # else if year <= the maximum cambium_year (currently 2050 in Cambium 2023) - # interpolate the values (copies existing values if year is already present) - elif year <= max(cambium_data.cambium_years): - for key in ts_EI_data_interpolated: - ts_EI_data_interpolated[key].append( - np.interp(year, cambium_data.cambium_years, ts_EI_data[key]) - ) - - # else if year > maximum cambium_year, copy data from maximum year (ie: copy data from 2050) - else: - for key in ts_EI_data_interpolated: - ts_EI_data_interpolated[key].append(ts_EI_data[key][-1]) - - # Put all cumulative metrics and relevant data into a dictionary, then dataframe - # return the dataframe, save results to csv in post_processing() - lca_dict = { - "Cambium Warning": [cambium_year_warning_message if cambium_warning_flag else "None"], - "Total Life Cycle H2 Production (kg-H2)": [h2_lifetime_prod_kg], - "Electrolysis Scope 3 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["electrolysis_Scope3_EI"])) / project_lifetime - ], - "Electrolysis Scope 2 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["electrolysis_Scope2_EI"])) / project_lifetime - ], - "Electrolysis Scope 1 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["electrolysis_Scope1_EI"])) / project_lifetime - ], - "Electrolysis Total Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["electrolysis_Total_EI"])) / project_lifetime - ], - "Ammonia Electrolysis Scope 3 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_electrolysis_Scope3_EI"])) - / project_lifetime - ], - "Ammonia Electrolysis Scope 2 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_electrolysis_Scope2_EI"])) - / project_lifetime - ], - "Ammonia Electrolysis Scope 1 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_electrolysis_Scope1_EI"])) - / project_lifetime - ], - "Ammonia Electrolysis Total Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_electrolysis_Total_EI"])) / project_lifetime - ], - "Steel Electrolysis Scope 3 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_electrolysis_Scope3_EI"])) - / project_lifetime - ], - "Steel Electrolysis Scope 2 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_electrolysis_Scope2_EI"])) - / project_lifetime - ], - "Steel Electrolysis Scope 1 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_electrolysis_Scope1_EI"])) - / project_lifetime - ], - "Steel Electrolysis Total Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_electrolysis_Total_EI"])) - / project_lifetime - ], - "H2 Electrolysis DRI Scope 3 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["h2_electrolysis_dri_Scope3_EI"])) - / project_lifetime - ], - "H2 Electrolysis DRI Scope 2 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["h2_electrolysis_dri_Scope2_EI"])) - / project_lifetime - ], - "H2 Electrolysis DRI Scope 1 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["h2_electrolysis_dri_Scope1_EI"])) - / project_lifetime - ], - "H2 Electrolysis DRI Total Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["h2_electrolysis_dri_Total_EI"])) - / project_lifetime - ], - "H2 Electrolysis DRI EAF Scope 3 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["h2_electrolysis_dri_eaf_Scope3_EI"])) - / project_lifetime - ], - "H2 Electrolysis DRI EAF Scope 2 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["h2_electrolysis_dri_eaf_Scope2_EI"])) - / project_lifetime - ], - "H2 Electrolysis DRI EAF Scope 1 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["h2_electrolysis_dri_eaf_Scope1_EI"])) - / project_lifetime - ], - "H2 Electrolysis DRI EAF Total Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["h2_electrolysis_dri_eaf_Total_EI"])) - / project_lifetime - ], - "SMR Scope 3 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["smr_Scope3_EI"])) / project_lifetime - ], - "SMR Scope 2 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["smr_Scope2_EI"])) / project_lifetime - ], - "SMR Scope 1 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["smr_Scope1_EI"])) / project_lifetime - ], - "SMR Total Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["smr_Total_EI"])) / project_lifetime - ], - "Ammonia SMR Scope 3 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_smr_Scope3_EI"])) / project_lifetime - ], - "Ammonia SMR Scope 2 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_smr_Scope2_EI"])) / project_lifetime - ], - "Ammonia SMR Scope 1 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_smr_Scope1_EI"])) / project_lifetime - ], - "Ammonia SMR Total Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_smr_Total_EI"])) / project_lifetime - ], - "Steel SMR Scope 3 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_smr_Scope3_EI"])) / project_lifetime - ], - "Steel SMR Scope 2 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_smr_Scope2_EI"])) / project_lifetime - ], - "Steel SMR Scope 1 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_smr_Scope1_EI"])) / project_lifetime - ], - "Steel SMR Total Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_smr_Total_EI"])) / project_lifetime - ], - "SMR with CCS Scope 3 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["smr_ccs_Scope3_EI"])) / project_lifetime - ], - "SMR with CCS Scope 2 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["smr_ccs_Scope2_EI"])) / project_lifetime - ], - "SMR with CCS Scope 1 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["smr_ccs_Scope1_EI"])) / project_lifetime - ], - "SMR with CCS Total Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["smr_ccs_Total_EI"])) / project_lifetime - ], - "Ammonia SMR with CCS Scope 3 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_smr_ccs_Scope3_EI"])) / project_lifetime - ], - "Ammonia SMR with CCS Scope 2 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_smr_ccs_Scope2_EI"])) / project_lifetime - ], - "Ammonia SMR with CCS Scope 1 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_smr_ccs_Scope1_EI"])) / project_lifetime - ], - "Ammonia SMR with CCS Total Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_smr_ccs_Total_EI"])) / project_lifetime - ], - "Steel SMR with CCS Scope 3 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_smr_ccs_Scope3_EI"])) / project_lifetime - ], - "Steel SMR with CCS Scope 2 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_smr_ccs_Scope2_EI"])) / project_lifetime - ], - "Steel SMR with CCS Scope 1 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_smr_ccs_Scope1_EI"])) / project_lifetime - ], - "Steel SMR with CCS Total Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_smr_ccs_Total_EI"])) / project_lifetime - ], - "ATR Scope 3 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["atr_Scope3_EI"])) / project_lifetime - ], - "ATR Scope 2 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["atr_Scope2_EI"])) / project_lifetime - ], - "ATR Scope 1 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["atr_Scope1_EI"])) / project_lifetime - ], - "ATR Total Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["atr_Total_EI"])) / project_lifetime - ], - "Ammonia ATR Scope 3 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_atr_Scope3_EI"])) / project_lifetime - ], - "Ammonia ATR Scope 2 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_atr_Scope2_EI"])) / project_lifetime - ], - "Ammonia ATR Scope 1 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_atr_Scope1_EI"])) / project_lifetime - ], - "Ammonia ATR Total Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_atr_Total_EI"])) / project_lifetime - ], - "Steel ATR Scope 3 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_atr_Scope3_EI"])) / project_lifetime - ], - "Steel ATR Scope 2 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_atr_Scope2_EI"])) / project_lifetime - ], - "Steel ATR Scope 1 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_atr_Scope1_EI"])) / project_lifetime - ], - "Steel ATR Total Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_atr_Total_EI"])) / project_lifetime - ], - "ATR with CCS Scope 3 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["atr_ccs_Scope3_EI"])) / project_lifetime - ], - "ATR with CCS Scope 2 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["atr_ccs_Scope2_EI"])) / project_lifetime - ], - "ATR with CCS Scope 1 Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["atr_ccs_Scope1_EI"])) / project_lifetime - ], - "ATR with CCS Total Lifetime Average GHG Emissions (kg-CO2e/kg-H2)": [ - sum(np.asarray(ts_EI_data_interpolated["atr_ccs_Total_EI"])) / project_lifetime - ], - "Ammonia ATR with CCS Scope 3 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_atr_ccs_Scope3_EI"])) / project_lifetime - ], - "Ammonia ATR with CCS Scope 2 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_atr_ccs_Scope2_EI"])) / project_lifetime - ], - "Ammonia ATR with CCS Scope 1 Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_atr_ccs_Scope1_EI"])) / project_lifetime - ], - "Ammonia ATR with CCS Total Lifetime Average GHG Emissions (kg-CO2e/kg-NH3)": [ - sum(np.asarray(ts_EI_data_interpolated["NH3_atr_ccs_Total_EI"])) / project_lifetime - ], - "Steel ATR with CCS Scope 3 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_atr_ccs_Scope3_EI"])) / project_lifetime - ], - "Steel ATR with CCS Scope 2 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_atr_ccs_Scope2_EI"])) / project_lifetime - ], - "Steel ATR with CCS Scope 1 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_atr_ccs_Scope1_EI"])) / project_lifetime - ], - "Steel ATR with CCS Total Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["steel_atr_ccs_Total_EI"])) / project_lifetime - ], - "NG DRI Scope 3 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["ng_dri_Scope3_EI"])) / project_lifetime - ], - "NG DRI Scope 2 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["ng_dri_Scope2_EI"])) / project_lifetime - ], - "NG DRI Scope 1 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["ng_dri_Scope1_EI"])) / project_lifetime - ], - "NG DRI Total Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["ng_dri_Total_EI"])) / project_lifetime - ], - "NG DRI EAF Scope 3 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["ng_dri_eaf_Scope3_EI"])) / project_lifetime - ], - "NG DRI EAF Scope 2 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["ng_dri_eaf_Scope2_EI"])) / project_lifetime - ], - "NG DRI EAF Scope 1 Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["ng_dri_eaf_Scope1_EI"])) / project_lifetime - ], - "NG DRI EAF Total Lifetime Average GHG Emissions (kg-CO2e/MT steel)": [ - sum(np.asarray(ts_EI_data_interpolated["ng_dri_eaf_Total_EI"])) / project_lifetime - ], - "Site Latitude": [site_latitude], - "Site Longitude": [site_longitude], - "Cambium Year": [cambium_year], - "Electrolysis Case": [electrolyzer_centralization], - "Grid Case": [grid_case], - "Renewables Case": [renewables_case], - "Wind Turbine Rating (MW)": [wind_turbine_rating_MW], - "Wind Model": [wind_model], - "Electrolyzer Degradation Modeled": [electrolyzer_degradation], - "Electrolyzer Stack Optimization": [electrolyzer_optimized], - f"Number of {electrolyzer_type} Electrolyzer Clusters": [number_of_electrolyzer_clusters], - "Electricity ITC (%/100 CapEx)": [tax_incentive_option["electricity_itc"]], - "Electricity PTC ($/kWh 1992 dollars)": [tax_incentive_option["electricity_ptc"]], - "H2 Storage ITC (%/100 CapEx)": [tax_incentive_option["h2_storage_itc"]], - "H2 PTC ($/kWh 2022 dollars)": [tax_incentive_option["h2_ptc"]], - } - - lca_df = pd.DataFrame(data=lca_dict) - - return lca_df - - -# set up function to post-process HOPP results -def post_process_simulation( - lcoe, - lcoh, - pf_lcoh, - pf_lcoe, - hopp_results, - electrolyzer_physics_results, - hopp_config, - h2integrate_config, - orbit_config, - turbine_config, - h2_storage_results, - total_accessory_power_renewable_kw, - total_accessory_power_grid_kw, - capex_breakdown, - opex_breakdown, - wind_cost_results, - platform_results, - desal_results, - design_scenario, - plant_design_number, - incentive_option, - solver_results=[], - show_plots=False, - save_plots=False, - verbose=False, - output_dir="./output/", -): # , lcoe, lcoh, lcoh_with_grid, lcoh_grid_only): - if any(i in h2integrate_config for i in ["iron", "iron_pre", "iron_win", "iron_post"]): - msg = ( - "Post processing not yet implemented for iron model. LCA can still be set up through " - "h2integrate_config.yaml -> lca_config" - ) - raise NotImplementedError(msg) - - if isinstance(output_dir, str): - output_dir = Path(output_dir).resolve() - # colors (official NREL color palette https://brand.nrel.gov/content/index/guid/color_palette?parent=61) - colors = [ - "#0079C2", - "#00A4E4", - "#F7A11A", - "#FFC423", - "#5D9732", - "#8CC63F", - "#5E6A71", - "#D1D5D8", - "#933C06", - "#D9531E", - ] - - # post process results - if verbose: - print("LCOE: ", round(lcoe * 1e3, 2), "$/MWh") - print("LCOH: ", round(lcoh, 2), "$/kg") - print( - "hybrid electricity plant capacity factor: ", - round( - np.sum(hopp_results["combined_hybrid_power_production_hopp"]) - / (hopp_results["hybrid_plant"].system_capacity_kw.hybrid * 365 * 24), - 2, - ), - ) - print( - "electrolyzer capacity factor: ", - round( - np.sum(electrolyzer_physics_results["power_to_electrolyzer_kw"]) - * 1e-3 - / (h2integrate_config["electrolyzer"]["rating"] * 365 * 24), - 2, - ), - ) - print( - "Electrolyzer CAPEX installed $/kW: ", - round( - capex_breakdown["electrolyzer"] - / (h2integrate_config["electrolyzer"]["rating"] * 1e3), - 2, - ), - ) - - # Run LCA analysis if config yaml flag = True - if h2integrate_config["lca_config"]["run_lca"]: - lca_df = calculate_lca( - hopp_results=hopp_results, - electrolyzer_physics_results=electrolyzer_physics_results, - hopp_config=hopp_config, - h2integrate_config=h2integrate_config, - total_accessory_power_renewable_kw=total_accessory_power_renewable_kw, - total_accessory_power_grid_kw=total_accessory_power_grid_kw, - plant_design_scenario_number=plant_design_number, - incentive_option_number=incentive_option, - ) - - if show_plots or save_plots: - visualize_plant( - hopp_config, - h2integrate_config, - turbine_config, - wind_cost_results, - hopp_results, - platform_results, - desal_results, - h2_storage_results, - electrolyzer_physics_results, - design_scenario, - colors, - plant_design_number, - show_plots=show_plots, - save_plots=save_plots, - output_dir=output_dir, - ) - savepaths = [ - output_dir / "data/", - output_dir / "data/lcoe/", - output_dir / "data/lcoh/", - output_dir / "data/lca/", - ] - for sp in savepaths: - if not sp.exists(): - sp.mkdir(parents=True) - - pf_lcoh.get_cost_breakdown().to_csv( - savepaths[2] - / f'cost_breakdown_lcoh_design{plant_design_number}_incentive{incentive_option}_{h2integrate_config["h2_storage"]["type"]}storage.csv' # noqa: E501 - ) - pf_lcoe.get_cost_breakdown().to_csv( - savepaths[1] - / f'cost_breakdown_lcoe_design{plant_design_number}_incentive{incentive_option}_{h2integrate_config["h2_storage"]["type"]}storage.csv' # noqa: E501 - ) - - # Save LCA results if analysis was run - if h2integrate_config["lca_config"]["run_lca"]: - lca_savepath = ( - savepaths[3] - / f'LCA_results_design{plant_design_number}_incentive{incentive_option}_{h2integrate_config["h2_storage"]["type"]}storage.csv' # noqa: E501 - ) - lca_df.to_csv(lca_savepath) - print("LCA Analysis was run as a postprocessing step. Results were saved to:") - print(lca_savepath) - - # create dataframe for saving all the stuff - h2integrate_config["design_scenario"] = design_scenario - h2integrate_config["plant_design_number"] = plant_design_number - h2integrate_config["incentive_options"] = incentive_option - - # save power usage data - if len(solver_results) > 0: - hours = len(hopp_results["combined_hybrid_power_production_hopp"]) - annual_energy_breakdown = { - "electricity_generation_kwh": sum( - hopp_results["combined_hybrid_power_production_hopp"] - ), - "electrolyzer_kwh": sum(electrolyzer_physics_results["power_to_electrolyzer_kw"]), - "renewable_kwh": sum(solver_results[0]), - "grid_power_kwh": sum(solver_results[1]), - "desal_kwh": solver_results[2] * hours, - "h2_transport_compressor_power_kwh": solver_results[3] * hours, - "h2_storage_power_kwh": solver_results[4] * hours, - "electrolyzer_bop_energy_kwh": sum(solver_results[5]), - } - - ######################### save detailed ORBIT cost information - if wind_cost_results.orbit_project: - _, orbit_capex_breakdown, wind_capex_multiplier = adjust_orbit_costs( - orbit_project=wind_cost_results.orbit_project, - h2integrate_config=h2integrate_config, - ) - - # orbit_capex_breakdown["Onshore Substation"] = orbit_project.phases["ElectricalDesign"].onshore_cost # noqa: E501 - # discount ORBIT cost information - for key in orbit_capex_breakdown: - orbit_capex_breakdown[key] = -npf.fv( - h2integrate_config["finance_parameters"]["costing_general_inflation"], - h2integrate_config["project_parameters"]["cost_year"] - - h2integrate_config["finance_parameters"]["discount_years"]["wind"], - 0.0, - orbit_capex_breakdown[key], - ) - - # save ORBIT cost information - ob_df = pd.DataFrame(orbit_capex_breakdown, index=[0]).transpose() - savedir = output_dir / "data/orbit_costs/" - if not savedir.exists(): - savedir.mkdir(parents=True) - ob_df.to_csv( - savedir - / f'orbit_cost_breakdown_lcoh_design{plant_design_number}_incentive{incentive_option}_{h2integrate_config["h2_storage"]["type"]}storage.csv' # noqa: E501 - ) - ############################### - - ###################### Save export system breakdown from ORBIT ################### - - _, orbit_capex_breakdown, wind_capex_multiplier = adjust_orbit_costs( - orbit_project=wind_cost_results.orbit_project, - h2integrate_config=h2integrate_config, - ) - - onshore_substation_costs = ( - wind_cost_results.orbit_project.phases["ElectricalDesign"].onshore_cost - * wind_capex_multiplier - ) - - orbit_capex_breakdown["Export System Installation"] -= onshore_substation_costs - - orbit_capex_breakdown["Onshore Substation and Installation"] = onshore_substation_costs - - # discount ORBIT cost information - for key in orbit_capex_breakdown: - orbit_capex_breakdown[key] = -npf.fv( - h2integrate_config["finance_parameters"]["costing_general_inflation"], - h2integrate_config["project_parameters"]["cost_year"] - - h2integrate_config["finance_parameters"]["discount_years"]["wind"], - 0.0, - orbit_capex_breakdown[key], - ) - - # save ORBIT cost information using directory defined above - ob_df = pd.DataFrame(orbit_capex_breakdown, index=[0]).transpose() - ob_df.to_csv( - savedir - / f'orbit_cost_breakdown_with_onshore_substation_lcoh_design{plant_design_number}_incentive{incentive_option}_{h2integrate_config["h2_storage"]["type"]}storage.csv' # noqa: E501 - ) - - ################################################################################## - if save_plots: - if ( - hasattr(hopp_results["hybrid_plant"], "dispatch_builder") - and hopp_results["hybrid_plant"].battery - ): - savedir = output_dir / "figures/production/" - if not savedir.exists(): - savedir.mkdir(parents=True) - plot_tools.plot_generation_profile( - hopp_results["hybrid_plant"], - start_day=0, - n_days=10, - plot_filename=(savedir / "generation_profile.pdf"), - font_size=14, - power_scale=1 / 1000, - solar_color="r", - wind_color="b", - # wave_color="g", - discharge_color="b", - charge_color="r", - gen_color="g", - price_color="r", - # show_price=False, - ) - else: - print( - "generation profile not plotted because HoppInterface does not have a " - "'dispatch_builder'" - ) - - # save production information - hourly_energy_breakdown = save_energy_flows( - hopp_results["hybrid_plant"], - electrolyzer_physics_results, - solver_results, - hours, - h2_storage_results, - output_dir=output_dir, - ) - - # save hydrogen information - key = "Hydrogen Hourly Production [kg/hr]" - np.savetxt( - output_dir / "h2_usage", - electrolyzer_physics_results["H2_Results"][key], - header="# " + key, - ) - - return annual_energy_breakdown, hourly_energy_breakdown diff --git a/tests/h2integrate/test_finance.py b/tests/h2integrate/test_finance.py deleted file mode 100644 index ed938d0c7..000000000 --- a/tests/h2integrate/test_finance.py +++ /dev/null @@ -1,248 +0,0 @@ -import os -from pathlib import Path - -import pytest -from pytest import approx - -from h2integrate import EXAMPLE_DIR -from h2integrate.core.dict_utils import update_defaults -from h2integrate.tools.profast_tools import ( - run_profast, - create_years_of_operation, - create_and_populate_profast, -) -from h2integrate.core.h2integrate_model import H2IntegrateModel -from h2integrate.core.inputs.validation import load_yaml - - -def test_calc_financial_parameter_weighted_average_by_capex(subtests): - from h2integrate.tools.eco.finance import calc_financial_parameter_weighted_average_by_capex - - with subtests.test("single value"): - h2integrate_config = {"finance_parameters": {"discount_rate": 0.1}} - - assert ( - calc_financial_parameter_weighted_average_by_capex( - "discount_rate", h2integrate_config=h2integrate_config, capex_breakdown={} - ) - == 0.1 - ) - - with subtests.test("weighted average value - all values specified"): - h2integrate_config = {"finance_parameters": {"discount_rate": {"wind": 0.05, "solar": 0.1}}} - - capex_breakdown = {"wind": 1e9, "solar": 1e8} - - return_value = calc_financial_parameter_weighted_average_by_capex( - "discount_rate", h2integrate_config=h2integrate_config, capex_breakdown=capex_breakdown - ) - - assert return_value == approx(0.05454545454545454) - - with subtests.test("weighted average value - not all values specified"): - h2integrate_config = { - "finance_parameters": {"discount_rate": {"wind": 0.05, "solar": 0.1, "general": 0.15}} - } - - capex_breakdown = {"wind": 1e9, "solar": 1e8, "electrolyzer": 3e8, "battery": 2e8} - - return_value = calc_financial_parameter_weighted_average_by_capex( - "discount_rate", h2integrate_config=h2integrate_config, capex_breakdown=capex_breakdown - ) - - assert return_value == approx(0.084375) - - -def test_variable_om_no_escalation(subtests): - os.chdir(EXAMPLE_DIR / "02_texas_ammonia") - - inflation_rate = 0.0 - # Create a H2Integrate model - model = H2IntegrateModel(Path.cwd() / "02_texas_ammonia.yaml") - - # Run the model - model.run() - - model.post_process() - - with subtests.test("Check original LCOH with zero escalation"): - assert ( - pytest.approx(model.prob.get_val("finance_subgroup_hydrogen.LCOH")[0], rel=1e-3) - == 3.9705799099 - ) - - outputs_dir = Path.cwd() / "outputs" - - yaml_fpath = outputs_dir / "profast_output_hydrogen_config.yaml" - - pf_dict = load_yaml(yaml_fpath) - - plant_life = int(pf_dict["params"]["operating life"]) - - years_of_operation = create_years_of_operation( - plant_life, - pf_dict["params"]["analysis start year"], - pf_dict["params"]["installation months"], - ) - - pf_dict = update_defaults(pf_dict, "escalation", inflation_rate) - pf_dict["params"].update({"general inflation rate": inflation_rate}) - - water_cost_per_gal = 0.003 # [$/gal] - gal_water_pr_kg_H2 = 3.8 # [gal H2O / kg-H2] - - # calculate annual water cost - annual_h2_kg = pf_dict["params"]["capacity"] * pf_dict["params"]["long term utilization"] * 365 - annual_water_gal = annual_h2_kg * gal_water_pr_kg_H2 - annual_water_cost_USD_per_kg = annual_water_gal * water_cost_per_gal / annual_h2_kg - water_feedstock_entry = { - "Water": { - "escalation": inflation_rate, - "unit": "$/kg", - "usage": 1.0, - "cost": annual_water_cost_USD_per_kg, - } - } - - # update feedstock entry - pf_dict["feedstocks"].update(water_feedstock_entry) - - # run profast for feedstock cost as a scalar - pf = create_and_populate_profast(pf_dict) - sol_scalar, summary_scalar, price_breakdown_scalar = run_profast(pf) - - with subtests.test( - "Check variable o&m as scalar LCOH against original LCOH with zero escalation" - ): - assert sol_scalar["price"] > model.prob.get_val("finance_subgroup_hydrogen.LCOH")[0] - - with subtests.test("Check variable o&m as scalar LCOH with zero escalation value"): - assert pytest.approx(sol_scalar["price"], rel=1e-3) == 3.98205152215 - - # create water cost entry as array - annual_water_cost_USD_per_year = [annual_water_cost_USD_per_kg] * plant_life - annual_water_cost_USD_per_year_dict = dict( - zip(years_of_operation, annual_water_cost_USD_per_year) - ) - water_feedstock_entry = { - "Water": { - "escalation": inflation_rate, - "unit": "$/kg", - "usage": 1.0, - "cost": annual_water_cost_USD_per_year_dict, - } - } - - # update feedstock entry - pf_dict["feedstocks"].update(water_feedstock_entry) - - pf = create_and_populate_profast(pf_dict) - sol_list, summary_list, price_breakdown_list = run_profast(pf) - with subtests.test( - "Check variable o&m as array LCOH against original LCOH with zero escalation" - ): - assert sol_list["price"] > model.prob.get_val("finance_subgroup_hydrogen.LCOH")[0] - - with subtests.test("Check variable o&m as array LCOH with zero escalation value"): - assert pytest.approx(sol_list["price"], rel=1e-3) == 3.98205152215 - - with subtests.test( - "Check variable o&m as scalar and as array have same LCOH with zero escalation" - ): - assert pytest.approx(sol_list["price"], rel=1e-6) == sol_scalar["price"] - - -def test_variable_om_with_escalation(subtests): - os.chdir(EXAMPLE_DIR / "02_texas_ammonia") - - inflation_rate = 0.025 - # Create a H2Integrate model - model = H2IntegrateModel(Path.cwd() / "02_texas_ammonia.yaml") - - # Run the model - model.run() - - outputs_dir = Path.cwd() / "outputs" - - yaml_fpath = outputs_dir / "profast_output_hydrogen_config.yaml" - - # load the profast dictionary - pf_dict = load_yaml(yaml_fpath) - - plant_life = int(pf_dict["params"]["operating life"]) - - years_of_operation = create_years_of_operation( - plant_life, - pf_dict["params"]["analysis start year"], - pf_dict["params"]["installation months"], - ) - - # update the inflation rate - pf_dict = update_defaults(pf_dict, "escalation", inflation_rate) - pf_dict["params"].update({"general inflation rate": inflation_rate}) - - # rerun profast without variable o&m costs - pf = create_and_populate_profast(pf_dict) - sol_init, summary_init, price_breakdown_init = run_profast(pf) - - with subtests.test("Check original LCOH with escalation"): - assert pytest.approx(sol_init["price"], rel=1e-3) == 2.9981730 - - # calculate annual water cost - water_cost_per_gal = 0.003 # [$/gal] - gal_water_pr_kg_H2 = 3.8 # [gal H2O / kg-H2] - annual_h2_kg = pf_dict["params"]["capacity"] * pf_dict["params"]["long term utilization"] * 365 - annual_water_gal = annual_h2_kg * gal_water_pr_kg_H2 - - # calculate water cost per kg H2 - annual_water_cost_USD_per_kg = annual_water_gal * water_cost_per_gal / annual_h2_kg - water_feedstock_entry = { - "Water": { - "escalation": inflation_rate, - "unit": "$/kg", - "usage": 1.0, - "cost": annual_water_cost_USD_per_kg, - } - } - - # update feedstock entry - pf_dict["feedstocks"].update(water_feedstock_entry) - - # run profast for feedstock cost as a scalar - pf = create_and_populate_profast(pf_dict) - sol_scalar, summary_scalar, price_breakdown_scalar = run_profast(pf) - - with subtests.test("Check variable o&m as scalar LCOH against original LCOH with escalation"): - assert sol_scalar["price"] > sol_init["price"] - - with subtests.test("Check variable o&m as scalar LCOH with escalation value"): - assert pytest.approx(sol_scalar["price"], rel=1e-3) == 3.00964412171 - - # calculate water cost per kg-H2 and format for costs per year - annual_water_cost_USD_per_year = [annual_water_cost_USD_per_kg] * plant_life - annual_water_cost_USD_per_year_dict = dict( - zip(years_of_operation, annual_water_cost_USD_per_year) - ) - water_feedstock_entry = { - "Water": { - "escalation": inflation_rate, - "unit": "$/kg", - "usage": 1.0, - "cost": annual_water_cost_USD_per_year_dict, - } - } - - # update feedstock entry - pf_dict["feedstocks"].update(water_feedstock_entry) - - # run profast for feedstock cost as an array - pf = create_and_populate_profast(pf_dict) - sol_list, summary_list, price_breakdown_list = run_profast(pf) - with subtests.test("Check variable o&m as array LCOH against original LCOH with escalation"): - assert sol_list["price"] > sol_init["price"] - - with subtests.test("Check variable o&m as array LCOH with escalation value"): - assert pytest.approx(sol_list["price"], rel=1e-3) == 3.0062575558 - - with subtests.test("Check variable o&m as array LCOH is less than variable o&m as scalar LCOH"): - assert sol_scalar["price"] > sol_list["price"] diff --git a/tests/h2integrate/test_h2integrate_utilities.py b/tests/h2integrate/test_h2integrate_utilities.py deleted file mode 100644 index 8ce345ea1..000000000 --- a/tests/h2integrate/test_h2integrate_utilities.py +++ /dev/null @@ -1,43 +0,0 @@ -from pytest import raises - -from h2integrate.tools.eco.utilities import ceildiv, visualize_plant - - -def test_visualize_plant(subtests): - with subtests.test("'visualize_plant()' only works with the 'floris' wind model"): - hopp_config = {"technologies": {"wind": {"model_name": "pysam"}}} - with raises(NotImplementedError, match="only works with the 'floris' wind model"): - visualize_plant( - hopp_config, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - ) - - -def test_ceildiv(subtests): - with subtests.test("ceildiv"): - a = 8 - b = 3 - - assert ceildiv(a, b) == 3 - - with subtests.test("ceildiv with one negative value"): - a = 8 - b = -3 - - assert ceildiv(a, b) == -2 - - with subtests.test("ceildiv with two negative values"): - a = -8 - b = -3 - - assert ceildiv(a, b) == 3 diff --git a/tests/h2integrate/test_steel.py b/tests/h2integrate/test_steel.py deleted file mode 100644 index b63956eb4..000000000 --- a/tests/h2integrate/test_steel.py +++ /dev/null @@ -1,298 +0,0 @@ -import copy - -from pytest import approx, raises, fixture - -from h2integrate.simulation.technologies.steel 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, -} - -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, -} - - -@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(617972269.2565368) - with subtests.test("Fixed OpEx"): - assert res.total_fixed_operating_cost == approx(104244740.28004119) - with subtests.test("Installation"): - assert res.installation_cost == approx(209403678.7623758) - - -def test_steel_finance_model(cost_config): - # Parameter -> Hydrogen/Steel/Ammonia - 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 = 1003.6498479621724 - - res: steel.SteelFinanceModelOutputs = steel.run_steel_finance_model(config) - - assert res.sol.get("price") == lcos_expected - - -def test_steel_size_h2_input(subtests): - config = steel.SteelCapacityModelConfig( - hydrogen_amount_kgpy=73288888.8888889, - input_capacity_factor_estimate=0.9, - feedstocks=steel.Feedstocks(natural_gas_prices=ng_prices_dict, oxygen_market_price=0), - ) - - res: steel.SteelCapacityModelOutputs = steel.run_size_steel_plant_capacity(config) - - with subtests.test("steel plant size"): - assert res.steel_plant_capacity_mtpy == approx(1000000) - with subtests.test("hydrogen input"): - assert res.hydrogen_amount_kgpy == approx(73288888.8888889) - - -def test_steel_size_steel_input(subtests): - config = steel.SteelCapacityModelConfig( - desired_steel_mtpy=1000000, - input_capacity_factor_estimate=0.9, - feedstocks=steel.Feedstocks(natural_gas_prices=ng_prices_dict, oxygen_market_price=0), - ) - - res: steel.SteelCapacityModelOutputs = steel.run_size_steel_plant_capacity(config) - - with subtests.test("steel plant size"): - assert res.steel_plant_capacity_mtpy == approx(1111111.111111111) - with subtests.test("hydrogen input"): - assert res.hydrogen_amount_kgpy == approx(73288888.8888889) - - -def test_run_steel_full_model(subtests): - config = { - "steel": { - "capacity": { - "input_capacity_factor_estimate": 0.9, - "desired_steel_mtpy": 1000000, - }, - "costs": { - "operational_year": 2035, - "o2_heat_integration": False, - "feedstocks": { - "natural_gas_prices": ng_prices_dict, - "oxygen_market_price": 0, - }, - "lcoh": 4.2986685034417045, - }, - "finances": { - "plant_life": 30, - "lcoh": 4.2986685034417045, - "grid_prices": grid_prices_dict, - "financial_assumptions": financial_assumptions, - }, - } - } - - res = steel.run_steel_full_model(config) - - with subtests.test("output length"): - assert len(res) == 3 - - with subtests.test("h2 mass per year"): - assert res[0].hydrogen_amount_kgpy == approx(73288888.8888889) - - with subtests.test("plant cost"): - assert res[1].total_plant_cost == approx(627667493.7760644) - with subtests.test("Installation"): - assert res[1].installation_cost == approx(212913296.16069925) - with subtests.test("steel price"): - assert res[2].sol.get("price") == approx(1000.0534906485253) - - -def test_run_steel_full_model_changing_lcoh(subtests): - config_0 = { - "steel": { - "capacity": { - "input_capacity_factor_estimate": 0.9, - "desired_steel_mtpy": 1000000, - }, - "costs": { - "operational_year": 2035, - "o2_heat_integration": False, - "feedstocks": { - "natural_gas_prices": ng_prices_dict, - "oxygen_market_price": 0, - }, - "lcoh": 4.2986685034417045, - }, - "finances": { - "plant_life": 30, - "lcoh": 4.2986685034417045, - "grid_prices": grid_prices_dict, - "financial_assumptions": financial_assumptions, - }, - } - } - - config_1 = copy.deepcopy(config_0) - config_1["steel"]["costs"]["lcoh"] = 20.0 - config_1["steel"]["finances"]["lcoh"] = 20.0 - - res0 = steel.run_steel_full_model(config_0) - res1 = steel.run_steel_full_model(config_1) - - with subtests.test("output length 0"): - assert len(res0) == 3 - with subtests.test("output length 1"): - assert len(res1) == 3 - with subtests.test("res0 res1 equal h2 mass per year"): - assert res0[0].hydrogen_amount_kgpy == res1[0].hydrogen_amount_kgpy - with subtests.test("res0 res1 equal plant cost"): - assert res0[1].total_plant_cost == res1[1].total_plant_cost - with subtests.test("res0 price lt res1 price"): - assert res0[2].sol.get("price") < res1[2].sol.get("price") - with subtests.test("raise value error when LCOH values do not match"): - config_1["steel"]["finances"]["lcoh"] = 40.0 - with raises(ValueError, match="steel cost LCOH and steel finance LCOH are not equal"): - res1 = steel.run_steel_full_model(config_1) - - -def test_run_steel_full_model_changing_feedstock_transport_costs(subtests): - config = { - "steel": { - "capacity": { - "input_capacity_factor_estimate": 0.9, - "desired_steel_mtpy": 1000000, - }, - "costs": { - "operational_year": 2035, - "o2_heat_integration": False, - "feedstocks": { - "natural_gas_prices": ng_prices_dict, - "oxygen_market_price": 0, - "lime_transport_cost": 47.72, - "carbon_transport_cost": 64.91, - "iron_ore_pellet_transport_cost": 0.63, - }, - "lcoh": 4.2986685034417045, - }, - "finances": { - "plant_life": 30, - "lcoh": 4.2986685034417045, - "grid_prices": grid_prices_dict, - "financial_assumptions": financial_assumptions, - }, - } - } - - res = steel.run_steel_full_model(config) - - with subtests.test("plant cost"): - assert res[1].total_plant_cost == approx(627667493.7760644) - - with subtests.test("Installation"): - assert res[1].installation_cost == approx(213896544.47120154) - - with subtests.test("steel price"): - assert res[2].sol.get("price") == approx(1005.7008348727317) From 7b2a6fccacd7cc8e3e1514d88ceeacc1344902a4 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Fri, 17 Oct 2025 13:39:30 -0600 Subject: [PATCH 02/30] WIP working through steel --- examples/01_onshore_steel_mn/tech_config.yaml | 184 ++-- h2integrate/converters/steel/steel.py | 522 ++++++++-- .../simulation/technologies/steel/__init__.py | 0 .../simulation/technologies/steel/steel.py | 951 ------------------ 4 files changed, 550 insertions(+), 1107 deletions(-) delete mode 100644 h2integrate/simulation/technologies/steel/__init__.py delete mode 100644 h2integrate/simulation/technologies/steel/steel.py diff --git a/examples/01_onshore_steel_mn/tech_config.yaml b/examples/01_onshore_steel_mn/tech_config.yaml index fd4775e5e..454b4246b 100644 --- a/examples/01_onshore_steel_mn/tech_config.yaml +++ b/examples/01_onshore_steel_mn/tech_config.yaml @@ -67,102 +67,100 @@ technologies: shared_parameters: capacity_factor: 0.9 plant_capacity_mtpy: 1000000. - cost_parameters: operational_year: 2035 o2_heat_integration: false lcoh: 7.37 - inflation_rate: installation_time: 36 # months inflation_rate: 0.0 # 0 for nominal analysis - feedstocks: - oxygen_market_price: 0.0 # 0.03 $/kgO2 if `o2_heat_integration` == 1 - excess_oxygen: 395 - lime_unitcost: 122.1 - carbon_unitcost: 236.97 - electricity_cost: 48.92 - iron_ore_pellet_unitcost: 207.35 - oxygen_market_price: 0.03 - raw_water_unitcost: 0.59289 - iron_ore_consumption: 1.62927 - raw_water_consumption: 0.80367 - lime_consumption: 0.01812 - carbon_consumption: 0.0538 - hydrogen_consumption: 0.06596 - natural_gas_consumption: 0.71657 - electricity_consumption: 0.5502 - slag_disposal_unitcost: 37.63 - slag_production: 0.17433 - maintenance_materials_unitcost: 7.72 - natural_gas_prices: - "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 - finances: - # plant_life: 30 - 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 - - # Additional parameters passed to ProFAST - 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 + # Feedstock parameters (flattened) + excess_oxygen: 395 + lime_unitcost: 122.1 + lime_transport_cost: 0.0 + carbon_unitcost: 236.97 + carbon_transport_cost: 0.0 + electricity_cost: 48.92 + iron_ore_pellet_unitcost: 207.35 + iron_ore_pellet_transport_cost: 0.0 + oxygen_market_price: 0.03 + raw_water_unitcost: 0.59289 + iron_ore_consumption: 1.62927 + raw_water_consumption: 0.80367 + lime_consumption: 0.01812 + carbon_consumption: 0.0538 + hydrogen_consumption: 0.06596 + natural_gas_consumption: 0.71657 + electricity_consumption: 0.5502 + slag_disposal_unitcost: 37.63 + slag_production: 0.17433 + maintenance_materials_unitcost: 7.72 + natural_gas_prices: + "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 + # Financial parameters (flattened) + 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: + "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 diff --git a/h2integrate/converters/steel/steel.py b/h2integrate/converters/steel/steel.py index ba8fdab27..96dae23bc 100644 --- a/h2integrate/converters/steel/steel.py +++ b/h2integrate/converters/steel/steel.py @@ -1,3 +1,4 @@ +import ProFAST from attrs import field, define from h2integrate.core.utilities import BaseConfig, merge_shared_inputs @@ -6,14 +7,6 @@ SteelCostBaseClass, SteelPerformanceBaseClass, ) -from h2integrate.simulation.technologies.steel.steel import ( - Feedstocks, - SteelCostModelConfig, - SteelFinanceModelConfig, - run_steel_model, - run_steel_cost_model, - run_steel_finance_model, -) @define @@ -22,6 +15,50 @@ class SteelPerformanceModelConfig(BaseConfig): capacity_factor: float = field() +@define +class SteelCostAndFinancialModelConfig(BaseConfig): + installation_time: int = field() + inflation_rate: float = field() + operational_year: int = field() + plant_capacity_mtpy: float = field() + capacity_factor: float = field() + o2_heat_integration: bool = field() + lcoh: float = field() + cost_year: int = field(default=2022, converter=int, validator=must_equal(2022)) + + # Feedstock parameters - flattened from the nested structure + natural_gas_prices: dict = field() + excess_oxygen: float = field(default=395) + lime_unitcost: float = field(default=122.1) + lime_transport_cost: float = field(default=0.0) + carbon_unitcost: float = field(default=236.97) + carbon_transport_cost: float = field(default=0.0) + electricity_cost: float = field(default=48.92) + iron_ore_pellet_unitcost: float = field(default=207.35) + iron_ore_pellet_transport_cost: float = field(default=0.0) + oxygen_market_price: float = field(default=0.03) + raw_water_unitcost: float = field(default=0.59289) + iron_ore_consumption: float = field(default=1.62927) + raw_water_consumption: float = field(default=0.80367) + lime_consumption: float = field(default=0.01812) + carbon_consumption: float = field(default=0.0538) + hydrogen_consumption: float = field(default=0.06596) + natural_gas_consumption: float = field(default=0.71657) + electricity_consumption: float = field(default=0.5502) + slag_disposal_unitcost: float = field(default=37.63) + slag_production: float = field(default=0.17433) + maintenance_materials_unitcost: float = field(default=7.72) + + # Financial parameters - flattened from the nested structure + grid_prices: dict = field() + financial_assumptions: dict = field() + + +def run_steel_model(plant_capacity_mtpy: float, plant_capacity_factor: float) -> float: + """Calculate annual steel production.""" + return plant_capacity_mtpy * plant_capacity_factor + + class SteelPerformanceModel(SteelPerformanceBaseClass): """ An OpenMDAO component for modeling the performance of an steel plant. @@ -45,20 +82,6 @@ def compute(self, inputs, outputs): outputs["steel"] = steel_production_mtpy / len(inputs["electricity_in"]) -@define -class SteelCostAndFinancialModelConfig(BaseConfig): - installation_time: int = field() - inflation_rate: float = field() - operational_year: int = field() - plant_capacity_mtpy: float = field() - capacity_factor: float = field() - o2_heat_integration: bool = field() - lcoh: float = field() - feedstocks: dict = field() # TODO: build validator for this large dictionary - finances: dict = field() # TODO: build validator for this large dictionary - cost_year: int = field(default=2022, converter=int, validator=must_equal(2022)) - - class SteelCostAndFinancialModel(SteelCostBaseClass): """ An OpenMDAO component for calculating the costs associated with steel production. @@ -73,50 +96,423 @@ def setup(self): merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") ) super().setup() - # TODO Bring the steel cost model config and feedstock classes into new h2integrate - self.cost_config = SteelCostModelConfig( - operational_year=self.config.operational_year, - feedstocks=Feedstocks(**self.config.feedstocks), - plant_capacity_mtpy=self.config.plant_capacity_mtpy, - lcoh=self.config.lcoh, - ) - # TODO Review whether to split plant and finance_parameters configs or combine somehow self.add_input("steel_production_mtpy", val=0.0, units="t/year") - self.add_output("LCOS", val=0.0, units="USD/t") def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - config = self.cost_config - - config.lcoh = inputs["LCOH"] + # Update config with runtime inputs + self.config.lcoh = inputs["LCOH"] if inputs["electricity_cost"] > 0: - self.config.feedstocks.update({"electricity_cost": inputs["electricity_cost"][0]}) - - cost_model_outputs = run_steel_cost_model(config) - - outputs["CapEx"] = cost_model_outputs.total_plant_cost - outputs["OpEx"] = cost_model_outputs.total_fixed_operating_cost - - # TODO Bring this config dict into new_h2integrate from old h2integrate - finance_config = SteelFinanceModelConfig( - plant_life=self.options["plant_config"]["plant"]["plant_life"], - plant_capacity_mtpy=self.config.plant_capacity_mtpy, - plant_capacity_factor=self.config.capacity_factor, - steel_production_mtpy=inputs["steel_production_mtpy"], - lcoh=config.lcoh, - grid_prices=self.config.finances["grid_prices"], - feedstocks=Feedstocks(**self.config.feedstocks), - costs=cost_model_outputs, - o2_heat_integration=self.config.o2_heat_integration, - financial_assumptions=self.config.finances["financial_assumptions"], - install_years=int(self.config.installation_time / 12), - gen_inflation=self.config.inflation_rate, - save_plots=False, - show_plots=False, - output_dir="./output/", - design_scenario_id=0, - ) - - finance_model_outputs = run_steel_finance_model(finance_config) - outputs["LCOS"] = finance_model_outputs.sol.get("price") + self.config.electricity_cost = inputs["electricity_cost"][0] + + # Calculate steel production costs directly + model_year_CEPCI = 816.0 # 2022 + equation_year_CEPCI = 708.8 # 2021 + + capex_eaf_casting = ( + model_year_CEPCI + / equation_year_CEPCI + * 352191.5237 + * self.config.plant_capacity_mtpy**0.456 + ) + capex_shaft_furnace = ( + model_year_CEPCI + / equation_year_CEPCI + * 489.68061 + * self.config.plant_capacity_mtpy**0.88741 + ) + capex_oxygen_supply = ( + model_year_CEPCI + / equation_year_CEPCI + * 1715.21508 + * self.config.plant_capacity_mtpy**0.64574 + ) + if self.config.o2_heat_integration: + capex_h2_preheating = ( + model_year_CEPCI + / equation_year_CEPCI + * (1 - 0.4) + * (45.69123 * self.config.plant_capacity_mtpy**0.86564) + ) + capex_cooling_tower = ( + model_year_CEPCI + / equation_year_CEPCI + * (1 - 0.3) + * (2513.08314 * self.config.plant_capacity_mtpy**0.63325) + ) + else: + capex_h2_preheating = ( + model_year_CEPCI + / equation_year_CEPCI + * 45.69123 + * self.config.plant_capacity_mtpy**0.86564 + ) + capex_cooling_tower = ( + model_year_CEPCI + / equation_year_CEPCI + * 2513.08314 + * self.config.plant_capacity_mtpy**0.63325 + ) + + capex_piping = ( + model_year_CEPCI + / equation_year_CEPCI + * 11815.72718 + * self.config.plant_capacity_mtpy**0.59983 + ) + capex_elec_instr = ( + model_year_CEPCI + / equation_year_CEPCI + * 7877.15146 + * self.config.plant_capacity_mtpy**0.59983 + ) + capex_buildings_storage_water = ( + model_year_CEPCI + / equation_year_CEPCI + * 1097.81876 + * self.config.plant_capacity_mtpy**0.8 + ) + capex_misc = ( + model_year_CEPCI + / equation_year_CEPCI + * 7877.1546 + * self.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 + * ((self.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) + ) + + (self.config.maintenance_materials_unitcost * self.config.plant_capacity_mtpy / 12) + ( + self.config.plant_capacity_mtpy + * ( + self.config.raw_water_consumption * self.config.raw_water_unitcost + + self.config.lime_consumption + * (self.config.lime_unitcost + self.config.lime_transport_cost) + + self.config.carbon_consumption + * (self.config.carbon_unitcost + self.config.carbon_transport_cost) + + self.config.iron_ore_consumption + * ( + self.config.iron_ore_pellet_unitcost + + self.config.iron_ore_pellet_transport_cost + ) + ) + / 12 + ) + + ( + self.config.plant_capacity_mtpy + * self.config.slag_disposal_unitcost + * self.config.slag_production + / 12 + ) + + ( + self.config.plant_capacity_mtpy + * ( + self.config.hydrogen_consumption * self.config.lcoh * 1000 + + self.config.natural_gas_consumption + * self.config.natural_gas_prices[str(self.config.operational_year)] + + self.config.electricity_consumption * self.config.electricity_cost + ) + / 12 + ) + two_percent_tpc = 0.02 * total_plant_cost + + fuel_consumables_60day_supply_cost = ( + self.config.plant_capacity_mtpy + * ( + self.config.raw_water_consumption * self.config.raw_water_unitcost + + self.config.lime_consumption + * (self.config.lime_unitcost + self.config.lime_transport_cost) + + self.config.carbon_consumption + * (self.config.carbon_unitcost + self.config.carbon_transport_cost) + + self.config.iron_ore_consumption + * ( + self.config.iron_ore_pellet_unitcost + + self.config.iron_ore_pellet_transport_cost + ) + ) + / 365 + * 60 + ) + + spare_parts_cost = 0.005 * total_plant_cost + land_cost = 0.775 * self.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 + ) + + outputs["CapEx"] = total_plant_cost + outputs["OpEx"] = total_fixed_operating_cost + + # Run finance model directly using ProFAST + pf = ProFAST.ProFAST("blank") + + # Apply all params passed through from config + for param, val in self.config.financial_assumptions.items(): + pf.set_params(param, val) + + analysis_start = int([*self.config.grid_prices][0]) - int( + self.config.installation_time / 12 + ) + plant_life = self.options["plant_config"]["plant"]["plant_life"] + + # Fill these in - can have most of them as 0 also + pf.set_params( + "commodity", + { + "name": "Steel", + "unit": "metric tons", + "initial price": 1000, + "escalation": self.config.inflation_rate, + }, + ) + pf.set_params("capacity", self.config.plant_capacity_mtpy / 365) # units/day + pf.set_params("maintenance", {"value": 0, "escalation": self.config.inflation_rate}) + pf.set_params("analysis start year", analysis_start) + pf.set_params("operating life", plant_life) + pf.set_params("installation months", self.config.installation_time) + pf.set_params( + "installation cost", + { + "value": installation_cost, + "depr type": "Straight line", + "depr period": 4, + "depreciable": False, + }, + ) + pf.set_params("non depr assets", land_cost) + pf.set_params( + "end of proj sale non depr assets", + land_cost * (1 + self.config.inflation_rate) ** plant_life, + ) + pf.set_params("demand rampup", 5.3) + pf.set_params("long term utilization", self.config.capacity_factor) + pf.set_params("credit card fees", 0) + pf.set_params("sales tax", 0) + pf.set_params("license and permit", {"value": 00, "escalation": self.config.inflation_rate}) + pf.set_params("rent", {"value": 0, "escalation": self.config.inflation_rate}) + 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", self.config.inflation_rate) + 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=capex_eaf_casting, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Shaft Furnace", + cost=capex_shaft_furnace, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Oxygen Supply", + cost=capex_oxygen_supply, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="H2 Pre-heating", + cost=capex_h2_preheating, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Cooling Tower", + cost=capex_cooling_tower, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Piping", + cost=capex_piping, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Electrical & Instrumentation", + cost=capex_elec_instr, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Buildings, Storage, Water Service", + cost=capex_buildings_storage_water, + depr_type="MACRS", + depr_period=7, + refurb=[0], + ) + pf.add_capital_item( + name="Other Miscellaneous Costs", + cost=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=labor_cost_annual_operation, + escalation=self.config.inflation_rate, + ) + pf.add_fixed_cost( + name="Maintenance Labor Cost", + usage=1, + unit="$/year", + cost=labor_cost_maintenance, + escalation=self.config.inflation_rate, + ) + pf.add_fixed_cost( + name="Administrative & Support Labor Cost", + usage=1, + unit="$/year", + cost=labor_cost_admin_support, + escalation=self.config.inflation_rate, + ) + pf.add_fixed_cost( + name="Property tax and insurance", + usage=1, + unit="$/year", + cost=property_tax_insurance, + escalation=0.0, + ) + + # Add feedstocks + pf.add_feedstock( + name="Maintenance Materials", + usage=1.0, + unit="Units per metric ton of steel", + cost=self.config.maintenance_materials_unitcost, + escalation=self.config.inflation_rate, + ) + pf.add_feedstock( + name="Raw Water Withdrawal", + usage=self.config.raw_water_consumption, + unit="metric tons of water per metric ton of steel", + cost=self.config.raw_water_unitcost, + escalation=self.config.inflation_rate, + ) + pf.add_feedstock( + name="Lime", + usage=self.config.lime_consumption, + unit="metric tons of lime per metric ton of steel", + cost=(self.config.lime_unitcost + self.config.lime_transport_cost), + escalation=self.config.inflation_rate, + ) + pf.add_feedstock( + name="Carbon", + usage=self.config.carbon_consumption, + unit="metric tons of carbon per metric ton of steel", + cost=(self.config.carbon_unitcost + self.config.carbon_transport_cost), + escalation=self.config.inflation_rate, + ) + pf.add_feedstock( + name="Iron Ore", + usage=self.config.iron_ore_consumption, + unit="metric tons of iron ore per metric ton of steel", + cost=( + self.config.iron_ore_pellet_unitcost + self.config.iron_ore_pellet_transport_cost + ), + escalation=self.config.inflation_rate, + ) + pf.add_feedstock( + name="Hydrogen", + usage=self.config.hydrogen_consumption, + unit="metric tons of hydrogen per metric ton of steel", + cost=self.config.lcoh * 1000, + escalation=self.config.inflation_rate, + ) + pf.add_feedstock( + name="Natural Gas", + usage=self.config.natural_gas_consumption, + unit="GJ-LHV per metric ton of steel", + cost=self.config.natural_gas_prices, + escalation=self.config.inflation_rate, + ) + pf.add_feedstock( + name="Electricity", + usage=self.config.electricity_consumption, + unit="MWh per metric ton of steel", + cost=self.config.grid_prices, + escalation=self.config.inflation_rate, + ) + pf.add_feedstock( + name="Slag Disposal", + usage=self.config.slag_production, + unit="metric tons of slag per metric ton of steel", + cost=self.config.slag_disposal_unitcost, + escalation=self.config.inflation_rate, + ) + + pf.add_coproduct( + name="Oxygen sales", + usage=self.config.excess_oxygen, + unit="kg O2 per metric ton of steel", + cost=self.config.oxygen_market_price, + escalation=self.config.inflation_rate, + ) + + # Solve + sol = pf.solve_price() + + outputs["LCOS"] = sol.get("price") diff --git a/h2integrate/simulation/technologies/steel/__init__.py b/h2integrate/simulation/technologies/steel/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/h2integrate/simulation/technologies/steel/steel.py b/h2integrate/simulation/technologies/steel/steel.py deleted file mode 100644 index 75037af97..000000000 --- a/h2integrate/simulation/technologies/steel/steel.py +++ /dev/null @@ -1,951 +0,0 @@ -from __future__ import annotations - -import copy -from pathlib import Path - -import pandas as pd -import ProFAST -from attrs import Factory, field, define - - -@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 ton of lime ($/metric ton). - lime_transport_cost (float): - Cost to transport lime per metric ton of lime ($/metric ton). - carbon_unitcost (float): - Cost per metric ton of carbon ($/metric ton). - carbon_transport_cost (float): - Cost to transport carbon per metric ton of carbon ($/metric ton). - electricity_cost (float): - Electricity cost per metric ton of steel production ($/metric ton). - iron_ore_pellet_unitcost (float): - Cost per metric ton of iron ore ($/metric ton). - iron_ore_pellet_transport_cost (float): - Cost to transport iron ore per metric ton of iron ore ($/metric ton). - oxygen_market_price (float): - Market price per kg of oxygen ($/kgO2). - raw_water_unitcost (float): - Cost per metric ton of raw water ($/metric ton). - iron_ore_consumption (float): - Iron ore consumption per metric ton of steel production (metric tons). - raw_water_consumption (float): - Raw water consumption per metric ton of steel production (metric tons). - lime_consumption (float): - Lime consumption per metric ton of steel production (metric tons). - carbon_consumption (float): - Carbon consumption per metric ton of steel production (metric tons). - hydrogen_consumption (float): - Hydrogen consumption per metric ton of steel production (metric tons). - natural_gas_consumption (float): - Natural gas consumption per metric ton of steel production (GJ-LHV). - electricity_consumption (float): - Electricity consumption per metric ton of steel production (MWh). - slag_disposal_unitcost (float): - Cost per metric ton of slag disposal ($/metric ton). - slag_production (float): - Slag production per metric ton of steel production (metric tons). - maintenance_materials_unitcost (float): - Cost per metric ton of annual steel slab production at real capacity - factor ($/metric ton). - """ - - natural_gas_prices: dict[str, float] - excess_oxygen: float = 395 - lime_unitcost: float = 122.1 - lime_transport_cost: float = 0.0 # USD/metric ton lime - carbon_unitcost: float = 236.97 - carbon_transport_cost: float = 0.0 # USD/metric ton carbon - electricity_cost: float = 48.92 - iron_ore_pellet_unitcost: float = 207.35 - iron_ore_pellet_transport_cost: float = 0.0 # USD/metric ton iron - 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 ton of steel production. - co2_carbon_emissions (float): - CO2 emissions from carbon per metric ton of steel production. - surface_water_discharge (float): - Surface water discharge per metric ton 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 - - -@define -class SteelCapacityModelConfig: - """ - Configuration inputs for the steel capacity sizing model, including plant capacity and - feedstock details. - - Attributes: - hydrogen_amount_kgpy Optional (float): The amount of hydrogen available in kilograms - per year to make steel. - desired_steel_mtpy Optional (float): The amount of desired steel production in - metric tons per year. - input_capacity_factor_estimate (float): The estimated steel plant capacity factor. - feedstocks (Feedstocks): An instance of the `Feedstocks` class detailing the - costs and consumption rates of resources used in production. - """ - - input_capacity_factor_estimate: float - feedstocks: Feedstocks - hydrogen_amount_kgpy: float | None = field(default=None) - desired_steel_mtpy: float | None = field(default=None) - - def __attrs_post_init__(self): - if self.hydrogen_amount_kgpy is None and self.desired_steel_mtpy is None: - raise ValueError("`hydrogen_amount_kgpy` or `desired_steel_mtpy` is a required input.") - - if self.hydrogen_amount_kgpy and self.desired_steel_mtpy: - raise ValueError( - "can only select one input: `hydrogen_amount_kgpy` or `desired_steel_mtpy`." - ) - - -@define -class SteelCapacityModelOutputs: - """ - Outputs from the steel size model. - - Attributes: - steel_plant_size_mtpy (float): If amount of hydrogen in kilograms per year is input, - the size of the steel plant in metric tons per year is output. - hydrogen_amount_kgpy (float): If amount of steel production in metric tons per year is - input, the amount of necessary hydrogen feedstock in kilograms per year is output. - """ - - steel_plant_capacity_mtpy: float - hydrogen_amount_kgpy: float - - -def run_size_steel_plant_capacity( - config: SteelCapacityModelConfig, -) -> SteelCapacityModelOutputs: - """ - Calculates either the annual steel production in metric tons based on plant capacity and - available hydrogen or the amount of required hydrogen based on a desired steel production. - - Args: - config (SteelCapacityModelConfig): - Configuration object containing all necessary parameters for the capacity sizing, - including capacity factor estimate and feedstock costs. - - Returns: - SteelCapacityModelOutputs: An object containing steel plant capacity in metric tons - per year and amount of hydrogen required in kilograms per year. - - """ - - if config.hydrogen_amount_kgpy: - steel_plant_capacity_mtpy = ( - config.hydrogen_amount_kgpy - / 1000 - / config.feedstocks.hydrogen_consumption - * config.input_capacity_factor_estimate - ) - hydrogen_amount_kgpy = config.hydrogen_amount_kgpy - - if config.desired_steel_mtpy: - hydrogen_amount_kgpy = ( - config.desired_steel_mtpy - * 1000 - * config.feedstocks.hydrogen_consumption - / config.input_capacity_factor_estimate - ) - steel_plant_capacity_mtpy = ( - config.desired_steel_mtpy / config.input_capacity_factor_estimate - ) - - return SteelCapacityModelOutputs( - steel_plant_capacity_mtpy=steel_plant_capacity_mtpy, - hydrogen_amount_kgpy=hydrogen_amount_kgpy, - ) - - -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 = 816.0 # 2022 - equation_year_CEPCI = 708.8 # 2021 - - 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.lime_transport_cost) - + feedstocks.carbon_consumption - * (feedstocks.carbon_unitcost + feedstocks.carbon_transport_cost) - + feedstocks.iron_ore_consumption - * (feedstocks.iron_ore_pellet_unitcost + feedstocks.iron_ore_pellet_transport_cost) - ) - / 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.lime_transport_cost) - + feedstocks.carbon_consumption - * (feedstocks.carbon_unitcost + feedstocks.carbon_transport_cost) - + feedstocks.iron_ore_consumption - * (feedstocks.iron_ore_pellet_unitcost + feedstocks.iron_ore_pellet_transport_cost) - ) - / 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. - save_plots (bool): select whether or not to save output plots - show_plots (bool): select whether or not to show output plots during run - output_dir (str): where to store any saved plots or data - design_scenario_id (int): what design scenario the plots correspond to - """ - - 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: SteelCosts | SteelCostModelOutputs - o2_heat_integration: bool = True - financial_assumptions: dict[str, float] = Factory(dict) - install_years: int = 3 - gen_inflation: float = 0.00 - save_plots: bool = False - show_plots: bool = False - output_dir: str = "./output/" - design_scenario_id: int = 0 - - -@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 - metric ton. 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(): - pf.set_params(param, val) - - analysis_start = int([*config.grid_prices][0]) - config.install_years - - # Fill these in - can have most of them as 0 also - pf.set_params( - "commodity", - { - "name": "Steel", - "unit": "metric tons", - "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 ton 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 tons of water per metric ton of steel", - cost=feedstocks.raw_water_unitcost, - escalation=config.gen_inflation, - ) - pf.add_feedstock( - name="Lime", - usage=feedstocks.lime_consumption, - unit="metric tons of lime per metric ton of steel", - cost=(feedstocks.lime_unitcost + feedstocks.lime_transport_cost), - escalation=config.gen_inflation, - ) - pf.add_feedstock( - name="Carbon", - usage=feedstocks.carbon_consumption, - unit="metric tons of carbon per metric ton of steel", - cost=(feedstocks.carbon_unitcost + feedstocks.carbon_transport_cost), - escalation=config.gen_inflation, - ) - pf.add_feedstock( - name="Iron Ore", - usage=feedstocks.iron_ore_consumption, - unit="metric tons of iron ore per metric ton of steel", - cost=(feedstocks.iron_ore_pellet_unitcost + feedstocks.iron_ore_pellet_transport_cost), - escalation=config.gen_inflation, - ) - pf.add_feedstock( - name="Hydrogen", - usage=feedstocks.hydrogen_consumption, - unit="metric tons of hydrogen per metric ton 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 ton of steel", - cost=feedstocks.natural_gas_prices, - escalation=config.gen_inflation, - ) - pf.add_feedstock( - name="Electricity", - usage=feedstocks.electricity_consumption, - unit="MWh per metric ton of steel", - cost=config.grid_prices, - escalation=config.gen_inflation, - ) - pf.add_feedstock( - name="Slag Disposal", - usage=feedstocks.slag_production, - unit="metric tons of slag per metric ton 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 ton 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() - - if config.save_plots or config.show_plots: - output_dir = Path(config.output_dir).resolve() - savepaths = [ - output_dir / "figures/capex/", - output_dir / "figures/annual_cash_flow/", - output_dir / "figures/lcos_breakdown/", - output_dir / "data/", - ] - for savepath in savepaths: - if not savepath.exists(): - savepath.mkdir(parents=True) - - pf.plot_capital_expenses( - fileout=savepaths[0] / f"steel_capital_expense_{config.design_scenario_id}.pdf", - show_plot=config.show_plots, - ) - pf.plot_cashflow( - fileout=savepaths[1] / f"steel_cash_flow_{config.design_scenario_id}.png", - show_plot=config.show_plots, - ) - - pd.DataFrame.from_dict(data=pf.cash_flow_out).to_csv( - savepaths[3] / f"steel_cash_flow_{config.design_scenario_id}.csv" - ) - - pf.plot_costs( - savepaths[2] / f"lcos_{config.design_scenario_id}", - show_plot=config.show_plots, - ) - - return SteelFinanceModelOutputs( - sol=sol, - summary=summary, - price_breakdown=price_breakdown, - ) - - -def run_steel_full_model( - h2integrate_config: dict, - save_plots=False, - show_plots=False, - output_dir="./output/", - design_scenario_id=0, -) -> tuple[SteelCapacityModelOutputs, SteelCostModelOutputs, SteelFinanceModelOutputs]: - """ - Runs the full steel model, including capacity, cost, and finance models. - - Args: - h2integrate_config (dict): The configuration for the h2integrate model. - - Returns: - Tuple[SteelCapacityModelOutputs, SteelCostModelOutputs, SteelFinanceModelOutputs]: - A tuple containing the outputs of the steel capacity, cost, and finance models. - """ - # this is likely to change as we refactor to use config dataclasses, but for now - # we'll just copy the config and modify it as needed - config = copy.deepcopy(h2integrate_config) - - if config["steel"]["costs"]["lcoh"] != config["steel"]["finances"]["lcoh"]: - msg = ( - "steel cost LCOH and steel finance LCOH are not equal. You must specify both values" - " or neither. If neither is specified, LCOH will be calculated." - ) - raise ValueError(msg) - - steel_costs = config["steel"]["costs"] - steel_capacity = config["steel"]["capacity"] - feedstocks = Feedstocks(**steel_costs["feedstocks"]) - - # run steel capacity model to get steel plant size - # uses hydrogen amount from electrolyzer physics model - capacity_config = SteelCapacityModelConfig(feedstocks=feedstocks, **steel_capacity) - steel_capacity = run_size_steel_plant_capacity(capacity_config) - - # run steel cost model - steel_costs["feedstocks"] = feedstocks - steel_cost_config = SteelCostModelConfig( - plant_capacity_mtpy=steel_capacity.steel_plant_capacity_mtpy, **steel_costs - ) - steel_cost_config.plant_capacity_mtpy = steel_capacity.steel_plant_capacity_mtpy - steel_costs = run_steel_cost_model(steel_cost_config) - - # run steel finance model - steel_finance = config["steel"]["finances"] - steel_finance["feedstocks"] = feedstocks - - steel_finance_config = SteelFinanceModelConfig( - plant_capacity_mtpy=steel_capacity.steel_plant_capacity_mtpy, - plant_capacity_factor=capacity_config.input_capacity_factor_estimate, - steel_production_mtpy=run_steel_model( - steel_capacity.steel_plant_capacity_mtpy, - capacity_config.input_capacity_factor_estimate, - ), - costs=steel_costs, - show_plots=show_plots, - save_plots=save_plots, - output_dir=output_dir, - design_scenario_id=design_scenario_id, - **steel_finance, - ) - steel_finance = run_steel_finance_model(steel_finance_config) - - return (steel_capacity, steel_costs, steel_finance) From cb5b04cb1946f739aa03a8b53de8db32fbe07453 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Mon, 27 Oct 2025 13:58:25 -0600 Subject: [PATCH 03/30] Removed offshore --- .../technologies/offshore/__init__.py | 8 - .../technologies/offshore/all_platforms.py | 65 ---- .../offshore/example_fixed_project.yaml | 16 - .../offshore/example_floating_project.yaml | 16 - .../technologies/offshore/fixed_platform.py | 311 --------------- .../offshore/floating_platform.py | 357 ------------------ tests/h2integrate/test_offshore/__init__.py | 0 .../test_offshore/test_fixed_platform.py | 80 ---- .../test_offshore/test_floating_platform.py | 102 ----- 9 files changed, 955 deletions(-) delete mode 100644 h2integrate/simulation/technologies/offshore/__init__.py delete mode 100644 h2integrate/simulation/technologies/offshore/all_platforms.py delete mode 100644 h2integrate/simulation/technologies/offshore/example_fixed_project.yaml delete mode 100644 h2integrate/simulation/technologies/offshore/example_floating_project.yaml delete mode 100644 h2integrate/simulation/technologies/offshore/fixed_platform.py delete mode 100644 h2integrate/simulation/technologies/offshore/floating_platform.py delete mode 100644 tests/h2integrate/test_offshore/__init__.py delete mode 100644 tests/h2integrate/test_offshore/test_fixed_platform.py delete mode 100644 tests/h2integrate/test_offshore/test_floating_platform.py diff --git a/h2integrate/simulation/technologies/offshore/__init__.py b/h2integrate/simulation/technologies/offshore/__init__.py deleted file mode 100644 index 719969766..000000000 --- a/h2integrate/simulation/technologies/offshore/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from h2integrate.simulation.technologies.offshore.fixed_platform import ( - FixedPlatformDesign, - FixedPlatformInstallation, -) -from h2integrate.simulation.technologies.offshore.floating_platform import ( - FloatingPlatformDesign, - FloatingPlatformInstallation, -) diff --git a/h2integrate/simulation/technologies/offshore/all_platforms.py b/h2integrate/simulation/technologies/offshore/all_platforms.py deleted file mode 100644 index 1b4606a9f..000000000 --- a/h2integrate/simulation/technologies/offshore/all_platforms.py +++ /dev/null @@ -1,65 +0,0 @@ -import math - - -def calc_platform_opex(capex, opex_rate=0.011): - """ - Simple opex calculation based on a capex - https://www.acm.nl/sites/default/files/documents/study-on-estimation-method-for-additional-efficient-offshore-grid-opex.pdf - - Output in $USD/year - """ - - opex = capex * opex_rate # USD/year - - return opex - - -def install_platform(mass, area, distance, install_duration=14, vessel=None, foundation="fixed"): - """ - A simplified platform installation costing model. - Total Cost = install_cost * duration - Compares the mass and/or deck space of equipment to the vessel limits to determine - the number of trips. Add an additional "at sea" install duration - - """ - - # If no ORBIT vessel is defined set default values (based on ORBIT's floating_heavy_lift_vessel) - if vessel is None: - if foundation == "fixed": - # If no ORBIT vessel is defined set default values (based on ORBIT's - # example_heavy_lift_vessel) - # Default values are from [3]. - vessel_cargo_mass = 7999 # t - vessel_deck_space = 3999 # m**2 - vessel_day_rate = 500001 # USD/day - vessel_speed = 5 # km/hr - elif foundation == "floating": - # If no ORBIT vessel is defined set default values (based on ORBIT's - # floating_heavy_lift_vessel) - vessel_cargo_mass = 7999 # t - vessel_deck_space = 3999 # m**2 - vessel_day_rate = 500001 # USD/day - vessel_speed = 7 # km/hr - else: - raise ( - ValueError( - "Invalid offshore platform foundation type. Must be one of" - " ['fixed', 'floating']" - ) - ) - else: - vessel_cargo_mass = vessel.storage.max_cargo_mass # t - vessel_deck_space = vessel.storage.max_deck_space # m**2 - vessel_day_rate = vessel.day_rate # USD/day - vessel_speed = vessel.transit_speed # km/hr - - # Get the # of trips based on ships cargo/space limits - num_of_trips = math.ceil(max((mass / vessel_cargo_mass), (area / vessel_deck_space))) - - # Total duration = double the trips + install_duration - duration = (2 * num_of_trips * distance) / (vessel_speed * 24) + install_duration # days\ - - # Final install cost is obtained by using the vessel's daily rate - install_cost = vessel_day_rate * duration # USD - - return install_cost diff --git a/h2integrate/simulation/technologies/offshore/example_fixed_project.yaml b/h2integrate/simulation/technologies/offshore/example_fixed_project.yaml deleted file mode 100644 index 71a51723f..000000000 --- a/h2integrate/simulation/technologies/offshore/example_fixed_project.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Modified orbit configuration file for a single platform to carry "X technology" -design_phases: -- FixedPlatformDesign # Register Design Phase -install_phases: - FixedPlatformInstallation: 0 # Register Install Phase -oss_install_vessel: example_heavy_lift_vessel -site: - depth: 22.5 # site depth [m] - distance: 124 # distance to port [km] -equipment: - tech_required_area: 300. # equipment area [m**2] - tech_combined_mass: 1000 # equipment mass [t] - topside_design_cost: 4500000 # topside design cost [USD] - installation_duration: 14 # time at sea [days] - -# set input values to -1 to use values calculated or input in other files during H2Integrate run (depth, distance, tech_required_area, tech_combined_mass) diff --git a/h2integrate/simulation/technologies/offshore/example_floating_project.yaml b/h2integrate/simulation/technologies/offshore/example_floating_project.yaml deleted file mode 100644 index d1ae52755..000000000 --- a/h2integrate/simulation/technologies/offshore/example_floating_project.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# Modified orbit configuration file for a single platform to carry "X technology" -design_phases: -- FloatingPlatformDesign # Resgister Design Phase -install_phases: - FloatingPlatformInstallation: 0 # Register Install Phase -oss_install_vessel: example_heavy_lift_vessel -site: - depth: 500.5 # site depth [m] Site depths for floating projects need to be at depths 500 m to 1500 m because of Orbit SemiTaut branch limitations (7/31) - distance: 124 # distance to port [km] -equipment: - tech_required_area: 300. # equipment area [m**2] - tech_combined_mass: 1000 # equipment mass [t] - topside_design_cost: 4500000 # topside design cost [USD] - installation_duration: 14 # time at sea [days] - -# set input values to -1 to use values calculated or input in other files during H2Integrate run (depth, distance, tech_required_area, tech_combined_mass) diff --git a/h2integrate/simulation/technologies/offshore/fixed_platform.py b/h2integrate/simulation/technologies/offshore/fixed_platform.py deleted file mode 100644 index 842f77c86..000000000 --- a/h2integrate/simulation/technologies/offshore/fixed_platform.py +++ /dev/null @@ -1,311 +0,0 @@ -from __future__ import annotations - - -""" -Author: Nick Riccobono and Charles Kiefer -Date: 1/31/2023 -Institution: National Renewable Energy Lab -Description: This file should handles the cost and sizing of a centralized offshore platform - dedicated to hydrogen production. It has been modeled off of existing BOS cost/sizing - calculations found in ORBIT (Thank you Jake Nunemaker). It can be run as standalone functions - or as appended ORBIT project phases. - - -Sources: - - [1] ORBIT: https://github.com/WISDEM/ORBIT electrical_refactor branch - - [2] J. Nunemaker, M. Shields, R. Hammond, and P. Duffy, - “ORBIT: Offshore Renewables Balance-of-System and Installation Tool,” - NREL/TP-5000-77081, 1660132, MainId:26027, Aug. 2020. doi: 10.2172/1660132. - - [3] M. Maness, B. Maples, and A. Smith, - “NREL Offshore Balance-of-System Model,” - NREL/TP--6A20-66874, 1339522, Jan. 2017. doi: 10.2172/1339522. -Args: - - tech_required_area: (float): area needed for combination of all tech (m^2), not including - buffer or working space - - tech_combined_mass: (float): mass of all tech being placed on the platform (kg or metric tons) - - - depth: (float): bathometry at the platform location (m) - - distance: (float): distance ships must travel from port to site location (km) - - Future arguments: (Not used at this time) - - construction year (int): - - lifetime (int): lifetime of the plant in years (may not be needed) - - Assembly costs and construction on land - -Returns: - - platform_mass (float): Adjusted mass of platform + substructure - - design_capex (float): capital expenditures (platform design + substructure fabrication) - - installation_capex (float): capital expenditures (installation cost) - - platform_opex (float): the OPEX (annual, fixed) in USD for the platform - -""" - -""" -Notes: - - Thank you Jake Nunemaker's oswh2 repository!!! - - pile_cost=0 $US/metric ton for monopile construction. Not a bug, this # is - consistent with the rest of ORBIT [1]. -""" - - -import typing -from pathlib import Path - -import ORBIT as orbit - -from h2integrate.simulation.technologies.offshore.all_platforms import ( - install_platform, - calc_platform_opex, -) - - -class FixedPlatformDesign(orbit.phases.design.DesignPhase): - """ - This is a modified class based on ORBIT's [1] design phase. The implementation - is discussed in [2], Section 2.5: Offshore Substation Design. Default values originate - from [3], Appendix A: Inputs, Key Assumptions and Caveats. - """ - - # phase = "H2 Fixed Platform Design" - - # Expected inputs from config yaml file - expected_config: typing.ClassVar = { - "site": { - "distance": "int | float", - "depth": "int | float", - }, - "equipment": { - "tech_required_area": "float", - "tech_combined_mass": "float", - "topside_design_cost": "USD (optional, default:4.5e6)", - "fabrication_cost_rate": "USD/t (optional, default: 14500.)", - "substructure_steel_rate": "USD/t (optional, default: 3000.)", - }, - } - - # Takes in arguments and initialize library files - def __init__(self, config, **kwargs): - self.phase = "H2 Fixed Platform Design" - - config = self.initialize_library(config, **kwargs) - self.config = self.validate_config(config) - - self._outputs = {} - - # Runs the design cost models - def run(self): - # print("Fixed Platform Design run() is working!!!") - - self.distance = self.config["site"]["distance"] # km - self.depth = self.config["site"]["depth"] # m - - _platform = self.config.get("equipment", {}) - - self.mass = _platform.get("tech_combined_mass", 999) # t - self.area = _platform.get("tech_required_area", 1000) # m**2 - - design_cost = _platform.get("topside_design_cost", 4.5e6) # USD - fab_cost = _platform.get("fabrication_cost_rate", 14500.0) # USD/t - steel_cost = _platform.get("substructure_steel_rate", 3000) # USD/t - - # Add individual calcs/functions in the run() method - total_cost, total_mass = calc_substructure_mass_and_cost( - self.mass, self.area, self.depth, fab_cost, design_cost, steel_cost - ) - - # Create an ouput dict - self._outputs["fixed_platform"] = { - "mass": total_mass, - "area": self.area, - "total_cost": total_cost, - } - - # A design object needs to have attribute design_result and detailed_output - @property - def design_result(self): - return { - "platform_design": { - "mass": self._outputs["fixed_platform"]["mass"], - "area": self._outputs["fixed_platform"]["area"], - "total_cost": self._outputs["fixed_platform"]["total_cost"], - } - } - - @property - def detailed_output(self): - return {} - - -class FixedPlatformInstallation(orbit.phases.install.InstallPhase): - """ - This is a modified class based on ORBIT's [1] install phase. The implementation - is duscussed in [2], Section 3.6: Offshore Substation Installation. Default values - originate from [3], Appendix A: Inputs, Key Assumptions and Caveats. - """ - - # phase = "H2 Fixed Platform Installation" - - # Expected inputs from config yaml file - expected_config: typing.ClassVar = { - "site": { - "distance": "int | float", - "depth": "int | float", - }, - "equipment": { - "tech_required_area": "float", - "tech_combined_mass": "float", - "install_duration": "days (optional, default: 14)", - }, - "oss_install_vessel": "str | dict", - } - - # Need to initialize arguments and weather files - def __init__(self, config, weather=None, **kwargs): - super().__init__(weather, **kwargs) - - config = self.initialize_library(config, **kwargs) - self.config = self.validate_config(config) - - self.initialize_port() - self.setup_simulation(**kwargs) - - # Setup simulation seems to be the install phase's equivalent run() module - def setup_simulation(self, **kwargs): - # print("Fixed Platform Install setup_sim() is working!!!") - - self.distance = self.config["site"]["distance"] - self.depth = self.config["site"]["depth"] - self.mass = self.config["equipment"]["tech_combined_mass"] - self.area = self.config["equipment"]["tech_required_area"] - - _platform = self.config.get("equipment", {}) - design_cost = _platform.get("topside_design_cost", 4.5e6) # USD - fab_cost = _platform.get("fabrication_cost_rate", 14500.0) # USD/t - steel_cost = _platform.get("substructure_steel_rate", 3000) # USD/t - - install_duration = _platform.get("install_duration", 14) # days - - # Initialize vessel - vessel_specs = self.config.get("oss_install_vessel", None) - name = vessel_specs.get("name", "Offshore Substation Install Vessel") - - vessel = orbit.core.Vessel(name, vessel_specs) - self.env.register(vessel) - - vessel.initialize() - self.install_vessel = vessel - - # Add in the mass of the substructure to total mass (may or may not impact the final - # install cost) - _, substructure_mass = calc_substructure_mass_and_cost( - self.mass, self.area, self.depth, fab_cost, design_cost, steel_cost - ) - - self.total_mass = substructure_mass # t - # Call the install_platform function - self.install_capex = install_platform( - self.total_mass, - self.area, - self.distance, - install_duration, - self.install_vessel, - foundation="fixed", - ) - - # An install object needs to have attribute system_capex, installation_capex, and detailed - # output - @property - def system_capex(self): - return {} - - @property - def installation_capex(self): - return self.install_capex - - @property - def detailed_output(self): - return {} - - -# Define individual calculations and functions to use outside or with ORBIT -def calc_substructure_mass_and_cost( - mass, area, depth, fab_cost=14500.0, design_cost=4.5e6, sub_cost=3000, pile_cost=0 -): - """ - calc_substructure_mass_and_cost returns the total mass including substructure, topside and - equipment. Also returns the cost of the substructure and topside - Inputs: mass | Mass of equipment on platform (metric tons) - area | Area needed for equipment (meter^2) (not necessary) - depth | Ocean depth at platform location (meters) (not necessary) - fab_cost_rate | Cost rate to fabricate topside (USD/metric ton) - design_cost | Design cost to design structural components (USD) from ORBIT - sub_cost_rate | Steel cost rate (USD/metric ton) from ORBIT""" - """ - Platform is substructure and topside combined - All functions are based off NREL's ORBIT [1] (oss_design.py) - default values are specified in [3], - """ - # Inputs needed - topside_mass = mass - topside_fab_cost_rate = fab_cost - topside_design_cost = design_cost - - """Topside Cost & Mass (repurposed eq. 2.26 from [2]) - Topside Mass is the required Mass the platform will hold - Topside Cost is a function of topside mass, fab cost and design cost""" - topside_cost = topside_mass * topside_fab_cost_rate + topside_design_cost - - """Substructure (repurposed eq. 2.31-2.33 from [2]) - Substructure Mass is a function of the topside mass - Substructure Cost is a function of of substructure mass pile mass and cost rates for each""" - - # inputs needed - substructure_cost_rate = sub_cost # USD/t - pile_cost_rate = pile_cost # USD/t - - substructure_mass = 0.4 * topside_mass # t - substructure_pile_mass = 8 * substructure_mass**0.5574 # t - substructure_cost = ( - substructure_mass * substructure_cost_rate + substructure_pile_mass * pile_cost_rate - ) # USD - - substructure_total_mass = substructure_mass + substructure_pile_mass # t - - """Total Platform capex = capex Topside + capex substructure""" - - platform_capex = substructure_cost + topside_cost # USD - platform_mass = substructure_total_mass + topside_mass # t - - return platform_capex, platform_mass - - -# Standalone test sections -if __name__ == "__main__": - print("\n*** New FixedPlatform Standalone test section ***\n") - - orbit_libpath = Path.cwd().parents[2] / "ORBIT/library" - print(orbit_libpath) - orbit.core.library.initialize_library(orbit_libpath) - - config_path = Path(__file__).parent - config_fname = orbit.load_config(config_path / "example_fixed_project.yaml") - - orbit.ProjectManager.register_design_phase(FixedPlatformDesign) - - orbit.ProjectManager.register_install_phase(FixedPlatformInstallation) - - platform = orbit.ProjectManager(config_fname) - platform.run() - - design_capex = platform.design_results["platform_design"]["total_cost"] - install_capex = platform.installation_capex - - # print("Project Params", h2platform.project_params.items()) - platform_opex = calc_platform_opex(design_capex + install_capex) - - print("ORBIT Phases: ", platform.phases.keys()) - print(f"\tH2 Platform Design Capex: {design_capex:.0f} USD") - print(f"\tH2 Platform Install Capex: {install_capex:.0f} USD") - print() - print(f"\tTotal H2 Platform Capex: {(design_capex+install_capex)/1e6:.0f} mUSD") - print(f"\tH2 Platform Opex: {platform_opex:.0f} USD/year") diff --git a/h2integrate/simulation/technologies/offshore/floating_platform.py b/h2integrate/simulation/technologies/offshore/floating_platform.py deleted file mode 100644 index 048cd5ea3..000000000 --- a/h2integrate/simulation/technologies/offshore/floating_platform.py +++ /dev/null @@ -1,357 +0,0 @@ -from __future__ import annotations - - -""" -Author:Charles Kiefer -Date: 4/11/2023 -Institution: National Renewable Energy Lab -Description: This file shall handle costing and sizing of offshore floating platforms deicated to - hydrogen production. It uses the same foundation as fixed_platform.py. Both have been modeled - off of existing BOS cost/sizing calculations fond in ORBIT. It can be run as standalone - functions or as appended ORBIT project phases. - - - -Sources: - - [1] ORBIT: https://github.com/WISDEM/ORBIT v1.1 -Args: - - tech_required_area: (float): area needed for combination of all tech (m^2), not including - buffer or working space - - tech_combined_mass: (float): mass of all tech being placed on the platform (kg or metric tons) - - - - depth: (float): bathometry at the platform location (m) ##Site depths for floating projects - need to be at depths 500 m to 1500 m because of Orbit Semitaut limitations (7/31) - - distance_to_port: (float): distance ships must travel from port to site location (km) - - Future arguments: (Not used at this time) - - construction year (int): - - lifetime (int): lifetime of the plant in years (may not be needed) - -Returns: - - platform_mass (float): Adjusted mass of platform + substructure - - design_capex (float): capital expenditures (platform design + substructure fabrication) - - installation_capex (float): capital expenditures (installation cost) - - platform_opex (float): the OPEX (annual, fixed) in USD for the platform - -""" - -""" -Notes: - Thank you Jake Nunemaker's oswh2 repository and Rebecca Fuchs SemiTaut_mooring repository!!! - pile_cost=0 $US/metric ton for monopile construction. Not a bug, this # is consistent with - the rest of ORBIT -""" - - -import typing -from pathlib import Path - -from ORBIT import ProjectManager, load_config -from ORBIT.core import Vessel -from ORBIT.core.library import initialize_library -from ORBIT.phases.design import DesignPhase, MooringSystemDesign -from ORBIT.phases.install import InstallPhase - -from h2integrate.simulation.technologies.offshore.all_platforms import ( - install_platform, - calc_platform_opex, -) - - -class FloatingPlatformDesign(DesignPhase): - """ - This is a modified class based on ORBIT's design phase - """ - - # phase = "H2 Floating Platform Design" - - # Expected inputs from config yaml file - expected_config: typing.ClassVar = { - "site": { - "distance": "int | float", - "depth": "int | float", - }, - "equipment": { - "tech_required_area": "float", - "tech_combined_mass": "float", - "topside_design_cost": "USD (optional, default:4.5e6)", - "fabrication_cost_rate": "USD/t (optional, default: 14500.)", - "substructure_steel_rate": "USD/t (optional, default: 3000.)", - }, - } - - # Takes in arguments and initialize library files - def __init__(self, config, **kwargs): - self.phase = "H2 Floating Platform Design" - - config = self.initialize_library(config, **kwargs) - self.config = self.validate_config(config) - - self._outputs = {} - # Runs the design cost models - - def run(self): - # print("Floating Platform Design run() is working!!!") - - self.distance = self.config["site"]["distance"] # km - self.depth = self.config["site"]["depth"] # m - - _platform = self.config.get("equipment", {}) - - self.mass = _platform.get("tech_combined_mass", 999) # t - self.area = _platform.get("tech_required_area", 1000) # m**2 - - design_cost = _platform.get("topside_design_cost", 4.5e6) # USD - fab_cost_rate = _platform.get("fabrication_cost_rate", 14500.0) # USD/t - steel_cost = _platform.get("substructure_steel_rate", 3000) # USD/t - ##NEED updated version - # Add individual calcs/functions in the run() method - """Calls in SemiTaut Costs and Variables for Substructure mass and cost""" - self.anchor_type = "Drag Embedment" - self.mooring_type = "Semitaut" - self.num_lines = 4 - MooringSystemDesign.MooringSystemDesign.calculate_line_length_mass(self) - MooringSystemDesign.MooringSystemDesign.calculate_anchor_mass_cost(self) - MooringSystemDesign.MooringSystemDesign.determine_mooring_line_cost(self) - total_cost, total_mass = calc_substructure_mass_and_cost( - self.mass, - self.area, - self.depth, - fab_cost_rate, - design_cost, - steel_cost, - self.line_cost, - self.anchor_cost, - self.anchor_mass, - self.line_mass, - self.num_lines, - ) - - # Create an ouput dict - self._outputs["floating_platform"] = { - "mass": total_mass, - "area": self.area, - "total_cost": total_cost, - } - - # A design object needs to have attribute design_result and detailed_output - @property - def design_result(self): - return { - "platform_design": { - "mass": self._outputs["floating_platform"]["mass"], - "area": self._outputs["floating_platform"]["area"], - "total_cost": self._outputs["floating_platform"]["total_cost"], - } - } - - @property - def detailed_output(self): - return {} - - -class FloatingPlatformInstallation(InstallPhase): - """ - This is a modified class based on ORBIT's install phase - """ - - # phase = "H2 Floating Platform Installation" - - # Expected inputs from config yaml file - expected_config: typing.ClassVar = { - "site": { - "distance": "int | float", - "depth": "int | float", - }, - "equipment": { - "tech_required_area": "float", - "tech_combined_mass": "float", - "install_duration": "days (optional, default: 14)", - }, - "oss_install_vessel": "str | dict", - } - - # Need to initialize arguments and weather files - def __init__(self, config, weather=None, **kwargs): - super().__init__(weather, **kwargs) - - config = self.initialize_library(config, **kwargs) - self.config = self.validate_config(config) - - self.initialize_port() - self.setup_simulation(**kwargs) - - # Setup simulation seems to be the install phase's equivalent run() module - def setup_simulation(self, **kwargs): - # print("Floating Platform Install setup_sim() is working!!!") - - self.distance = self.config["site"]["distance"] - self.depth = self.config["site"]["depth"] - self.mass = self.config["equipment"]["tech_combined_mass"] - self.area = self.config["equipment"]["tech_required_area"] - - _platform = self.config.get("equipment", {}) - design_cost = _platform.get("topside_design_cost", 4.5e6) # USD - fab_cost_rate = _platform.get("fabrication_cost_rate", 14500.0) # USD/t - steel_cost = _platform.get("substructure_steel_rate", 3000) # USD/t - - install_duration = _platform.get("install_duration", 14) # days - - # Initialize vessel - vessel_specs = self.config.get("oss_install_vessel", None) - name = vessel_specs.get("name", "Offshore Substation Install Vessel") - - vessel = Vessel(name, vessel_specs) - self.env.register(vessel) - - vessel.initialize() - self.install_vessel = vessel - - # Add in the mass of the substructure to total mass (may or may not impact the final - # install cost) - - """Calls in SemiTaut Costs and Variables""" - self.anchor_type = "Drag Embedment" - self.mooring_type = "Semitaut" - self.num_lines = 4 - MooringSystemDesign.MooringSystemDesign.calculate_line_length_mass(self) - MooringSystemDesign.MooringSystemDesign.calculate_anchor_mass_cost(self) - MooringSystemDesign.MooringSystemDesign.determine_mooring_line_cost(self) - - _, substructure_mass = calc_substructure_mass_and_cost( - self.mass, - self.area, - self.depth, - fab_cost_rate, - design_cost, - steel_cost, - self.line_cost, - self.anchor_cost, - self.anchor_mass, - self.line_mass, - self.num_lines, - ) - - total_mass = substructure_mass # t - - # Call the install_platform function - self.install_capex = install_platform( - total_mass, - self.area, - self.distance, - install_duration, - self.install_vessel, - foundation="floating", - ) - - # An install object needs to have attribute system_capex, installation_capex, and detailed - # output - @property - def system_capex(self): - return {} - - @property - def installation_capex(self): - return self.install_capex - - @property - def detailed_output(self): - return {} - - -# Define individual calculations and functions to use outside or with ORBIT -def calc_substructure_mass_and_cost( - mass, - area, - depth, - fab_cost_rate=14500.0, - design_cost=4.5e6, - sub_cost_rate=3000, - line_cost=0, - anchor_cost=0, - anchor_mass=0, - line_mass=0, - num_lines=4, -): - """ - calc_substructure_mass_and_cost returns the total mass including substructure, topside and - equipment. Also returns the cost of the substructure and topside - Inputs: mass | Mass of equipment on platform (metric tons) - area | Area needed for equipment (meter^2) (not necessary) - depth | Ocean depth at platform location (meters) - fab_cost_rate | Cost rate to fabricate topside (USD/metric ton) - design_cost | Design cost to design structural components (USD) from ORBIT - sub_cost_rate | Steel cost rate (USD/metric ton) from ORBIT - """ - - """ - Platform is substructure and topside combined - All functions are based off NREL's ORBIT (oss_design) - default values are specified in ORBIT - """ - topside_mass = mass - topside_fab_cost_rate = fab_cost_rate - topside_design_cost = design_cost - - """Topside Cost & Mass - Topside Mass is the required Mass the platform will hold - Topside Cost is a function of topside mass, fab cost and design cost""" - topside_cost = topside_mass * topside_fab_cost_rate + topside_design_cost # USD - - """Substructure - Substructure Mass is a function of the topside mass - Substructure Cost is a function of of substructure mass pile mass and cost rates for each""" - - substructure_cost_rate = sub_cost_rate # USD/t - - substructure_mass = 0.4 * topside_mass # t - substructure_cost = substructure_mass * substructure_cost_rate # USD - substructure_total_mass = substructure_mass # t - - """Total Mooring cost and mass for the substructure - Line_cost, anchor_cost, line_mass, anchor_mass are grabbed from MooringSystemDesign in ORBIT - Mooring_mass is returned in kilograms and will need to """ - mooring_cost = (line_cost + anchor_cost) * num_lines # USD - mooring_mass = (line_mass + anchor_mass) * num_lines # kg - - """Total Platform capex = capex Topside + capex substructure""" - total_capex = 2 * (topside_cost + substructure_cost + mooring_cost) - platform_capex = total_capex # USD - platform_mass = substructure_total_mass + topside_mass + mooring_mass / 1000 # t - # mass of equipment and floating substructure for substation - - return platform_capex, platform_mass - - -# Standalone test sections -if __name__ == "__main__": - print("\n*** New FloatingPlatform Standalone test section ***\n") - - orbit_libpath = Path.cwd().parents[2] / "ORBIT/library" - print(orbit_libpath) - initialize_library(orbit_libpath) - - config_path = Path(__file__).parent - config_fname = load_config(config_path / "example_floating_project.yaml") - - # ProjectManager._design_phases.append(FloatingPlatformDesign) - ProjectManager.register_design_phase(FloatingPlatformDesign) - # ProjectManager._install_phases.append(FloatingPlatformInstallation) - ProjectManager.register_install_phase(FloatingPlatformInstallation) - - platform = ProjectManager(config_fname) - platform.run() - - design_capex = platform.design_results["platform_design"]["total_cost"] - install_capex = platform.installation_capex - - # print("Project Params", h2platform.project_params.items()) - platform_opex = calc_platform_opex(design_capex + install_capex) - - print("ORBIT Phases: ", platform.phases.keys()) - print(f"\tH2 Platform Design Capex: {design_capex:.0f} USD") - print(f"\tH2 Platform Install Capex: {install_capex:.0f} USD") - print() - print(f"\tTotal H2 Platform Capex: {(design_capex+install_capex)/1e6:.0f} mUSD") - print(f"\tH2 Platform Opex: {platform_opex:.0f} USD/year") diff --git a/tests/h2integrate/test_offshore/__init__.py b/tests/h2integrate/test_offshore/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/h2integrate/test_offshore/test_fixed_platform.py b/tests/h2integrate/test_offshore/test_fixed_platform.py deleted file mode 100644 index f9a52b4eb..000000000 --- a/tests/h2integrate/test_offshore/test_fixed_platform.py +++ /dev/null @@ -1,80 +0,0 @@ -from pathlib import Path - -import ORBIT as orbit -import pytest - -from h2integrate.simulation.technologies.offshore.fixed_platform import ( - install_platform, - calc_platform_opex, - calc_substructure_mass_and_cost, -) - - -"""Sources: - - [1] M. Maness, B. Maples and A. Smith, "NREL Offshore Balance-of-System Model," National - Renewable Energy Laboratory, 2017. https://www.nrel.gov/docs/fy17osti/66874.pdf -""" - - -@pytest.mark.skip(reason="no way of currently testing this") -@pytest.fixture -def config(): - offshore_path = ( - Path(__file__).parents[3] / "h2integrate" / "simulation" / "technologies" / "offshore" - ) - - return orbit.load_config(offshore_path / "example_fixed_project.yaml") - - -@pytest.mark.skip(reason="no way of currently testing this") -def test_install_platform(config): - """ - Test the code that calculates the platform installation cost - [1]: equations (91),(113),(98) - """ - distance = 24 - mass = 2100 - area = 500 - - cost = install_platform(mass, area, distance, install_duration=14) - - assert pytest.approx(cost) == 7200014 - - -def test_calc_substructure_cost(config): - """ - Test the code that calculates the CapEx from fixed_platform.py - [1]: equations (81),(83),(84) - """ - topmass = 200 - toparea = 1000 - depth = 45 - - cost, _ = calc_substructure_mass_and_cost(topmass, toparea, depth) - - assert pytest.approx(cost) == 7640000 - - -def test_calc_substructure_mass(config): - """ - Test the code that calculates the CapEx from fixed_platform.py - [1]: equations (81),(83),(84) - """ - topmass = 200 - toparea = 1000 - depth = 45 - - _, mass = calc_substructure_mass_and_cost(topmass, toparea, depth) - - assert pytest.approx(mass, 0.1) == 372.02 - - -def test_calc_platform_opex(): - """ - Test the code that calculates the OpEx from fixed_platform.py - """ - capex = 28e6 - opex_rate = 0.01 - cost = calc_platform_opex(capex, opex_rate) - - assert pytest.approx(cost) == 28e4 diff --git a/tests/h2integrate/test_offshore/test_floating_platform.py b/tests/h2integrate/test_offshore/test_floating_platform.py deleted file mode 100644 index 5cc996549..000000000 --- a/tests/h2integrate/test_offshore/test_floating_platform.py +++ /dev/null @@ -1,102 +0,0 @@ -from pathlib import Path - -import ORBIT as orbit -import pytest - -from h2integrate.simulation.technologies.offshore.floating_platform import ( - install_platform, - calc_platform_opex, - calc_substructure_mass_and_cost, -) - - -"""Sources: - - [1] M. Maness, B. Maples and A. Smith, "NREL Offshore Balance-of-System Model," National - Renewable Energy Laboratory, 2017. https://www.nrel.gov/docs/fy17osti/66874.pdf -""" - - -@pytest.fixture -def config(): - offshore_path = ( - Path(__file__).parents[3] / "h2integrate" / "simulation" / "technologies" / "offshore" - ) - - return orbit.load_config(offshore_path / "example_floating_project.yaml") - - -def test_install_platform(config): - """ - Test the code that calculates the platform installation cost - [1]: equations (91),(113),(98) - """ - distance = 24 - mass = 2100 - area = 500 - - cost = install_platform(mass, area, distance, install_duration=14, foundation="floating") - - assert pytest.approx(cost) == 7142871 - - -def test_calc_substructure_cost(config): - """ - Test the code that calculates the CapEx from floating_platform.py - [1]: equations (81),(83),(84) - """ - topmass = 200 - toparea = 1000 - depth = 500 - - cost, _ = calc_substructure_mass_and_cost( - topmass, - toparea, - depth, - fab_cost_rate=14500, - design_cost=4500000, - sub_cost_rate=3000, - line_cost=850000, - anchor_cost=120000, - anchor_mass=20, - line_mass=100000, - num_lines=4, - ) - - assert pytest.approx(cost) == 23040000 - - -def test_calc_substructure_mass(config): - """ - Test the code that calculates the CapEx from floating_platform.py - [1]: equations (81),(83),(84) - """ - topmass = 200 - toparea = 1000 - depth = 500 - - _, mass = calc_substructure_mass_and_cost( - topmass, - toparea, - depth, - fab_cost_rate=14500, - design_cost=4500000, - sub_cost_rate=3000, - line_cost=850000, - anchor_cost=120000, - anchor_mass=20, - line_mass=100000, - num_lines=4, - ) - - assert pytest.approx(mass, 0.1) == 680.0 - - -def test_calc_platform_opex(): - """ - Test the code that calculates the OpEx from floating_platform.py - """ - capex = 28e6 - opex_rate = 0.01 - cost = calc_platform_opex(capex, opex_rate) - - assert pytest.approx(cost) == 28e4 From 70320f8ca8ef999752a3eb553061b01785d61fc6 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Mon, 27 Oct 2025 14:02:58 -0600 Subject: [PATCH 04/30] Deleting more old files --- .../technologies/hydrogen/desal/__init__.py | 0 .../hydrogen/desal/desal_model.py | 133 -------- .../hydrogen/desal/desal_model_eco.py | 136 --------- .../electrolysis/optimization_utils_linear.py | 200 ------------ .../hydrogen/electrolysis/run_PEM_master.py | 289 ------------------ .../hydrogen/electrolysis/test_opt.ipynb | 146 --------- 6 files changed, 904 deletions(-) delete mode 100644 h2integrate/simulation/technologies/hydrogen/desal/__init__.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/desal/desal_model.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/desal/desal_model_eco.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/optimization_utils_linear.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/test_opt.ipynb diff --git a/h2integrate/simulation/technologies/hydrogen/desal/__init__.py b/h2integrate/simulation/technologies/hydrogen/desal/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/h2integrate/simulation/technologies/hydrogen/desal/desal_model.py b/h2integrate/simulation/technologies/hydrogen/desal/desal_model.py deleted file mode 100644 index 4dc57d64d..000000000 --- a/h2integrate/simulation/technologies/hydrogen/desal/desal_model.py +++ /dev/null @@ -1,133 +0,0 @@ -## High-Pressure Reverse Osmosis Desalination Model -""" -Python model of High-Pressure Reverse Osmosis Desalination (HPRO). - -Reverse Osmosis (RO) is a membrane separation process. No heating or phase change is necessary. -The majority of energy required is for pressurizing the feed water. - -A typical RO system is made up of the following basic components: -Pre-treatment: Removes suspended solids and microorganisms through sterilization, fine filtration - and adding chemicals to inhibit precipitation. -High-pressure pump: Supplies the pressure needed to enable the water to pass through the membrane - (pressure ranges from 54 to 80 bar for seawater). -Membrane Modules: Membrane assembly consists of a pressure vessel and the membrane. Either sprial - wound membranes or hollow fiber membranes are used. -Post-treatment: Consists of sterilization, stabilization, mineral enrichment and pH adjustment of - product water. -Energy recovery system: A system where a portion of the pressure energy of the brine is recovered. -""" - -import numpy as np - - -def RO_desal( - net_power_supply_kW, - desal_sys_size, - useful_life, - plant_life, - water_recovery_ratio=0.30, - energy_conversion_factor=4.2, - high_pressure_pump_efficency=0.70, - pump_pressure_kPa=5366, - energy_recovery=0.40, -): - """ - Calculates the fresh water flow rate (m^3/hr) as - a function of supplied power (kW) in RO desal. - Calculates CAPEX (USD), OPEX (USD/yr), annual cash flows - based on system's rated capacity (m^3/hr). - - Args: - net_power_supply_kW (list): Hourly power input [kW]. - desal_sys_size (float): Desired fresh water flow rate [m^3/hr]. - useful_life (int): Useful life of desal system [years]. - plant_life (int): Years of plant operation [years]. - - Assumed values: - Common set points from: - https://www.sciencedirect.com/science/article/abs/pii/S0011916409008443 - water_recovery_ratio (float): 0.30 - energy_conversion_factor (float): 4.2 - high_pressure_pump_efficency (float): 0.70 - pump_pressure_kPa (float): 5366 (kept static for simplicity) - energy_recovery (float): 0.40 - Assumed energy savings by energy recovery device to be 40% of total energy - https://www.sciencedirect.com/science/article/pii/S0360544210005578?casa_token=aEz_d_LiSgYAAAAA:88Xa6uHMTZee-djvJIF9KkhpuZmwZCLPHNiThmcwv9k9RC3H17JuSoRWI-l92rrTl_E3kO4oOA - - TODO: - Modify water recovery to vary based on salinity. - SWRO: Sea water Reverse Osmosis, water >18,000 ppm - SWRO energy_conversion_factor range 2.5 to 4.2 kWh/m^3 - Modify pressure through RO process - - BWRO: Brakish water Reverse Osmosis, water < 18,000 ppm - BWRO energy_conversion_factor range 1.0 to 1.5 kWh/m^3 - Source: https://www.sciencedirect.com/science/article/pii/S0011916417321057 - """ - # net_power_supply_kW = np.array(net_power_supply_kW) - - desal_power_max = desal_sys_size * energy_conversion_factor # kW - - # Modify power to not exceed system's power maximum (100% rated power capacity) or - # minimum (approx 50% rated power capacity --> affects filter fouling below this level) - net_power_for_desal = [] - operational_flags = [] - feed_water_flowrate = [] - fresh_water_flowrate = [] - for power_at_time_step in net_power_supply_kW: - if power_at_time_step > desal_power_max: - current_net_power_available = desal_power_max - operational_flag = 2 - elif (0.5 * desal_power_max) <= power_at_time_step <= desal_power_max: - current_net_power_available = power_at_time_step - operational_flag = 1 - elif power_at_time_step <= 0.5 * desal_power_max: - current_net_power_available = 0 - operational_flag = 0 - - # Append Operational Flags to a list - operational_flags.append(operational_flag) - # Create list of net power available for desal at each timestep - net_power_for_desal.append(current_net_power_available) - - # Create list of feedwater flowrates based on net power available for desal - # https://www.sciencedirect.com/science/article/abs/pii/S0011916409008443 - instantaneous_feed_water_flowrate = ( - ((current_net_power_available * (1 + energy_recovery)) * high_pressure_pump_efficency) - / pump_pressure_kPa - * 3600 - ) # m^3/hr - - instantaneous_fresh_water_flowrate = ( - instantaneous_feed_water_flowrate * water_recovery_ratio - ) # m^3/hr - - feed_water_flowrate.append(instantaneous_feed_water_flowrate) - fresh_water_flowrate.append(instantaneous_fresh_water_flowrate) - - """Values for CAPEX and OPEX given as $/(kg/s) - Source: https://www.nrel.gov/docs/fy16osti/66073.pdf - Assumed density of recovered water = 997 kg/m^3""" - - desal_capex = 32894 * (997 * desal_sys_size / 3600) # Output in USD - - desal_opex = 4841 * (997 * desal_sys_size / 3600) # Output in USD/yr - - """ - Assumed useful life = payment period for capital expenditure. - compressor amortization interest = 3% - """ - - return ( - fresh_water_flowrate, - feed_water_flowrate, - operational_flags, - desal_capex, - desal_opex, - ) - - -if __name__ == "__main__": - Power = np.array([446, 500, 183, 200, 250, 100]) - test = RO_desal(Power, 300, 30, 30) - print(test) diff --git a/h2integrate/simulation/technologies/hydrogen/desal/desal_model_eco.py b/h2integrate/simulation/technologies/hydrogen/desal/desal_model_eco.py deleted file mode 100644 index dd0f09172..000000000 --- a/h2integrate/simulation/technologies/hydrogen/desal/desal_model_eco.py +++ /dev/null @@ -1,136 +0,0 @@ -################## needed addition ###################### -""" -Description: This file already contains a desal model, but we need an estimate of the desal unit - size, particularly mass and footprint (m^2) -Sources: - - [1] Singlitico 2021 (use this as a jumping off point, I think there may be other good sources - available) - - [2] See sources in existing model below and the model itself -Args: - - electrolyzer_rating (float): electrolyzer rating in MW - - input and output values from RO_desal() below - - others may be added as needed -Returns (can be from separate functions and/or methods as it makes sense): - - mass (float): approximate mass of the desalination system (kg or metric tons) - - footprint (float): approximate area required for the desalination system (m^2) -""" - - -#################### existing model ######################## - -## High-Pressure Reverse Osmosis Desalination Model -""" -Python model of High-Pressure Reverse Osmosis Desalination (HPRO). - -Reverse Osmosis (RO) is a membrane separation process. No heating or phase change is necessary. -The majority of energy required is for pressurizing the feed water. - -A typical RO system is made up of the following basic components: -Pre-treatment: Removes suspended solids and microorganisms through sterilization, fine filtration - and adding chemicals to inhibit precipitation. -High-pressure pump: Supplies the pressure needed to enable the water to pass through the membrane - (pressure ranges from 54 to 80 bar for seawater). -Membrane Modules: Membrane assembly consists of a pressure vessel and the membrane. Either sprial - wound membranes or hollow fiber membranes are used. -Post-treatment: Consists of sterilization, stabilization, mineral enrichment and pH adjustment of - product water. -Energy recovery system: A system where a portion of the pressure energy of the brine is recovered. - -Costs are in 2013 dollars -""" - - -def RO_desal_eco(freshwater_kg_per_hr, salinity): - """ - param: freshwater_kg_per_hr: Maximum freshwater requirements of system [kg/hr] - - param: salinity: (str) "Seawater" >18,000 ppm or "Brackish" <18,000 ppm - - output: feedwater_m3_per_hr: feedwater flow rate [m^3/hr] - - output: desal_power: reqired power [kW] - - output: desal_capex: Capital cost [USD] - - output: desal_opex: OPEX (USD/yr) - - Costs from: https://pdf.sciencedirectassets.com/271370/1-s2.0-S0011916408X00074/1-s2.0-S0011916408002683/main.pdf?X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEcaCXVzLWVhc3QtMSJGMEQCIBNfL%2Frp%2BWpMGUW7rWBm3dkXztvOFIdswOdqI23VkBTGAiALG4NJuAiUkzKnukw233sXHF1OFBPnogJP1ZkboPkaiSrVBAjA%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8BEAUaDDA1OTAwMzU0Njg2NSIMWZ%2Fh3cDnrPjUJMleKqkELlVPKjinHYk85KwguMS3panLr1RRD9qkoxIASocYCbkvKLE9xW%2BT8QMCtEaH3Is7NRZ2Efc6YFQiO0DHbRzXYTfgz6Er5qqvSAFTrfgp%2B5bB3NYvtDI3kEGH%2F%2BOrEiL8iDK9TmgUjojvnKt86zidswBSDWrzclxcLrw6dfsqZf6dVjJT2g3Cyy8LKnP9vc33tCbACRLeszW1Zce%2BTlBbON22W%2FJq0qLcXDxI9JpRDqL8T%2Fo7SsetEif2DWovTLnv%2B%2FX2tJotFp630ZTVpd37ukGtanjAr5pl0nHgjnUtOJVtNksHQwc8XElFpBGKEXmvRo2uZJFd%2BeNtPEB1dWIZlZul6B8%2BJ7D%2FSPJsclPfpkMU92YUStQpw4Mc%2FOJFCILFyb4416DsL6PVWsdcYu9bbry8c0hQGZlE7oXTFoUy9SKdpEOguXAUi3X4JxjZisy3esVH8zNS3%2FiFsNr2FkTB6MLaSjSKj344AuDCkQYZ7CnenAiCHgf4a2tSnfiXzAvAFnpeQkr4iCnZOQ4Eis6L3fVRpWlluX5HUpbvUMN6rvtmAzq0APJn1b3NmFHy4ORoemTGvmI%2FHTRYKuAu257XBMe7X1qAJlnmpt6yGXrelXCz%2FmUvmbT1SzxETA5ss4KR0OM4YdXNnFLUrsV44ZkUM%2B8FlwZr%2F%2FePjz4QeG4ApR821IYTyre3%2FY%2BBZxaMs5AcXKiTHGwfE7CDi%2BQQ7CnDKk0lleZcas6kxzDl9%2BmeBjqqAeZhBVwd5sEx6aDGxAQC0eWpux6HauoVfuPOCkkv621szF0kTBqcoOlJlJav4eUPW4efAzBremirjiRLI2GdP72lVqXz9oaCg5NFXeKJAWbWkLdzHnDOu8ecSUPn%2F0jcR2IO2mznLspx6wKQA%2BAPEVGgptkwZtDqHcw8FNx7Q8tWJ1C4qL1bEMl0%2FatDXOHiJfuzCFp4%2B4uijTNfpVXO%2BzYQuNJA7ZNUMroa&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230201T155950Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAQ3PHCVTY7RLVF2MG%2F20230201%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=a3770ee910f7f78c94bb84206538810ca03f7a653183191b3794c633b9e3f08f&hash=2e8904ff0d2a6ef567a5894d5bb773524bf1a90bc3ed88d8592e3f9d4cc3c531&host=68042c943591013ac2b2430a89b270f6af2c76d8dfd086a07176afe7c76c2c61&pii=S0011916408002683&tid=spdf-27339dc5-0d03-4078-a244-c049a9bb014d&sid=50eb5802654ba84dc80a5675e9bbf644ed4dgxrqa&type=client&tsoh=d3d3LnNjaWVuY2VkaXJlY3QuY29t&ua=0f1650585c05065559515c&rr=792be5868a1a8698&cc=us - - A desal system capacity is given as desired freshwater flow rate [m^3/hr] - """ - - freshwater_density = 997 # [kg/m^3] - freshwater_m3_per_hr = freshwater_kg_per_hr / freshwater_density - desal_capacity = freshwater_m3_per_hr - - if salinity == "Seawater": - # SWRO: Sea Water Reverse Osmosis, water >18,000 ppm - # Water recovery - recovery_ratio = 0.5 # https://www.usbr.gov/research/dwpr/reportpdfs/report072.pdf - feedwater_m3_per_hr = freshwater_m3_per_hr / recovery_ratio - - # Power required - energy_conversion_factor = ( - 4.0 # [kWh/m^3] SWRO energy_conversion_factor range 2.5 to 4.0 kWh/m^3 - ) - # https://www.sciencedirect.com/science/article/pii/S0011916417321057 - desal_power = freshwater_m3_per_hr * energy_conversion_factor - - elif salinity == "Brackish": - # BWRO: Brakish water Reverse Osmosis, water < 18,000 ppm - # Water recovery - recovery_ratio = 0.75 # https://www.usbr.gov/research/dwpr/reportpdfs/report072.pdf - feedwater_m3_per_hr = freshwater_m3_per_hr / recovery_ratio - - # Power required - energy_conversion_factor = ( - 1.5 # [kWh/m^3] BWRO energy_conversion_factor range 1.0 to 1.5 kWh/m^3 - ) - # https://www.sciencedirect.com/science/article/pii/S0011916417321057 - - desal_power = freshwater_m3_per_hr * energy_conversion_factor - - else: - raise Exception("Salinity parameter must be set to Brackish or Seawater") - - # Costing - # https://www.nrel.gov/docs/fy16osti/66073.pdf - desal_capex = 32894 * (freshwater_density * desal_capacity / 3600) # [USD] - - desal_opex = 4841 * (freshwater_density * desal_capacity / 3600) # [USD/yr] - - """Mass and Footprint - Based on Commercial Industrial RO Systems - https://www.appliedmembranes.com/s-series-seawater-reverse-osmosis-systems-2000-to-100000-gpd.html - - All Mass and Footprint Estimates are estimated from Largest RO System: - S-308F - -436 m^3/day - -6330 kg - -762 cm (L) x 112 cm (D) x 183 cm (H) - - 436 m^3/day = 18.17 m^3/hr = 8.5 m^2, 6330 kg - 1 m^3/hr = .467 m^2, 346.7 kg - - Voltage Codes - 460 or 480v/ 3ph/ 60 Hz - """ - desal_mass_kg = freshwater_m3_per_hr * 346.7 # [kg] - desal_size_m2 = freshwater_m3_per_hr * 0.467 # [m^2] - - return ( - desal_capacity, - feedwater_m3_per_hr, - desal_power, - desal_capex, - desal_opex, - desal_mass_kg, - desal_size_m2, - ) - - -if __name__ == "__main__": - desal_freshwater_kg_hr = 75000 - salinity = "Brackish" - test = RO_desal_eco(desal_freshwater_kg_hr, salinity) - print(test) diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/optimization_utils_linear.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/optimization_utils_linear.py deleted file mode 100644 index f417834b7..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/optimization_utils_linear.py +++ /dev/null @@ -1,200 +0,0 @@ -import time -from pathlib import Path - -import numpy as np -from pyomo.environ import * # FIXME: no * imports, delete whole comment when fixed # noqa: F403 - - -def optimize( - P_wind_t, - T=50, - n_stacks=3, - c_wp=0, - c_sw=12, - rated_power=500, - dt=1, - P_init=None, - I_init=None, - T_init=None, - AC_init=0, - F_tot_init=0, -): - model = ConcreteModel() # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - # Things to solve: - C_INV = 1.47e6 - LT = 90000 - - # Initializations - if P_init is None: - P_init = [0] * (n_stacks * T) - else: - P_init = P_init.flatten() - if I_init is None: - I_init = [0] * (n_stacks * T) - else: - I_init = I_init.flatten() - if T_init is None: - T_init = [0] * (n_stacks * T) - else: - T_init = T_init.flatten() - - model.p = Var( # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - list(range(n_stacks * T)), - bounds=(-1e-2, rated_power), - initialize=P_init, - ) - model.I = Var( # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - list(range(n_stacks * T)), - within=Binary, # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - initialize=I_init, # .astype(int), - ) - model.T = Var( # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - list(range(n_stacks * T)), - within=Binary, # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - initialize=T_init, # .astype(int), - ) - model.AC = Var( # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - [0], bounds=(1e-3, 1.2 * rated_power * n_stacks * T), initialize=float(AC_init) - ) - model.F_tot = Var( # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - [0], bounds=(1e-3, 8 * rated_power * n_stacks * T), initialize=float(F_tot_init) - ) - model.eps = Param( # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - initialize=1, mutable=True - ) - - C_WP = c_wp * np.ones( - T, - ) # could vary with time - C_SW = c_sw * np.ones( - T, - ) # could vary with time - - P_max = rated_power - P_min = 0.1 * rated_power - - def obj(model): - return model.AC[0] - model.eps * model.F_tot[0] - - def physical_constraint_AC(model): - AC = 0 - for t in range(T): - for stack in range(n_stacks): - AC = ( - AC - + C_WP[t] * model.p[t * n_stacks + stack] - + C_SW[t] * model.T[t * n_stacks + stack] - ) - return model.AC[0] == AC + C_INV * n_stacks / LT - - def physical_constraint_F_tot(model): - """Objective function""" - F_tot = 0 - for t in range(T): - for stack in range(n_stacks): - F_tot = ( - F_tot - + ( - 0.0145 * model.p[t * n_stacks + stack] - + 0.3874 * model.I[t * n_stacks + stack] * rated_power / 500 - ) - * dt - ) - return model.F_tot[0] == F_tot - - def power_constraint(model, t): - """Make sure sum of stack powers is below available wind power.""" - power_full_stack = 0 - for stack in range(n_stacks): - power_full_stack = power_full_stack + model.p[t * n_stacks + stack] - return power_full_stack <= P_wind_t[t] - - def safety_bounds_lower(model, t, stack): - """Make sure input powers don't exceed safety bounds.""" - return P_min * model.I[t * n_stacks + stack] <= model.p[t * n_stacks + stack] - - def safety_bounds_upper(model, t, stack): - """Make sure input powers don't exceed safety bounds.""" - return P_max * model.I[t * n_stacks + stack] >= model.p[t * n_stacks + stack] - - def switching_constraint_pos(model, stack, t): - trans = model.I[t * n_stacks + stack] - model.I[(t - 1) * n_stacks + stack] - return model.T[t * n_stacks + stack] >= trans - - def switching_constraint_neg(model, stack, t): - trans = model.I[t * n_stacks + stack] - model.I[(t - 1) * n_stacks + stack] - return -model.T[t * n_stacks + stack] <= trans - - model.pwr_constraints = ( - ConstraintList() # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - ) - model.safety_constraints = ( - ConstraintList() # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - ) - model.switching_constraints = ( - ConstraintList() # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - ) - model.physical_constraints = ( - ConstraintList() # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - ) - - for t in range(T): - model.pwr_constraints.add(power_constraint(model, t)) - for stack in range(n_stacks): - model.safety_constraints.add(safety_bounds_lower(model, t, stack)) - model.safety_constraints.add(safety_bounds_upper(model, t, stack)) - - if t > 0: - model.switching_constraints.add(switching_constraint_pos(model, stack, t)) - model.switching_constraints.add(switching_constraint_neg(model, stack, t)) - model.physical_constraints.add(physical_constraint_F_tot(model)) - model.physical_constraints.add(physical_constraint_AC(model)) - model.objective = ( - Objective( # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - expr=obj(model), - sense=minimize, # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - ) - ) - eps = 10 - cbc_path = Path(__file__).parent / "hybrid/PEM_Model_2Push/cbc.exe" - - # Use this if you have a Windows machine; also make sure that cbc.exe is in the same folder as - # this script - solver = SolverFactory( # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - "cbc", executable=cbc_path - ) - - # Use this if you don't have a windows machine - # solver = SolverFactory("cbc") - - j = 1 - while eps > 1e-3: - start = time.process_time() - solver.solve(model) - print("time to solve", time.process_time() - start) - - model.eps = value( # FIXME: no * imports, delete whole comment when fixed # noqa: F405 - model.AC[0] / model.F_tot[0] - ) - eps = model.AC[0].value - model.eps.value * model.F_tot[0].value - j = j + 1 - - I = np.array([model.I[i].value for i in range(n_stacks * T)]) # noqa: E741 - I_ = np.reshape(I, (T, n_stacks)) - P = np.array([model.p[i].value for i in range(n_stacks * T)]) - P_ = np.reshape(P, (T, n_stacks)) - Tr = np.array([model.T[i].value for i in range(n_stacks * T)]).reshape((T, n_stacks)) - P_tot_opt = np.sum(P_, axis=1) - H2f = np.zeros((T, n_stacks)) - for stack in range(n_stacks): - H2f[:, stack] = (0.0145 * P_[:, stack] + 0.3874 * I_[:, stack] * rated_power / 500) * dt - return ( - P_tot_opt, - P_, - H2f, - I_, - Tr, - P_wind_t, - model.AC[0].value, - model.F_tot[0].value, - ) diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py deleted file mode 100644 index 29fa22de5..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py +++ /dev/null @@ -1,289 +0,0 @@ -import time -import warnings - -import numpy as np -import pandas as pd -from pyomo.environ import * # FIXME: no * imports, delete whole comment when fixed # noqa: F403 - -from h2integrate.simulation.technologies.hydrogen.electrolysis.optimization_utils_linear import ( - optimize, -) -from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_H2_LT_electrolyzer_Clusters import ( # noqa: E501 - PEM_H2_Clusters as PEMClusters, -) - - -# from PyOMO import ipOpt !! FOR SANJANA!! -warnings.filterwarnings("ignore") - -""" -Perform a LCOH analysis for an offshore wind + Hydrogen PEM system - -1. Offshore wind site locations and cost details (4 sites, $1300/kw capex + BOS cost which will come - from Orbit Runs)~ -2. Cost Scaling Based on Year (Have Weiser et. al report with cost scaling for fixed and floating - tech, will implement) -3. Cost Scaling Based on Plant Size (Shields et. Al report) -4. Future Model Development Required: -- Floating Electrolyzer Platform -""" - - -# -# --------------------------- -# -class run_PEM_clusters: - """Inputs: - `electrical_power_signal`: plant power signal in kWh - `system_size_mw`: total installed electrolyzer capacity (for green steel this is 1000 MW) - `num_clusters`: number of PEM clusters that can be run independently - ->ESG note: I have been using num_clusters = 8 for centralized cases - Nomenclature: - `cluster`: cluster is built up of 1MW stacks - `stack`: must be 1MW (because of current PEM model) - """ - - def __init__( - self, - electrical_power_signal, - system_size_mw, - num_clusters, - electrolyzer_direct_cost_kw, - useful_life, - user_defined_electrolyzer_params, - verbose=True, - ): - # nomen - self.cluster_cap_mw = np.round(system_size_mw / num_clusters) - # capacity of each cluster, must be a multiple of 1 MW - - self.num_clusters = num_clusters - self.user_params = user_defined_electrolyzer_params - self.plant_life_yrs = useful_life - # Do not modify stack_rating_kw or stack_min_power_kw - # these represent the hard-coded and unmodifiable - # PEM model basecode - turndown_ratio = user_defined_electrolyzer_params["turndown_ratio"] - self.stack_rating_kw = 1000 # single stack rating - DO NOT CHANGE - self.stack_min_power_kw = turndown_ratio * self.stack_rating_kw - # self.stack_min_power_kw = 0.1 * self.stack_rating_kw - self.input_power_kw = electrical_power_signal - self.cluster_min_power = self.stack_min_power_kw * self.cluster_cap_mw - self.cluster_max_power = self.stack_rating_kw * self.cluster_cap_mw - - # For the optimization problem: - self.T = len(self.input_power_kw) - self.farm_power = 1e9 - self.switching_cost = ( - (electrolyzer_direct_cost_kw * 0.15 * self.cluster_cap_mw * 1000) - * (1.48e-4) - / (0.26586) - ) - self.verbose = verbose - - def run_grid_connected_pem(self, system_size_mw, hydrogen_production_capacity_required_kgphr): - pem = PEMClusters( - system_size_mw, - self.plant_life_yrs, - **self.user_params, - ) - - power_timeseries, stack_current = pem.grid_connected_func( - hydrogen_production_capacity_required_kgphr - ) - h2_ts, h2_tot = pem.run_grid_connected_workaround(power_timeseries, stack_current) - # h2_ts, h2_tot = pem.run(power_timeseries) - h2_df_ts = pd.Series(h2_ts, name="Cluster #0") - h2_df_tot = pd.Series(h2_tot, name="Cluster #0") - # h2_df_ts = pd.DataFrame(h2_ts, index=list(h2_ts.keys()), columns=['Cluster #0']) - # h2_df_tot = pd.DataFrame(h2_tot, index=list(h2_tot.keys()), columns=['Cluster #0']) - return pd.DataFrame(h2_df_ts), pd.DataFrame(h2_df_tot) - - def run(self, optimize=False): - # TODO: add control type as input! - clusters = self.create_clusters() # initialize clusters - if optimize: - power_to_clusters = self.optimize_power_split() # run Sanjana's code - else: - power_to_clusters = self.even_split_power() - h2_df_ts = pd.DataFrame() - h2_df_tot = pd.DataFrame() - - col_names = [] - start = time.perf_counter() - for ci in range(len(clusters)): - cl_name = f"Cluster #{ci}" - col_names.append(cl_name) - h2_ts, h2_tot = clusters[ci].run(power_to_clusters[ci]) - # h2_dict_ts['Cluster #{}'.format(ci)] = h2_ts - - h2_ts_temp = pd.Series(h2_ts, name=cl_name) - h2_tot_temp = pd.Series(h2_tot, name=cl_name) - if len(h2_df_tot) == 0: - # h2_df_ts=pd.concat([h2_df_ts,h2_ts_temp],axis=0,ignore_index=False) - h2_df_tot = pd.concat([h2_df_tot, h2_tot_temp], axis=0, ignore_index=False) - h2_df_tot.columns = col_names - - h2_df_ts = pd.concat([h2_df_ts, h2_ts_temp], axis=0, ignore_index=False) - h2_df_ts.columns = col_names - else: - # h2_df_ts = h2_df_ts.join(h2_ts_temp) - h2_df_tot = h2_df_tot.join(h2_tot_temp) - h2_df_tot.columns = col_names - - h2_df_ts = h2_df_ts.join(h2_ts_temp) - h2_df_ts.columns = col_names - - end = time.perf_counter() - self.clusters = clusters - if self.verbose: - print(f"Took {round(end - start, 3)} sec to run the RUN function") - return h2_df_ts, h2_df_tot - # return h2_dict_ts, h2_df_tot - - def optimize_power_split(self): - number_of_stacks = self.num_clusters - rated_power = self.cluster_cap_mw * 1000 - tf = 96 - n_times_to_run = int(np.ceil(self.T / tf)) - df = pd.DataFrame({"Wind + PV Generation": self.input_power_kw}) - P_ = None - I_ = None - Tr_ = None - AC = 1 - F_tot = 1 - diff = 0 - - for start_time in range(n_times_to_run): - print(f"Optimizing {number_of_stacks} stacks tarting {start_time*tf}hr/{self.T}hr") - if start_time == 0: - df["Wind + PV Generation"] = df["Wind + PV Generation"].replace(0, np.nan) - df = df.interpolate() - - P_wind_t = df["Wind + PV Generation"][ - (start_time * tf) : ((start_time * tf) + tf) - ].values - start = time.time() - if P_ is not None: - P_ = P_[: len(P_wind_t), :] - I_ = I_[: len(P_wind_t), :] - Tr_ = Tr_[: len(P_wind_t), :] - P_tot_opt, P_, H2f, I_, Tr_, P_wind_t, AC, F_tot = optimize( - P_wind_t, - T=(len(P_wind_t)), - n_stacks=(number_of_stacks), - c_wp=0, - c_sw=self.switching_cost, - rated_power=rated_power, - P_init=P_, - I_init=I_, - T_init=Tr_, - AC_init=AC, - F_tot_init=F_tot, - ) - - diff += time.time() - start - if type(AC).__module__ != "numpy": - AC = np.array(AC) - F_tot = np.array(F_tot) - if start_time == 0: - P_full = P_ - H2f_full = H2f - I_full = I_ - Tr_full = np.sum(Tr_, axis=0) - AC_full = AC - F_tot_full = F_tot - - else: - P_full = np.vstack((P_full, P_)) - H2f_full = np.vstack((H2f_full, H2f)) - I_full = np.vstack((I_full, I_)) - Tr_full = np.vstack((Tr_full, np.sum(Tr_, axis=0))) - AC_full = np.vstack((AC_full, (AC))) - F_tot_full = np.vstack((F_tot_full, (F_tot))) - - return np.transpose(P_full) - - def even_split_power(self): - start = time.perf_counter() - # determine how much power to give each cluster - num_clusters_on = np.floor(self.input_power_kw / self.cluster_min_power) - num_clusters_on = np.where( - num_clusters_on > self.num_clusters, self.num_clusters, num_clusters_on - ) - power_per_cluster = [ - self.input_power_kw[ti] / num_clusters_on[ti] if num_clusters_on[ti] > 0 else 0 - for ti, pwr in enumerate(self.input_power_kw) - ] - - power_per_to_active_clusters = np.array(power_per_cluster) - power_to_clusters = np.zeros((len(self.input_power_kw), self.num_clusters)) - for i, cluster_power in enumerate( - power_per_to_active_clusters - ): # np.arange(0,self.n_stacks,1): - clusters_off = self.num_clusters - int(num_clusters_on[i]) - no_power = np.zeros(clusters_off) - with_power = cluster_power * np.ones(int(num_clusters_on[i])) - tot_power = np.concatenate((with_power, no_power)) - power_to_clusters[i] = tot_power - - # power_to_clusters = np.repeat([power_per_cluster],self.num_clusters,axis=0) - end = time.perf_counter() - - if self.verbose: - print(f"Took {round(end - start, 3)} sec to run even_split_power function") - # rows are power, columns are stacks [300 x n_stacks] - - return np.transpose(power_to_clusters) - - def max_h2_cntrl(self): - # run as many at lower power as possible - ... - - def min_deg_cntrl(self): - # run as few as possible - ... - - def create_clusters(self): - start = time.perf_counter() - # TODO fix the power input - don't make it required! - # in_dict={'dt':3600} - clusters = PEMClusters(self.cluster_cap_mw, self.plant_life_yrs, **self.user_params) - stacks = [clusters] * self.num_clusters - end = time.perf_counter() - if self.verbose: - print(f"Took {round(end - start, 3)} sec to run the create clusters") - return stacks - - -if __name__ == "__main__": - system_size_mw = 1000 - num_clusters = 20 - cluster_cap_mw = system_size_mw / num_clusters - stack_rating_kw = 1000 - cluster_min_power_kw = 0.1 * stack_rating_kw * cluster_cap_mw - num_steps = 200 - power_rampup = np.arange( - cluster_min_power_kw, system_size_mw * stack_rating_kw, cluster_min_power_kw - ) - - plant_life = 30 - electrolyzer_model_parameters = { - "eol_eff_percent_loss": 10, - "uptime_hours_until_eol": 77600, - "include_degradation_penalty": True, - "turndown_ratio": 0.1, - } - # power_rampup = np.linspace(cluster_min_power_kw,system_size_mw*1000,num_steps) - power_rampdown = np.flip(power_rampup) - power_in = np.concatenate((power_rampup, power_rampdown)) - pem = run_PEM_clusters( - power_in, - system_size_mw, - num_clusters, - plant_life, - electrolyzer_model_parameters, - ) - - h2_ts, h2_tot = pem.run() - # pem.clusters[0].cell_design(80,1920*2) diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/test_opt.ipynb b/h2integrate/simulation/technologies/hydrogen/electrolysis/test_opt.ipynb deleted file mode 100644 index 8033a7e9f..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/test_opt.ipynb +++ /dev/null @@ -1,146 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The autoreload extension is already loaded. To reload it, use:\n", - " %reload_ext autoreload\n" - ] - } - ], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2\n", - "\n", - "from pyomo.environ import * # FIXME: no * imports, delete whole comment when fixed # noqa: F403\n", - "import numpy as np\n", - "from h2integrate.simulation.technologies.Electrolyzer_Models import run_PEM_clusters\n", - "\n", - "# from run_PEM_master import run_PEM_clusters" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Took 1.021 sec to run the create clusters\n", - "Optimizing 20 stacks tarting 0hr/398hr\n", - "time to solve 0.8617440000000016\n", - "Optimizing 20 stacks tarting 219hr/398hr\n", - "time to solve 0.5715739999999983\n", - "Took 0.075 sec to run the RUN function\n" - ] - } - ], - "source": [ - "system_size_mw = 1000\n", - "num_clusters = 20\n", - "cluster_cap_mw = system_size_mw / num_clusters\n", - "stack_rating_kw = 1000\n", - "cluster_min_power_kw = 0.1 * stack_rating_kw * cluster_cap_mw\n", - "num_steps = 200\n", - "power_rampup = np.arange(\n", - " cluster_min_power_kw, system_size_mw * stack_rating_kw, cluster_min_power_kw\n", - ")\n", - "\n", - "plant_life = 30\n", - "deg_penalty = True\n", - "user_defined_electrolyzer_EOL_eff_drop = False\n", - "EOL_eff_drop = 13\n", - "user_defined_electrolyzer_BOL_kWh_per_kg = False\n", - "BOL_kWh_per_kg = []\n", - "electrolyzer_model_parameters = {\n", - " \"Modify BOL Eff\": user_defined_electrolyzer_BOL_kWh_per_kg,\n", - " \"BOL Eff [kWh/kg-H2]\": BOL_kWh_per_kg,\n", - " \"Modify EOL Degradation Value\": user_defined_electrolyzer_EOL_eff_drop,\n", - " \"EOL Rated Efficiency Drop\": EOL_eff_drop,\n", - "}\n", - "# power_rampup = np.linspace(cluster_min_power_kw,system_size_mw*1000,num_steps)\n", - "power_rampdown = np.flip(power_rampup)\n", - "power_in = np.concatenate((power_rampup, power_rampdown))\n", - "pem = run_PEM_clusters(\n", - " power_in,\n", - " system_size_mw,\n", - " num_clusters,\n", - " plant_life,\n", - " electrolyzer_model_parameters,\n", - " deg_penalty,\n", - ")\n", - "\n", - "h2_ts, h2_tot = pem.run(optimize=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "9.125" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "219 / 24" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3.9.2 ('aibias')", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.2" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "006fa253eb2b24f25ab550317f005ab784f102b1e9b70f76ded10bb7ec2196b2" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From 6cb074ca2c9c7b7e2b25564d3cc67fd2802333e5 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Wed, 26 Nov 2025 15:54:14 -0700 Subject: [PATCH 05/30] Removed simple unused files --- .../hydrogen/electrolysis/H2_cost_model.py | 127 ----------- .../PEM_BOP/BOP_efficiency_BOL.csv | 87 -------- .../hydrogen/electrolysis/PEM_BOP/PEM_BOP.py | 136 ------------ .../hydrogen/electrolysis/PEM_BOP/README.md | 26 --- .../hydrogen/electrolysis/pem_cost_tools.py | 201 ------------------ 5 files changed, 577 deletions(-) delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_BOP/BOP_efficiency_BOL.csv delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_BOP/PEM_BOP.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_BOP/README.md delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/pem_cost_tools.py diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/H2_cost_model.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/H2_cost_model.py index 74b347122..95477ea56 100644 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/H2_cost_model.py +++ b/h2integrate/simulation/technologies/hydrogen/electrolysis/H2_cost_model.py @@ -44,25 +44,6 @@ def basic_H2_cost_model( " Capacity factor has been reduced to 1 for electrolyzer cost estimate purposes." ) - # print(cap_factor) - # if cap_factor != approx(1.0): - # raise(ValueError("Capacity factor must equal 1")) - # print("cap_factor",cap_factor) - - # #Apply PEM Cost Estimates based on year based on GPRA pathway (H2New) - # if atb_year == 2022: - # electrolyzer_capex_kw = 1100 #[$/kW capacity] stack capital cost - # time_between_replacement = 40000 #[hrs] - # elif atb_year == 2025: - # electrolyzer_capex_kw = 300 - # time_between_replacement = 80000 #[hrs] - # elif atb_year == 2030: - # electrolyzer_capex_kw = 150 - # time_between_replacement = 80000 #[hrs] - # elif atb_year == 2035: - # electrolyzer_capex_kw = 100 - # time_between_replacement = 80000 #[hrs] - # Hydrogen Production Cost From PEM Electrolysis - 2019 (HFTO Program Record) # https://www.hydrogen.energy.gov/pdfs/19009_h2_production_cost_pem_electrolysis_2019.pdf @@ -203,111 +184,3 @@ def basic_H2_cost_model( h2_tax_credit, h2_itc, ) - - -if __name__ == "__main__": - import matplotlib.pyplot as plt - - # plot a sweep of sizes for OPEX and CAPEX - - electrolyzer_capex_kw = 1300 # $/kW - time_between_replacement = 65000 # hours - electrolyzer_sizes_mw = np.arange(1, 1000) - useful_life = 30 # years - atb_year = 2025 - # electrical_generation_timeseries_kw = np.sin(np.arange(0,24*365)*1E-3)*0.5E6 + 0.6E6 - hydrogen_annual_output = 0 - - # for distributed - ndivs = [2, 5, 10] - - opex = [] - capex = [] - opex_distributed = np.zeros((len(ndivs), len(electrolyzer_sizes_mw))) - capex_distributed = np.zeros((len(ndivs), len(electrolyzer_sizes_mw))) - - for i, electrolyzer_size_mw in enumerate(electrolyzer_sizes_mw): - electrical_generation_timeseries_kw = electrolyzer_size_mw * 1000 * np.ones(365 * 24) - - # centralized - _, electrolyzer_total_capital_cost, electrolyzer_OM_cost, _, _, _, _ = basic_H2_cost_model( - electrolyzer_capex_kw, - time_between_replacement, - electrolyzer_size_mw, - useful_life, - electrical_generation_timeseries_kw, - hydrogen_annual_output, - 0, - 0, - include_refurb_in_opex=False, - offshore=0, - ) - - opex.append(electrolyzer_OM_cost) - capex.append(electrolyzer_total_capital_cost) - - for j, div in enumerate(ndivs): - # divided - electrolyzer_size_mw_distributed = electrolyzer_size_mw / div - electrical_generation_timeseries_kw_distibuted = ( - electrical_generation_timeseries_kw / div - ) - - ( - _, - electrolyzer_capital_cost_distributed, - electrolyzer_OM_cost_distributed, - electrolyzer_capex_kw_distributed, - time_between_replacement, - h2_tax_credit, - h2_itc, - ) = basic_H2_cost_model( - electrolyzer_capex_kw, - time_between_replacement, - electrolyzer_size_mw_distributed, - useful_life, - electrical_generation_timeseries_kw_distibuted, - hydrogen_annual_output, - 0, - 0, - include_refurb_in_opex=False, - offshore=0, - ) - # print(opex_distributed) - opex_distributed[j, i] = electrolyzer_OM_cost_distributed * div - capex_distributed[j, i] = electrolyzer_capital_cost_distributed * div - - fig, ax = plt.subplots(1, 2, figsize=(6, 3)) - ax[0].plot(electrolyzer_sizes_mw, np.asarray(capex) * 1e-6, label="Centralized") - ax[1].plot(electrolyzer_sizes_mw, np.asarray(opex) * 1e-6, label="Centralized") - - for i, div in enumerate(ndivs): - # dims(capex_distributed) - ax[0].plot( - electrolyzer_sizes_mw, - np.asarray(capex_distributed[i]) * 1e-6, - "--", - label=f"{div} Divisions", - ) - ax[1].plot( - electrolyzer_sizes_mw, - np.asarray(opex_distributed[i]) * 1e-6, - "--", - label=f"{div} Divisions", - ) - - ax[0].set(ylabel="CAPEX (M USD)", xlabel="Electrolyzer Size (MW)") - ax[1].set(ylabel="Annual OPEX (M USD)", xlabel="Electrolyzer Size (MW)") - plt.legend(frameon=False) - plt.tight_layout() - plt.show() - - ## plot divided energy signals - fig, ax = plt.subplots(1) - ax.plot(electrical_generation_timeseries_kw, label="1") - for div in ndivs: - ax.plot(electrical_generation_timeseries_kw / div, label=f"{div}") - - ax.set(xlabel="Hour", ylabel="Power (MW)") - plt.tight_layout() - plt.show() diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_BOP/BOP_efficiency_BOL.csv b/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_BOP/BOP_efficiency_BOL.csv deleted file mode 100644 index df25ab18e..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_BOP/BOP_efficiency_BOL.csv +++ /dev/null @@ -1,87 +0,0 @@ -operating_ratio,efficiency -0.937732223,4.285714286 -0.933250382,4.258241758 -0.929665402,4.230769231 -0.925182329,4.217032967 -0.920700488,4.18956044 -0.916217415,4.175824176 -0.911735574,4.148351648 -0.904564382,4.107142857 -0.896495097,4.07967033 -0.887531415,4.024725275 -0.878566501,3.983516484 -0.870498448,3.942307692 -0.862430395,3.901098901 -0.856154832,3.873626374 -0.847189918,3.832417582 -0.837328143,3.791208791 -0.827466368,3.75 -0.818501454,3.708791209 -0.805950328,3.653846154 -0.796985414,3.612637363 -0.786226778,3.571428571 -0.775468142,3.53021978 -0.757537082,3.461538462 -0.743192234,3.406593407 -0.730641108,3.351648352 -0.717193121,3.296703297 -0.703743902,3.255494505 -0.687605332,3.200549451 -0.674156113,3.159340659 -0.661603755,3.118131868 -0.646362046,3.063186813 -0.632017198,3.008241758 -0.614979303,2.980769231 -0.588977726,2.898351648 -0.570147341,2.857142857 -0.550420096,2.815934066 -0.530694082,2.760989011 -0.513656187,2.733516484 -0.490341497,2.692307692 -0.475096092,2.678571429 -0.449089588,2.651098901 -0.415906963,2.637362637 -0.388104272,2.637362637 -0.372856404,2.651098901 -0.348638693,2.678571429 -0.329802149,2.706043956 -0.309169418,2.760989011 -0.286741734,2.82967033 -0.266102843,2.953296703 -0.245462721,3.090659341 -0.228410043,3.228021978 -0.207766225,3.406593407 -0.194302222,3.53021978 -0.181735081,3.653846154 -0.173653477,3.763736264 -0.167365594,3.873626374 -0.16107648,3.997252747 -0.153889272,4.134615385 -0.146698369,4.313186813 -0.138608141,4.519230769 -0.131417237,4.697802198 -0.125120731,4.903846154 -0.120620411,5.082417582 -0.114320209,5.32967033 -0.110717982,5.494505495 -0.107116986,5.645604396 -0.103513527,5.824175824 -0.099907604,6.03021978 -0.097199773,6.222527473 -0.093595082,6.414835165 -0.089987927,6.634615385 -0.086377076,6.895604396 -0.084566107,7.087912088 -0.081858276,7.28021978 -0.079147982,7.5 -0.078235106,7.678571429 -0.076422904,7.884615385 -0.074610703,8.090659341 -0.070994924,8.406593407 -0.069177795,8.667582418 -0.067361898,8.914835165 -0.064645444,9.203296703 -0.06372764,9.436813187 -0.061910511,9.697802198 -0.061894496,9.876373626 -0.060088454,10.01373626 diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_BOP/PEM_BOP.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_BOP/PEM_BOP.py deleted file mode 100644 index 084217480..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_BOP/PEM_BOP.py +++ /dev/null @@ -1,136 +0,0 @@ -from pathlib import Path - -import numpy as np -import pandas as pd -import scipy.optimize - -from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_tools import ( - get_electrolyzer_BOL_efficiency, -) - - -file_path = Path(__file__).parent - - -def calc_efficiency_curve(operating_ratio, a, b, c, d): - """Calculates efficiency [kWh/kg] given operation ratio with flattened end curves. - - Efficiency curve and general equation structure from Wang et. al (2023). See README.md - in PEM_BOP directory for more details. - - Wang, X.; Star, A.G.; Ahluwalia, R.K. Performance of Polymer Electrolyte Membrane Water - Electrolysis Systems: Configuration, Stack Materials, Turndown and Efficiency. Energies 2023, - 16, 4964. https://doi.org/10.3390/en16134964 - - Args: - operating_ratio (list or np.array): Operation ratios. - a (float): Coefficient a. - b (float): Coefficient b. - c (float): Coefficient c. - d (float): Coefficient d. - - Returns: - efficiency (list or np.array): Efficiency of electrolyzer BOP in kWh/kg. - """ - efficiency = a + b * operating_ratio + c * operating_ratio**2 + d / operating_ratio - - return efficiency - - -def calc_efficiency( - operating_ratio, efficiency, min_ratio, max_ratio, min_efficiency, max_efficiency -): - """Adjust efficiency list to not go above minimum or maximum operating ratios in - BOP_efficiency_BOL.csv - - Args: - operating_ratio (list or np.array): Operation ratios. - efficiency (list or np.array): Efficiencies calculated using curve fit. - min_ratio (float): Minimum operating ratio from the CSV. - max_ratio (float): Maximum operating ratio from the CSV. - min_efficiency (float): Efficiency at the minimum operating ratio. - max_efficiency (float): Efficiency at the maximum operating ratio. - - Returns: - efficiency (list or np.array): Efficiencies limited with minimum and maximum values - in kWh/kg. - """ - efficiency = np.where(operating_ratio <= min_ratio, min_efficiency, efficiency) - - efficiency = np.where(operating_ratio >= max_ratio, max_efficiency, efficiency) - return efficiency - - -def calc_curve_coefficients(): - """Calculates curve coefficients from BOP_efficiency_BOL.csv""" - df = pd.read_csv(file_path / "BOP_efficiency_BOL.csv") - operating_ratios = df["operating_ratio"].values - efficiency = df["efficiency"].values - - # Get min and max operating ratios - min_ratio_idx = df["operating_ratio"].idxmin() # Index of minimum operating ratio - max_ratio_idx = df["operating_ratio"].idxmax() # Index of maximum operating ratio - - # Get the efficiency at the min and max operating ratios - min_efficiency = df["efficiency"].iloc[min_ratio_idx] - max_efficiency = df["efficiency"].iloc[max_ratio_idx] - - # Get the actual min and max ratios - min_ratio = df["operating_ratio"].iloc[min_ratio_idx] - max_ratio = df["operating_ratio"].iloc[max_ratio_idx] - - curve_coeff, curve_cov = scipy.optimize.curve_fit( - calc_efficiency_curve, operating_ratios, efficiency, p0=(1.0, 1.0, 1.0, 1.0) - ) - return curve_coeff, min_ratio, max_ratio, min_efficiency, max_efficiency - - -def pem_bop( - power_profile_to_electrolyzer_kw, - electrolyzer_rated_mw, - electrolyzer_turn_down_ratio, -): - """ - Calculate PEM balance of plant energy consumption at the beginning-of-life - based on power provided to the electrolyzer. - - Args: - power_profile_to_electrolyzer_kw (list or np.array): Power profile to electrolyzer in kW. - electrolyzer_rated_mw (float): The rating of the PEM electrolyzer in MW. - electrolyzer_turn_down_ratio (float): The electrolyzer turndown ratio. - - Returns: - energy_consumption_bop_kwh (list or np.array): Energy consumed by electrolyzer BOP in kWh. - """ - operating_ratios = power_profile_to_electrolyzer_kw / (electrolyzer_rated_mw * 1e3) - - curve_coeff, min_ratio, max_ratio, min_efficiency, max_efficiency = calc_curve_coefficients() - - efficiencies = calc_efficiency_curve( - operating_ratios, - *curve_coeff, - ) # kwh/kg - - efficiencies = calc_efficiency( - operating_ratios, - efficiencies, - min_ratio, - max_ratio, - min_efficiency, - max_efficiency, - ) - - BOL_efficiency = get_electrolyzer_BOL_efficiency() # kwh/kg - - BOL_kg = (electrolyzer_rated_mw * 1000) / BOL_efficiency # kg/hr - - energy_consumption_bop_kwh = efficiencies * BOL_kg # kwh - - energy_consumption_bop_kwh = np.where( - power_profile_to_electrolyzer_kw - < electrolyzer_turn_down_ratio * electrolyzer_rated_mw * 1000, - 0, - energy_consumption_bop_kwh, - ) - - return energy_consumption_bop_kwh diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_BOP/README.md b/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_BOP/README.md deleted file mode 100644 index 267bf00f1..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_BOP/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Proton Exchange Membrane Water Electrolysis Balance-of-Plant - -This balance-of-plant (BOP) model is derived from Wang et. al (2023). It is represented as an overall BOP efficiency curve (kWh/kg) at different operating ratios at the beginning-of-life (BOL) of the electrolyzer. The operating ratios are the percentage of rated power provided to the electrolyzer. - -The BOP curve is largely dominated by electrical losses from the power electronics which includes a transformer and a rectifier to condition alternating current power, but there are additional mechanical and hydrogen losses. - -The model in H2Integrate calculates a curve fit based on the provided CSV it also limits the calculated efficiencies to the maximum and minimum operating ratio values in the CSV, making the overall function a piecewise implementation. - -**NOTE**: BOP assumes AC current as an input and assumes power electronics for AC to DC conversion. BOP efficiency curve has not been optimized for economies of scale or other electrical infrastructure connections. - - -Citation for BOP model. Losses from BOP are shown in Figure 8. in Wang et. al (2023). -``` -@Article{en16134964, -AUTHOR = {Wang, Xiaohua and Star, Andrew G. and Ahluwalia, Rajesh K.}, -TITLE = {Performance of Polymer Electrolyte Membrane Water Electrolysis Systems: Configuration, Stack Materials, Turndown and Efficiency}, -JOURNAL = {Energies}, -VOLUME = {16}, -YEAR = {2023}, -NUMBER = {13}, -ARTICLE-NUMBER = {4964}, -URL = {https://www.mdpi.com/1996-1073/16/13/4964}, -ISSN = {1996-1073}, -DOI = {10.3390/en16134964} -} -``` diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/pem_cost_tools.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/pem_cost_tools.py deleted file mode 100644 index c5d7d1e6d..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/pem_cost_tools.py +++ /dev/null @@ -1,201 +0,0 @@ -import numpy as np -from attrs import field, define - -from h2integrate.tools.profast_tools import create_years_of_operation - - -@define -class ElectrolyzerLCOHInputConfig: - """Calculates inputs for LCOH functions related to electrolyzer - performance outside of capex and opex. - - Args: - electrolyzer_physics_results (dict): results from run_electrolyzer_physics() - electrolyzer_config (dict): sub-dictionary of h2integrate_config - financial_analysis_start_year (int): analysis start year - installation_period_months (int|float|None): installation period in months. defaults to 36. - """ - - electrolyzer_physics_results: dict - electrolyzer_config: dict - financial_analysis_start_year: int - installation_period_months: int | float | None = field(default=36) - - electrolyzer_capacity_kW: int | float = field(init=False) - project_lifetime_years: int = field(init=False) - long_term_utilization: dict = field(init=False) - rated_capacity_kg_pr_day: float = field(init=False) - water_usage_gal_pr_kg: float = field(init=False) - - electrolyzer_annual_energy_usage_kWh: list[float] = field(init=False) - electrolyzer_eff_kWh_pr_kg: list[float] = field(init=False) - electrolyzer_annual_h2_production_kg: list[float] = field(init=False) - - refurb_cost_percent: list[float] = field(init=False) - replacement_schedule: list[float] = field(init=False) - - def __attrs_post_init__(self): - annual_performance = self.electrolyzer_physics_results["H2_Results"][ - "Performance Schedules" - ] - - #: electrolyzer system capacity in kW - self.electrolyzer_capacity_kW = self.electrolyzer_physics_results["H2_Results"][ - "system capacity [kW]" - ] - - #: int: lifetime of project in years - self.project_lifetime_years = len(annual_performance) - - #: float: electrolyzer beginnning-of-life rated H2 production capacity in kg/day - self.rated_capacity_kg_pr_day = ( - self.electrolyzer_physics_results["H2_Results"]["Rated BOL: H2 Production [kg/hr]"] * 24 - ) - - #: float: water usage in gallons of water per kg of H2 - self.water_usage_gal_pr_kg = self.electrolyzer_physics_results["H2_Results"][ - "Rated BOL: Gal H2O per kg-H2" - ] - #: list(float): annual energy consumed by electrolyzer per year of operation in kWh/year - self.electrolyzer_annual_energy_usage_kWh = annual_performance[ - "Annual Energy Used [kWh/year]" - ].to_list() - #: list(float): annual avg efficiency of electrolyzer for each year of operation in kWh/kg - self.electrolyzer_eff_kWh_pr_kg = annual_performance[ - "Annual Average Efficiency [kWh/kg]" - ].to_list() - #: list(float): annual hydrogen production for each year of operation in kg/year - self.electrolyzer_annual_h2_production_kg = annual_performance[ - "Annual H2 Production [kg/year]" - ] - #: dict: annual capacity factor of electrolyzer for each year of operation - self.long_term_utilization = self.make_lifetime_utilization() - - use_complex_refurb = False - if "complex_refurb" in self.electrolyzer_config.keys(): - if self.electrolyzer_config["complex_refurb"]: - use_complex_refurb = True - - # complex schedule assumes stacks are replaced in the year they reach end-of-life - if use_complex_refurb: - self.replacement_schedule = self.calc_complex_refurb_schedule() - self.refurb_cost_percent = list( - np.array( - self.replacement_schedule * self.electrolyzer_config["replacement_cost_percent"] - ) - ) - - # simple schedule assumes all stacks are replaced in the same year - else: - self.replacement_schedule = self.calc_simple_refurb_schedule() - self.refurb_cost_percent = list( - np.array( - self.replacement_schedule * self.electrolyzer_config["replacement_cost_percent"] - ) - ) - - def calc_simple_refurb_schedule(self): - """Calculate electrolyzer refurbishment schedule - assuming that all stacks are replaced in the same year. - - Returns: - list: list of years when stacks are replaced. - a value of 1 means stacks are replaced that year. - """ - annual_performance = self.electrolyzer_physics_results["H2_Results"][ - "Performance Schedules" - ] - refurb_simple = np.zeros(len(annual_performance)) - refurb_period = int( - round( - self.electrolyzer_physics_results["H2_Results"]["Time Until Replacement [hrs]"] - / 8760 - ) - ) - refurb_simple[refurb_period : len(annual_performance) : refurb_period] = 1.0 - - return refurb_simple - - def calc_complex_refurb_schedule(self): - """Calculate electrolyzer refurbishment schedule - stacks are replaced in the year they reach EOL. - - Returns: - list: list of years when stacks are replaced. values are are fraction of - the total installed capacity. - """ - annual_performance = self.electrolyzer_physics_results["H2_Results"][ - "Performance Schedules" - ] - refurb_complex = annual_performance["Refurbishment Schedule [MW replaced/year]"].values / ( - self.electrolyzer_capacity_kW / 1e3 - ) - return refurb_complex - - def make_lifetime_utilization(self): - """Make long term utilization dictionary for electrolyzer system. - - Returns: - dict: keys are years of operation and values are the capacity factor for that year. - """ - annual_performance = self.electrolyzer_physics_results["H2_Results"][ - "Performance Schedules" - ] - - years_of_operation = create_years_of_operation( - self.project_lifetime_years, - self.financial_analysis_start_year, - self.installation_period_months, - ) - - cf_per_year = annual_performance["Capacity Factor [-]"].to_list() - utilization_dict = dict(zip(years_of_operation, cf_per_year)) - return utilization_dict - - -def calc_electrolyzer_variable_om(electrolyzer_physics_results, h2integrate_config): - """Calculate variable O&M of electrolyzer system in $/kg-H2. - - Args: - electrolyzer_physics_results (dict): results from run_electrolyzer_physics() - h2integrate_config (:obj:`h2integrate_simulation.H2IntegrateSimulationConfig`): h2integrate - simulation config. - - Returns: - dict | float: electrolyzer variable o&m in $/kg-H2. - """ - electrolyzer_config = h2integrate_config["electrolyzer"] - annual_performance = electrolyzer_physics_results["H2_Results"]["Performance Schedules"] - - if "var_om" in electrolyzer_config.keys(): - electrolyzer_vopex_pr_kg = ( - electrolyzer_config["var_om"] - * annual_performance["Annual Average Efficiency [kWh/kg]"].values - ) - - if "financial_analysis_start_year" not in h2integrate_config["finance_parameters"]: - financial_analysis_start_year = h2integrate_config["project_parameters"][ - "financial_analysis_start_year" - ] - else: - financial_analysis_start_year = h2integrate_config["finance_parameters"][ - "financial_analysis_start_year" - ] - if "installation_time" not in h2integrate_config["project_parameters"]: - installation_period_months = 36 - else: - installation_period_months = h2integrate_config["project_parameters"][ - "installation_time" - ] - - years_of_operation = create_years_of_operation( - h2integrate_config["project_parameters"]["project_lifetime"], - financial_analysis_start_year, - installation_period_months, - ) - # $/kg-year - vopex_elec = dict(zip(years_of_operation, electrolyzer_vopex_pr_kg)) - - else: - vopex_elec = 0.0 - return vopex_elec From 2af6cdac70bcdd5fd30a266479e973c2cb0220fb Mon Sep 17 00:00:00 2001 From: John Jasa Date: Wed, 26 Nov 2025 16:56:19 -0700 Subject: [PATCH 06/30] Moving basic H2 cost --- .../converters/hydrogen/basic_cost_model.py | 147 +++++++++++--- .../hydrogen/test/test_basic_cost_model.py | 136 +++++++++++++ .../hydrogen/electrolysis/H2_cost_model.py | 186 ------------------ .../test_hydrogen/test_basic_h2_cost.py | 177 ----------------- 4 files changed, 260 insertions(+), 386 deletions(-) create mode 100644 h2integrate/converters/hydrogen/test/test_basic_cost_model.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/H2_cost_model.py delete mode 100644 tests/h2integrate/test_hydrogen/test_basic_h2_cost.py diff --git a/h2integrate/converters/hydrogen/basic_cost_model.py b/h2integrate/converters/hydrogen/basic_cost_model.py index bb7071b2a..cc7758079 100644 --- a/h2integrate/converters/hydrogen/basic_cost_model.py +++ b/h2integrate/converters/hydrogen/basic_cost_model.py @@ -1,11 +1,11 @@ +import warnings + +import numpy as np from attrs import field, define from h2integrate.core.utilities import CostModelBaseConfig, merge_shared_inputs from h2integrate.core.validators import gt_zero, contains, must_equal from h2integrate.converters.hydrogen.electrolyzer_baseclass import ElectrolyzerCostBaseClass -from h2integrate.simulation.technologies.hydrogen.electrolysis.H2_cost_model import ( - basic_H2_cost_model, -) @define @@ -51,9 +51,12 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # unpack inputs plant_config = self.options["plant_config"] - total_hydrogen_produced = float(inputs["total_hydrogen_produced"]) - electrolyzer_size_mw = inputs["electrolyzer_rating_mw"][0] + float(inputs["total_hydrogen_produced"][0]) + electrolyzer_size_mw = float(inputs["electrolyzer_size_mw"][0]) useful_life = plant_config["plant"]["plant_life"] + electrical_generation_timeseries_kw = inputs["electricity_in"] + electrolyzer_capex_kw = self.config.electrolyzer_capex + time_between_replacement = self.config.time_between_replacement # run hydrogen production cost model - from hopp examples if self.config.location == "onshore": @@ -61,25 +64,123 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): else: offshore = 1 - ( - electrolyzer_total_capital_cost, - electrolyzer_OM_cost, - electrolyzer_capex_kw, - time_between_replacement, - h2_tax_credit, - h2_itc, - ) = basic_H2_cost_model( - self.config.electrolyzer_capex, - self.config.time_between_replacement, - electrolyzer_size_mw, - useful_life, - inputs["electricity_in"], - total_hydrogen_produced, - 0.0, - 0.0, - include_refurb_in_opex=False, - offshore=offshore, + # Basic cost modeling for a PEM electrolyzer. + # Looking at cost projections for PEM electrolyzers over years 2022, 2025, 2030, 2035. + # Electricity costs are calculated outside of hydrogen cost model + + # Basic information in our analysis + kw_continuous = electrolyzer_size_mw * 1000 + + # Capacity factor + avg_generation = np.mean(electrical_generation_timeseries_kw) # Avg Generation + cap_factor = avg_generation / kw_continuous + + if cap_factor > 1.0: + cap_factor = 1.0 + warnings.warn( + "Electrolyzer capacity factor would be greater than 1 with provided energy profile." + " Capacity factor has been reduced to 1 for electrolyzer cost estimate purposes." + ) + + # Hydrogen Production Cost From PEM Electrolysis - 2019 (HFTO Program Record) + # https://www.hydrogen.energy.gov/pdfs/19009_h2_production_cost_pem_electrolysis_2019.pdf + + # Capital costs provide by Hydrogen Production Cost From PEM Electrolysis - 2019 (HFTO + # Program Record) + stack_capital_cost = 342 # [$/kW] + mechanical_bop_cost = 36 # [$/kW] for a compressor + electrical_bop_cost = 82 # [$/kW] for a rectifier + + # Installed capital cost + stack_installation_factor = 12 / 100 # [%] for stack cost + elec_installation_factor = 12 / 100 # [%] and electrical BOP + + # scale installation fraction if offshore (see Singlitico 2021 https://doi.org/10.1016/j.rset.2021.100005) + stack_installation_factor *= 1 + offshore + elec_installation_factor *= 1 + offshore + + # mechanical BOP install cost = 0% + + # Indirect capital cost as a percentage of installed capital cost + site_prep = 2 / 100 # [%] + engineering_design = 10 / 100 # [%] + project_contingency = 15 / 100 # [%] + permitting = 15 / 100 # [%] + land = 250000 # [$] + + stack_replacment_cost = 15 / 100 # [% of installed capital cost] + fixed_OM = 0.24 # [$/kg H2] + + program_record = False + + # Chose to use numbers provided by GPRA pathways + if program_record: + total_direct_electrolyzer_cost_kw = ( + (stack_capital_cost * (1 + stack_installation_factor)) + + mechanical_bop_cost + + (electrical_bop_cost * (1 + elec_installation_factor)) + ) + else: + total_direct_electrolyzer_cost_kw = ( + (electrolyzer_capex_kw * (1 + stack_installation_factor)) + + mechanical_bop_cost + + (electrical_bop_cost * (1 + elec_installation_factor)) + ) + + # Assign CapEx for electrolyzer from capacity based installed CapEx + electrolyzer_total_installed_capex = ( + total_direct_electrolyzer_cost_kw * electrolyzer_size_mw * 1000 + ) + + # Add indirect capital costs + electrolyzer_total_capital_cost = ( + ( + (site_prep + engineering_design + project_contingency + permitting) + * electrolyzer_total_installed_capex + ) + + land + + electrolyzer_total_installed_capex + ) + + # O&M costs + # https://www.sciencedirect.com/science/article/pii/S2542435121003068 + # for 700 MW electrolyzer (https://www.hydrogen.energy.gov/pdfs/19009_h2_production_cost_pem_electrolysis_2019.pdf) + h2_FOM_kg = 0.24 # [$/kg] + + # linearly scaled current central fixed O&M for a 700MW electrolyzer up to a + # 1000MW electrolyzer + scaled_h2_FOM_kg = h2_FOM_kg * electrolyzer_size_mw / 700 + + h2_FOM_kWh = scaled_h2_FOM_kg / 55.5 # [$/kWh] used 55.5 kWh/kg for efficiency + fixed_OM = h2_FOM_kWh * 8760 # [$/kW-y] + property_tax_insurance = 1.5 / 100 # [% of Cap/y] + variable_OM = 1.30 # [$/MWh] + + # Amortized refurbishment expense [$/MWh] + include_refurb_in_opex = False + if not include_refurb_in_opex: + amortized_refurbish_cost = 0.0 + else: + amortized_refurbish_cost = ( + (total_direct_electrolyzer_cost_kw * stack_replacment_cost) + * max(((useful_life * 8760 * cap_factor) / time_between_replacement - 1), 0) + / useful_life + / 8760 + / cap_factor + * 1000 + ) + + # Total O&M costs [% of installed cap/year] + total_OM_costs = ( + fixed_OM + (property_tax_insurance * total_direct_electrolyzer_cost_kw) + ) / total_direct_electrolyzer_cost_kw + ( + (variable_OM + amortized_refurbish_cost) + / 1000 + * 8760 + * (cap_factor / total_direct_electrolyzer_cost_kw) ) + electrolyzer_OM_cost = electrolyzer_total_installed_capex * total_OM_costs # Capacity based + outputs["CapEx"] = electrolyzer_total_capital_cost outputs["OpEx"] = electrolyzer_OM_cost diff --git a/h2integrate/converters/hydrogen/test/test_basic_cost_model.py b/h2integrate/converters/hydrogen/test/test_basic_cost_model.py new file mode 100644 index 000000000..6b3969f78 --- /dev/null +++ b/h2integrate/converters/hydrogen/test/test_basic_cost_model.py @@ -0,0 +1,136 @@ +import numpy as np +import openmdao.api as om +from pytest import approx + +from h2integrate.converters.hydrogen.basic_cost_model import BasicElectrolyzerCostModel + + +class TestBasicH2Costs: + electrolyzer_size_mw = 100 + h2_annual_output = 500 + nturbines = 10 + n_timesteps = 500 + electrical_generation_timeseries = ( + electrolyzer_size_mw * (np.sin(range(0, n_timesteps))) * 0.5 + electrolyzer_size_mw * 0.5 + ) + + per_turb_electrolyzer_size_mw = electrolyzer_size_mw / nturbines + per_turb_h2_annual_output = h2_annual_output / nturbines + per_turb_electrical_generation_timeseries = electrical_generation_timeseries / nturbines + + elec_capex = 600 # $/kW + time_between_replacement = 80000 # hours + useful_life = 30 # years + + def _create_problem(self, location, electrolyzer_size_mw, electrical_generation_timeseries): + """Helper method to create and set up an OpenMDAO problem.""" + prob = om.Problem() + prob.model.add_subsystem( + "basic_cost_model", + BasicElectrolyzerCostModel( + plant_config={ + "plant": { + "plant_life": self.useful_life, + "simulation": { + "n_timesteps": self.n_timesteps, + }, + }, + }, + tech_config={ + "model_inputs": { + "cost_parameters": { + "location": location, + "electrolyzer_capex": self.elec_capex, + "time_between_replacement": self.time_between_replacement, + }, + } + }, + ), + promotes=["*"], + ) + prob.setup() + prob.set_val("electricity_in", electrical_generation_timeseries, units="kW") + prob.set_val("electrolyzer_size_mw", electrolyzer_size_mw, units="MW") + return prob + + def test_on_turbine_capex(self): + prob = self._create_problem( + "offshore", + self.per_turb_electrolyzer_size_mw, + self.per_turb_electrical_generation_timeseries, + ) + prob.set_val("total_hydrogen_produced", self.per_turb_h2_annual_output, units="kg/year") + prob.run_model() + + per_turb_electrolyzer_total_capital_cost = prob["CapEx"] + electrolyzer_total_capital_cost = per_turb_electrolyzer_total_capital_cost * self.nturbines + + assert electrolyzer_total_capital_cost == approx(127698560.0) + + def test_on_platform_capex(self): + prob = self._create_problem( + "offshore", self.electrolyzer_size_mw, self.electrical_generation_timeseries + ) + prob.set_val("total_hydrogen_produced", self.h2_annual_output, units="kg/year") + prob.run_model() + + electrolyzer_total_capital_cost = prob["CapEx"] + + assert electrolyzer_total_capital_cost == approx(125448560.0) + + def test_on_land_capex(self): + prob = self._create_problem( + "onshore", + self.per_turb_electrolyzer_size_mw, + self.per_turb_electrical_generation_timeseries, + ) + prob.set_val("total_hydrogen_produced", self.per_turb_h2_annual_output, units="kg/year") + prob.run_model() + + per_turb_electrolyzer_total_capital_cost = prob["CapEx"] + electrolyzer_total_capital_cost = per_turb_electrolyzer_total_capital_cost * self.nturbines + + assert electrolyzer_total_capital_cost == approx(116077280.00000003) + + def test_on_turbine_opex(self): + prob = self._create_problem( + "offshore", + self.per_turb_electrolyzer_size_mw, + self.per_turb_electrical_generation_timeseries, + ) + prob.set_val("total_hydrogen_produced", self.per_turb_h2_annual_output, units="kg/year") + prob.run_model() + + per_turb_electrolyzer_OM_cost = prob["OpEx"] + electrolyzer_OM_cost = per_turb_electrolyzer_OM_cost * self.nturbines + + assert electrolyzer_OM_cost == approx(1377207.4599629682) + + def test_on_platform_opex(self): + prob = self._create_problem( + "offshore", self.electrolyzer_size_mw, self.electrical_generation_timeseries + ) + prob.set_val("total_hydrogen_produced", self.h2_annual_output, units="kg/year") + prob.run_model() + + electrolyzer_OM_cost = prob["OpEx"] + + assert electrolyzer_OM_cost == approx(1864249.9310054395) + + def test_on_land_opex(self): + prob = self._create_problem( + "onshore", + self.per_turb_electrolyzer_size_mw, + self.per_turb_electrical_generation_timeseries, + ) + prob.set_val("total_hydrogen_produced", self.per_turb_h2_annual_output, units="kg/year") + prob.run_model() + + per_turb_electrolyzer_OM_cost = prob["OpEx"] + electrolyzer_OM_cost = per_turb_electrolyzer_OM_cost * self.nturbines + + assert electrolyzer_OM_cost == approx(1254447.4599629682) + + +if __name__ == "__main__": + test_set = TestBasicH2Costs() diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/H2_cost_model.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/H2_cost_model.py deleted file mode 100644 index 95477ea56..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/H2_cost_model.py +++ /dev/null @@ -1,186 +0,0 @@ -import warnings - -import numpy as np - - -def basic_H2_cost_model( - electrolyzer_capex_kw, - time_between_replacement, - electrolyzer_size_mw, - useful_life, - electrical_generation_timeseries_kw, - hydrogen_annual_output, - PTC_USD_kg, - ITC_perc, - include_refurb_in_opex=False, - offshore=0, -): - """ - Basic cost modeling for a PEM electrolyzer. - Looking at cost projections for PEM electrolyzers over years 2022, 2025, 2030, 2035. - Electricity costs are calculated outside of hydrogen cost model - - Needs: - Scaling factor for off-shore electrolysis - Verifying numbers are appropriate for simplified cash flows - Verify how H2 PTC works/factors into cash flows - - If offshore = 1, then additional cost scaling is added to account for added difficulties for - offshore installation, offshore=0 means onshore - """ - - # Basic information in our analysis - kw_continuous = electrolyzer_size_mw * 1000 - - # Capacity factor - avg_generation = np.mean(electrical_generation_timeseries_kw) # Avg Generation - # print("avg_generation: ", avg_generation) - cap_factor = avg_generation / kw_continuous - - if cap_factor > 1.0: - cap_factor = 1.0 - warnings.warn( - "Electrolyzer capacity factor would be greater than 1 with provided energy profile." - " Capacity factor has been reduced to 1 for electrolyzer cost estimate purposes." - ) - - # Hydrogen Production Cost From PEM Electrolysis - 2019 (HFTO Program Record) - # https://www.hydrogen.energy.gov/pdfs/19009_h2_production_cost_pem_electrolysis_2019.pdf - - # Capital costs provide by Hydrogen Production Cost From PEM Electrolysis - 2019 (HFTO - # Program Record) - stack_capital_cost = 342 # [$/kW] - mechanical_bop_cost = 36 # [$/kW] for a compressor - electrical_bop_cost = 82 # [$/kW] for a rectifier - - # Installed capital cost - stack_installation_factor = 12 / 100 # [%] for stack cost - elec_installation_factor = 12 / 100 # [%] and electrical BOP - - # scale installation fraction if offshore (see Singlitico 2021 https://doi.org/10.1016/j.rset.2021.100005) - stack_installation_factor *= 1 + offshore - elec_installation_factor *= 1 + offshore - - # mechanical BOP install cost = 0% - - # Indirect capital cost as a percentage of installed capital cost - site_prep = 2 / 100 # [%] - engineering_design = 10 / 100 # [%] - project_contingency = 15 / 100 # [%] - permitting = 15 / 100 # [%] - land = 250000 # [$] - - stack_replacment_cost = 15 / 100 # [% of installed capital cost] - fixed_OM = 0.24 # [$/kg H2] - - program_record = False - - # Chose to use numbers provided by GPRA pathways - if program_record: - total_direct_electrolyzer_cost_kw = ( - (stack_capital_cost * (1 + stack_installation_factor)) - + mechanical_bop_cost - + (electrical_bop_cost * (1 + elec_installation_factor)) - ) - else: - total_direct_electrolyzer_cost_kw = ( - (electrolyzer_capex_kw * (1 + stack_installation_factor)) - + mechanical_bop_cost - + (electrical_bop_cost * (1 + elec_installation_factor)) - ) - - # Assign CapEx for electrolyzer from capacity based installed CapEx - electrolyzer_total_installed_capex = ( - total_direct_electrolyzer_cost_kw * electrolyzer_size_mw * 1000 - ) - - # Add indirect capital costs - electrolyzer_total_capital_cost = ( - ( - (site_prep + engineering_design + project_contingency + permitting) - * electrolyzer_total_installed_capex - ) - + land - + electrolyzer_total_installed_capex - ) - - # O&M costs - # https://www.sciencedirect.com/science/article/pii/S2542435121003068 - # for 700 MW electrolyzer (https://www.hydrogen.energy.gov/pdfs/19009_h2_production_cost_pem_electrolysis_2019.pdf) - h2_FOM_kg = 0.24 # [$/kg] - - # linearly scaled current central fixed O&M for a 700MW electrolyzer up to a 1000MW electrolyzer - scaled_h2_FOM_kg = h2_FOM_kg * electrolyzer_size_mw / 700 - - h2_FOM_kWh = scaled_h2_FOM_kg / 55.5 # [$/kWh] used 55.5 kWh/kg for efficiency - fixed_OM = h2_FOM_kWh * 8760 # [$/kW-y] - property_tax_insurance = 1.5 / 100 # [% of Cap/y] - variable_OM = 1.30 # [$/MWh] - - # Amortized refurbishment expense [$/MWh] - if not include_refurb_in_opex: - amortized_refurbish_cost = 0.0 - else: - amortized_refurbish_cost = ( - (total_direct_electrolyzer_cost_kw * stack_replacment_cost) - * max(((useful_life * 8760 * cap_factor) / time_between_replacement - 1), 0) - / useful_life - / 8760 - / cap_factor - * 1000 - ) - - # Total O&M costs [% of installed cap/year] - total_OM_costs = ( - fixed_OM + (property_tax_insurance * total_direct_electrolyzer_cost_kw) - ) / total_direct_electrolyzer_cost_kw + ( - (variable_OM + amortized_refurbish_cost) - / 1000 - * 8760 - * (cap_factor / total_direct_electrolyzer_cost_kw) - ) - - capacity_based_OM = True - if capacity_based_OM: - electrolyzer_OM_cost = electrolyzer_total_installed_capex * total_OM_costs # Capacity based - else: - electrolyzer_OM_cost = ( - fixed_OM * hydrogen_annual_output - ) # Production based - likely not very accurate - - # Add in electrolyzer repair schedule (every 7 years) - # Use if not using time between replacement given in hours - # Currently not added into further calculations - electrolyzer_repair_schedule = [] - counter = 1 - for year in range(0, useful_life): - if year == 0: - electrolyzer_repair_schedule = np.append(electrolyzer_repair_schedule, [0]) - - elif counter % time_between_replacement == 0: - electrolyzer_repair_schedule = np.append(electrolyzer_repair_schedule, [1]) - - else: - electrolyzer_repair_schedule = np.append(electrolyzer_repair_schedule, [0]) - counter += 1 - electrolyzer_repair_schedule * (stack_replacment_cost * electrolyzer_total_installed_capex) - - # Include Hydrogen PTC from the Inflation Reduction Act (range $0.60 - $3/kg-H2) - h2_tax_credit = [0] * useful_life - h2_tax_credit[0:10] = [hydrogen_annual_output * PTC_USD_kg] * 10 - - # Include ITC from IRA (range 0% - 50%) - # ITC is expressed as a percentage of the total installed cost which reduces the annual tax - # liabiity in year one of the project cash flow. - h2_itc = (ITC_perc / 100) * electrolyzer_total_installed_capex - cf_h2_itc = [0] * 30 - cf_h2_itc[1] = h2_itc - - return ( - electrolyzer_total_capital_cost, - electrolyzer_OM_cost, - electrolyzer_capex_kw, - time_between_replacement, - h2_tax_credit, - h2_itc, - ) diff --git a/tests/h2integrate/test_hydrogen/test_basic_h2_cost.py b/tests/h2integrate/test_hydrogen/test_basic_h2_cost.py deleted file mode 100644 index 7397e2fb5..000000000 --- a/tests/h2integrate/test_hydrogen/test_basic_h2_cost.py +++ /dev/null @@ -1,177 +0,0 @@ -import numpy as np -from pytest import approx - -from h2integrate.simulation.technologies.hydrogen.electrolysis.H2_cost_model import ( - basic_H2_cost_model, -) - - -class TestBasicH2Costs: - electrolyzer_size_mw = 100 - h2_annual_output = 500 - nturbines = 10 - electrical_generation_timeseries = ( - electrolyzer_size_mw * (np.sin(range(0, 500))) * 0.5 + electrolyzer_size_mw * 0.5 - ) - - per_turb_electrolyzer_size_mw = electrolyzer_size_mw / nturbines - per_turb_h2_annual_output = h2_annual_output / nturbines - per_turb_electrical_generation_timeseries = electrical_generation_timeseries / nturbines - - elec_capex = 600 # $/kW - time_between_replacement = 80000 # hours - useful_life = 30 # years - - def test_on_turbine_capex(self): - ( - per_turb_electrolyzer_total_capital_cost, - per_turb_electrolyzer_OM_cost, - per_turb_electrolyzer_capex_kw, - time_between_replacement, - h2_tax_credit, - h2_itc, - ) = basic_H2_cost_model( - self.elec_capex, - self.time_between_replacement, - self.per_turb_electrolyzer_size_mw, - self.useful_life, - self.per_turb_electrical_generation_timeseries, - self.per_turb_h2_annual_output, - 0.0, - 0.0, - include_refurb_in_opex=False, - offshore=1, - ) - - electrolyzer_total_capital_cost = per_turb_electrolyzer_total_capital_cost * self.nturbines - per_turb_electrolyzer_OM_cost * self.nturbines - - assert electrolyzer_total_capital_cost == approx(127698560.0) - - def test_on_platform_capex(self): - ( - electrolyzer_total_capital_cost, - electrolyzer_OM_cost, - electrolyzer_capex_kw, - time_between_replacement, - h2_tax_credit, - h2_itc, - ) = basic_H2_cost_model( - self.elec_capex, - self.time_between_replacement, - self.electrolyzer_size_mw, - self.useful_life, - self.electrical_generation_timeseries, - self.h2_annual_output, - 0.0, - 0.0, - include_refurb_in_opex=False, - offshore=1, - ) - - assert electrolyzer_total_capital_cost == approx(125448560.0) - - def test_on_land_capex(self): - ( - per_turb_electrolyzer_total_capital_cost, - per_turb_electrolyzer_OM_cost, - per_turb_electrolyzer_capex_kw, - time_between_replacement, - h2_tax_credit, - h2_itc, - ) = basic_H2_cost_model( - self.elec_capex, - self.time_between_replacement, - self.per_turb_electrolyzer_size_mw, - self.useful_life, - self.per_turb_electrical_generation_timeseries, - self.per_turb_h2_annual_output, - 0.0, - 0.0, - include_refurb_in_opex=False, - offshore=0, - ) - - electrolyzer_total_capital_cost = per_turb_electrolyzer_total_capital_cost * self.nturbines - per_turb_electrolyzer_OM_cost * self.nturbines - - assert electrolyzer_total_capital_cost == approx(116077280.00000003) - - def test_on_turbine_opex(self): - ( - per_turb_electrolyzer_total_capital_cost, - per_turb_electrolyzer_OM_cost, - per_turb_electrolyzer_capex_kw, - time_between_replacement, - h2_tax_credit, - h2_itc, - ) = basic_H2_cost_model( - self.elec_capex, - self.time_between_replacement, - self.per_turb_electrolyzer_size_mw, - self.useful_life, - self.per_turb_electrical_generation_timeseries, - self.per_turb_h2_annual_output, - 0.0, - 0.0, - include_refurb_in_opex=False, - offshore=1, - ) - - (per_turb_electrolyzer_total_capital_cost * self.nturbines) - electrolyzer_OM_cost = per_turb_electrolyzer_OM_cost * self.nturbines - - assert electrolyzer_OM_cost == approx(1377207.4599629682) - - def test_on_platform_opex(self): - ( - electrolyzer_total_capital_cost, - electrolyzer_OM_cost, - electrolyzer_capex_kw, - time_between_replacement, - h2_tax_credit, - h2_itc, - ) = basic_H2_cost_model( - self.elec_capex, - self.time_between_replacement, - self.electrolyzer_size_mw, - self.useful_life, - self.electrical_generation_timeseries, - self.h2_annual_output, - 0.0, - 0.0, - include_refurb_in_opex=False, - offshore=1, - ) - - assert electrolyzer_OM_cost == approx(1864249.9310054395) - - def test_on_land_opex(self): - ( - per_turb_electrolyzer_total_capital_cost, - per_turb_electrolyzer_OM_cost, - per_turb_electrolyzer_capex_kw, - time_between_replacement, - h2_tax_credit, - h2_itc, - ) = basic_H2_cost_model( - self.elec_capex, - self.time_between_replacement, - self.per_turb_electrolyzer_size_mw, - self.useful_life, - self.per_turb_electrical_generation_timeseries, - self.per_turb_h2_annual_output, - 0.0, - 0.0, - include_refurb_in_opex=False, - offshore=0, - ) - - (per_turb_electrolyzer_total_capital_cost * self.nturbines) - electrolyzer_OM_cost = per_turb_electrolyzer_OM_cost * self.nturbines - - assert electrolyzer_OM_cost == approx(1254447.4599629682) - - -if __name__ == "__main__": - test_set = TestBasicH2Costs() From 88affb74507880341d3d18b84c389b37adb6bf63 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Wed, 26 Nov 2025 17:23:49 -0700 Subject: [PATCH 07/30] Removed defunct pressure vessel and pem_control_type --- .github/workflows/ci.yml | 2 +- .../how_to_set_up_an_analysis.ipynb | 1 - examples/01_onshore_steel_mn/tech_config.yaml | 1 - examples/02_texas_ammonia/tech_config.yaml | 1 - .../co2_hydrogenation/tech_config_co2h.yaml | 1 - .../tech_config_co2h.yaml | 1 - examples/05_wind_h2_opt/tech_config.yaml | 1 - .../08_wind_electrolyzer/tech_config.yaml | 1 - examples/10_electrolyzer_om/tech_config.yaml | 1 - examples/12_ammonia_synloop/tech_config.yaml | 1 - .../inputs/tech_config.yaml | 1 - .../tech_config.yaml | 1 - .../17_splitter_wind_doc_h2/tech_config.yaml | 1 - .../tech_config.yaml | 1 - .../hydrogen/eco_tools_pem_electrolyzer.py | 3 - .../hydrogen/test/test_wombat_model.py | 2 - .../old_input/h2integrate_config_modular.yaml | 1 - h2integrate/converters/steel/steel.py | 10 +- .../hydrogen/electrolysis/PEM_costs_custom.py | 24 - .../hydrogen/electrolysis/run_PEM_master.py | 220 +++++ .../hydrogen/electrolysis/run_h2_PEM.py | 44 +- .../h2_storage/pressure_vessel/__init__.py | 8 - .../Compressed_all.py | 255 ------ .../Compressed_gas_function.py | 791 ----------------- .../README.md | 54 -- .../Tankinator.xlsx | Bin 120062 -> 0 bytes .../__init__.py | 6 - .../pressure_vessel/material_properties.json | 503 ----------- .../h2_storage/pressure_vessel/tankinator.py | 818 ------------------ .../h2_storage/pressure_vessel/von_mises.py | 97 --- h2integrate/storage/hydrogen/eco_storage.py | 2 +- .../input_files/plant/h2integrate_config.yaml | 344 -------- .../plant/h2integrate_config_onshore.yaml | 368 -------- .../test_hydrogen/test_PEM_costs_custom.py | 24 - .../test_hydrogen/test_pressure_vessel.py | 317 ------- .../input/h2integrate_config_modular.yaml | 1 - 36 files changed, 228 insertions(+), 3679 deletions(-) delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_costs_custom.py create mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/__init__.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/Compressed_all.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/Compressed_gas_function.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/README.md delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/Tankinator.xlsx delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/__init__.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/material_properties.json delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/tankinator.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/von_mises.py delete mode 100644 tests/h2integrate/input_files/plant/h2integrate_config.yaml delete mode 100644 tests/h2integrate/input_files/plant/h2integrate_config_onshore.yaml delete mode 100644 tests/h2integrate/test_hydrogen/test_PEM_costs_custom.py delete mode 100644 tests/h2integrate/test_hydrogen/test_pressure_vessel.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b3f88a0c..e5869099c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: Testing on: push: - branches: [main, develop] + branches: [main, develop, remove_more_of_old_greenheart] pull_request: branches: [main, develop] schedule: diff --git a/docs/user_guide/how_to_set_up_an_analysis.ipynb b/docs/user_guide/how_to_set_up_an_analysis.ipynb index 6f28035c2..4a7687e79 100644 --- a/docs/user_guide/how_to_set_up_an_analysis.ipynb +++ b/docs/user_guide/how_to_set_up_an_analysis.ipynb @@ -93,7 +93,6 @@ " hydrogen_dmd:\n", " n_clusters: 13\n", " cluster_rating_MW: 40\n", - " pem_control_type: basic\n", " eol_eff_percent_loss: 13 #eol defined as x% change in efficiency from bol\n", " uptime_hours_until_eol: 77600 #number of 'on' hours until electrolyzer reaches eol\n", " include_degradation_penalty: True #include degradation\n", diff --git a/examples/01_onshore_steel_mn/tech_config.yaml b/examples/01_onshore_steel_mn/tech_config.yaml index a75299b2f..c00a0d205 100644 --- a/examples/01_onshore_steel_mn/tech_config.yaml +++ b/examples/01_onshore_steel_mn/tech_config.yaml @@ -29,7 +29,6 @@ technologies: hydrogen_dmd: n_clusters: 18 cluster_rating_MW: 40 - pem_control_type: 'basic' eol_eff_percent_loss: 13 #eol defined as x% change in efficiency from bol uptime_hours_until_eol: 80000. #number of 'on' hours until electrolyzer reaches eol include_degradation_penalty: True #include degradation diff --git a/examples/02_texas_ammonia/tech_config.yaml b/examples/02_texas_ammonia/tech_config.yaml index 5c22caf07..76eb972fc 100644 --- a/examples/02_texas_ammonia/tech_config.yaml +++ b/examples/02_texas_ammonia/tech_config.yaml @@ -29,7 +29,6 @@ technologies: hydrogen_dmd: n_clusters: 16 cluster_rating_MW: 40 - pem_control_type: 'basic' eol_eff_percent_loss: 10 #eol defined as x% change in efficiency from bol uptime_hours_until_eol: 80000 #number of 'on' hours until electrolyzer reaches eol include_degradation_penalty: True #include degradation diff --git a/examples/03_methanol/co2_hydrogenation/tech_config_co2h.yaml b/examples/03_methanol/co2_hydrogenation/tech_config_co2h.yaml index f90f58190..43158f3ef 100644 --- a/examples/03_methanol/co2_hydrogenation/tech_config_co2h.yaml +++ b/examples/03_methanol/co2_hydrogenation/tech_config_co2h.yaml @@ -28,7 +28,6 @@ technologies: hydrogen_dmd: n_clusters: 4 cluster_rating_MW: 39 - pem_control_type: 'basic' eol_eff_percent_loss: 10 #eol defined as x% change in efficiency from bol uptime_hours_until_eol: 80000 #number of 'on' hours until electrolyzer reaches eol include_degradation_penalty: True #include degradation diff --git a/examples/03_methanol/co2_hydrogenation_doc/tech_config_co2h.yaml b/examples/03_methanol/co2_hydrogenation_doc/tech_config_co2h.yaml index d21a394c0..94620ffdf 100644 --- a/examples/03_methanol/co2_hydrogenation_doc/tech_config_co2h.yaml +++ b/examples/03_methanol/co2_hydrogenation_doc/tech_config_co2h.yaml @@ -36,7 +36,6 @@ technologies: hydrogen_dmd: n_clusters: 4 cluster_rating_MW: 40 - pem_control_type: 'basic' eol_eff_percent_loss: 10 #eol defined as x% change in efficiency from bol uptime_hours_until_eol: 80000 #number of 'on' hours until electrolyzer reaches eol include_degradation_penalty: True #include degradation diff --git a/examples/05_wind_h2_opt/tech_config.yaml b/examples/05_wind_h2_opt/tech_config.yaml index a1226db2e..17625aef9 100644 --- a/examples/05_wind_h2_opt/tech_config.yaml +++ b/examples/05_wind_h2_opt/tech_config.yaml @@ -45,7 +45,6 @@ technologies: hydrogen_dmd: cluster_rating_MW: 20 n_clusters: 25 - pem_control_type: 'basic' eol_eff_percent_loss: 13 #eol defined as x% change in efficiency from bol uptime_hours_until_eol: 80000. #number of 'on' hours until electrolyzer reaches eol include_degradation_penalty: True #include degradation diff --git a/examples/08_wind_electrolyzer/tech_config.yaml b/examples/08_wind_electrolyzer/tech_config.yaml index 546554de7..e74dba46a 100644 --- a/examples/08_wind_electrolyzer/tech_config.yaml +++ b/examples/08_wind_electrolyzer/tech_config.yaml @@ -46,7 +46,6 @@ technologies: hydrogen_dmd: n_clusters: 13 #should be 12.5 to get 500 MW cluster_rating_MW: 40 - pem_control_type: 'basic' eol_eff_percent_loss: 13 #eol defined as x% change in efficiency from bol uptime_hours_until_eol: 80000. #number of 'on' hours until electrolyzer reaches eol include_degradation_penalty: True #include degradation diff --git a/examples/10_electrolyzer_om/tech_config.yaml b/examples/10_electrolyzer_om/tech_config.yaml index b699cdfe4..238cfc356 100644 --- a/examples/10_electrolyzer_om/tech_config.yaml +++ b/examples/10_electrolyzer_om/tech_config.yaml @@ -44,7 +44,6 @@ technologies: hydrogen_dmd: n_clusters: 1 cluster_rating_MW: 40 - pem_control_type: 'basic' eol_eff_percent_loss: 13 #eol defined as x% change in efficiency from bol uptime_hours_until_eol: 80000. #number of 'on' hours until electrolyzer reaches eol include_degradation_penalty: True #include degradation diff --git a/examples/12_ammonia_synloop/tech_config.yaml b/examples/12_ammonia_synloop/tech_config.yaml index 13b3bc4e9..5fcb1f4a2 100644 --- a/examples/12_ammonia_synloop/tech_config.yaml +++ b/examples/12_ammonia_synloop/tech_config.yaml @@ -29,7 +29,6 @@ technologies: hydrogen_dmd: n_clusters: 16 cluster_rating_MW: 40 - pem_control_type: 'basic' eol_eff_percent_loss: 10 #eol defined as x% change in efficiency from bol uptime_hours_until_eol: 80000 #number of 'on' hours until electrolyzer reaches eol include_degradation_penalty: True #include degradation diff --git a/examples/14_wind_hydrogen_dispatch/inputs/tech_config.yaml b/examples/14_wind_hydrogen_dispatch/inputs/tech_config.yaml index beefd8ba6..c8936397d 100644 --- a/examples/14_wind_hydrogen_dispatch/inputs/tech_config.yaml +++ b/examples/14_wind_hydrogen_dispatch/inputs/tech_config.yaml @@ -45,7 +45,6 @@ technologies: hydrogen_dmd: n_clusters: 18 cluster_rating_MW: 40 - pem_control_type: 'basic' eol_eff_percent_loss: 13 #eol defined as x% change in efficiency from bol uptime_hours_until_eol: 80000. #number of 'on' hours until electrolyzer reaches eol include_degradation_penalty: True #include degradation diff --git a/examples/15_wind_solar_electrolyzer/tech_config.yaml b/examples/15_wind_solar_electrolyzer/tech_config.yaml index 19ccd1ee2..f955555be 100644 --- a/examples/15_wind_solar_electrolyzer/tech_config.yaml +++ b/examples/15_wind_solar_electrolyzer/tech_config.yaml @@ -81,7 +81,6 @@ technologies: hydrogen_dmd: n_clusters: 18 cluster_rating_MW: 40 - pem_control_type: 'basic' eol_eff_percent_loss: 13 #eol defined as x% change in efficiency from bol uptime_hours_until_eol: 80000. #number of 'on' hours until electrolyzer reaches eol include_degradation_penalty: True #include degradation diff --git a/examples/17_splitter_wind_doc_h2/tech_config.yaml b/examples/17_splitter_wind_doc_h2/tech_config.yaml index ea576a146..d518934ec 100644 --- a/examples/17_splitter_wind_doc_h2/tech_config.yaml +++ b/examples/17_splitter_wind_doc_h2/tech_config.yaml @@ -68,7 +68,6 @@ technologies: hydrogen_dmd: n_clusters: 4 #should be 12.5 to get 500 MW cluster_rating_MW: 20 - pem_control_type: 'basic' eol_eff_percent_loss: 13 #eol defined as x% change in efficiency from bol uptime_hours_until_eol: 80000. #number of 'on' hours until electrolyzer reaches eol include_degradation_penalty: True #include degradation diff --git a/examples/20_solar_electrolyzer_doe/tech_config.yaml b/examples/20_solar_electrolyzer_doe/tech_config.yaml index cef47354f..4289076c3 100644 --- a/examples/20_solar_electrolyzer_doe/tech_config.yaml +++ b/examples/20_solar_electrolyzer_doe/tech_config.yaml @@ -42,7 +42,6 @@ technologies: hydrogen_dmd: n_clusters: 18 cluster_rating_MW: 10 - pem_control_type: 'basic' eol_eff_percent_loss: 10 #eol defined as x% change in efficiency from bol uptime_hours_until_eol: 80000. #number of 'on' hours until electrolyzer reaches eol include_degradation_penalty: True #include degradation diff --git a/h2integrate/converters/hydrogen/eco_tools_pem_electrolyzer.py b/h2integrate/converters/hydrogen/eco_tools_pem_electrolyzer.py index eedb6340a..49b8b5ea8 100644 --- a/h2integrate/converters/hydrogen/eco_tools_pem_electrolyzer.py +++ b/h2integrate/converters/hydrogen/eco_tools_pem_electrolyzer.py @@ -25,7 +25,6 @@ class ECOElectrolyzerPerformanceModelConfig(BaseConfig): location (str): The location of the electrolyzer; options include "onshore" or "offshore". cluster_rating_MW (float): The rating of the clusters that the electrolyzer is grouped into, in MW. - pem_control_type (str): The control strategy to be used by the electrolyzer. eol_eff_percent_loss (float): End-of-life (EOL) defined as a percent change in efficiency from beginning-of-life (BOL). uptime_hours_until_eol (int): Number of "on" hours until the electrolyzer reaches EOL. @@ -42,7 +41,6 @@ class ECOElectrolyzerPerformanceModelConfig(BaseConfig): n_clusters: int = field(validator=gt_zero) location: str = field(validator=contains(["onshore", "offshore"])) cluster_rating_MW: float = field(validator=gt_zero) - pem_control_type: str = field(validator=contains(["basic"])) eol_eff_percent_loss: float = field(validator=gt_zero) uptime_hours_until_eol: int = field(validator=gt_zero) include_degradation_penalty: bool = field() @@ -127,7 +125,6 @@ def compute(self, inputs, outputs): electrolyzer_size=electrolyzer_size_mw, useful_life=plant_life, n_pem_clusters=n_pem_clusters, - pem_control_type=self.config.pem_control_type, electrolyzer_direct_cost_kw=electrolyzer_capex_kw, user_defined_pem_param_dictionary=pem_param_dict, grid_connection_scenario=grid_connection_scenario, # if not offgrid, assumes steady h2 demand in kgphr for full year # noqa: E501 diff --git a/h2integrate/converters/hydrogen/test/test_wombat_model.py b/h2integrate/converters/hydrogen/test/test_wombat_model.py index c54aa3586..7654a37ce 100644 --- a/h2integrate/converters/hydrogen/test/test_wombat_model.py +++ b/h2integrate/converters/hydrogen/test/test_wombat_model.py @@ -31,7 +31,6 @@ def test_wombat_model_outputs(subtests): }, "n_clusters": 1, "cluster_rating_MW": 40, - "pem_control_type": "basic", "eol_eff_percent_loss": 13, "uptime_hours_until_eol": 80000.0, "include_degradation_penalty": True, @@ -93,7 +92,6 @@ def test_wombat_error(subtests): }, "n_clusters": 0.75, "cluster_rating_MW": 40, - "pem_control_type": "basic", "eol_eff_percent_loss": 13, "uptime_hours_until_eol": 80000.0, "include_degradation_penalty": True, diff --git a/h2integrate/converters/iron/old_input/h2integrate_config_modular.yaml b/h2integrate/converters/iron/old_input/h2integrate_config_modular.yaml index f616a0f37..7b5b3c1b5 100644 --- a/h2integrate/converters/iron/old_input/h2integrate_config_modular.yaml +++ b/h2integrate/converters/iron/old_input/h2integrate_config_modular.yaml @@ -61,7 +61,6 @@ electrolyzer: hydrogen_dmd: rating: 1160 # MW cluster_rating_MW: 40 - pem_control_type: 'basic' eol_eff_percent_loss: 13 #eol defined as x% change in efficiency from bol uptime_hours_until_eol: 77600 #number of 'on' hours until electrolyzer reaches eol include_degradation_penalty: True #include degradation diff --git a/h2integrate/converters/steel/steel.py b/h2integrate/converters/steel/steel.py index 96dae23bc..36e27cb8e 100644 --- a/h2integrate/converters/steel/steel.py +++ b/h2integrate/converters/steel/steel.py @@ -24,10 +24,14 @@ class SteelCostAndFinancialModelConfig(BaseConfig): capacity_factor: float = field() o2_heat_integration: bool = field() lcoh: float = field() + natural_gas_prices: dict = field() + + # Financial parameters - flattened from the nested structure + grid_prices: dict = field() + financial_assumptions: dict = field() cost_year: int = field(default=2022, converter=int, validator=must_equal(2022)) # Feedstock parameters - flattened from the nested structure - natural_gas_prices: dict = field() excess_oxygen: float = field(default=395) lime_unitcost: float = field(default=122.1) lime_transport_cost: float = field(default=0.0) @@ -49,10 +53,6 @@ class SteelCostAndFinancialModelConfig(BaseConfig): slag_production: float = field(default=0.17433) maintenance_materials_unitcost: float = field(default=7.72) - # Financial parameters - flattened from the nested structure - grid_prices: dict = field() - financial_assumptions: dict = field() - def run_steel_model(plant_capacity_mtpy: float, plant_capacity_factor: float) -> float: """Calculate annual steel production.""" diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_costs_custom.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_costs_custom.py deleted file mode 100644 index eb02807a0..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_costs_custom.py +++ /dev/null @@ -1,24 +0,0 @@ -def calc_custom_electrolysis_capex_fom(electrolyzer_capacity_kW, electrolyzer_config): - """Calculates electrolyzer total installed capex and fixed O&M based on user-input values. - - Only used if h2integrate_config["electrolyzer"]["cost_model"] is set to "basic_custom" - Requires additional inputs in h2integrate_config["electrolyzer"]: - - fixed_om_per_kw: electrolyzer fixed o&m in $/kW-year - - electrolyzer_capex: electrolyzer capex in $/kW - - Args: - electrolyzer_capacity_kW (float or int): electrolyzer capacity in kW - electrolyzer_config (dict): ``h2integrate_config["electrolyzer"]`` - - Returns: - 2-element tuple containing - - - **capex** (float): electrolyzer overnight capex in $ - - **fixed_om** (float): electrolyzer fixed O&M in $/year - """ - electrolyzer_capex = electrolyzer_config["electrolyzer_capex"] * electrolyzer_capacity_kW - if "fixed_om_per_kw" in electrolyzer_config.keys(): - electrolyzer_fopex = electrolyzer_config["fixed_om_per_kw"] * electrolyzer_capacity_kW - else: - electrolyzer_fopex = 0.0 - return electrolyzer_capex, electrolyzer_fopex diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py new file mode 100644 index 000000000..e0db3c726 --- /dev/null +++ b/h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py @@ -0,0 +1,220 @@ +import time +import warnings + +import numpy as np +import pandas as pd +from pyomo.environ import * # FIXME: no * imports, delete whole comment when fixed # noqa: F403 + +from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_H2_LT_electrolyzer_Clusters import ( # noqa: E501 + PEM_H2_Clusters as PEMClusters, +) + + +# from PyOMO import ipOpt !! FOR SANJANA!! +warnings.filterwarnings("ignore") + +""" +Perform a LCOH analysis for an offshore wind + Hydrogen PEM system + +1. Offshore wind site locations and cost details (4 sites, $1300/kw capex + BOS cost which will come + from Orbit Runs)~ +2. Cost Scaling Based on Year (Have Weiser et. al report with cost scaling for fixed and floating + tech, will implement) +3. Cost Scaling Based on Plant Size (Shields et. Al report) +4. Future Model Development Required: +- Floating Electrolyzer Platform +""" + + +# +# --------------------------- +# +class run_PEM_clusters: + """Inputs: + `electrical_power_signal`: plant power signal in kWh + `system_size_mw`: total installed electrolyzer capacity (for green steel this is 1000 MW) + `num_clusters`: number of PEM clusters that can be run independently + ->ESG note: I have been using num_clusters = 8 for centralized cases + Nomenclature: + `cluster`: cluster is built up of 1MW stacks + `stack`: must be 1MW (because of current PEM model) + """ + + def __init__( + self, + electrical_power_signal, + system_size_mw, + num_clusters, + electrolyzer_direct_cost_kw, + useful_life, + user_defined_electrolyzer_params, + verbose=True, + ): + # nomen + self.cluster_cap_mw = np.round(system_size_mw / num_clusters) + # capacity of each cluster, must be a multiple of 1 MW + + self.num_clusters = num_clusters + self.user_params = user_defined_electrolyzer_params + self.plant_life_yrs = useful_life + # Do not modify stack_rating_kw or stack_min_power_kw + # these represent the hard-coded and unmodifiable + # PEM model basecode + turndown_ratio = user_defined_electrolyzer_params["turndown_ratio"] + self.stack_rating_kw = 1000 # single stack rating - DO NOT CHANGE + self.stack_min_power_kw = turndown_ratio * self.stack_rating_kw + # self.stack_min_power_kw = 0.1 * self.stack_rating_kw + self.input_power_kw = electrical_power_signal + self.cluster_min_power = self.stack_min_power_kw * self.cluster_cap_mw + self.cluster_max_power = self.stack_rating_kw * self.cluster_cap_mw + + # For the optimization problem: + self.T = len(self.input_power_kw) + self.farm_power = 1e9 + self.switching_cost = ( + (electrolyzer_direct_cost_kw * 0.15 * self.cluster_cap_mw * 1000) + * (1.48e-4) + / (0.26586) + ) + self.verbose = verbose + + def run_grid_connected_pem(self, system_size_mw, hydrogen_production_capacity_required_kgphr): + pem = PEMClusters( + system_size_mw, + self.plant_life_yrs, + **self.user_params, + ) + + power_timeseries, stack_current = pem.grid_connected_func( + hydrogen_production_capacity_required_kgphr + ) + h2_ts, h2_tot = pem.run_grid_connected_workaround(power_timeseries, stack_current) + # h2_ts, h2_tot = pem.run(power_timeseries) + h2_df_ts = pd.Series(h2_ts, name="Cluster #0") + h2_df_tot = pd.Series(h2_tot, name="Cluster #0") + # h2_df_ts = pd.DataFrame(h2_ts, index=list(h2_ts.keys()), columns=['Cluster #0']) + # h2_df_tot = pd.DataFrame(h2_tot, index=list(h2_tot.keys()), columns=['Cluster #0']) + return pd.DataFrame(h2_df_ts), pd.DataFrame(h2_df_tot) + + def run(self): + # TODO: add control type as input! + clusters = self.create_clusters() # initialize clusters + power_to_clusters = self.even_split_power() + h2_df_ts = pd.DataFrame() + h2_df_tot = pd.DataFrame() + + col_names = [] + start = time.perf_counter() + for ci in range(len(clusters)): + cl_name = f"Cluster #{ci}" + col_names.append(cl_name) + h2_ts, h2_tot = clusters[ci].run(power_to_clusters[ci]) + # h2_dict_ts['Cluster #{}'.format(ci)] = h2_ts + + h2_ts_temp = pd.Series(h2_ts, name=cl_name) + h2_tot_temp = pd.Series(h2_tot, name=cl_name) + if len(h2_df_tot) == 0: + # h2_df_ts=pd.concat([h2_df_ts,h2_ts_temp],axis=0,ignore_index=False) + h2_df_tot = pd.concat([h2_df_tot, h2_tot_temp], axis=0, ignore_index=False) + h2_df_tot.columns = col_names + + h2_df_ts = pd.concat([h2_df_ts, h2_ts_temp], axis=0, ignore_index=False) + h2_df_ts.columns = col_names + else: + # h2_df_ts = h2_df_ts.join(h2_ts_temp) + h2_df_tot = h2_df_tot.join(h2_tot_temp) + h2_df_tot.columns = col_names + + h2_df_ts = h2_df_ts.join(h2_ts_temp) + h2_df_ts.columns = col_names + + end = time.perf_counter() + self.clusters = clusters + if self.verbose: + print(f"Took {round(end - start, 3)} sec to run the RUN function") + return h2_df_ts, h2_df_tot + # return h2_dict_ts, h2_df_tot + + def even_split_power(self): + start = time.perf_counter() + # determine how much power to give each cluster + num_clusters_on = np.floor(self.input_power_kw / self.cluster_min_power) + num_clusters_on = np.where( + num_clusters_on > self.num_clusters, self.num_clusters, num_clusters_on + ) + power_per_cluster = [ + self.input_power_kw[ti] / num_clusters_on[ti] if num_clusters_on[ti] > 0 else 0 + for ti, pwr in enumerate(self.input_power_kw) + ] + + power_per_to_active_clusters = np.array(power_per_cluster) + power_to_clusters = np.zeros((len(self.input_power_kw), self.num_clusters)) + for i, cluster_power in enumerate( + power_per_to_active_clusters + ): # np.arange(0,self.n_stacks,1): + clusters_off = self.num_clusters - int(num_clusters_on[i]) + no_power = np.zeros(clusters_off) + with_power = cluster_power * np.ones(int(num_clusters_on[i])) + tot_power = np.concatenate((with_power, no_power)) + power_to_clusters[i] = tot_power + + # power_to_clusters = np.repeat([power_per_cluster],self.num_clusters,axis=0) + end = time.perf_counter() + + if self.verbose: + print(f"Took {round(end - start, 3)} sec to run even_split_power function") + # rows are power, columns are stacks [300 x n_stacks] + + return np.transpose(power_to_clusters) + + def max_h2_cntrl(self): + # run as many at lower power as possible + ... + + def min_deg_cntrl(self): + # run as few as possible + ... + + def create_clusters(self): + start = time.perf_counter() + # TODO fix the power input - don't make it required! + # in_dict={'dt':3600} + clusters = PEMClusters(self.cluster_cap_mw, self.plant_life_yrs, **self.user_params) + stacks = [clusters] * self.num_clusters + end = time.perf_counter() + if self.verbose: + print(f"Took {round(end - start, 3)} sec to run the create clusters") + return stacks + + +if __name__ == "__main__": + system_size_mw = 1000 + num_clusters = 20 + cluster_cap_mw = system_size_mw / num_clusters + stack_rating_kw = 1000 + cluster_min_power_kw = 0.1 * stack_rating_kw * cluster_cap_mw + num_steps = 200 + power_rampup = np.arange( + cluster_min_power_kw, system_size_mw * stack_rating_kw, cluster_min_power_kw + ) + + plant_life = 30 + electrolyzer_model_parameters = { + "eol_eff_percent_loss": 10, + "uptime_hours_until_eol": 77600, + "include_degradation_penalty": True, + "turndown_ratio": 0.1, + } + # power_rampup = np.linspace(cluster_min_power_kw,system_size_mw*1000,num_steps) + power_rampdown = np.flip(power_rampup) + power_in = np.concatenate((power_rampup, power_rampdown)) + pem = run_PEM_clusters( + power_in, + system_size_mw, + num_clusters, + plant_life, + electrolyzer_model_parameters, + ) + + h2_ts, h2_tot = pem.run() + # pem.clusters[0].cell_design(80,1920*2) diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_h2_PEM.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/run_h2_PEM.py index 1672e9433..96957a54b 100644 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_h2_PEM.py +++ b/h2integrate/simulation/technologies/hydrogen/electrolysis/run_h2_PEM.py @@ -61,7 +61,6 @@ def run_h2_PEM( electrolyzer_size, useful_life, n_pem_clusters, - pem_control_type, electrolyzer_direct_cost_kw, user_defined_pem_param_dictionary, grid_connection_scenario, @@ -86,10 +85,7 @@ def run_h2_PEM( electrolyzer_size, hydrogen_production_capacity_required_kgphr ) else: - if pem_control_type == "optimize": - h2_ts, h2_tot = pem.run(optimize=True) - else: - h2_ts, h2_tot = pem.run() + h2_ts, h2_tot = pem.run() # dictionaries of performance during each year of simulation, # good to use for a more accurate financial analysis annual_avg_performance = combine_cluster_annual_performance_info(h2_tot) @@ -245,41 +241,3 @@ def run_h2_PEM( H2_Results.update({"# Stacks Never Used": n_stacks_new}) return H2_Results, h2_ts, h2_tot, energy_input_to_electrolyzer - - -def run_h2_PEM_IVcurve( - energy_to_electrolyzer, - electrolyzer_size_mw, - kw_continuous, - electrolyzer_capex_kw, - lcoe, - adjusted_installed_cost, - useful_life, - net_capital_costs=0, -): - # electrical_generation_timeseries = combined_pv_wind_storage_power_production_hopp - electrical_generation_timeseries = np.zeros_like(energy_to_electrolyzer) - electrical_generation_timeseries[:] = energy_to_electrolyzer[:] - - # system_rating = electrolyzer_size - H2_Results, H2A_Results = ( - kernel_PEM_IVcurve( # FIXME: undefined, delete whole comment when fixed # noqa: F821 - electrical_generation_timeseries, - electrolyzer_size_mw, - useful_life, - kw_continuous, - electrolyzer_capex_kw, - lcoe, - adjusted_installed_cost, - net_capital_costs, - ) - ) - - H2_Results["hydrogen_annual_output"] = H2_Results["hydrogen_annual_output"] - H2_Results["cap_factor"] = H2_Results["cap_factor"] - - print(f"Total power input to electrolyzer: {np.sum(electrical_generation_timeseries)}") - print("Hydrogen Annual Output (kg): {}".format(H2_Results["hydrogen_annual_output"])) - print("Water Consumption (kg) Total: {}".format(H2_Results["water_annual_usage"])) - - return H2_Results, H2A_Results # , electrical_generation_timeseries diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/__init__.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/__init__.py deleted file mode 100644 index 787241b9c..000000000 --- a/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from h2integrate.simulation.technologies.hydrogen.h2_storage.pressure_vessel.tankinator import ( - LinedTank, - MetalMaterial, - Tank, - TypeIIITank, - TypeITank, - TypeIVTank, -) diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/Compressed_all.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/Compressed_all.py deleted file mode 100644 index 95c0f39ce..000000000 --- a/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/Compressed_all.py +++ /dev/null @@ -1,255 +0,0 @@ -from __future__ import annotations - - -""" -Created on Mon Oct 17 20:08:09 2022 -@author: ppeng - -Revisions: -- 20221118: - Author: Jared J. Thomas - Description: - - Reformatted to be a class -""" - -""" -Model Revision Needed: storage space and mass -Description: This file should handle physical size (footprint and mass) needed for pressure vessel - storage -Sources: - - [1] ./README.md and other elements in this directory -Args: - - same as for the physics and cost model contained herein - - others may be added as needed -Returns:(can be from separate functions and/or methods as it makes sense): - - mass_empty (float): mass (approximate) for pressure vessel storage components ignoring stored - H2 - - footprint (float): area required for pressure vessel storage - - others may be added as needed -""" - - -from pathlib import Path - -import numpy as np - -from .Compressed_gas_function import CompressedGasFunction - - -class PressureVessel: - def __init__( - self, - Wind_avai=80, - H2_flow=200, - cdratio=1, - Energy_cost=0.07, - cycle_number=1, - parent_path=Path(__file__).parent, - spread_sheet_name="Tankinator.xlsx", - verbose=False, - ): - ########Key inputs########## - self.Wind_avai = Wind_avai # Wind availability in % - self.H2_flow = H2_flow # Flow rate of steel plants in metric ton/day - - # NOTE: Charge/discharge ratio, i.e. 2 means the charging is 2x faster than discharge - self.cdratio = cdratio - self.Energy_cost = Energy_cost # Renewable energy cost in $/kWh - - #######Other inputs######## - # NOTE: Equivalent cycle number for a year, only affects operation (the higher the number - # is the less effect there will be), set as now as I am not sure how the maximum storage - # capacity is determined and how the storage will be cycled - self.cycle_number = cycle_number - - _fn = parent_path / spread_sheet_name - self.compressed_gas_function = CompressedGasFunction(path_tankinator=_fn) - self.compressed_gas_function.verbose = verbose - - def run(self): - #####Run calculation######## - self.compressed_gas_function.func( - Wind_avai=self.Wind_avai, - H2_flow=self.H2_flow, - cdratio=self.cdratio, - Energy_cost=self.Energy_cost, - cycle_number=self.cycle_number, - ) - - ########Outputs################ - - ######Maximum equivalent storage capacity and duration - self.capacity_max = ( - self.compressed_gas_function.capacity_max - ) # This is the maximum equivalent H2 storage in kg - self.t_discharge_hr_max = ( - self.compressed_gas_function.t_discharge_hr_max - ) # This is tha maximum storage duration in kg - - ###Parameters for capital cost fitting for optimizing capital cost - self.a_fit_capex = self.compressed_gas_function.a_cap_fit - self.b_fit_capex = self.compressed_gas_function.b_cap_fit - self.c_fit_capex = self.compressed_gas_function.c_cap_fit - - # Parameters for operational cost fitting for optimizing capital cost - self.a_fit_opex = self.compressed_gas_function.a_op_fit - self.b_fit_opex = self.compressed_gas_function.b_op_fit - self.c_fit_opex = self.compressed_gas_function.c_op_fit - - def calculate_from_fit(self, capacity_kg): - capex_per_kg = self.compressed_gas_function.exp_log_fit( - [self.a_fit_capex, self.b_fit_capex, self.c_fit_capex], capacity_kg - ) - opex_per_kg = self.compressed_gas_function.exp_log_fit( - [self.a_fit_opex, self.b_fit_opex, self.c_fit_opex], capacity_kg - ) - energy_per_kg_h2 = self.compressed_gas_function.energy_function(capacity_kg) / capacity_kg - - # NOTE ON ENERGY: the energy value returned here is the energy used to fill the - # tanks initially for the first fill and so can be used as an approximation for the energy - # used on a per kg basis. - # If cycle_number > 1, the energy model output is incorrect. - - capex = capex_per_kg * capacity_kg - opex = opex_per_kg * capacity_kg - return capex, opex, energy_per_kg_h2 - - def get_tanks(self, capacity_kg): - """gets the number of tanks necessary""" - return np.ceil(capacity_kg / self.compressed_gas_function.m_H2_tank) - - def get_tank_footprint( - self, - capacity_kg, - upright: bool = True, - custom_packing: bool = False, - packing_ratio: float | None = None, - ): - """ - gets the footprint required for the H2 tanks - - assumes that packing is square (unless custom_packing is true) - - diameter D upright tank occupies D^2 - - diameter D, length L tank occupies D*L - - parameters: - - `upright`: place tanks vertically (default yes)? - - `custom_packing`: pack tanks at an alternate packing fraction? - - `packing_ratio`: ratio for custom packing, defaults to theoretical max (if known) - returns: - - `tank_footprint`: footprint of each tank in m^2 - - `array_footprint`: total footprint of all tanks in m^2 - """ - - tank_radius = self.compressed_gas_function.Router / 100 - tank_length = self.compressed_gas_function.Louter / 100 - Ntank = self.get_tanks(capacity_kg=capacity_kg) - - if upright: - tank_area = np.pi * tank_radius**2 - tank_footprint = 4 * tank_radius**2 - else: - tank_area = ( - np.pi * tank_radius**2 * ((tank_length - 2 * tank_radius) * (2 * tank_radius)) - ) - tank_footprint = tank_radius * tank_length - - if custom_packing: - if upright: - if packing_ratio is None: - packing_ratio = np.pi * np.sqrt(3.0) / 6.0 # default to tight packing - tank_footprint = tank_area * packing_ratio - else: - if packing_ratio is None: - raise NotImplementedError("tight packing ratio for cylinders isn't derived yet") - tank_footprint = tank_area * packing_ratio - - return (tank_footprint, Ntank * tank_footprint) - - def get_tank_mass(self, capacity_kg): - """ - gets the mass required for the H2 tanks - - returns - - `tank_mass`: mass of each tank - - `array_mass`: total mass of all tanks - """ - - tank_mass = self.compressed_gas_function.Mempty_tank - Ntank = self.get_tanks(capacity_kg=capacity_kg) - - return (tank_mass, Ntank * tank_mass) - - def plot(self): - self.compressed_gas_function.plot() - - def distributed_storage_vessels(self, capacity_total_tgt, N_sites): - """ - compute modified pressure vessel storage requirements for distributed - pressure vessels - - parameters: - - capacity_total_tgt: target gaseous H2 capacity in kilograms - - N_sites: number of sites (e.g. turbines) where pressure vessels will be placed - - returns: - - - """ - - # assume that the total target capacity is equally distributed across sites - capacity_site_tgt = capacity_total_tgt / N_sites - - # capex_centralized_total, opex_centralized_total, energy_kg_centralized_total= self.calculate_from_fit(capacity_total_tgt) # noqa: E501 - capex_site, opex_site, energy_kg_site = self.calculate_from_fit(capacity_site_tgt) - - # get the resulting capex & opex costs, incl. equivalent - capex_distributed_total = ( - N_sites * capex_site - ) # the cost for the total distributed storage facilities - opex_distributed_total = ( - N_sites * opex_site - ) # the cost for the total distributed storage facilities - - # get footprint stuff - area_footprint_site = self.get_tank_footprint(capacity_site_tgt)[1] - mass_tank_empty_site = self.get_tank_mass(capacity_site_tgt)[1] - - # return the outputs - return ( - capex_distributed_total, - opex_distributed_total, - energy_kg_site, - area_footprint_site, - mass_tank_empty_site, - capacity_site_tgt, - ) - - -if __name__ == "__main__": - storage = PressureVessel() - storage.run() - - capacity_req = 1e3 - print("tank type:", storage.compressed_gas_function.tank_type) - print("tank mass:", storage.get_tank_mass(capacity_req)[0]) - print("tank radius:", storage.compressed_gas_function.Router) - print("tank length:", storage.compressed_gas_function.Louter) - print( - "tank footprint (upright):", - storage.get_tank_footprint(capacity_req, upright=True)[0], - ) - print( - "tank footprint (flat):", - storage.get_tank_footprint(capacity_req, upright=False)[0], - ) - - print("\nnumber of tanks req'd:", storage.get_tanks(capacity_req)) - print( - "total footprint (upright):", - storage.get_tank_footprint(capacity_req, upright=True)[1], - ) - print( - "total footprint (flat):", - storage.get_tank_footprint(capacity_req, upright=False)[1], - ) - print("total mass:", storage.get_tank_mass(capacity_req)[1]) diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/Compressed_gas_function.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/Compressed_gas_function.py deleted file mode 100644 index e4f95a4ee..000000000 --- a/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/Compressed_gas_function.py +++ /dev/null @@ -1,791 +0,0 @@ -""" -Created on Fri Jan 15 15:06:21 2021 - -@author: ppeng -""" - -import math as math - -import numpy as np -import openpyxl as openpyxl -import matplotlib.pyplot as plt -from scipy.optimize import leastsq -from CoolProp.CoolProp import PropsSI - - -plt.rcParams.update({"font.size": 13}) - - -class CompressedGasFunction: - def __init__(self, path_tankinator): - # path to the excel spreadsheet to store material properties - self.wb_tankinator = openpyxl.load_workbook( - path_tankinator, data_only=True - ) # Add file name - - ################Other key inputs besides the main script########################## - self.MW_H2 = 2.02e-03 # molecular weight of H2 in kg/mol - - # NOTE: Important, if you change storage pressure, make sure to change it in the - # corresponding tab in Tankinator and save again - self.Pres = 350 # Define storage pressure in bar - self.Temp_c = 293 # Define storage temperature in K - self.Pin = 30 # Deinfe pressure out of electrolyzer in bar - self.Tin = 353 # Define temperature out of electrolyzer in K - self.T_amb = 295 - self.Pres3 = 35 # Define outlet pressure in bar - self.Temp3 = 353 # Define outlet temperature in K - - self.start_point = 10 # For setting the smallest capacity for fitting and plotting - - #################Economic parameters - self.CEPCI2007 = 525.4 - self.CEPCI2001 = 397 - self.CEPCI2017 = 567.5 - - self.CEPCI_current = 708 ####Change this value for current CEPCI - - self.wage = 36 - self.maintanance = 0.03 - self.Site_preparation = 100 # Site preparation in $/kg - - self.Tank_manufacturing = 1.8 # self.Markup for tank manufacturing - self.Markup = 1.5 # self.Markup for installation engineering/contingency - - #################Other minor input parameters############ - self.R = 8.314 # gas onstant m3*Pa/(molK) - self.Heat_Capacity_Wall = ( - 0.92 ##wall heat capacity at 298 K in kJ/kg*K for carbon fiber composite - ) - self.Efficiency_comp = 0.7 # Compressor efficiency - self.Efficiency_heater = 0.7 # Heat efficiency - - def exp_log_fit(self, var_op, capacity_1): - a_op = var_op[0] - b_op = var_op[1] - c_op = var_op[2] - - fit_op_kg = np.exp(a_op * (np.log(capacity_1)) ** 2 - b_op * np.log(capacity_1) + c_op) - - return fit_op_kg - - def residual_op(self, var_op, capacity_1, Op_c_Costs_kg): - fit_op_kg = self.exp_log_fit(var_op, capacity_1) - - return fit_op_kg - Op_c_Costs_kg - - def exp_fit(self, x, a, b): - return a * x**b - - def calculate_max_storage_capacity(self, Wind_avai, H2_flow, Release_efficiency): - # reference flow rate of steel plants in metric ton/day, in case in the future it is not - # 200 metric ton/day - H2_flow_ref = 200 - - capacity_max = ( - (0.8044 * Wind_avai**2 - 57.557 * Wind_avai + 4483.1) - * (H2_flow / H2_flow_ref) - / Release_efficiency - * 1000 - ) ###Total max equivalent storage capacity kg - - return capacity_max - - def calculate_max_storage_duration(self, Release_efficiency, H2_flow): - t_discharge_hr_max = ( - self.capacity_max / 1000 * Release_efficiency / H2_flow - ) ###This is the theoretical maximum storage duration - - return t_discharge_hr_max - - # TODO keep breaking this up so we can run the model without running the curve fit - def func( - self, - Wind_avai, - H2_flow, - cdratio, - Energy_cost, - cycle_number, - capacity_max_spec=None, - t_discharge_hr_max_spec=None, - ): - """ - Run the compressor and storage container cost models - - Wind_avai is only used for calculating the theoretical maximum storage capacity prior to - curve fitting - - H2_flow is (I think) the rate the H2 is being removed from the tank in metric ton/day - - cdratio is the charge/discharge ratio (1 means charge rate equals the discharge rate, 2 - means charge is 2x the discharge rate) - - Energy_cost is the renewable energy cost in $/kWh, or can be set to 0 to exclude energy - costs - - cycle number should just be left as 1 (see compressed_all.py) - """ - - ##############Calculation of storage capacity from duration############# - if 1 - self.Pres3 / self.Pres < 0.9: - Release_efficiency = 1 - self.Pres3 / self.Pres - else: - Release_efficiency = 0.9 - - if capacity_max_spec is None: - self.capacity_max = self.calculate_max_storage_capacity( - Wind_avai, H2_flow, Release_efficiency - ) - else: - self.capacity_max = capacity_max_spec - - if t_discharge_hr_max_spec is None: - self.t_discharge_hr_max = self.calculate_max_storage_duration( - Release_efficiency, H2_flow - ) - else: - self.t_discharge_hr_max = t_discharge_hr_max_spec - - if self.verbose: - print("Maximum capacity is", self.capacity_max, "kg H2") - print("Maximum storage duration is", self.t_discharge_hr_max, "hr") - - if self.Pres > 170: - ####Use this if use type IV tanks - tank_type = 4 - sheet_tankinator = self.wb_tankinator["type4_rev3"] # Add Sheet name - Vtank_c_cell = sheet_tankinator.cell(row=19, column=3) # tank internal volume in cm3 - Vtank_c = Vtank_c_cell.value / (10**6) # tank volume in m3 - m_c_wall_cell = sheet_tankinator.cell(row=55, column=3) - m_c_wall = m_c_wall_cell.value # Wall mass in kg - Mtank_c = m_c_wall # TODO why is this set but not used? - Louter_c_cell = sheet_tankinator.cell(row=36, column=3) - length_outer_c = Louter_c_cell.value # outer length of tank - Router_c_cell = sheet_tankinator.cell(row=37, column=3) - radius_outer_c = Router_c_cell.value # outer radius of tank - Cost_c_tank_cell = sheet_tankinator.cell(row=65, column=3) # Cost of one tank - Cost_c_tank = Cost_c_tank_cell.value ##Cost of the tank in $/tank - - if self.Pres <= 170: - ####Use this if use type I tanks - tank_type = 1 - sheet_tankinator = self.wb_tankinator["type1_rev3"] # Add Sheet nam - Vtank_c_cell = sheet_tankinator.cell(row=20, column=3) ##Tank's outer volume in cm^3 - Vtank_c = Vtank_c_cell.value / (10**6) # tank volume in m3 - m_c_wall_cell = sheet_tankinator.cell(row=188, column=3) - m_c_wall = m_c_wall_cell.value # Wall mass in kg - Mtank_c = m_c_wall # TODO why is this set but not used? - Louter_c_cell = sheet_tankinator.cell(row=184, column=3) - length_outer_c = Louter_c_cell.value - Router_c_cell = sheet_tankinator.cell(row=185, column=3) - radius_outer_c = Router_c_cell.value - Cost_c_tank_cell = sheet_tankinator.cell(row=193, column=3) # Cost of one tank - Cost_c_tank = Cost_c_tank_cell.value ##Cost of the tank in $/tank - - self.tank_type = tank_type - self.Vtank = Vtank_c - self.m_H2_tank = self.Vtank * PropsSI( - "D", "P", self.Pres * 10**5, "T", self.Temp_c, "Hydrogen" - ) - self.Mempty_tank = Mtank_c - self.Router = radius_outer_c - self.Louter = length_outer_c - - #####Define arrays for plotting and fitting - - self.t_discharge_hr_1 = np.linspace( - self.t_discharge_hr_max, self.t_discharge_hr_max / self.start_point, num=15 - ) - self.cost_kg = np.zeros(len(self.t_discharge_hr_1)) - cost_kg_tank = np.zeros(len(self.t_discharge_hr_1)) - cost_kg_comp = np.zeros(len(self.t_discharge_hr_1)) - cost_kg_ref = np.zeros(len(self.t_discharge_hr_1)) - cost_kg_heat = np.zeros(len(self.t_discharge_hr_1)) - self.number_of_tanks = np.zeros(len(self.t_discharge_hr_1)) - self.capacity_1 = np.zeros(len(self.t_discharge_hr_1)) - self.Op_c_Costs_kg = np.zeros(len(self.t_discharge_hr_1)) - self.total_energy_used_kwh = np.zeros(len(self.t_discharge_hr_1)) - - ########################################################################################### - ######################################################################################## - ############################################################################################ - ###############Starting detailed calculations################################# - ###############Stage 1 calculations################################# - - for i in range(0, len(self.t_discharge_hr_1 - 1)): - t_discharge_hr = self.t_discharge_hr_1[i] - capacity = ( - H2_flow * t_discharge_hr * 1000 / Release_efficiency - ) # Maximum capacity in kg H2 - - self.capacity_1[i] = capacity - - rgas = PropsSI( - "D", "P", self.Pres * 10**5, "T", self.Temp_c, "Hydrogen" - ) # h2 density in kg/m3 under storage conditions - H2_c_mass_gas_tank = Vtank_c * rgas # hydrogen mass per tank in kg - H2_c_mass_tank = H2_c_mass_gas_tank # Estimation of H2 amount per tank in kg - self.single_tank_h2_capacity_kg = H2_c_mass_tank - - number_c_of_tanks = np.ceil(capacity / H2_c_mass_tank) - self.number_of_tanks[i] = number_c_of_tanks - - # NOTE: This will be useful when changing to assume all tanks are full, but will cause - # the model to not perform well for small scales, where 1 tank makes a large difference - H2_c_Cap_Storage = H2_c_mass_tank * (number_c_of_tanks - 1) + capacity % H2_c_mass_tank - - #################Energy balance for adsorption (state 1 to state 2)######## - self.t_charge_hr = t_discharge_hr * (1 / cdratio) - - # NOTE: correcting first cycle, useful to size based on maximum power and also when - # calculating the operational cost - t_precondition_hr = self.t_charge_hr - m_c_flow_rate_1_2 = ( - H2_c_Cap_Storage / t_precondition_hr / 3600 - ) # mass flow rate in kg/s - Temp2 = self.Temp_c - Temp1_gas = self.Tin - Temp1_solid = self.T_amb - Pres2 = self.Pres * 10**5 - Pres1 = self.Pin * 10**5 - H_c_1_spec_g = ( - PropsSI("H", "P", Pres1, "T", Temp1_gas, "Hydrogen") / 1000 - ) # specific enthalpy of the gas under T1 P1 in kJ/kg - H_c_2_spec_g = ( - PropsSI("H", "P", Pres2, "T", Temp2, "Hydrogen") / 1000 - ) # specific enthalpy of the gas under T2 P2 in kJ/kg - H_c_1_gas = H2_c_Cap_Storage * H_c_1_spec_g - H_c_2_gas = H2_c_Cap_Storage * H_c_2_spec_g - deltaE_c_H2_1_2 = H_c_2_gas - H_c_1_gas - deltaE_c_Uwall_1_2 = ( - self.Heat_Capacity_Wall * (Temp2 - Temp1_solid) * m_c_wall * number_c_of_tanks - ) # Net energy/enthalpy change of adsorbent in kJ - deltaE_c_net_1_2 = ( - deltaE_c_H2_1_2 + deltaE_c_Uwall_1_2 - ) # Net energy/enthalpy change in kJ - deltaP_c_net_1_2 = deltaE_c_net_1_2 / self.t_charge_hr / 3600 # Net power change in kW - - #################Energy balance for desorption (state 2 to state 3)######## - Temp3_gas = self.Temp3 - Temp3_solid = Temp2 - self.Pres3 = self.Pres3 - Pres3_tank = self.Pres * (1 - Release_efficiency) * 10**5 * 10 - H_c_3_spec_g_fuel_cell = ( - PropsSI("H", "P", self.Pres3, "T", Temp3_gas, "Hydrogen") / 1000 - ) # specific enthalpy of the released gas in kJ/kg - H_c_3_spec_g_tank = ( - PropsSI("H", "P", Pres3_tank, "T", Temp2, "Hydrogen") / 1000 - ) # specific enthalpy of the remaining free volume gas in kJ/kg - H_c_3_gas = ( - H2_c_Cap_Storage * Release_efficiency * H_c_3_spec_g_fuel_cell - + H2_c_Cap_Storage * (1 - Release_efficiency) * H_c_3_spec_g_tank - ) # Total gas phase enthalpy in stage 3 in kJ - deltaE_c_H2_2_3 = H_c_3_gas - H_c_2_gas # Total h2 enthalpy change in kJ - deltaE_c_Uwall_2_3 = ( - self.Heat_Capacity_Wall * (Temp3_solid - Temp2) * m_c_wall * number_c_of_tanks - ) # kJ - deltaE_c_net_2_3 = ( - deltaE_c_H2_2_3 + deltaE_c_Uwall_2_3 - ) # Net enthalpy change during desorption - detlaP_c_net_2_3 = deltaE_c_net_2_3 / t_discharge_hr / 3600 - - ###############Energy balance for adsorption (state 4 to state 2)########## - m_c_flow_rate_4_2 = H2_c_Cap_Storage * Release_efficiency / self.t_charge_hr / 3600 - Temp4_tank = Temp2 - Pres4_tank = Pres3_tank - H_c_4_spec_g_electrolyzer = ( - PropsSI("H", "P", self.Pin, "T", self.Tin, "Hydrogen") / 1000 - ) # specific enthalpy of the released gas in kJ/kg - H_c_4_spec_g_tank = ( - PropsSI("H", "P", Pres4_tank, "T", Temp2 - 5, "Hydrogen") / 1000 - ) # specific enthalpy of the remaining free volume gas in kJ/kg - H_c_4_gas = ( - H2_c_Cap_Storage * Release_efficiency * H_c_4_spec_g_electrolyzer - + H2_c_Cap_Storage * (1 - Release_efficiency) * H_c_4_spec_g_tank - ) # Total gas phase enthalpy in stage 3 in kJ - deltaE_c_H2_4_2 = H_c_2_gas - H_c_4_gas # Total h2 enthalpy change in kJ - deltaE_c_Uwall_4_2 = ( - self.Heat_Capacity_Wall * (Temp2 - Temp4_tank) * m_c_wall * number_c_of_tanks - ) # kJ - deltaE_c_net_4_2 = ( - deltaE_c_H2_4_2 + deltaE_c_Uwall_4_2 - ) # Net enthalpy change during desorption - deltaP_c_net_4_2 = deltaE_c_net_4_2 / self.t_charge_hr / 3600 - - ########################################Costs for cycle 1 adsorption#################### - - ###############CAPITAL COSTS (sized based on cycle 1 requirements)###################### - - ###############################Compressor costs ### axial/centrifugal - if self.Pres >= self.Pin: - K = PropsSI( - "ISENTROPIC_EXPANSION_COEFFICIENT", - "P", - self.Pin * 10**5, - "T", - self.Tin, - "Hydrogen", - ) - P2nd = self.Pin * (self.Pres / self.Pin) ** (1 / 3) - P3rd = ( - self.Pin * (self.Pres / self.Pin) ** (1 / 3) * (self.Pres / self.Pin) ** (1 / 3) - ) - work_c_comp_1 = ( - K - / (K - 1) - * self.R - * self.Tin - / self.MW_H2 - * ((P2nd / self.Pin) ** ((K - 1) / K) - 1) - ) - work_c_comp_2 = ( - K - / (K - 1) - * self.R - * self.Tin - / self.MW_H2 - * ((P3rd / P2nd) ** ((K - 1) / K) - 1) - ) - work_c_comp_3 = ( - K - / (K - 1) - * self.R - * self.Tin - / self.MW_H2 - * ((self.Pres / P3rd) ** ((K - 1) / K) - 1) - ) - Work_c_comp = work_c_comp_1 + work_c_comp_2 + work_c_comp_3 - # Work_c_comp=K/(K-1)*self.R*self.Tin/self.MW_H2*((self.Pres/self.Pin)**((K-1)/K)-1) #mechanical energy required for compressor in J/kg (single stage) # noqa: E501 - Power_c_comp_1_2 = ( - Work_c_comp / 1000 * m_c_flow_rate_1_2 - ) # mechanical power of the pump in kW - Power_c_comp_4_2 = Work_c_comp / 1000 * m_c_flow_rate_4_2 - A_c_comp_1_2 = Power_c_comp_1_2 / self.Efficiency_comp # total power in kW - A_c_comp_4_2 = Power_c_comp_4_2 / self.Efficiency_comp # total power in kW - if A_c_comp_1_2 >= A_c_comp_4_2: - A_c_comp = A_c_comp_1_2 - else: - A_c_comp = A_c_comp_4_2 - - # print ('work of compressor is', Work_c_comp,'J/kg') - # print ('Adjusted storage capacity is', H2_c_Cap_Storage, 'kg') - # print ('flow rate is', m_c_flow_rate_1_2, 'and', m_c_flow_rate_4_2, 'kg/s') - # print('Total fluid power of compressor', A_c_comp, 'kW') - Number_c_Compressors = np.floor( - A_c_comp / 3000 - ) # Number of compressors excluding the last one - A_c_comp_1 = A_c_comp % 3000 # power of the last compressor - # print('Number of compressors', Number_c_Compressors+1) - k1 = 2.2897 - k2 = 1.3604 - k3 = -0.1027 - Compr_c_Cap_Cost = ( - 10 ** (k1 + k2 * np.log10(3000) + k3 * (np.log10(3000)) ** 2) - ) * Number_c_Compressors - Compr_c_Cap_Cost_1 = 10 ** ( - k1 + k2 * np.log10(A_c_comp_1) + k3 * (np.log10(A_c_comp_1)) ** 2 - ) - - compressor_energy_used_1 = Work_c_comp * H2_c_Cap_Storage * 2.8e-7 - compressor_energy_used_2 = ( - Work_c_comp * H2_c_Cap_Storage * Release_efficiency * 2.8e-7 - ) - - Compr_c_Energy_Costs_1 = ( - compressor_energy_used_1 * Energy_cost - ) # compressor electricity cost in cycle 1 - Compr_c_Energy_Costs_2 = ( - compressor_energy_used_2 * Energy_cost - ) # compressor electricity cost assuming in regular charging cycle - - Total_c_Compr_Cap_Cost = Compr_c_Cap_Cost + Compr_c_Cap_Cost_1 - Total_c_Compr_Cap_Cost = Total_c_Compr_Cap_Cost * ( - self.CEPCI_current / self.CEPCI2001 - ) ##Inflation - else: - Power_c_comp_1_2 = 0 # mechanical power of the pump in kW - Power_c_comp_4_2 = 0 - A_c_comp_1_2 = 0 # total power in kW - A_c_comp_4_2 = 0 # total power in kW - Work_c_comp = 0 - Compr_c_Cap_Cost = 0 - compressor_energy_used_1 = 0 - compressor_energy_used_2 = 0 - Compr_c_Energy_Costs_1 = 0 - Compr_c_Energy_Costs_2 = 0 - Total_c_Compr_Cap_Cost = 0 - - self.total_compressor_energy_used_kwh = ( - compressor_energy_used_1 # + compressor_energy_used_2 - ) - - # print ('Compressor energy cost is $', Compr_c_Energy_Costs) - # print ('refrigeration capcost for compressor is $') - # print('compressor capcost is $', Total_c_Compr_Cap_Cost) - # print("----------") - - ########################################Costs associated with storage tanks - - # print("Number of tanks is: ", number_c_of_tanks) - Storage_c_Tank_Cap_Costs = Cost_c_tank * number_c_of_tanks * self.Tank_manufacturing - Storage_c_Tank_Cap_Costs = Storage_c_Tank_Cap_Costs * ( - self.CEPCI_current / self.CEPCI2007 - ) ##Inflation - # print('Capcost for storage tank is', Storage_c_Tank_Cap_Costs) - # print("----------") - - ###############################Refrigeration costs estimation adsorption process - # print ('pre-conditioning time is', round (t_precondition_hr), 'hr') - Ref_c_P_net_1_2 = -( - deltaP_c_net_1_2 - Power_c_comp_1_2 - ) # Refrigeration power in kW from state 1 to state 2 (precondition) - Ref_c_P_net_4_2 = -( - deltaP_c_net_4_2 - Power_c_comp_4_2 - ) # Refrigeration power in kW from state 1 to state 2 (normal charging) - if Ref_c_P_net_1_2 >= Ref_c_P_net_4_2: - Net_c_Cooling_Power_Adsorption = Ref_c_P_net_1_2 # Net refrigeration power in kW - else: - Net_c_Cooling_Power_Adsorption = Ref_c_P_net_4_2 - # print( - # "Net Cooling power for refrigeration sizing is", - # Net_c_Cooling_Power_Adsorption, - # "kW", - # ) # Cooling power in kW - - if Net_c_Cooling_Power_Adsorption < 1000: - A1 = -3.53e-09 - A2 = -9.94e-06 - A3 = 3.30e-03 - nc = ( - (A1 * (self.Temp_c**3)) + (A2 * (self.Temp_c**2)) + A3 * self.Temp_c - ) # Carnot efficiency factor - COP = (self.Temp_c / (318 - self.Temp_c)) * nc # Coefficient of performance - B1 = 24000 - B2 = 3500 - B3 = 0.9 - Total_c_Refrig_Cap_Costs_adsorption = ( - B1 + (B2 * (Net_c_Cooling_Power_Adsorption / COP) ** B3) - ) * (self.CEPCI_current / 550.8) - else: - Total_c_Refrig_Cap_Costs_adsorption = ( - 2 - * 10**11 - * self.Temp_c**-2.077 - * (Net_c_Cooling_Power_Adsorption / 1000) ** 0.6 - ) - Total_c_Refrig_Cap_Costs_adsorption = Total_c_Refrig_Cap_Costs_adsorption * ( - self.CEPCI_current / self.CEPCI2017 - ) - - ####Utility for refrigeration - # NOTE: Utility in $/GJ, here, the utility is mostly for energy assumes 16.8 $/GJ - # (57 $/MWh) - Utility_c_ref = 4.07 * 10**7 * self.Temp_c ** (-2.669) - # Utility_c_refrigeration_1 = (self.CEPCI_current/self.CEPCI2017)*Utility_c_ref*-(deltaE_c_net_1_2-Work_c_comp*H2_c_Cap_Storage/1000)/1e6 # noqa: E501 - energy_consumption_refrigeration_1_kj = -( - deltaE_c_net_1_2 - Work_c_comp * H2_c_Cap_Storage / 1000 - ) # in kJ - - # NOTE: changed based on discussion with original author 20221216, energy separated - # out 20230317 - Utility_c_refrigeration_1 = ( - (Energy_cost / 0.057) * Utility_c_ref * energy_consumption_refrigeration_1_kj / 1e6 - ) - # print ('refrigerator capital cost for adsorption is $', Total_c_Refrig_Cap_Costs_adsorption) # noqa: E501 - # print("------------") - - # Utility_c_refrigeration_2 = ( - # (self.CEPCI_current / self.CEPCI2017) - # * Utility_c_ref - # * -(deltaE_c_net_4_2 - Work_c_comp * H2_c_Cap_Storage * Release_efficiency / 1000) - # / 1e6 - # ) - energy_consumption_refrigeration_2_kj = -( - deltaE_c_net_4_2 - Work_c_comp * H2_c_Cap_Storage * Release_efficiency / 1000 - ) # in kJ - - # NOTE: changed based on discussion with original author 20221216, energy separated - # out 20230317 - Utility_c_refrigeration_2 = ( - (Energy_cost / 0.057) * Utility_c_ref * energy_consumption_refrigeration_2_kj / 1e6 - ) - - # specify energy usage separately so energy usage can be used externally if desired - joule2watthour = 1.0 / 3600.0 # 3600 joules in a watt hour (as also 3600 kJ in a kWh) - energy_consumption_refrigeration_1_kwh = ( - energy_consumption_refrigeration_1_kj * joule2watthour - ) - (energy_consumption_refrigeration_2_kj * joule2watthour) - self.total_refrigeration_energy_used_kwh = ( - energy_consumption_refrigeration_1_kwh # + energy_consumption_refrigeration_2_kwh - ) - - if self.total_refrigeration_energy_used_kwh < 0: - raise (ValueError("energy usage must be greater than 0")) - ###############################Heating costs desorption process - k1 = 6.9617 - k2 = -1.48 - k3 = 0.3161 - Net_c_Heating_Power_Desorption = ( - detlaP_c_net_2_3 / self.Efficiency_heater - ) ## steam boiler power at 0.7 efficiency in kW - Number_c_Heaters = np.floor( - Net_c_Heating_Power_Desorption / 9400 - ) # Number of compressors excluding the last one - Heater_c_Power_1 = Net_c_Heating_Power_Desorption % 9400 # power of the last compressor - # print('Number of heaters', Number_c_Heaters+1) - Heater_c_Cap_Cost = ( - 10 ** (k1 + k2 * np.log10(9400) + k3 * (np.log10(9400)) ** 2) - ) * Number_c_Heaters - if Heater_c_Power_1 < 1000: - Heater_c_Cap_Cost_1 = ( - 10 ** (k1 + k2 * np.log10(1000) + k3 * (np.log10(1000)) ** 2) - ) * (Heater_c_Power_1 / 1000) - else: - Heater_c_Cap_Cost_1 = 10 ** ( - k1 + k2 * np.log10(Heater_c_Power_1) + k3 * (np.log10(Heater_c_Power_1)) ** 2 - ) - Total_c_Heater_Cap_Cost = Heater_c_Cap_Cost + Heater_c_Cap_Cost_1 - Total_c_Heater_Cap_Cost = Total_c_Heater_Cap_Cost * ( - self.CEPCI_current / self.CEPCI2001 - ) ##Inflation #TODO make inflation optional per user input - - # NOTE: Jared Thomas set to zero as per discussion with Peng Peng through Abhineet Gupta - # 20221215 was 13.28*deltaE_c_net_2_3/1e6 #$13.28/GJ for low pressure steam - Utility_c_Heater = 0 - - self.total_heating_energy_used_kwh = Net_c_Heating_Power_Desorption * t_discharge_hr - Total_c_Heating_Energy_Costs = self.total_heating_energy_used_kwh * Energy_cost - - # print('heater capcost is $', Total_c_Heater_Cap_Cost) - - #############Operational costs (sized based on cycle 1 requirements)#################### - Op_c_Costs_1 = ( - Compr_c_Energy_Costs_1 - + Utility_c_refrigeration_1 - + Utility_c_Heater - + Total_c_Heating_Energy_Costs - ) - Op_c_Costs_2 = ( - Compr_c_Energy_Costs_2 - + Utility_c_refrigeration_2 - + Utility_c_Heater - + Total_c_Heating_Energy_Costs - ) - Total_c_Cap_Costs = ( - Storage_c_Tank_Cap_Costs - + Total_c_Refrig_Cap_Costs_adsorption - + Total_c_Compr_Cap_Cost - + Total_c_Heater_Cap_Cost - ) - - # Op_c_Costs = ( - # ( - # Op_c_Costs_1 - # + Op_c_Costs_2 * (cycle_number - 1) - # + self.maintanance * Total_c_Cap_Costs - # + self.wage * 360 * 2 - # ) - # / cycle_number - # / capacity - # ) - # TODO check this. I changed the 2 to a 24 because it looks like it should be working hours in a year. - Op_c_Costs = ( - ( - Op_c_Costs_1 - + Op_c_Costs_2 * (cycle_number - 1) - + self.maintanance * Total_c_Cap_Costs - + self.wage * 360 * 2 - ) - / cycle_number - ) # checked, this was divided by capacity, but Peng Peng confirmed it was duplicating - # the following divisions by capacity - - ######################writing costs##################################################### - self.cost_kg[i] = (Total_c_Cap_Costs / capacity + self.Site_preparation) * self.Markup - cost_kg_tank[i] = Storage_c_Tank_Cap_Costs / capacity - cost_kg_comp[i] = Total_c_Compr_Cap_Cost / capacity - cost_kg_ref[i] = Total_c_Refrig_Cap_Costs_adsorption / capacity - cost_kg_heat[i] = Total_c_Heater_Cap_Cost / capacity - self.Op_c_Costs_kg[i] = Op_c_Costs / capacity - # print("\n Pressure Vessel Costs: ") - # print("cost_kg ") - # print("cost_kg_tank ") - # print("cost_kg_comp ") - # print("cost_kg_ref ") - # print("cost_kg_heat ") - ######################################## Total Energy Use (kWh) ###################### - self.total_energy_used_kwh[i] = ( - self.total_compressor_energy_used_kwh - + self.total_heating_energy_used_kwh - + self.total_refrigeration_energy_used_kwh - ) - - self.curve_fit() - - def curve_fit(self): - ################### plot prep ########### - self.plot_range = range(int(np.min(self.capacity_1)), int(np.max(self.capacity_1)), 100) - - ###################Fitting capital#################################################### - - var_cap = [0.01, 0.5, 5] # Initial guesses for the parameters, can be flexible - - varfinal_cap_fitted, success = leastsq( - self.residual_op, - var_cap, - args=(self.capacity_1, self.cost_kg), - maxfev=100000, - ) - - self.a_cap_fit = varfinal_cap_fitted[0] - self.b_cap_fit = varfinal_cap_fitted[1] - self.c_cap_fit = varfinal_cap_fitted[2] - - if self.verbose: - print("a_cap is", self.a_cap_fit) - print("b_cap is", self.b_cap_fit) - print("c_cap is", self.c_cap_fit) - print("***********") - - self.fitted_capex = self.exp_log_fit(varfinal_cap_fitted, self.plot_range) - - # popt, pcov = curve_fit(self.exp_fit, self.capacity_1, self.cost_kg, maxfev=100000) - - # self.a_cap_fit=popt[0] - # self.b_cap_fit=popt[1] - - # print ('a is', self.a_cap_fit) - # print ('b is', self.b_cap_fit) - # print ('***********') - - # self.fitted_kg = self.exp_fit(self.plot_range,self.a_cap_fit,self.b_cap_fit) - - ####################### fitting OpEx ################################# - var_op = [0.01, 0.5, 5] # Initial guesses for the parameters, can be flexible - - varfinal_op_fitted, success = leastsq( - self.residual_op, - var_op, - args=(self.capacity_1, self.Op_c_Costs_kg), - maxfev=100000, - ) - - self.a_op_fit = varfinal_op_fitted[0] - self.b_op_fit = varfinal_op_fitted[1] - self.c_op_fit = varfinal_op_fitted[2] - - if self.verbose: - print("a_op is", self.a_op_fit) - print("b_op is", self.b_op_fit) - print("c_op is", self.c_op_fit) - print("***********") - - self.fitted_op_kg = self.exp_log_fit(varfinal_op_fitted, self.plot_range) - - ##################### Fit energy usage ################################ - self.energy_coefficients = np.polyfit(self.capacity_1, self.total_energy_used_kwh, 1) - self.energy_function = np.poly1d(self.energy_coefficients) # kWh - self.fit_energy_wrt_capacity_kwh = self.energy_function(self.plot_range) - - def plot(self): - fig, ax = plt.subplots(2, 2, sharex=True, figsize=(10, 6)) - - ##################### CAPEX ####################### - ax[0, 0].scatter(self.capacity_1 * 1e-3, self.cost_kg, color="r", label="Calc") - ax[0, 0].plot(np.asarray(self.plot_range) * 1e-3, self.fitted_capex, label="Fit") - # ax[0,0].plot(self.capacity_1,cost_kg_tank, color='b', label = 'tank') - # ax[0,0].plot(self.capacity_1,cost_kg_comp, color='c', label = 'compressor') - # ax[0,0].plot(self.capacity_1,cost_kg_ref, color='m', label = 'refrigeration') - # ax[0,0].plot(self.capacity_1,cost_kg_heat, color='y', label = 'heater') - - np.round(self.a_cap_fit, 2) - np.round(self.b_cap_fit, 2) - # plt.ylim(0,np.amax(self.cost_kg)*2) - # equation_cap = 'y='+str(a_disp)+'x'+'^'+str(b_disp) - a_cap_fit_disp = np.round(self.a_cap_fit, 2) - b_cap_fit_disp = np.round(self.b_cap_fit, 2) - c_cap_fit_disp = np.round(self.c_cap_fit, 2) - equation_cap = ( - "y=" - + "exp(" - + str(a_cap_fit_disp) - + "(ln(x))^2\n-" - + str(b_cap_fit_disp) - + "ln(x)+" - + str(c_cap_fit_disp) - + ")" - ) - - ax[0, 0].annotate( - equation_cap, - xy=(np.amax(self.capacity_1) * 1e-3 * 0.4, np.amax(self.cost_kg) * 0.8), - ) - - ax[0, 0].set_ylabel("CAPEX ($/kg)") - ax[0, 0].legend(loc="best", frameon=False) - # plt.legend(loc='best') - # ax[0,0].title('Capital') - - ##################### OPEX ############################ - a_op_fit_disp = np.round(self.a_op_fit, 2) - b_op_fit_disp = np.round(self.b_op_fit, 2) - c_op_fit_disp = np.round(self.c_op_fit, 2) - - equation_op = ( - "y=" - + "exp(" - + str(a_op_fit_disp) - + "(ln(x))^2\n-" - + str(b_op_fit_disp) - + "ln(x)+" - + str(c_op_fit_disp) - + ")" - ) - - ax[0, 1].plot(np.asarray(self.plot_range) * 1e-3, self.fitted_op_kg, label="Fit") - ax[0, 1].scatter(self.capacity_1 * 1e-3, self.Op_c_Costs_kg, color="r", label="Calc") - ax[0, 1].set_ylabel("OPEX ($/kg)") - ax[0, 1].annotate( - equation_op, - xy=( - np.amax(self.capacity_1) * 1e-3 * 0.2, - np.amax(self.Op_c_Costs_kg) * 0.4, - ), - ) - ax[0, 1].legend(loc="best", frameon=False) - # plt.legend(loc='best') - # ax[0,1].title('Annual operational') - - ################## Energy ###################### - ax[1, 1].plot( - np.asarray(self.plot_range) * 1e-3, - self.fit_energy_wrt_capacity_kwh * 1e-6, - label="Fit", - ) - ax[1, 1].scatter( - self.capacity_1 * 1e-3, - self.total_energy_used_kwh * 1e-6, - color="r", - label="Calc", - ) - ax[1, 1].set_xlabel("Capacity (metric tons H2)") - ax[1, 1].set_ylabel("Energy Use (GWh)") - - equation_energy = ( - "y=" - + str(round(self.energy_coefficients[0], 2)) - + "x+" - + str(round(self.energy_coefficients[1], 2)) - ) - ax[1, 1].annotate(equation_energy, xy=(3000, 5)) - ax[1, 1].legend(loc="best", frameon=False) - ax[1, 1].legend(loc="best", frameon=False) - # ax[1,1].title('Annual operational') - - ################ Wrap Up ###################### - - ax[1, 0].set_xlabel("Capacity (metric tons H2)") - - plt.tight_layout() - plt.show() diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/README.md b/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/README.md deleted file mode 100644 index d27f04793..000000000 --- a/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Compressed gas storage for wind-H2-steel project -- Author: Peng Peng (ppeng@lbl.gov) -- Date: 10/21/2022 -- Brief description: This script is for a high-level overall estimation of energy consumption for compressed gas H2 storage for a steel facility requiring 200 metric tons of hydrogen per day. - -## Required files: -1. Compressed_all.py -2. Compressed_gas_function.py -3. Tankinator_large.xlsx -## Key inputs: -1. Wind availability in % -2. Charge and discharge ratio -3. Flow rate -4. Energy (renewable) cost in $/kWh -## Key output: -1. Maximum equivalent storage capacity. -2. Maximum equivalent storage duration. -3. Fitting parameters for further calculations and optimizations. - 1. purchase capital storage cost ($/kg) vs. capacity - 2. annual operation cost ($/kg) vs. capacity. - -![](images/2022-11-18-15-30-34.png) -![](images/2022-11-18-15-31-18.png) - -## How to use: -1. Put all three files in one folder -2. Change the path of the Tankinator file -3. Change inputs and run Compressed_all.py - -## Notes: -1. Max storage capacity obtained from empirical relation from another project for 200 metric ton/day H2 flow into steel facility. See below. And is assumed to scale linearly with the steel facility. - - ![](images/2022-11-18-15-31-54.png) -2. Operation cost does not include electrolyzer. -3. Costs include the following dummy values, which can be changed later in the “Economic parameters” section (around line 64) in Compressed_gas_function.py - 1. Site preparation $100/kg H2 stored - 2. 80% markup for tank manufacturing based on materials - 3. 50% markup from purchase cost for engineering, installation, etc. - 4. labor @ 36 $/h, 360 days x2 - 5. annual maintenance @ 3% purchase capital -4. Capital cost is for 2021 CEPCI, which can be changed at the same place as above -5. The code is for 350 bar compressed gas storage. If change pressure make sure to change the pressure in the Tankinator file and save it again before running. -6. The main components included are storage tanks, compressors, refrigeration, and heater. -7. Calculations are based on high-level energy balances, detailed energy consumption should be performed with more comprehensive process simulation. -8. Heat transfer between the ambient, and friction losses are not included. -9. From the storage capacity (x) derived from the wind availability, the cost relation is derived from the Cost relation fitted from 0.1x to x. This is done because the fitting correlations do not work very well across a very large range (Let me know if you want to optimize for a larger range). This will lead to a small difference for the same capacity when the max changes. See the example below for different wind availabilities, the fitted values for 105 kg capacity are different. I tested different step sizes and ranges and found 0.1x-x, 15-25 steps tend to give the smallest difference for this fitting correlation. -![](images/2022-11-18-15-32-24.png) -![](images/2022-11-18-15-32-32.png) -10. Cost year for the model is 2021 with the chemical plant cost index set as 708. - -## Main references: -1. Geankoplis, C. J. "Transport processes and separation." Process Principles. Prentice Hall NJ, 2003. -2. Dicks, A. L. & Rand, D. A. J. in Fuel Cell Systems Explained 351-399 (Wiley, 2018). -3. Turton, R., Bailie, R. C., Whiting, W. B., & Shaeiwitz, J. A. (2018). Analysis, synthesis and design of chemical processes. Pearson Education. 5th edition. -4. Luyben, W. L. (2017). Estimating refrigeration costs at cryogenic temperatures. Computers & Chemical Engineering, 103, 144-150. diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/Tankinator.xlsx b/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/compressed_gas_storage_model_20221021/Tankinator.xlsx deleted file mode 100644 index 93fcd67824db259ef9f4d1b6274fd19ec32b144d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120062 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%0Dw`P=IMc#kb# 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 EFZ0Yl!T float: - assert L >= 2 * R # cylindrical tank with hemispherical ends - return np.pi * R**2 * (L - 2.0 * R / 3.0) - - def compute_hemicylinder_outer_length(L: float, t: float) -> float: - return L + 2 * t - - def compute_hemicylinder_outer_radius(R: float, t: float) -> float: - return R + t - - def check_thinwall(Rinner: float, t: float, thinwallratio_crit=10) -> bool: - return Rinner / t >= thinwallratio_crit - - -class TypeITank(Tank): - """ - a class I tank: metal shell tank - """ - - def __init__( - self, - material: str, - yield_factor: float = 3.0 / 2.0, - ultimate_factor: float = 2.25, - shear_approx="interp", - ): - super().__init__(1, yield_factor, ultimate_factor) - - self.material = MetalMaterial(material, approx_method=shear_approx) - - # initial geometry values undefined - self.thickness = None # thickness of tank - - # return functions for symmetry - def get_thickness(self): - return self.thickness - - # get the outer dimensions - def get_length_outer(self): - """returns the outer length of the pressure vessel in cm""" - if None in [self.length_inner, self.thickness]: - return None - return Tank.compute_hemicylinder_outer_length(self.length_inner, self.thickness) - - def get_radius_outer(self): - """returns the outer radius of the pressure vessel in cm""" - if None in [self.radius_inner, self.thickness]: - return None - return Tank.compute_hemicylinder_outer_radius(self.radius_inner, self.thickness) - - def get_volume_outer(self): - """ - returns the outer volume of the pressure vessel in ccm - """ - if None in [self.length_inner, self.radius_inner, self.thickness]: - return None - return Tank.compute_hemicylinder_volume(self.get_radius_outer(), self.get_length_outer()) - - def get_volume_metal(self): - """ - returns the (unsealed) displacement volume of the pressure vessel in ccm - """ - volume_inner = self.get_volume_inner() - volume_outer = self.get_volume_outer() - if None in [volume_inner, volume_outer]: - return None - assert volume_outer >= volume_inner - return volume_outer - volume_inner - - def get_mass_metal(self): - """returns the mass of the pressure vessel in kg""" - volume_metal = self.get_volume_metal() - if volume_metal is None: - return None - return self.material.density * volume_metal - - def get_cost_metal(self): - """ - returns the cost of the metal in the pressure vessel in dollars - """ - mass_metal = self.get_mass_metal() - if mass_metal is None: - return None - return self.material.cost_rate * mass_metal - - def get_gravimetric_tank_efficiency(self): - """ - returns the gravimetric tank efficiency: - $$ \frac{m_{metal}}{V_{inner}} $$ - in L/kg - """ - mass_metal = self.get_mass_metal() - volume_inner = self.get_volume_inner() - return (volume_inner / 1e3) / mass_metal - - def get_yield_thickness(self, pressure: float | None = None, temperature: float | None = None): - """ - gets the yield thickness - - returns the yield thickness given by: - $$ - t_y= \frac{p R_0}{S_y} \times SF_{yield} - $$ - with yield safety factor $SF_{yield}= 3/2$ by default - - temperature and pressure must be set in the class, or specified in this - function - - :param pressure: operating pressure, in bar - :type pressure: float - :param temperature: operating temperature, in degrees C - :type temperature: float - """ - - if (temperature is None) and (self.operating_temperature is None): - raise LookupError("you must specify an operating temperature.") - elif temperature is None: - temperature = self.operating_temperature - - if (pressure is None) and (self.operating_pressure is None): - raise LookupError("you must specify an operating pressure.") - elif pressure is None: - pressure = self.operating_pressure - - Sy = self.material.yield_shear_fun(temperature) - - thickness_yield = pressure * self.radius_inner / Sy * self.yield_factor - - return thickness_yield - - def get_ultimate_thickness( - self, pressure: float | None = None, temperature: float | None = None - ): - """ - get the ultimate thickness - - returns the ultimate thicnkess given by: - $$ - t_u= \frac{p R_0}{S_u} \times SF_{ultimate} - $$ - with ultimate safety factor $SF_{yield}= 2.25$ by default - - temperature and pressure must be set in the class, or specified in this - function - - :param pressure: operating pressure, in bar - :type pressure: float - :param temperature: operating temperature, in degrees C - :type temperature: float - """ - - if (temperature is None) and (self.operating_temperature is None): - raise LookupError("you must specify an operating temperature.") - elif temperature is None: - temperature = self.operating_temperature - - if (pressure is None) and (self.operating_pressure is None): - raise LookupError("you must specify an operating pressure.") - elif pressure is None: - pressure = self.operating_pressure - - Su = self.material.ultimate_shear_fun(temperature) - - thickness_ultimate = pressure * self.radius_inner / Su * self.ultimate_factor - - return thickness_ultimate - - def get_thickness_thinwall( - self, pressure: float | None = None, temperature: float | None = None - ): - """ - get the thickness based on thinwall assumptions - - maximum between yield and ultimate thickness - - temperature and pressure must be set in the class, or specified in this - function - - :param pressure: operating pressure, in bar - :type pressure: float - :param temperature: operating temperature, in degrees C - :type temperature: float - """ - - t_y = self.get_yield_thickness(pressure, temperature) - t_u = self.get_ultimate_thickness(pressure, temperature) - - thickness = max(t_y, t_u) - - return thickness - - def set_thickness_thinwall( - self, pressure: float | None = None, temperature: float | None = None - ): - """ - set the thickness based on thinwall assumptions - - maximum between yield and ultimate thickness - - temperature and pressure must be set in the class, or specified in this - function - - :param pressure: operating pressure, in bar - :type pressure: float - :param temperature: operating temperature, in degrees C - :type temperature: float - """ - - self.thickness = self.get_thickness_thinwall(pressure, temperature) - - def get_thickness_vonmises( - self, - pressure: float | None = None, - temperature: float | None = None, - max_cycle_iter: int = 10, - adj_fac_tol: float = 1e-6, - ): - """ - get the thickness based on a von Mises cycle - - temperature and pressure must be set in the class, or specified here - - :param pressure: operating pressure, in bar - :type pressure: float - :param temperature: operating temperature, in degrees C - :type temperature: float - :param max_cycle_iter: maximum iterations for von Mises cycle - :type max_cycle_iter: int - :param adj_fac_tol: tolerance for close enough wall thickness adjustment - factor - """ - - if (temperature is None) and (self.operating_temperature is None): - raise LookupError("you must specify an operating temperature.") - elif temperature is None: - temperature = self.operating_temperature - - if (pressure is None) and (self.operating_pressure is None): - raise LookupError("you must specify an operating pressure.") - elif pressure is None: - pressure = self.operating_pressure - - # get the limit shears - Sy = self.material.yield_shear_fun(temperature) - Su = self.material.ultimate_shear_fun(temperature) - - # start from the thinwall analysis - thickness_init = self.get_thickness_thinwall(pressure, temperature) - - # check to see if von Mises analysis is even needed - if (Tank.check_thinwall(self.radius_inner, thickness_init)) and ( - von_mises.wallThicknessAdjustmentFactor( - pressure, self.radius_inner + thickness_init, self.radius_inner, Sy, Su - ) - == 1.0 - ): - thickness_cycle = thickness_init # trivially satisfied - iter_cycle = -1 - print("trivially satisfied") - else: - print("running von mises cycle") - print(pressure, self.radius_inner, thickness_init, Sy, Su) - (thickness_cycle, WTAF_cycle, iter_cycle) = von_mises.cycle( - pressure, - self.radius_inner, - thickness_init, - Sy, - Su, - max_iter=max_cycle_iter, - WTAF_tol=adj_fac_tol, - ) - return thickness_cycle, iter_cycle - - def set_thickness_vonmises( - self, - pressure: float | None = None, - temperature: float | None = None, - max_cycle_iter: int = 10, - adj_fac_tol: float | None = 1e-6, - ): - """ - set the thickness based on a von Mises cycle - - temperature and pressure must be set in the class, or specified here - - :param pressure: operating pressure, in bar - :type pressure: float - :param temperature: operating temperature, in degrees C - :type temperature: float - :param max_cycle_iter: maximum iterations for von Mises cycle - :type max_cycle_iter: int - :param adj_fac_tol: tolerance for close enough wall thickness adjustment - factor - """ - - thickness, _ = self.get_thickness_vonmises( - pressure, temperature, max_cycle_iter, adj_fac_tol - ) - self.thickness = thickness - - -class LinedTank(Tank): - """ - a lined tank for Type III or Type III: aluminum-lined carbon fiber-jacketed tank - """ - - def __init__( - self, - tanktype: int, - load_bearing_liner, - liner_design_load_factor=0.21, - liner_thickness_min=0.3, - yield_factor: float = 3.0 / 2.0, - ultimate_factor: float = 2.25, - ): - super().__init__(tanktype, yield_factor, ultimate_factor) - - if tanktype not in [3, 4]: - raise NotImplementedError("unknown tank type.") - - self.load_bearing_liner = load_bearing_liner - self.liner_design_load_factor = liner_design_load_factor - - with (Path(__file__).parent / "material_properties.json").open() as mmprop_file: - mmprop = json.load(mmprop_file) - - # liner characteristics - if tanktype == 3: - self.shear_ultimate_liner = mmprop["liner_aluminum"]["shear_ultimate_bar"] - self.density_liner = mmprop["liner_aluminum"]["density_kgccm"] - self.costrate_liner = mmprop["liner_aluminum"]["costrate_$kg"] - else: - self.shear_ultimate_liner = None - self.density_liner = mmprop["HDPE"]["density_kgccm"] - self.costrate_liner = mmprop["HDPE"]["costrate_$kg"] - self.thickness_liner_min = liner_thickness_min - - # jacket characteristics - self.shear_ultimate_jacket = mmprop["carbon_fiber"]["shear_ultimate_bar"] - self.density_jacket = mmprop["carbon_fiber"]["density_kgccm"] - self.costrate_jacket = mmprop["carbon_fiber"]["costrate_$kg"] - self.fiber_translation_efficiency_jacket = mmprop["carbon_fiber"][ - "fiber_translation_efficiency" - ] - self.fiber_layer_thickness = mmprop["carbon_fiber"]["layer_thickness_cm"] - self.fiber_layer_min = mmprop["carbon_fiber"]["min_layer"] - - # thicknesses (to be computed) - self.thickness_liner = None - self.thickness_jacket = None - self.thickness_ideal_jacket = None - self.Nlayer_jacket = None - - def get_thicknesses_thinwall(self, pressure: float | None = None): - """???""" - - if (pressure is None) and (self.operating_pressure is None): - raise LookupError("you must specify an operating pressure.") - elif pressure is None: - pressure = self.operating_pressure - - # compute the liner thickness - pressure_burst_target = self.ultimate_factor * pressure - if self.tank_type != 4: - pressure_liner_target = pressure_burst_target * self.liner_design_load_factor - thickness_burst = pressure_liner_target * self.radius_inner / self.shear_ultimate_liner - thickness_liner = max(thickness_burst, self.thickness_liner_min) - else: - thickness_liner = self.thickness_liner_min - - # compute ideal jacket thickness - radius_liner = Tank.compute_hemicylinder_outer_radius(self.radius_inner, thickness_liner) - thickness_jacket_ideal = ( - pressure_burst_target - * radius_liner - / self.shear_ultimate_jacket - / self.fiber_translation_efficiency_jacket - ) - if self.load_bearing_liner: - # subdivide pressure if liner is load-bearing - pressure_liner = thickness_liner * self.shear_ultimate_liner / self.radius_inner - pressure_jacket = pressure_burst_target - pressure_liner - assert pressure_jacket >= 0 - thickness_jacket_ideal = ( - pressure_jacket - * radius_liner - / self.shear_ultimate_jacket - / self.fiber_translation_efficiency_jacket - ) - - # compute number of layers, real thickness - Nlayer_jacket = max( - self.fiber_layer_min, - int(np.ceil(thickness_jacket_ideal / self.fiber_layer_thickness)), - ) - thickness_jacket_real = Nlayer_jacket * self.fiber_layer_thickness - - return ( - thickness_liner, - thickness_jacket_ideal, - Nlayer_jacket, - thickness_jacket_real, - ) - - def set_thicknesses_thinwall(self, pressure: float | None = None): - """???""" - - if (pressure is None) and (self.operating_pressure is None): - raise LookupError("you must specify an operating pressure.") - elif pressure is None: - pressure = self.operating_pressure - - # pass the returns of the previous function - ( - self.thickness_liner, - self.thickness_ideal_jacket, - self.Nlayer_jacket, - self.thickness_jacket, - ) = self.get_thicknesses_thinwall(pressure) - - def get_safetyfactor_real_jacket(self): - """ - figure out the integer layer safety factor - """ - - if None in [self.thickness_jacket, self.thickness_ideal_jacket]: - return None - - sf_real = self.ultimate_factor * self.thickness_jacket / self.thickness_jacket_ideal - - return sf_real - - # get the liner dimensions - def get_length_liner(self): - """returns the outer length of the pressure vessel in cm""" - if None in [self.length_inner, self.thickness_liner]: - return None - return Tank.compute_hemicylinder_outer_length(self.length_inner, self.thickness_liner) - - def get_radius_liner(self): - """returns the outer radius of the pressure vessel in cm""" - if None in [self.radius_inner, self.thickness_liner]: - return None - return Tank.compute_hemicylinder_outer_radius(self.radius_inner, self.thickness_liner) - - def get_volume_outer_liner(self): - """ - returns the outer volume of the pressure vessel in ccm - """ - if None in [self.length_inner, self.radius_inner, self.thickness_liner]: - return None - return Tank.compute_hemicylinder_volume(self.get_radius_liner(), self.get_length_liner()) - - def get_volume_liner(self): - """ - returns the (unsealed) displacement volume of the liner in ccm - """ - volume_inner = self.get_volume_inner() - volume_outer_liner = self.get_volume_outer_liner() - if None in [volume_inner, volume_outer_liner]: - return None - assert volume_outer_liner >= volume_inner - return volume_outer_liner - volume_inner - - def get_mass_liner(self): - """ - returns the mass of the liner in kg - """ - volume_liner = self.get_volume_liner() - if volume_liner is None: - return None - return self.density_liner * volume_liner - - def get_cost_liner(self): - """ - returns the cost of the liner material in $ - """ - mass_liner = self.get_mass_liner() - if mass_liner is None: - return None - return self.costrate_liner * mass_liner - - # get the outer dimensions - def get_length_outer(self): - """returns the outer length of the pressure vessel in cm""" - if None in [self.length_inner, self.thickness_liner, self.thickness_jacket]: - return None - return Tank.compute_hemicylinder_outer_length( - self.length_inner, self.thickness_liner + self.thickness_jacket - ) - - def get_radius_outer(self): - """returns the outer radius of the pressure vessel in cm""" - if None in [self.radius_inner, self.thickness_liner, self.thickness_jacket]: - return None - return Tank.compute_hemicylinder_outer_radius( - self.radius_inner, self.thickness_liner + self.thickness_jacket - ) - - def get_volume_outer(self): - """ - returns the outer volume of the pressure vessel in ccm - """ - if None in [ - self.length_inner, - self.radius_inner, - self.thickness_liner, - self.thickness_jacket, - ]: - return None - return Tank.compute_hemicylinder_volume(self.get_radius_outer(), self.get_length_outer()) - - def get_volume_jacket(self): - """ - returns the (unsealed) displacement volume of the carbon fiber jacket in ccm - """ - volume_outer = self.get_volume_outer() - volume_outer_liner = self.get_volume_outer_liner() - if None in [volume_outer, volume_outer_liner]: - return None - assert volume_outer >= volume_outer_liner - return volume_outer - volume_outer_liner - - def get_mass_jacket(self): - """ - returns the mass of the carbon fiber jacket in kg - """ - volume_jacket = self.get_volume_jacket() - if volume_jacket is None: - return None - return self.density_jacket * volume_jacket - - def get_cost_jacket(self): - """ - returns the cost of the jacket material in $ - """ - mass_jacket = self.get_mass_jacket() - if mass_jacket is None: - return None - return self.costrate_jacket * mass_jacket - - def get_mass_tank(self): - """ - returns the mass of the empty tank in kg - """ - mass_liner = self.get_mass_liner() - mass_jacket = self.get_mass_jacket() - if None in [mass_liner, mass_jacket]: - return None - return mass_liner + mass_jacket - - def get_cost_tank(self): - """ - returns the material cost of the tank in $ - """ - cost_liner = self.get_cost_liner() - cost_jacket = self.get_cost_jacket() - if None in [cost_liner, cost_jacket]: - return None - return cost_liner + cost_jacket - - def get_gravimetric_tank_efficiency(self): - """ - returns the gravimetric tank efficiency: - $$ \frac{m_{tank}}{V_{inner}} $$ - in L/kg - """ - mass_tank = self.get_mass_tank() - volume_inner = self.get_volume_inner() - return (volume_inner / 1e3) / mass_tank - - -class TypeIIITank(LinedTank): - def __init__( - self, - conservative=False, - liner_design_load_factor=0.21, - liner_thickness_min=0.3, - yield_factor: float = 3 / 2, - ultimate_factor: float = 2.25, - ): - load_bearing_liner = not conservative # use load bearing liner iff not conservative - super().__init__( - 3, - load_bearing_liner, - liner_design_load_factor, - liner_thickness_min, - yield_factor, - ultimate_factor, - ) - - -class TypeIVTank(LinedTank): - def __init__( - self, - liner_design_load_factor=0.21, - liner_thickness=0.4, - yield_factor: float = 3 / 2, - ultimate_factor: float = 2.25, - ): - super().__init__( - 4, - False, - liner_design_load_factor, - liner_thickness, - yield_factor, - ultimate_factor, - ) diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/von_mises.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/von_mises.py deleted file mode 100644 index e52794f32..000000000 --- a/h2integrate/simulation/technologies/hydrogen/h2_storage/pressure_vessel/von_mises.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -Author: Cory Frontin -Date: 23 Jan 2023 -Institution: National Renewable Energy Lab -Description: This file computes von Mises quantities for hemicylindrical tanks, - replacing Tankinator.xlsx -Sources: - - Tankinator.xlsx -""" - -import numpy as np - - -def S1(p, Re, R0): # von Mises hoop stress - return p * (Re**2 + R0**2) / (Re**2 - R0**2) - - -def S2(p, Re, R0): # von Mises axial stress - return p * R0**2 / (Re**2 - R0**2) - - -def S3(p, Re, R0): # von Mises radial stress - return -p - - -def getPeakStresses(p, Re, R0, proof_factor=3.0 / 2.0, burst_factor=2.25): - aVM = np.sqrt(2) / 2 - bVM = (S2(p, Re, R0) - S1(p, Re, R0)) ** 2 - cVM = (S3(p, Re, R0) - S1(p, Re, R0)) ** 2 - dVM = (S3(p, Re, R0) - S2(p, Re, R0)) ** 2 - eVM = np.sqrt(bVM + cVM + dVM) - Sproof = proof_factor * aVM * eVM - Sburst = burst_factor * aVM * eVM - return (Sproof, Sburst) - - -def wallThicknessAdjustmentFactor( - p, Re, R0, Syield, Sultimate, proof_factor=3.0 / 2.0, burst_factor=2.25 -): - """ - get factor by which to increase thickness when von Mises stresses exceed - material yield safety margins - """ - Sproof, Sburst = getPeakStresses(p, Re, R0, proof_factor, burst_factor) - WTAF_proof = Sproof / Syield - WTAF_burst = Sburst / Sultimate - WTAF = max(WTAF_proof, WTAF_burst) - return WTAF - - -def iterate_thickness( - p, R0, thickness_in, Syield, Sultimate, proof_factor=3.0 / 2.0, burst_factor=2.25 -): - """ - apply the wall thickness adjustment factor, return it w/ new thickness - """ - - Router = R0 + thickness_in - WTAF = wallThicknessAdjustmentFactor( - p, Router, R0, Syield, Sultimate, proof_factor, burst_factor - ) - - return max(1.0, WTAF), max(1.0, WTAF) * thickness_in - - -def cycle( - p, - R0, - thickness_init, - Syield, - Sultimate, - proof_factor=3.0 / 2.0, - burst_factor=2.25, - max_iter=10, - WTAF_tol=1e-6, -): - """ - cycle to find a thickness that satisfies the von Mises criteria - """ - - # compute initial thickness, WTAF - thickness = thickness_init - WTAF = wallThicknessAdjustmentFactor( - p, R0 + thickness, R0, Syield, Sultimate, proof_factor, burst_factor - ) - - # iterate while WTAF is greater than zero - n_iter = 0 - while (WTAF - 1.0 > WTAF_tol) and (n_iter < max_iter): - n_iter += 1 # this cycle iteration number - - # get the next thickness - WTAF, thickness = iterate_thickness( - p, R0, thickness, Syield, Sultimate, proof_factor, burst_factor - ) - - return (thickness, WTAF, n_iter) diff --git a/h2integrate/storage/hydrogen/eco_storage.py b/h2integrate/storage/hydrogen/eco_storage.py index 59a47c88c..c99261d18 100644 --- a/h2integrate/storage/hydrogen/eco_storage.py +++ b/h2integrate/storage/hydrogen/eco_storage.py @@ -200,7 +200,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): else: msg = ( "H2 storage type %s was given, but must be one of ['none', 'turbine', 'pipe'," - " 'pressure_vessel', 'salt_cavern', 'lined_rock_cavern', 'mch']" + " 'salt_cavern', 'lined_rock_cavern', 'mch']" ) raise ValueError(msg) diff --git a/tests/h2integrate/input_files/plant/h2integrate_config.yaml b/tests/h2integrate/input_files/plant/h2integrate_config.yaml deleted file mode 100644 index 544700bb7..000000000 --- a/tests/h2integrate/input_files/plant/h2integrate_config.yaml +++ /dev/null @@ -1,344 +0,0 @@ -site: - mean_windspeed: False - depth: 45 #m - wind_layout: - row_spacing: 7 # Also defined in ORBIT config for offshore layout. H2Integrate config values override the values in ORBIT. - turbine_spacing: 7 # Also defined in ORBIT config for offshore layout. H2Integrate config values override the values in ORBIT. - grid_angle: 0 # wind layout grid angle in degrees where 0 is north, increasing clockwise - row_phase_offset: 0 # wind layout offset of turbines along row from one row to the next -project_parameters: - project_lifetime: 30 - grid_connection: False # option, can be turned on or off - ppa_price: 0.025 # $/kWh based on 2022 land based wind market report (ERCOT area ppa prices) https://www.energy.gov/sites/default/files/2022-08/land_based_wind_market_report_2202.pdf - hybrid_electricity_estimated_cf: 0.492 #should equal 1 if grid_connection = True - financial_analysis_start_year: 2027 - cost_year: 2022 # to match ATB - # installation_time: 0 #36 # months -finance_parameters: - costing_general_inflation: 0.025 # used to adjust modeled costs to cost_year - inflation_rate: 0.025 # based on 2022 ATB - discount_rate: - general: 0.10 # nominal return based on 2022 ATB basline workbook - wind: 0.10 - wave: 0.10 - debt_equity_split: - general: 68.5 # 2022 ATB uses 68.5% debt - wind: 68.5 - wave: 68.5 - property_tax: 0.01 # percent of CAPEX # combined with property insurance then between H2A and H2FAST defaults - property_insurance: 0.005 # percent of CAPEX # combined with property tax then between H2A and H2FAST defaults - total_income_tax_rate: 0.257 # 0.257 tax rate in 2022 atb baseline workbook # current federal income tax rate, but proposed 2023 rate is 0.28. No state income tax in Texas - capital_gains_tax_rate: 0.15 # H2FAST default - sales_tax_rate: 0.0 #Verify that a different rate shouldn't be used # minimum total sales tax rate in Corpus Christi https://www.cctexas.com/detail/corpus-christi-type-fund-purpose - does this apply to H2? - debt_interest_rate: - general: 0.06 - wind: 0.06 - wave: 0.06 - 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: 0 # H2FAST default, not used for revolving debt - cash_onhand_months: 1 # H2FAST default - administrative_expense_percent_of_sales: 0.00 #Check this # percent of sales H2FAST default - depreciation_method: "MACRS" # can be "MACRS" or "Straight line" - MACRS may be better and can reduce LCOH by more than $1/kg and is spec'd in the IRS MACRS schedule https://www.irs.gov/publications/p946#en_US_2020_publink1000107507 - depreciation_period: 5 # years - as specified by the IRS MACRS schedule https://www.irs.gov/publications/p946#en_US_2020_publink1000107507 - depreciation_period_electrolyzer: 7 # based on PEM Electrolysis H2A Production Case Study Documentation estimate of 7 years. also see https://www.irs.gov/publications/p946#en_US_2020_publink1000107507 - discount_years: - wind: 2022 # based on turbine capex value provided to ORBIT from 2022 ATB - wind_and_electrical: 2022 # for ORBIT opex - wave: 2020 # confirmed by Kaitlin Brunik 20240103 - solar: 2022 # TODO check - battery: 2022 # TODO check - platform: 2022 # TODO ask Nick and Charlie - electrical_export_system: 2022 # also from ORBIT, so match wind assumptions. TODO ask Sophie Bradenkamp - desal: 2013 # from code citation: https://www.nrel.gov/docs/fy16osti/66073.pdf - electrolyzer: 2020 # 2020 for singlitico2021, 2016 # for simple h2 cost model in hopp (see https://www.hydrogen.energy.gov/pdfs/19009_h2_production_cost_pem_electrolysis_2019.pdf) ## 2020 # based on IRENA report https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2020/Dec/IRENA_Green_hydrogen_cost_2020.pdf - h2_transport_compressor: 2016 # listed in code header - h2_storage: - pressure_vessel: 2022 # based on readme for Compressed_gas_function - pipe: 2019 # Papadias 2021 - salt_cavern: 2019 # Papadias 2021 - turbine: 2003 # assumed based on Kottenstette 2004 - lined_rock_cavern: 2018 # based on Papadias 2021 and HD SAM - none: 2022 # arbitrary - h2_pipe_array: 2018 # ANL costs - h2_transport_pipeline: 2018 # same model for costs as the h2_pipe_array - wind: - expected_plant_cost: 'none' -electrolyzer: - sizing: - resize_for_enduse: False - size_for: 'BOL' #'BOL' (generous) or 'EOL' (conservative) - hydrogen_dmd: - rating: 180 # MW # 0.9*Plant rating appears near-optimal for 400 MW wind plant with 3 days of underground pipe storage # MW - cluster_rating_MW: 180 - pem_control_type: 'basic' - eol_eff_percent_loss: 10 #eol defined as x% change in efficiency from bol - uptime_hours_until_eol: 77600 #number of 'on' hours until electrolyzer reaches eol - include_degradation_penalty: True #include degradation - turndown_ratio: 0.1 #turndown_ratio = minimum_cluster_power/cluster_rating_MW - electrolyzer_capex: 700 # $/kW conservative 2025 centralized. high 700, low 300 # based on https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2020/Dec/IRENA_Green_hydrogen_cost_2020.pdf - replacement_cost_percent: 0.15 # percent of capex - H2A default case - cost_model: "singlitico2021" # "basic" is a basic cost model based on H2a and HFTO program record for PEM electrolysis. "singlitico2021" uses cost estimates from that paper - -h2_transport_compressor: - outlet_pressure: 68 # bar based on HDSAM -h2_storage_compressor: - output_pressure: 100 # bar (1 bar = 100 kPa) - flow_rate: 89 # kg/hr - energy_rating: 802 # kWe (aka 1 kWh) - mean_days_between_failures: 200 # days - # annual_h2_throughput: 18750 # [kg/yr] -> kg of H2 per year -h2_transport_pipe: - outlet_pressure: 10 # bar - from example in code from Jamie #TODO check this value -h2_storage: - size_capacity_from_demand: - flag: False # If True, then storage is sized to provide steady-state storage - capacity_from_max_on_turbine_storage: False # if True, then days of storage is ignored and storage capacity is based on how much h2 storage fits on the turbines in the plant using Kottenstete 2003. - type: "none" # can be one of ["none", "pipe", "turbine", "pressure_vessel", "salt_cavern", "lined_rock_cavern"] - days: 3 # [days] how many days worth of production we should be able to store (this is ignored if `capacity_from_max_on_turbine_storage` or `size_capacity_from_demand` is set to True) - -platform: - opex_rate: 0.0111 # % of capex to determine opex (see table 5 in https://www.acm.nl/sites/default/files/documents/study-on-estimation-method-for-additional-efficient-offshore-grid-opex.pdf) - # Modified orbit configuration file for a single platform to carry "X technology" - design_phases: - - FixedPlatformDesign # Register Design Phase - install_phases: - FixedPlatformInstallation: 0 # Register Install Phase - oss_install_vessel: example_heavy_lift_vessel - site: - depth: -1 # site depth [m] (if -1, then will use the full plant depth) - distance: -1 # distance to port [km] (if -1, then will use the full plant distance) - equipment: - tech_required_area: -1. # equipment area [m**2] (-1 will require the input during run) - tech_combined_mass: -1 # equipment mass [t] (-1 will require the input during run) - topside_design_cost: 4500000 # topside design cost [USD] - installation_duration: 14 # time at sea [days] - -policy_parameters: # these should be adjusted for inflation prior to application - order of operations: rate in 1992 $, -#then prevailing wage multiplier if applicable, then inflation - option1: # base # no policy included ---> see files/task1/regulation and policy revue/ page 4 of 13 middle - read this - # and look at assumptions - electricity_itc: 0 - electricity_ptc: 0 - h2_ptc: 0 - h2_storage_itc: 0 - option2: # base credit levels with H2 - electricity_itc: 0 - electricity_ptc: 0.003 # $0.003/kW (this is base, see inflation adjustment in option 3) - h2_ptc: 0.6 # $0.60/kg h2 produced - assumes net zero but not meeting prevailing wage requirements - does this need to be - # adjusted for inflation from 2022 dollars to claim date, probably constant after claim date? - h2_storage_itc: 0.06 - option3: # same as option 5, but assuming prevailing wages are met --> 5x multiplier on both PTCs - electricity_itc: 0 - electricity_ptc: 0.015 # $/kWh 1992 dollars - h2_ptc: 3.00 # $/kg 2022 dollars - do not adjust for inflation - h2_storage_itc: 0.3 - # bonus options, option 5 and 6 but ITC equivalents - option4: # prevailing wages not met - electricity_itc: 0.06 # %/100 capex - electricity_ptc: 0.00 # $/kW 1992 dollars - h2_ptc: 0.6 # $0.60/kg produced 2022 dollars - assumes net zero but not meeting prevailing wage requirements - does this need to be - # do not adjust for inflation, probably constant after claim date? - h2_storage_itc: 0.06 - option5: # prevailing wages met - electricity_itc: 0.30 # %/100 capex - electricity_ptc: 0.0 # $/kWh 1992 dollars - h2_ptc: 3.00 # $/kg of h2 produced 2022 dollars - do adjust for inflation every year applied and until application year - h2_storage_itc: 0.3 - option6: # assumes prevailing wages are met, and includes 10% bonus credit of domestic content (100% of steel and iron - # and mfg. components from the US) - electricity_itc: 0.40 # %/100 capex - electricity_ptc: 0.0 # $/kWh 1992 dollars - h2_ptc: 3.00 # $/kg of h2 produced 2022 dollars - do adjust for inflation every year applied and until application year - h2_storage_itc: 0.4 - option7: # assumes prevailing wages are met, and includes 10% bonus credit of domestic content (100% of steel and iron - # and mfg. components from the US) - electricity_itc: 0.0 # %/100 capex - electricity_ptc: 0.0165 # $/kWh 1992 dollars (0.015*1.1) - h2_ptc: 3.00 # $/kg of h2 produced 2022 dollars - do adjust for inflation every year applied and until application year - # you can elect itc_for_h2 in leu of the h2_ptc - this choice is independent of the other tech credit selections - # 6% or %50 for itc_for_h2 - h2_storage_itc: 0.5 - -plant_design: - scenario0: - electrolyzer_location: "platform" # can be one of ["onshore", "turbine", "platform"] - transportation: "pipeline" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "platform" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario1: - electrolyzer_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - transportation: "hvdc" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario2: - electrolyzer_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - transportation: "hvdc" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "platform" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario3: - electrolyzer_location: "turbine" # can be one of ["onshore", "turbine", "platform"] - transportation: "none" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "turbine" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario4: - electrolyzer_location: "turbine" # can be one of ["onshore", "turbine", "platform"] - transportation: "none" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "platform" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario5: - electrolyzer_location: "turbine" # can be one of ["onshore", "turbine", "platform"] - transportation: "pipeline" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario6: - electrolyzer_location: "platform" # can be one of ["onshore", "turbine", "platform"] - transportation: "none" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "platform" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario7: - electrolyzer_location: "platform" # can be one of ["onshore", "turbine", "platform"] - transportation: "pipeline" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario8: - electrolyzer_location: "platform" # can be one of ["onshore", "turbine", "platform"] - transportation: "hvdc+pipeline" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario9: - electrolyzer_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - transportation: "none" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - wind_location: "onshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario10: - electrolyzer_location: "platform" # can be one of ["onshore", "turbine", "platform"] - transportation: "pipeline" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "platform" # can be one of ["none", "onshore", "platform"] - battery_location: "platform" # can be one of ["none", "onshore", "platform"] - scenario11: - electrolyzer_location: "platform" # can be one of ["onshore", "turbine", "platform"] - transportation: "pipeline" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "platform" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - -lca_config: - run_lca: False #True - electrolyzer_type: pem #alkaline, soec - feedstock_water_type: ground #desal, surface - cambium: #cambium API argument, see cambium_data.py for additional argument options - project_uuid: '0f92fe57-3365-428a-8fe8-0afc326b3b43' - scenario: 'Mid-case with 100% decarbonization by 2035' - location_type: 'GEA Regions 2023' - time_type: 'hourly' - -opt_options: - opt_flag: True - general: - folder_output: "output" - fname_output: "test_run_h2integrate_optimization" - design_variables: - electrolyzer_rating_kw: - flag: False - lower: 1000.0 - upper: 400000.0 - units: "kW" - pv_capacity_kw: - flag: False - lower: 1000.0 - upper: 10000.0 - units: "kW" - wave_capacity_kw: - flag: False - lower: 1000.0 - upper: 1500000.0 - units: "kW*h" - battery_capacity_kw: - flag: False - lower: 1000.0 - upper: 10000.0 - units: "kW" - battery_capacity_kwh: - flag: False - lower: 1000.0 - upper: 10000.0 - units: "kW*h" - turbine_x: - flag: False - lower: 0.0 - upper: 20000.0 - units: "m" - turbine_y: - flag: False - lower: 0.0 - upper: 20000.0 - units: "m" - constraints: - turbine_spacing: - flag: False - lower: 0.0 - boundary_distance: - flag: False - lower: 0.0 - pv_to_platform_area_ratio: - flag: True - upper: 1.0 # relative size of solar pv area to platform area - user: {} - merit_figure: "lcoh" - merit_figure_user: - name: "lcoh" - max_flag: False - ref: 1.0 # value of objective that scales to 1.0 - driver: - optimization: - flag: True - solver: "SNOPT" - tol: 1E-6 - max_major_iter: 50 - max_minor_iter: 500 - # time_limit: 10 # (sec) optional - # "hist_file_name: "snopt_history.txt", # optional - verify_level: 0 # optional - step_calc: None - form: "central" # type of finite differences to use, can be one of ["forward", "backward", "central"] - debug_print: False - gradient_method: "openmdao" - step_size_study: - flag: False - design_of_experiments: - flag: False - run_parallel: False - generator: FullFact # [Uniform, FullFact, PlackettBurman, BoxBehnken, LatinHypercube] - num_samples: 5 # Number of samples to evaluate model at (Uniform and LatinHypercube only) - seed: 2 - levels: 5 # Number of evenly spaced levels between each design variable lower and upper bound (FullFactorial only) - criterion: None # [None, center, c, maximin, m, centermaximin, cm, correelation, corr] - iterations: 1 - debug_print: False - recorder: - flag: True - file_name: "recorder_doe_pv.sql" - includes: ["lcoe", "platform_area", "pv_area", "pv_platform_ratio"] diff --git a/tests/h2integrate/input_files/plant/h2integrate_config_onshore.yaml b/tests/h2integrate/input_files/plant/h2integrate_config_onshore.yaml deleted file mode 100644 index 3413cf6cb..000000000 --- a/tests/h2integrate/input_files/plant/h2integrate_config_onshore.yaml +++ /dev/null @@ -1,368 +0,0 @@ -site: - mean_windspeed: False - depth: 45 #m - wind_layout: - row_spacing: 7 # Also defined in ORBIT config for offshore layout. H2Integrate config values override the values in ORBIT. - turbine_spacing: 7 # Also defined in ORBIT config for offshore layout. H2Integrate config values override the values in ORBIT. - grid_angle: 0 # wind layout grid angle in degrees where 0 is north, increasing clockwise - row_phase_offset: 0 # wind layout offset of turbines along row from one row to the next -project_parameters: - project_lifetime: 30 - grid_connection: False # option, can be turned on or off - ppa_price: 0.025 # $/kWh based on 2022 land based wind market report (ERCOT area ppa prices) https://www.energy.gov/sites/default/files/2022-08/land_based_wind_market_report_2202.pdf - hybrid_electricity_estimated_cf: 0.492 #should equal 1 if grid_connection = True - financial_analysis_start_year: 2027 - cost_year: 2022 # to match ATB - installation_time: 36 # months -finance_parameters: - costing_general_inflation: 0.025 # used to adjust modeled costs to cost_year - inflation_rate: 0.025 # based on 2022 ATB - discount_rate: 0.10 # nominal return based on 2022 ATB basline workbook - debt_equity_split: 68.5 # 2022 ATB uses 68.5% debt - property_tax: 0.01 # percent of CAPEX # combined with property insurance then between H2A and H2FAST defaults - property_insurance: 0.005 # percent of CAPEX # combined with property tax then between H2A and H2FAST defaults - total_income_tax_rate: 0.257 # 0.257 tax rate in 2022 atb baseline workbook # current federal income tax rate, but proposed 2023 rate is 0.28. No state income tax in Texas - capital_gains_tax_rate: 0.15 # H2FAST default - sales_tax_rate: 0.0 #Verify that a different rate shouldn't be used # minimum total sales tax rate in Corpus Christi https://www.cctexas.com/detail/corpus-christi-type-fund-purpose - does this apply to H2? - debt_interest_rate: 0.06 - 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: 0 # H2FAST default, not used for revolving debt - cash_onhand_months: 1 # H2FAST default - administrative_expense_percent_of_sales: 0.00 #Check this # percent of sales H2FAST default - depreciation_method: "MACRS" # can be "MACRS" or "Straight line" - MACRS may be better and can reduce LCOH by more than $1/kg and is spec'd in the IRS MACRS schedule https://www.irs.gov/publications/p946#en_US_2020_publink1000107507 - depreciation_period: 5 # years - as specified by the IRS MACRS schedule https://www.irs.gov/publications/p946#en_US_2020_publink1000107507 - depreciation_period_electrolyzer: 7 # based on PEM Electrolysis H2A Production Case Study Documentation estimate of 7 years. also see https://www.irs.gov/publications/p946#en_US_2020_publink1000107507 - discount_years: - wind: 2022 # based on turbine capex value provided to ORBIT from 2022 ATB - wind_and_electrical: 2022 # for ORBIT opex - wave: 2020 # confirmed by Kaitlin Brunik 20240103 - solar: 2022 # TODO check - battery: 2022 # TODO check - platform: 2022 # TODO ask Nick and Charlie - electrical_export_system: 2022 # also from ORBIT, so match wind assumptions. TODO ask Sophie Bradenkamp - desal: 2013 # from code citation: https://www.nrel.gov/docs/fy16osti/66073.pdf - electrolyzer: 2020 # 2020 for singlitico2021, 2016 # for simple h2 cost model in hopp (see https://www.hydrogen.energy.gov/pdfs/19009_h2_production_cost_pem_electrolysis_2019.pdf) ## 2020 # based on IRENA report https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2020/Dec/IRENA_Green_hydrogen_cost_2020.pdf - h2_transport_compressor: 2016 # listed in code header - h2_storage: - pressure_vessel: 2022 # based on readme for Compressed_gas_function - pipe: 2019 # Papadias 2021 - salt_cavern: 2019 # Papadias 2021 - turbine: 2003 # assumed based on Kottenstette 2004 - lined_rock_cavern: 2018 # based on Papadias 2021 and HD SAM - none: 2022 # arbitrary - h2_pipe_array: 2018 # ANL costs - h2_transport_pipeline: 2018 # same model for costs as the h2_pipe_array - wind: - expected_plant_cost: 'none' -electrolyzer: - sizing: - resize_for_enduse: False - size_for: 'BOL' #'BOL' (generous) or 'EOL' (conservative) - hydrogen_dmd: - rating: 180 # MW # 0.9*Plant rating appears near-optimal for 400 MW wind plant with 3 days of underground pipe storage # MW - cluster_rating_MW: 180 - pem_control_type: 'basic' - eol_eff_percent_loss: 10 #eol defined as x% change in efficiency from bol - uptime_hours_until_eol: 77600 #number of 'on' hours until electrolyzer reaches eol - include_degradation_penalty: True #include degradation - turndown_ratio: 0.1 #turndown_ratio = minimum_cluster_power/cluster_rating_MW - electrolyzer_capex: 700 # $/kW conservative 2025 centralized. high 700, low 300 # based on https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2020/Dec/IRENA_Green_hydrogen_cost_2020.pdf - replacement_cost_percent: 0.15 # percent of capex - H2A default case - cost_model: "singlitico2021" # "basic" is a basic cost model based on H2a and HFTO program record for PEM electrolysis. "singlitico2021" uses cost estimates from that paper - -h2_transport_compressor: - outlet_pressure: 68 # bar based on HDSAM -h2_storage_compressor: - output_pressure: 100 # bar (1 bar = 100 kPa) - flow_rate: 89 # kg/hr - energy_rating: 802 # kWe (aka 1 kWh) - mean_days_between_failures: 200 # days - # annual_h2_throughput: 18750 # [kg/yr] -> kg of H2 per year -h2_transport_pipe: - outlet_pressure: 10 # bar - from example in code from Jamie #TODO check this value -h2_storage: - size_capacity_from_demand: - flag: False # If True, then storage is sized to provide steady-state storage - capacity_from_max_on_turbine_storage: False # if True, then days of storage is ignored and storage capacity is based on how much h2 storage fits on the turbines in the plant using Kottenstete 2003. - type: "none" # can be one of ["none", "pipe", "turbine", "pressure_vessel", "salt_cavern", "lined_rock_cavern"] - days: 3 # [days] how many days worth of production we should be able to store (this is ignored if `capacity_from_max_on_turbine_storage` is set to True) - -policy_parameters: # these should be adjusted for inflation prior to application - order of operations: rate in 1992 $, - #then prevailing wage multiplier if applicable, then inflation - option1: # base # no policy included ---> see files/task1/regulation and policy revue/ page 4 of 13 middle - read this - # and look at assumptions - electricity_itc: 0 - electricity_ptc: 0 - h2_ptc: 0 - h2_storage_itc: 0 - option2: # base credit levels with H2 - electricity_itc: 0 - electricity_ptc: 0.003 # $0.003/kW (this is base, see inflation adjustment in option 3) - h2_ptc: 0.6 # $0.60/kg h2 produced - assumes net zero but not meeting prevailing wage requirements - does this need to be - # adjusted for inflation from 2022 dollars to claim date, probably constant after claim date? - h2_storage_itc: 0.06 - option3: # same as option 5, but assuming prevailing wages are met --> 5x multiplier on both PTCs - electricity_itc: 0 - electricity_ptc: 0.015 # $/kWh 1992 dollars - h2_ptc: 3.00 # $/kg 2022 dollars - do not adjust for inflation - h2_storage_itc: 0.3 - # bonus options, option 5 and 6 but ITC equivalents - option4: # prevailing wages not met - electricity_itc: 0.06 # %/100 capex - electricity_ptc: 0.00 # $/kW 1992 dollars - h2_ptc: 0.6 # $0.60/kg produced 2022 dollars - assumes net zero but not meeting prevailing wage requirements - does this need to be - # do not adjust for inflation, probably constant after claim date? - h2_storage_itc: 0.06 - option5: # prevailing wages met - electricity_itc: 0.30 # %/100 capex - electricity_ptc: 0.0 # $/kWh 1992 dollars - h2_ptc: 3.00 # $/kg of h2 produced 2022 dollars - do adjust for inflation every year applied and until application year - h2_storage_itc: 0.3 - option6: # assumes prevailing wages are met, and includes 10% bonus credit of domestic content (100% of steel and iron - # and mfg. components from the US) - electricity_itc: 0.40 # %/100 capex - electricity_ptc: 0.0 # $/kWh 1992 dollars - h2_ptc: 3.00 # $/kg of h2 produced 2022 dollars - do adjust for inflation every year applied and until application year - h2_storage_itc: 0.4 - option7: # assumes prevailing wages are met, and includes 10% bonus credit of domestic content (100% of steel and iron - # and mfg. components from the US) - electricity_itc: 0.0 # %/100 capex - electricity_ptc: 0.0165 # $/kWh 1992 dollars (0.015*1.1) - h2_ptc: 3.00 # $/kg of h2 produced 2022 dollars - do adjust for inflation every year applied and until application year - # you can elect itc_for_h2 in leu of the h2_ptc - this choice is independent of the other tech credit selections - # 6% or %50 for itc_for_h2 - h2_storage_itc: 0.5 - -plant_design: - scenario0: - electrolyzer_location: "platform" # can be one of ["onshore", "turbine", "platform"] - transportation: "pipeline" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "platform" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario1: - electrolyzer_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - transportation: "hvdc" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario2: - electrolyzer_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - transportation: "hvdc" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "platform" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario3: - electrolyzer_location: "turbine" # can be one of ["onshore", "turbine", "platform"] - transportation: "none" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "turbine" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario4: - electrolyzer_location: "turbine" # can be one of ["onshore", "turbine", "platform"] - transportation: "none" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "platform" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario5: - electrolyzer_location: "turbine" # can be one of ["onshore", "turbine", "platform"] - transportation: "pipeline" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario6: - electrolyzer_location: "platform" # can be one of ["onshore", "turbine", "platform"] - transportation: "none" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "platform" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario7: - electrolyzer_location: "platform" # can be one of ["onshore", "turbine", "platform"] - transportation: "pipeline" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario8: - electrolyzer_location: "platform" # can be one of ["onshore", "turbine", "platform"] - transportation: "hvdc+pipeline" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - scenario9: - electrolyzer_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - transportation: "none" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - wind_location: "onshore" # can be one of ["onshore", "offshore"] - pv_location: "none" # can be one of ["none", "onshore", "platform"] - battery_location: "none" # can be one of ["none", "onshore", "platform"] - scenario10: - electrolyzer_location: "platform" # can be one of ["onshore", "turbine", "platform"] - transportation: "pipeline" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - pv_location: "platform" # can be one of ["none", "onshore", "platform"] - battery_location: "platform" # can be one of ["none", "onshore", "platform"] - wind_location: "offshore" # can be one of ["onshore", "offshore"] - scenario11: - electrolyzer_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - transportation: "none" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - wind_location: "onshore" # can be one of ["onshore", "offshore"] - pv_location: "onshore" # can be one of ["none", "onshore", "platform"] - battery_location: "onshore" # can be one of ["none", "onshore", "platform"] - scenario12: - electrolyzer_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - transportation: "none" # can be one of ["hvdc", "pipeline", "none", hvdc+pipeline, "colocated"] - h2_storage_location: "onshore" # can be one of ["onshore", "turbine", "platform"] - wind_location: "onshore" # can be one of ["onshore", "offshore"] - pv_location: "onshore" # can be one of ["none", "onshore", "platform"] - battery_location: "onshore" # can be one of ["none", "onshore", "platform"] - -steel: - capacity: - input_capacity_factor_estimate: 0.9 - costs: - operational_year: 2035 - o2_heat_integration: false - feedstocks: - oxygen_market_price: 0.03 - natural_gas_prices: - "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 - finances: - plant_life: 30 - 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 - - # Additional parameters passed to ProFAST - 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 - -ammonia: - capacity: - input_capacity_factor_estimate: 0.9 - costs: - feedstocks: - electricity_cost: 89.42320514456621 - hydrogen_cost: 4.2986685034417045 - cooling_water_cost: 0.00291 - iron_based_catalyst_cost: 23.19977341 - oxygen_cost: 0 - finances: - plant_life: 30 - 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 - - # Additional parameters passed to ProFAST - 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 - -lca_config: - run_lca: False #True - electrolyzer_type: pem #alkaline, soec - feedstock_water_type: ground #desal, surface - cambium: #cambium API argument, see cambium_data.py for additional argument options - project_uuid: '0f92fe57-3365-428a-8fe8-0afc326b3b43' - scenario: 'Mid-case with 100% decarbonization by 2035' - location_type: 'GEA Regions 2023' - time_type: 'hourly' diff --git a/tests/h2integrate/test_hydrogen/test_PEM_costs_custom.py b/tests/h2integrate/test_hydrogen/test_PEM_costs_custom.py deleted file mode 100644 index 675fe5248..000000000 --- a/tests/h2integrate/test_hydrogen/test_PEM_costs_custom.py +++ /dev/null @@ -1,24 +0,0 @@ -from pytest import approx - -from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_costs_custom import ( - calc_custom_electrolysis_capex_fom, -) - - -TOL = 1e-6 - -electrolyzer_size_MW = 1 -electrolyzer_size_kW = electrolyzer_size_MW * 1e3 -fom_usd_pr_kW = 10.0 -capex_usd_pr_kW = 15.0 -elec_config = {"electrolyzer_capex": capex_usd_pr_kW, "fixed_om_per_kw": fom_usd_pr_kW} - - -def test_custom_capex(): - capex, fom = calc_custom_electrolysis_capex_fom(electrolyzer_size_kW, elec_config) - assert capex == approx(capex_usd_pr_kW * electrolyzer_size_kW, TOL) - - -def test_custom_fixed_om(): - capex, fom = calc_custom_electrolysis_capex_fom(electrolyzer_size_kW, elec_config) - assert fom == approx(fom_usd_pr_kW * electrolyzer_size_kW, TOL) diff --git a/tests/h2integrate/test_hydrogen/test_pressure_vessel.py b/tests/h2integrate/test_hydrogen/test_pressure_vessel.py deleted file mode 100644 index 25de53872..000000000 --- a/tests/h2integrate/test_hydrogen/test_pressure_vessel.py +++ /dev/null @@ -1,317 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt -from pytest import approx - -from h2integrate.simulation.technologies.hydrogen.h2_storage.pressure_vessel.compressed_gas_storage_model_20221021.Compressed_all import ( # noqa: E501 - PressureVessel, -) - - -# test that we the results we got when the code was recieved -class TestPressureVessel: - # pressure_vessel_instance_no_cost = PressureVessel(Energy_cost=0.0) - # pressure_vessel_instance_no_cost.run() - pressure_vessel_instance = PressureVessel(Energy_cost=0.07) - pressure_vessel_instance.run() - - pressure_vessel_instance_no_cost = PressureVessel(Energy_cost=0.0) - pressure_vessel_instance_no_cost.run() - - def test_capacity_max(self): - assert self.pressure_vessel_instance.capacity_max == 5585222.222222222 - - def test_t_discharge_hr_max(self): - assert self.pressure_vessel_instance.t_discharge_hr_max == 25.133499999999998 - - def test_a_fit_capex(self): - # assert self.pressure_vessel_instance.a_fit_capex == 9084.035219940572 - assert self.pressure_vessel_instance.a_fit_capex == approx(0.053925726563169414) - - def test_b_fit_capex(self): - # assert self.pressure_vessel_instance.b_fit_capex == -0.127478041731842 - assert self.pressure_vessel_instance.b_fit_capex == approx(1.6826965840450498) - - def test_c_fit_capex(self): - assert self.pressure_vessel_instance.c_fit_capex == approx(20.297862568544417) - - def test_a_fit_opex(self): - assert self.pressure_vessel_instance.a_fit_opex == approx(0.05900068374896024) - - def test_b_fit_opex(self): - assert self.pressure_vessel_instance.b_fit_opex == approx(1.8431485607717895) - - def test_c_fit_opex(self): - assert self.pressure_vessel_instance.c_fit_opex == approx(17.538017086792006) - - def test_energy_fit(self): - capacity = 1e6 # 1000 metric tons h2 - _, _, energy_per_kg = self.pressure_vessel_instance.calculate_from_fit(capacity_kg=capacity) - assert energy_per_kg == approx(2.688696, 1e-5) # kWh/kg - - def test_compare_price_change_capex(self): - capacity = 1e6 # 1000 metric tons h2 - capex_07, _, _ = self.pressure_vessel_instance.calculate_from_fit(capacity_kg=capacity) - capex_00, _, _ = self.pressure_vessel_instance_no_cost.calculate_from_fit( - capacity_kg=capacity - ) - - assert capex_00 == capex_07 - - def test_compare_price_change_opex(self): - capacity = 1e6 # 1000 metric tons h2 - _, opex_07, _ = self.pressure_vessel_instance.calculate_from_fit(capacity_kg=capacity) - _, opex_00, _ = self.pressure_vessel_instance_no_cost.calculate_from_fit( - capacity_kg=capacity - ) - - assert opex_00 < opex_07 - - def test_compare_price_change_energy(self): - capacity = 1e6 # 1000 metric tons h2 - _, _, energy_per_kg_07 = self.pressure_vessel_instance.calculate_from_fit( - capacity_kg=capacity - ) - _, _, energy_per_kg_00 = self.pressure_vessel_instance_no_cost.calculate_from_fit( - capacity_kg=capacity - ) - - assert energy_per_kg_00 == energy_per_kg_07 - - def test_mass_footprint(self): - """ - extension of gold standard test to new tank footprint outputs - """ - cap_H2_tank_ref = 179.6322517351785 - capacity_req = 1.2e3 - Ntank_ref = np.ceil(capacity_req / cap_H2_tank_ref) - Atank_ref = (2 * 53.7e-2) ** 2 - footprint_ref = Ntank_ref * Atank_ref - mass_ref = 1865.0 - - assert self.pressure_vessel_instance.get_tanks(capacity_req) == approx(Ntank_ref) - assert self.pressure_vessel_instance.get_tank_footprint(capacity_req)[0] == approx( - Atank_ref, rel=0.01 - ) - assert self.pressure_vessel_instance.get_tank_footprint(capacity_req)[1] == approx( - footprint_ref, rel=0.01 - ) - - assert self.pressure_vessel_instance.get_tank_mass(capacity_req)[0] == approx( - mass_ref, rel=0.01 - ) - - def test_output_function(self): - capacity = self.pressure_vessel_instance.compressed_gas_function.capacity_1[5] - capex, opex, energy = self.pressure_vessel_instance.calculate_from_fit(capacity) - tol = 1.0 - - assert capex == approx( - self.pressure_vessel_instance.compressed_gas_function.cost_kg[5] * capacity, - tol, - ) - assert opex == approx( - self.pressure_vessel_instance.compressed_gas_function.Op_c_Costs_kg[5] * capacity, - tol, - ) - assert energy == approx( - self.pressure_vessel_instance.compressed_gas_function.total_energy_used_kwh[5] - * capacity, - tol, - ) - - def test_distributed(self): - capacity = self.pressure_vessel_instance.compressed_gas_function.capacity_1[5] - capex, opex, energy_kg = self.pressure_vessel_instance.calculate_from_fit(capacity) - print(capex) - self.pressure_vessel_instance.get_tank_mass(capacity) - self.pressure_vessel_instance.get_tank_footprint(capacity) - - ( - capex_dist_05, - opex_dist_05, - energy_kg_dist_05, - area_footprint_site_05, - mass_tank_empty_site_05, - capacity_site_05, - ) = self.pressure_vessel_instance.distributed_storage_vessels(capacity, 5) - assert capex_dist_05 == approx(6205232868.4722595) - assert opex_dist_05 == approx(113433768.86938927) - assert energy_kg_dist_05 == approx(2.6886965443907727) - assert area_footprint_site_05 == approx(4866.189496204457) - assert mass_tank_empty_site_05 == approx(7870274.025926539) - assert capacity_site_05 == approx(capacity / 5) - - ( - capex_dist_10, - opex_dist_10, - energy_kg_dist_10, - area_footprint_site_10, - mass_tank_empty_site_10, - capacity_site_10, - ) = self.pressure_vessel_instance.distributed_storage_vessels(capacity, 10) - assert capex_dist_10 == approx(7430302244.729572) - assert opex_dist_10 == approx(138351814.3102437) - assert energy_kg_dist_10 == approx(2.6886965443907727) - assert area_footprint_site_10 == approx(2433.0947481022286) - assert mass_tank_empty_site_10 == approx(3935137.0129632694) - assert capacity_site_10 == approx(capacity / 10) - - ( - capex_dist_20, - opex_dist_20, - energy_kg_dist_20, - area_footprint_site_20, - mass_tank_empty_site_20, - capacity_site_20, - ) = self.pressure_vessel_instance.distributed_storage_vessels(capacity, 20) - assert capex_dist_20 == approx(9370417735.496975) - assert opex_dist_20 == approx(178586780.2083488) - assert energy_kg_dist_20 == approx(2.6886965443907727) - assert area_footprint_site_20 == approx(1216.5473740511143) - assert mass_tank_empty_site_20 == approx(1967568.5064816347) - assert capacity_site_20 == approx(capacity / 20) - - assert ( - (capex < capex_dist_05) - and (capex_dist_05 < capex_dist_10) - and (capex_dist_10 < capex_dist_20) - ), "capex should increase w/ number of sites" - assert ( - (opex < opex_dist_05) - and (opex_dist_05 < opex_dist_10) - and (opex_dist_10 < opex_dist_20) - ), "opex should increase w/ number of sites" - assert ( - (energy_kg == approx(energy_kg_dist_05)) - and (energy_kg_dist_05 == approx(energy_kg_dist_10)) - and (energy_kg_dist_10 == approx(energy_kg_dist_20)) - ), "energy_kg be approx. equal across number of sites" - - # assert False - - # def test_plots(self): - # self.pressure_vessel_instance.plot() - - -class PlotTestPressureVessel: - def __init__(self) -> None: - self.pressure_vessel_instance = PressureVessel(Energy_cost=0.07) - self.pressure_vessel_instance.run() - - self.pressure_vessel_instance_no_cost = PressureVessel(Energy_cost=0.0) - self.pressure_vessel_instance_no_cost.run() - - def plot_size_and_divisions(self): - # set sweep ranges - capacity_range = np.arange(1e6, 5e6, step=1e3) # kg - divisions = np.array([1, 5, 10, 15, 20]) - - # initialize outputs - capex_results = np.zeros((len(divisions), len(capacity_range))) - opex_results = np.zeros((len(divisions), len(capacity_range))) - - # run capex and opex for range - for ( - j, - div, - ) in enumerate(divisions): - for i, capacity in enumerate(capacity_range): - if div == 1: - capex_results[j, i], opex_results[j, i], energy_kg = ( - self.pressure_vessel_instance.calculate_from_fit(capacity) - ) - else: - ( - capex_results[j, i], - opex_results[j, i], - energy_kg_dist_05, - area_footprint_site_05, - mass_tank_empty_site_05, - capacity_site_05, - ) = self.pressure_vessel_instance.distributed_storage_vessels(capacity, div) - - # plot results - fig, ax = plt.subplots(2, 2, sharex=True) - for j in np.arange(0, len(divisions)): - ax[0, 0].plot( - capacity_range / 1e3, - capex_results[j] / capacity_range, - label=f"{divisions[j]} Divisions", - ) - ax[0, 1].plot( - capacity_range / 1e3, - opex_results[j] / capacity_range, - label=f"{divisions[j]} Divisions", - ) - - # ax[0,0].set(ylabel="CAPEX (USD/kg)", ylim=[0, 5000], yticks=[0,1000,2000,3000,4000,5000]) - ax[0, 1].set(ylabel="OPEX (USD/kg)", ylim=[0, 100]) - - for j in np.arange(0, len(divisions)): - ax[1, 0].plot( - capacity_range / 1e3, - capex_results[j] / capex_results[0], - label=f"{divisions[j]} Divisions", - ) - ax[1, 1].plot( - capacity_range / 1e3, - opex_results[j] / opex_results[0], - label=f"{divisions[j]} Divisions", - ) - - ax[1, 0].set(ylabel="CAPEX_div/CAPEX_cent", ylim=[0, 4]) - ax[1, 1].set(ylabel="OPEX_div/OPEX_cent", ylim=[0, 4]) - - for axi in ax[1]: - axi.set(xlabel="H$_2$ Capacity (metric tons)") - ax[1, 1].legend() - - plt.tight_layout() - - plt.show() - - -if __name__ == "__main__": - # test_set = TestPressureVessel() - plot_tests = PlotTestPressureVessel() - plot_tests.plot_size_and_divisions() - -# 0.0 -# 6322420.744236805 -# 1331189.5844818645 -# 7363353.502353448 - -# 0.07 -# 6322420.744236805 -# 1331189.5844818645 -# 7363353.502353448 - -# energy cost for both cases match as per above - - -# op costs - 0.07 -# 442569.45209657634 -# 345243.94167843653 -# 0 -# 93183.27091373052 - -# op costs - 0.0 -# 0.0 -# 0.0 -# 0 -# 0.0 - -# op c costs -# op_c_costs 0.07 -# 880996.6646887433 -# 799322.4503233839 -# 0.03 -# 4262490675.039804 -# 25920 - -# op_c_costs 0.00 -# 0.0 -# 0.0 -# 0.03 -# 4262490675.039804 -# 25920 diff --git a/tests/h2integrate/test_iron/input/h2integrate_config_modular.yaml b/tests/h2integrate/test_iron/input/h2integrate_config_modular.yaml index 32311e176..e080e847b 100644 --- a/tests/h2integrate/test_iron/input/h2integrate_config_modular.yaml +++ b/tests/h2integrate/test_iron/input/h2integrate_config_modular.yaml @@ -61,7 +61,6 @@ electrolyzer: hydrogen_dmd: rating: 1160 # MW cluster_rating_MW: 40 - pem_control_type: 'basic' eol_eff_percent_loss: 13 #eol defined as x% change in efficiency from bol uptime_hours_until_eol: 77600 #number of 'on' hours until electrolyzer reaches eol include_degradation_penalty: True #include degradation From 71ba00f33c02c79890f6a4821d301ac09eab62b4 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Wed, 26 Nov 2025 17:42:17 -0700 Subject: [PATCH 08/30] Removed old tests --- .../test_hydrogen/test_RO_desal.py | 72 ---- .../test_hydrogen/test_tankinator.py | 348 ------------------ 2 files changed, 420 deletions(-) delete mode 100644 tests/h2integrate/test_hydrogen/test_RO_desal.py delete mode 100644 tests/h2integrate/test_hydrogen/test_tankinator.py diff --git a/tests/h2integrate/test_hydrogen/test_RO_desal.py b/tests/h2integrate/test_hydrogen/test_RO_desal.py deleted file mode 100644 index 218284732..000000000 --- a/tests/h2integrate/test_hydrogen/test_RO_desal.py +++ /dev/null @@ -1,72 +0,0 @@ -from pytest import approx - -from h2integrate.simulation.technologies.hydrogen.desal.desal_model_eco import RO_desal_eco - - -# Test values are based on hand calculations - - -class TestRODesal: - rel_tol = 1e-2 - - freshwater_needed = 10000 # [kg/hr] - - saltwater = RO_desal_eco(freshwater_needed, "Seawater") - brackish = RO_desal_eco(freshwater_needed, "Brackish") - - def test_capacity_m3_per_hr(self): - assert self.saltwater[0] == approx(10.03, rel=1e-5) - assert self.brackish[0] == approx(10.03, rel=1e-5) - - def test_feedwater(self): - assert self.saltwater[1] == approx(20.06, rel=1e-5) - assert self.brackish[1] == approx(13.37, rel=1e-3) - - def test_power(self): - assert self.saltwater[2] == approx(40.12, rel=1e-5) - assert self.brackish[2] == approx(15.04, rel=1e-3) - - def test_capex(self): - assert self.saltwater[3] == approx(91372, rel=1e-2) - assert self.brackish[3] == approx(91372, rel=1e-2) - - def test_opex(self): - assert self.saltwater[4] == approx(13447, rel=1e-2) - assert self.brackish[4] == approx(13447, rel=1e-2) - - def test_RO_Desal_Seawater(self): - """Test Seawater RO Model""" - outputs = RO_desal_eco(freshwater_kg_per_hr=997, salinity="Seawater") - RO_desal_mass = outputs[5] - RO_desal_footprint = outputs[6] - assert approx(RO_desal_mass) == 346.7 - assert approx(RO_desal_footprint) == 0.467 - - def test_RO_Desal_distributed(self): - """Test Seawater RO Model""" - n_systems = 2 - total_freshwater_kg_per_hr_required = 997 - per_system_freshwater_kg_per_hr_required = total_freshwater_kg_per_hr_required / n_systems - - total_outputs = RO_desal_eco( - freshwater_kg_per_hr=total_freshwater_kg_per_hr_required, - salinity="Seawater", - ) - per_system_outputs = RO_desal_eco( - per_system_freshwater_kg_per_hr_required, salinity="Seawater" - ) - - for t, s in zip(total_outputs, per_system_outputs): - assert t == approx(s * n_systems) - - def test_RO_Desal_Brackish(self): - """Test Brackish Model""" - outputs = RO_desal_eco(freshwater_kg_per_hr=997, salinity="Brackish") - RO_desal_mass = outputs[5] - RO_desal_footprint = outputs[6] - assert approx(RO_desal_mass) == 346.7 - assert approx(RO_desal_footprint) == 0.467 - - -if __name__ == "__main__": - test_set = TestRODesal() diff --git a/tests/h2integrate/test_hydrogen/test_tankinator.py b/tests/h2integrate/test_hydrogen/test_tankinator.py deleted file mode 100644 index a8913a960..000000000 --- a/tests/h2integrate/test_hydrogen/test_tankinator.py +++ /dev/null @@ -1,348 +0,0 @@ -import numpy as np -import pytest - -from h2integrate.simulation.technologies.hydrogen.h2_storage.pressure_vessel import von_mises -from h2integrate.simulation.technologies.hydrogen.h2_storage.pressure_vessel.tankinator import ( - Tank, - TypeITank, - TypeIVTank, - TypeIIITank, -) - - -# test that we the results we got when the code was recieved -class TestTankinator: - def test_tank_type(self): - tank1 = TypeITank("6061_T6_Aluminum") - tank3 = TypeIIITank() - tank4 = TypeIVTank() - - assert tank1.tank_type == 1 - assert tank3.tank_type == 3 - assert tank4.tank_type == 4 - - # def test_tank_type_exc(self): - # with pytest.raises(TypeError): - # TypeITank(2) - - def test_hemicylindrical_static(self): - """a random sphere, and a random cylinder""" - - # L = 2R; should reduce to a sphere! - radius = 1.5 - length = 2 * radius - thickness = 0.19 - volume_exact = 4.0 / 3.0 * np.pi * radius**3 - - assert Tank.compute_hemicylinder_outer_length(length, thickness) == pytest.approx( - length + 2.0 * thickness - ) - assert Tank.compute_hemicylinder_outer_radius(radius, thickness) == pytest.approx( - radius + thickness - ) - assert Tank.compute_hemicylinder_volume(radius, length) == pytest.approx(volume_exact) - - # a lil bit between sphere halves - radius = 2.1 - length = 6.4 - thickness = 0.02 - volume_exact = 4.0 / 3.0 * np.pi * radius**3 + (length - 2 * radius) * (np.pi * radius**2) - - assert Tank.compute_hemicylinder_outer_length(length, thickness) == pytest.approx( - length + 2.0 * thickness - ) - assert Tank.compute_hemicylinder_outer_radius(radius, thickness) == pytest.approx( - radius + thickness - ) - assert Tank.compute_hemicylinder_volume(radius, length) == pytest.approx(volume_exact) - - def test_tankI_geometric(self): - """make sure geometric calls work correctly""" - - # L = 2R; should reduce to a sphere! - radius = 1.5 - length = 2 * radius - thickness = 0.19 - volume_exact = 4.0 / 3.0 * np.pi * radius**3 - - # create tank, set dimensions, check dimensioning - tank = TypeITank("6061_T6_Aluminum") - tank.set_length_radius(length, radius) - tank.thickness = thickness # manual override - tank.volume_inner = Tank.compute_hemicylinder_volume( - tank.get_radius_inner(), tank.get_length_inner() - ) - - assert tank.get_length_outer() == pytest.approx(length + 2.0 * thickness) - assert tank.get_radius_outer() == pytest.approx(radius + thickness) - assert tank.get_volume_inner() == pytest.approx(volume_exact) - - volume_outer_exact = 4.0 / 3.0 * np.pi * (radius + thickness) ** 3 - dvolume_exact = volume_outer_exact - volume_exact - rho_ref = 0.002663 - cost_ref = 4.45 - mass_exact = dvolume_exact * rho_ref # mass of spherical vessel - cost_exact = mass_exact * cost_ref - - assert tank.get_volume_outer() == pytest.approx(volume_outer_exact) - assert tank.get_volume_metal() == pytest.approx(dvolume_exact) - assert tank.get_mass_metal() == pytest.approx(mass_exact) - assert tank.get_cost_metal() == pytest.approx(cost_exact) - - assert tank.get_gravimetric_tank_efficiency() == pytest.approx( - (volume_exact / 1e3) / mass_exact - ) - - def test_tankI_set_functions(self): - """make sure that the inverse geometry spec works""" - - radius = 4.3 - length = 14.9 - volume_exact = 4.0 / 3.0 * np.pi * radius**3 + (length - 2 * radius) * (np.pi * radius**2) - - tank = Tank(1, "316SS") - tank.set_length_radius(length, radius) - assert tank.get_volume_inner() == pytest.approx(volume_exact) - - tank.length_inner = tank.radius_inner = None # reset - tank.set_length_volume(length, volume_exact) - assert tank.get_radius_inner() == pytest.approx(radius) - - tank.length_inner = tank.radius_inner = None # reset - tank.set_radius_volume(radius, volume_exact) - assert tank.get_length_inner() == pytest.approx(length) - - def test_tankinator_typeI_comp(self): - """compare to the tankinator case""" - - T_op = -50 # degC - p_op = 170 # bar - Ltank = 1000 # cm - Vtank = 2994542 # ccm - - # reference values from the excel sheet default values - R_ref = 31.2 - Sy_ref = 2953.475284 - Su_ref = 3327.58062 - density_ref = 0.002663 - costrate_ref = 4.45 - yield_thickness_ref = 2.69 - ultimate_thickness_ref = 3.586389441300914 - disp_vol_tw_ref = 7.462e5 - mass_tw_ref = 1987.08 - cost_tw_ref = 8842.51 - grav_eff_tw_ref = 1.51 - vmS1_0_ref = 1568.544508 - vmS2_0_ref = 699.2722538 - vmS3_0_ref = -170.0 - vmSproof_0_ref = 2258.435564 - vmSburst_0_ref = 3387.653346 - WTAF_0_ref = 1.018052974 - thickness_1_ref = ultimate_thickness_ref * WTAF_0_ref - WTAF_1_ref = 1.002742019 - thickness_2_ref = thickness_1_ref * WTAF_1_ref - thickness_f_ref = 3.6626944898294997 - mass_f_ref = 2031.9 - cost_f_ref = 9041.80 - - # set up w/ lookup shear approximation - tank = TypeITank("6061_T6_Aluminum", shear_approx="lookup") - tank.set_operating_temperature(T_op) - tank.set_operating_pressure(p_op) - tank.set_length_volume(Ltank, Vtank) - - # check agains reference values - assert tank.get_radius_inner() == pytest.approx(R_ref) - assert tank.material.ultimate_shear_fun(T_op) == pytest.approx(Su_ref) - assert tank.material.yield_shear_fun(T_op) == pytest.approx(Sy_ref) - assert tank.material.density == pytest.approx(density_ref) - assert tank.material.cost_rate == pytest.approx(costrate_ref) - - # check the thinwall calculations - assert tank.get_yield_thickness() == pytest.approx(yield_thickness_ref, abs=0.01) - assert tank.get_ultimate_thickness() == pytest.approx(ultimate_thickness_ref, abs=0.001) - assert tank.get_thickness_thinwall() == pytest.approx(ultimate_thickness_ref, abs=0.001) - - # check the implied geometry if we set the thickness to the thinwall - tank.set_thickness_thinwall() - assert tank.get_volume_metal() == pytest.approx(disp_vol_tw_ref, rel=0.001) - assert tank.get_mass_metal() == pytest.approx(mass_tw_ref, rel=0.001) - assert tank.get_cost_metal() == pytest.approx(cost_tw_ref) - assert tank.get_gravimetric_tank_efficiency() == pytest.approx(grav_eff_tw_ref, abs=0.01) - - # check von Mises analysis variables - assert von_mises.S1(p_op, R_ref + ultimate_thickness_ref, R_ref) == pytest.approx( - vmS1_0_ref - ) - assert von_mises.S2(p_op, R_ref + ultimate_thickness_ref, R_ref) == pytest.approx( - vmS2_0_ref - ) - assert von_mises.S3(p_op, R_ref + ultimate_thickness_ref, R_ref) == pytest.approx( - vmS3_0_ref - ) - vmSproof, vmSburst = von_mises.getPeakStresses(p_op, R_ref + ultimate_thickness_ref, R_ref) - assert vmSproof == pytest.approx(vmSproof_0_ref) - assert vmSburst == pytest.approx(vmSburst_0_ref) - assert not Tank.check_thinwall(R_ref, ultimate_thickness_ref) - assert von_mises.wallThicknessAdjustmentFactor( - p_op, R_ref + ultimate_thickness_ref, R_ref, Sy_ref, Su_ref - ) == pytest.approx(WTAF_0_ref) - - # check cycle iterations, through two - WTAF_0, thickness_1 = von_mises.iterate_thickness( - p_op, R_ref, ultimate_thickness_ref, Sy_ref, Su_ref - ) - assert WTAF_0 == pytest.approx(WTAF_0_ref) - assert thickness_1 == pytest.approx(thickness_1_ref) - - WTAF_1, thickness_2 = von_mises.iterate_thickness(p_op, R_ref, thickness_1, Sy_ref, Su_ref) - assert WTAF_1 == pytest.approx(WTAF_1_ref) - assert thickness_2 == pytest.approx(thickness_2_ref) - - # check final value: cycle three times (no tol) to match tankinator - (thickness_cycle, WTAF_cycle, n_iter) = von_mises.cycle( - p_op, R_ref, ultimate_thickness_ref, Sy_ref, Su_ref, max_iter=3, WTAF_tol=0 - ) - - print(thickness_cycle, WTAF_cycle, n_iter) # DEBUG - assert thickness_cycle == pytest.approx(thickness_f_ref) - - # make sure final calculations are correct - tank.set_thickness_vonmises(p_op, T_op, max_cycle_iter=3, adj_fac_tol=0.0) - assert tank.get_thickness() == pytest.approx(thickness_f_ref) - assert tank.get_mass_metal() == pytest.approx(mass_f_ref, abs=0.1) - assert tank.get_cost_metal() == pytest.approx(cost_f_ref, abs=0.01) - - def test_tankinator_typeIII_comp(self): - """compare to the tankinator case""" - - T_op = 20.0 # degC - p_op = 250.0 # bar - Rtank = 16.0 - Ltank = 1219.0 # cm - Vtank = 971799.0 # ccm - - # reference values from the excel sheet default values, best estimate - R_ref = 16.0 - thickness_liner_ref = 0.61 - thickness_ideal_jacket_ref = 0.602759006 - Nlayer_jacket_ref = 7 - thickness_jacket_ref = 0.64008 - length_liner_ref = 1220.218176 - radius_liner_ref = 16.60908798 - V_outer_liner_ref = 1047.900361 * 1000 - V_liner_ref = 76101.03372 - m_liner_ref = 202.66 - cost_liner_ref = 901.82 - length_outer_ref = 1221.498336 - radius_outer_ref = 17.24916798 - V_outer_ref = 1131.022248 * 1000 - V_jacket_ref = 83121.88687 - m_jacket_ref = 133.91 - cost_jacket_ref = 4104.32 - m_tank_ref = 336.57 - cost_tank_ref = 5006.15 - gravimetric_tank_efficiency_ref = 2.89 - - # set up w/ lookup shear approximation - tank = TypeIIITank() - tank.set_operating_temperature(T_op) - tank.set_operating_pressure(p_op) - tank.set_length_radius(Ltank, Rtank) - - tank.set_thicknesses_thinwall() - - # check against reference values - assert tank.get_radius_inner() == pytest.approx(R_ref) - assert tank.get_volume_inner() == pytest.approx(Vtank) - assert tank.thickness_liner == pytest.approx(thickness_liner_ref, abs=0.01) - assert tank.thickness_ideal_jacket == pytest.approx(thickness_ideal_jacket_ref) - assert tank.Nlayer_jacket == pytest.approx(Nlayer_jacket_ref) - assert tank.thickness_jacket == pytest.approx(thickness_jacket_ref) - - assert tank.get_length_liner() == pytest.approx(length_liner_ref) - assert tank.get_radius_liner() == pytest.approx(radius_liner_ref) - assert tank.get_volume_outer_liner() == pytest.approx(V_outer_liner_ref) - assert tank.get_volume_liner() == pytest.approx(V_liner_ref) - - assert tank.get_length_outer() == pytest.approx(length_outer_ref) - assert tank.get_radius_outer() == pytest.approx(radius_outer_ref) - assert tank.get_volume_outer() == pytest.approx(V_outer_ref) - assert tank.get_volume_jacket() == pytest.approx(V_jacket_ref) - - assert tank.get_mass_liner() == pytest.approx(m_liner_ref, abs=0.01) - assert tank.get_mass_jacket() == pytest.approx(m_jacket_ref, abs=0.01) - assert tank.get_cost_liner() == pytest.approx(cost_liner_ref, abs=0.01) - assert tank.get_cost_jacket() == pytest.approx(cost_jacket_ref, abs=0.01) - assert tank.get_mass_tank() == pytest.approx(m_tank_ref, abs=0.01) - assert tank.get_cost_tank() == pytest.approx(cost_tank_ref, abs=0.01) - assert tank.get_gravimetric_tank_efficiency() == pytest.approx( - gravimetric_tank_efficiency_ref, abs=0.01 - ) - - def test_tankinator_typeIV_comp(self): - """compare to the tankinator case""" - - T_op = 20.0 # degC - p_op = 350.0 # bar - Rtank = 50.0 - Ltank = 1000.0 # cm - Vtank = 7592182.0 # ccm - - # reference values from the excel sheet default values, best estimate - R_ref = 50.0 - thickness_liner_ref = 0.4 - thickness_ideal_jacket_ref = 3.241375931 - Nlayer_jacket_ref = 36 - thickness_jacket_ref = 3.29184 - # length_liner_ref= 1220.218176 - radius_liner_ref = 50.4 - # V_outer_liner_ref= 1047.900361*1000 - # V_liner_ref= 76101.03372 - # m_liner_ref= 202.66 - # cost_liner_ref= 901.82 - # length_outer_ref= 1221.498336 - # radius_outer_ref= 17.24916798 - # V_outer_ref= 1131.022248*1000 - # V_jacket_ref= 83121.88687 - # m_jacket_ref= 133.91 - # cost_jacket_ref= 4104.32 - # m_tank_ref= 336.57 - # cost_tank_ref= 5006.15 - # gravimetric_tank_efficiency_ref= 2.89 - - # set up w/ lookup shear approximation - tank = TypeIVTank() - tank.set_operating_temperature(T_op) - tank.set_operating_pressure(p_op) - tank.set_length_radius(Ltank, Rtank) - - tank.set_thicknesses_thinwall() - - # check against reference values - assert tank.get_radius_inner() == pytest.approx(R_ref) - assert tank.get_volume_inner() == pytest.approx(Vtank) - assert tank.thickness_liner == pytest.approx(thickness_liner_ref, abs=0.01) - assert tank.thickness_ideal_jacket == pytest.approx(thickness_ideal_jacket_ref) - assert tank.Nlayer_jacket == pytest.approx(Nlayer_jacket_ref) - assert tank.thickness_jacket == pytest.approx(thickness_jacket_ref) - - # assert tank.get_length_liner() == pytest.approx(length_liner_ref) - assert tank.get_radius_liner() == pytest.approx(radius_liner_ref) - # assert tank.get_volume_outer_liner() == pytest.approx(V_outer_liner_ref) - # assert tank.get_volume_liner() == pytest.approx(V_liner_ref) - - # assert tank.get_length_outer() == pytest.approx(length_outer_ref) - # assert tank.get_radius_outer() == pytest.approx(radius_outer_ref) - # assert tank.get_volume_outer() == pytest.approx(V_outer_ref) - # assert tank.get_volume_jacket() == pytest.approx(V_jacket_ref) - - # assert tank.get_mass_liner() == pytest.approx(m_liner_ref, abs= 0.01) - # assert tank.get_mass_jacket() == pytest.approx(m_jacket_ref, abs= 0.01) - # assert tank.get_cost_liner() == pytest.approx(cost_liner_ref, abs= 0.01) - # assert tank.get_cost_jacket() == pytest.approx(cost_jacket_ref, abs= 0.01) - # assert tank.get_mass_tank() == pytest.approx(m_tank_ref, abs= 0.01) - # assert tank.get_cost_tank() == pytest.approx(cost_tank_ref, abs= 0.01) - # assert tank.get_gravimetric_tank_efficiency() == pytest.approx( - # gravimetric_tank_efficiency_ref, abs= 0.01 - # ) From 14785ef02a284c48336cd014b4efc8ad1e8d26dc Mon Sep 17 00:00:00 2001 From: John Jasa Date: Thu, 27 Nov 2025 09:26:14 -0700 Subject: [PATCH 09/30] Updated test values --- h2integrate/converters/hydrogen/basic_cost_model.py | 1 - tests/h2integrate/test_all_examples.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/h2integrate/converters/hydrogen/basic_cost_model.py b/h2integrate/converters/hydrogen/basic_cost_model.py index cc7758079..3c728d902 100644 --- a/h2integrate/converters/hydrogen/basic_cost_model.py +++ b/h2integrate/converters/hydrogen/basic_cost_model.py @@ -51,7 +51,6 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # unpack inputs plant_config = self.options["plant_config"] - float(inputs["total_hydrogen_produced"][0]) electrolyzer_size_mw = float(inputs["electrolyzer_size_mw"][0]) useful_life = plant_config["plant"]["plant_life"] electrical_generation_timeseries_kw = inputs["electricity_in"] diff --git a/tests/h2integrate/test_all_examples.py b/tests/h2integrate/test_all_examples.py index afe024a55..1fad90eeb 100644 --- a/tests/h2integrate/test_all_examples.py +++ b/tests/h2integrate/test_all_examples.py @@ -31,7 +31,7 @@ def test_steel_example(subtests): ) with subtests.test("Check LCOS"): - assert pytest.approx(model.prob.get_val("steel.LCOS")[0], rel=1e-3) == 1213.87728644 + assert pytest.approx(model.prob.get_val("steel.LCOS")[0], rel=1e-3) == 1216.29426045 with subtests.test("Check total adjusted CapEx"): assert ( @@ -53,7 +53,7 @@ def test_steel_example(subtests): assert pytest.approx(model.prob.get_val("steel.CapEx"), rel=1e-3) == 5.78060014e08 with subtests.test("Check steel OpEx"): - assert pytest.approx(model.prob.get_val("steel.OpEx"), rel=1e-3) == 1.0129052e08 + assert pytest.approx(model.prob.get_val("steel.OpEx"), rel=1e-3) == 1.0156052e08 def test_simple_ammonia_example(subtests): From 35e88fe35b6250ab984f5864a9018a84db673402 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Thu, 27 Nov 2025 09:46:56 -0700 Subject: [PATCH 10/30] moved singlitico model --- .../adding_a_new_technology.md | 15 +- docs/technology_models/technology_overview.md | 2 - .../converters/hydrogen/pem_electrolyzer.py | 101 --------- .../hydrogen/singlitico_cost_model.py | 77 +++++-- h2integrate/core/supported_models.py | 6 - .../PEM_costs_Singlitico_model.py | 201 ------------------ .../hydrogen/electrolysis/PEM_tools.py | 59 ----- .../test_PEM_costs_Singlitico_model.py | 79 ------- 8 files changed, 70 insertions(+), 470 deletions(-) delete mode 100644 h2integrate/converters/hydrogen/pem_electrolyzer.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_costs_Singlitico_model.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_tools.py delete mode 100644 tests/h2integrate/test_hydrogen/test_PEM_costs_Singlitico_model.py diff --git a/docs/developer_guide/adding_a_new_technology.md b/docs/developer_guide/adding_a_new_technology.md index 15cc7fdd0..053c2e821 100644 --- a/docs/developer_guide/adding_a_new_technology.md +++ b/docs/developer_guide/adding_a_new_technology.md @@ -165,13 +165,14 @@ Here's what the updated `supported_models.py` file looks like with our new solar from h2integrate.converters.solar.solar_pysam import PYSAMSolarPlantPerformanceComponent supported_models = { - 'pysam_solar_plant_performance' : PYSAMSolarPlantPerformanceComponent, - - 'pem_electrolyzer_performance': ElectrolyzerPerformanceModel, - 'pem_electrolyzer_cost': ElectrolyzerCostModel, - - 'eco_pem_electrolyzer_performance': ECOElectrolyzerPerformanceModel, - 'eco_pem_electrolyzer_cost': ECOElectrolyzerCostModel, + "pysam_solar_plant_performance" : PYSAMSolarPlantPerformanceComponent, + + "run_of_river_hydro_performance": RunOfRiverHydroPerformanceModel, + "run_of_river_hydro_cost": RunOfRiverHydroCostModel, + "eco_pem_electrolyzer_performance": ECOElectrolyzerPerformanceModel, + "singlitico_electrolyzer_cost": SingliticoCostModel, + "basic_electrolyzer_cost": BasicElectrolyzerCostModel, + "custom_electrolyzer_cost": CustomElectrolyzerCostModel, ... } diff --git a/docs/technology_models/technology_overview.md b/docs/technology_models/technology_overview.md index 610967676..9133cc66f 100644 --- a/docs/technology_models/technology_overview.md +++ b/docs/technology_models/technology_overview.md @@ -147,10 +147,8 @@ Below summarizes the available performance, cost, and financial models for each - combined performance and cost: + `'wombat'` - performance models: - + `'pem_electrolyzer_performance'` + `'eco_pem_electrolyzer_performance'` - cost models: - + `'pem_electrolyzer_cost'` + `'singlitico_electrolyzer_cost'` + `'basic_electrolyzer_cost'` - `geoh2_well_subsurface`: geologic hydrogen well subsurface diff --git a/h2integrate/converters/hydrogen/pem_electrolyzer.py b/h2integrate/converters/hydrogen/pem_electrolyzer.py deleted file mode 100644 index 23583c325..000000000 --- a/h2integrate/converters/hydrogen/pem_electrolyzer.py +++ /dev/null @@ -1,101 +0,0 @@ -from attrs import field, define - -from h2integrate.core.utilities import BaseConfig, CostModelBaseConfig, merge_shared_inputs -from h2integrate.core.validators import must_equal -from h2integrate.converters.hydrogen.electrolyzer_baseclass import ( - ElectrolyzerCostBaseClass, - ElectrolyzerPerformanceBaseClass, -) -from h2integrate.simulation.technologies.hydrogen.electrolysis import ( - PEM_H2_LT_electrolyzer_Clusters, -) -from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_costs_Singlitico_model import ( - PEMCostsSingliticoModel, -) - - -@define -class ElectrolyzerPerformanceModelConfig(BaseConfig): - cluster_size_mw: float = field() - plant_life: int = field() - eol_eff_percent_loss: float = field() - uptime_hours_until_eol: int = field() - include_degradation_penalty: bool = field() - turndown_ratio: float = field() - - -class ElectrolyzerPerformanceModel(ElectrolyzerPerformanceBaseClass): - """ - An OpenMDAO component that wraps the PEM electrolyzer model. - Takes electricity input and outputs hydrogen and oxygen generation rates. - """ - - def setup(self): - super().setup() - self.config = ElectrolyzerPerformanceModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") - ) - self.electrolyzer = PEM_H2_LT_electrolyzer_Clusters( - self.config.cluster_size_mw, - self.config.plant_life, - self.config.eol_eff_percent_loss, - self.config.uptime_hours_until_eol, - self.config.include_degradation_penalty, - self.config.turndown_ratio, - ) - self.add_input("cluster_size", val=1.0, units="MW") - - def compute(self, inputs, outputs): - # Run the PEM electrolyzer model using the input power signal - self.electrolyzer.max_stacks = inputs["cluster_size"] - h2_results, h2_results_aggregates = self.electrolyzer.run(inputs["electricity_in"]) - - # Assuming `h2_results` includes hydrogen and oxygen rates per timestep - outputs["hydrogen_out"] = h2_results["hydrogen_hourly_production"] - outputs["total_hydrogen_produced"] = h2_results_aggregates["Total H2 Production [kg]"] - - -@define -class ElectrolyzeCostModelConfig(CostModelBaseConfig): - cluster_size_mw: float = field() - electrolyzer_cost: float = field() - cost_year: int = field(default=2021, converter=int, validator=must_equal(2021)) - - -class ElectrolyzerCostModel(ElectrolyzerCostBaseClass): - """ - An OpenMDAO component that computes the cost of a PEM electrolyzer cluster - using PEMCostsSinglicitoModel which outputs costs in 2021 USD. - """ - - def setup(self): - self.cost_model = PEMCostsSingliticoModel(elec_location=1) - # Define inputs: electrolyzer capacity and reference cost - self.config = ElectrolyzeCostModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") - ) - super().setup() - - self.add_input( - "P_elec", - val=self.config.cluster_size_mw, - units="MW", - desc="Nominal capacity of the electrolyzer", - ) - self.add_input( - "RC_elec", - val=self.config.electrolyzer_cost, - units="MUSD/GW", - desc="Reference cost of the electrolyzer", - ) - - def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - # Call the cost model to compute costs - P_elec = inputs["P_elec"] * 1.0e-3 # Convert MW to GW - RC_elec = inputs["RC_elec"] - - cost_model = self.cost_model - capex, opex = cost_model.run(P_elec, RC_elec) - - outputs["CapEx"] = capex * 1.0e-6 # Convert to MUSD - outputs["OpEx"] = opex * 1.0e-6 # Convert to MUSD diff --git a/h2integrate/converters/hydrogen/singlitico_cost_model.py b/h2integrate/converters/hydrogen/singlitico_cost_model.py index 03ebff382..d668e8e18 100644 --- a/h2integrate/converters/hydrogen/singlitico_cost_model.py +++ b/h2integrate/converters/hydrogen/singlitico_cost_model.py @@ -3,9 +3,6 @@ from h2integrate.core.utilities import CostModelBaseConfig, merge_shared_inputs from h2integrate.core.validators import contains, must_equal from h2integrate.converters.hydrogen.electrolyzer_baseclass import ElectrolyzerCostBaseClass -from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_costs_Singlitico_model import ( - PEMCostsSingliticoModel, -) @define @@ -46,28 +43,78 @@ def setup(self): ) def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - electrolyzer_size_mw = inputs["electrolyzer_size_mw"][0] + electrolyzer_size_mw = float(inputs["electrolyzer_size_mw"][0]) # run hydrogen production cost model - from hopp examples if self.config.location == "onshore": - offshore = 0 + elec_location = 0 else: - offshore = 1 + elec_location = 1 P_elec = electrolyzer_size_mw * 1e-3 # [GW] RC_elec = self.config.electrolyzer_capex # [USD/kW] - pem_offshore = PEMCostsSingliticoModel(elec_location=offshore) + # PEM costs based on Singlitico et al. 2021 + # Values for CapEX & OpEx taken from paper, Table B.2, PEMEL. + # Installation costs include land, contingency, contractors, legal fees, construction, + # engineering, yard improvements, buildings, electrics, piping, instrumentation, + # and installation and grid connection. + IF = 0.33 # installation fraction [% RC_elec] + RP_elec = 10 # reference power [MW] + + # Choose the scale factor based on electrolyzer size + if P_elec < 10 / 10**3: + SF_elec = -0.21 # scale factor, -0.21 for <10MW, -0.14 for >10MW + else: + SF_elec = -0.14 # scale factor, -0.21 for <10MW, -0.14 for >10MW + + # If electrolyzer capacity is >100MW, fix unit cost to 100MW electrolyzer as economies of + # scale stop at sizes above this, according to assumption in paper. + if P_elec > 100 / 10**3: + P_elec_cost_per_unit_calc = 0.1 + else: + P_elec_cost_per_unit_calc = P_elec + + # Calculate CapEx for a single electrolyzer + # Return the cost of a single electrolyzer of the specified capacity in millions of USD + # MUSD = GW * MUSD/GW * - * GW * MW/GW / MW ** - + capex_musd = ( + P_elec_cost_per_unit_calc + * RC_elec + * (1 + IF * elec_location) + * ((P_elec_cost_per_unit_calc * 10**3 / RP_elec) ** SF_elec) + ) + capex_per_unit = capex_musd / P_elec_cost_per_unit_calc + electrolyzer_capital_cost_musd = capex_per_unit * P_elec + + # Calculate OpEx for a single electrolyzer + # If electrolyzer capacity is >100MW, fix unit cost to 100MW electrolyzer + P_elec_opex = P_elec + if P_elec > 100 / 10**3: + P_elec_opex = 0.1 + + # Including material cost for planned and unplanned maintenance, labor cost in central + # Europe, which all depend on a system scale. Excluding the cost of electricity and the + # stack replacement, calculated separately. + # MUSD*MW MUSD * - * - * GW * MW/GW + opex_elec_eq = ( + electrolyzer_capital_cost_musd + * (1 - IF * (1 + elec_location)) + * 0.0344 + * (P_elec_opex * 10**3) ** -0.155 + ) + + # Covers the other operational expenditure related to the facility level. This includes site + # management, land rent and taxes, administrative fees (insurance, legal fees...), and site + # maintenance. + # MUSD MUSD + opex_elec_neq = 0.04 * electrolyzer_capital_cost_musd * IF * (1 + elec_location) - ( - electrolyzer_capital_cost_musd, - electrolyzer_om_cost_musd, - ) = pem_offshore.run(P_elec, RC_elec) + electrolyzer_om_cost_musd = opex_elec_eq + opex_elec_neq - electrolyzer_total_capital_cost = ( - electrolyzer_capital_cost_musd * 1e6 - ) # convert from M USD to USD - electrolyzer_OM_cost = electrolyzer_om_cost_musd * 1e6 # convert from M USD to USD + # Convert from M USD to USD + electrolyzer_total_capital_cost = electrolyzer_capital_cost_musd * 1e6 + electrolyzer_OM_cost = electrolyzer_om_cost_musd * 1e6 outputs["CapEx"] = electrolyzer_total_capital_cost outputs["OpEx"] = electrolyzer_OM_cost diff --git a/h2integrate/core/supported_models.py b/h2integrate/core/supported_models.py index 264ce03f6..4fc848e72 100644 --- a/h2integrate/core/supported_models.py +++ b/h2integrate/core/supported_models.py @@ -53,10 +53,6 @@ ReverseOsmosisPerformanceModel, ) from h2integrate.converters.hydrogen.basic_cost_model import BasicElectrolyzerCostModel -from h2integrate.converters.hydrogen.pem_electrolyzer import ( - ElectrolyzerCostModel, - ElectrolyzerPerformanceModel, -) from h2integrate.converters.solar.atb_res_com_pv_cost import ATBResComPVCostModel from h2integrate.converters.solar.atb_utility_pv_cost import ATBUtilityPVCostModel from h2integrate.resource.wind.nrel_developer_wtk_api import WTKNRELDeveloperAPIWindResource @@ -156,8 +152,6 @@ "atb_comm_res_pv_cost": ATBResComPVCostModel, "run_of_river_hydro_performance": RunOfRiverHydroPerformanceModel, "run_of_river_hydro_cost": RunOfRiverHydroCostModel, - "pem_electrolyzer_performance": ElectrolyzerPerformanceModel, - "pem_electrolyzer_cost": ElectrolyzerCostModel, "eco_pem_electrolyzer_performance": ECOElectrolyzerPerformanceModel, "singlitico_electrolyzer_cost": SingliticoCostModel, "basic_electrolyzer_cost": BasicElectrolyzerCostModel, diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_costs_Singlitico_model.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_costs_Singlitico_model.py deleted file mode 100644 index 48077ef85..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_costs_Singlitico_model.py +++ /dev/null @@ -1,201 +0,0 @@ -""" -Author: Christopher Bay -Date: 01/24/2023 -Institution: National Renewable Energy Laboratory -Description: This file implements electrolzyer CapEx and OpEx models from [1]. The exact extent of - what is included in the costs is unclear in [1]. Source [2] (cited by [1]) states that - "equipment costs include the electrolyser system, the filling centre or compressor skids and - storage systems". -Sources: - - [1] Singlitico, Alessandro, Jacob Østergaard, and Spyros Chatzivasileiadis. "Onshore, offshore - or in-turbine electrolysis? Techno-economic overview of alternative integration designs for - green hydrogen production into Offshore Wind Power Hubs." Renewable and Sustainable Energy - Transition 1 (2021): 100005. - - [2] [E. Tractebel , H. Engie , Study on early business cases for h2 in energy storage and more - broadly power to h2 applications, EU Comm, 2017, p. 228 .] - https://hsweb.hs.uni-hamburg.de/projects/star-formation/hydrogen/P2H_Full_Study_FCHJU.pdf -""" - -from __future__ import annotations - - -class PEMCostsSingliticoModel: - def __init__( - self, - elec_location: int, - ): - """ - Initialize object for PEM costs based on [1]. - - Args: - elec_location (int): Parameter for indicating the electrolyzer location; - 0 is for onshore, 1 is for offshore or in-turbine. - """ - # Values for CapEX & OpEx taken from [1], Table B.2, PEMEL. - # Installation costs include land, contingency, contractors, legal fees, construction, - # engineering, yard improvements, buildings, electrics, piping, instrumentation, - # and installation and grid connection. - self.IF = 0.33 # installation fraction [% RC_elec] - self.RP_elec = 10 # reference power [MW] - - # Values for OpEx taken from [1], Table B.3, PEMEL. - self.RP_SR = 5 # reference power [MW] - self.RU_SR = 0.41 # reference cost share [%], for a reference power, RP_SR, of 5MW - self.P_stack_max_bar = 2 # average max size [MW] - self.SF_SR_0 = 0.11 # average scale factor - - # NOTE: 1 for offshore or in-turbine electrolyzer location, 0 for onshore; from [1], - self.OS = elec_location - # Table B.1 notes for CapEx_el - - # NOTE: This is used in the stack replacement cost code that is currently commented out; - # more work needs to be done to make sure this is set and used correctly. - # self.P_elec_bar = 1 * 10**3 # scaled max [MW] from [1], Table B.1 notes forOpEx_elec_eq - - # NOTE: This is used in the stack replacement cost code that is currently commented out. - # self.OH_max = 85000 # Lifetime maximum operating hours [h], taken from [1], Table 1, PEMEL - - def run( - self, - P_elec: float, - RC_elec: float, - ) -> tuple: - """ - Computes the CapEx and OpEx costs for a single electrolyzer. - - Args: - P_elec (float): Nominal capacity of the electrolyzer [GW]. - RC_elec (float): Reference cost of the electrolyzer [MUSD/GW] for a 10 MW electrolyzer - plant installed. - - Returns: - tuple: CapEx and OpEx costs for a single electrolyzer. - """ - capex = self.calc_capex(P_elec, RC_elec) - - opex = self.calc_opex(P_elec, capex) - - return capex, opex - - def calc_capex( - self, - P_elec: float, - RC_elec: float, - ) -> float: - """ - CapEx for a single electrolyzer, given the electrolyzer capacity and reference cost. - Equation from [1], Table B.1, CapEx_EL. For in-turbine electrolyzers, - it is assumed that the maximum electrolyzer size is equal to the turbine rated capacity. - - NOTE: If the single electrolyzer capacity exceeds 100MW, the CapEx becomes fixed at the cost - of a 100MW system, due to decreasing economies of scale (based on assumption from [1]). As - such, if you use the output to calculate a cost per unit of electrolyzer, you will need to - divide the cost by 100MW and not the user-specified size of the electrolyzer for sizes above - 100MW. - - Args: - P_elec (float): Nominal capacity of the electrolyzer [GW]. - RC_elec (float): Reference cost of the electrolyzer [MUSD/GW]. - - Returns: - float: CapEx for electrolyzer [MUSD]. - """ - # Choose the scale factor based on electrolyzer size, [1], Table B.2. - if P_elec < 10 / 10**3: - self.SF_elec = -0.21 # scale factor, -0.21 for <10MW, -0.14 for >10MW - else: - self.SF_elec = -0.14 # scale factor, -0.21 for <10MW, -0.14 for >10MW - - # If electrolyzer capacity is >100MW, fix unit cost to 100MW electrolyzer as economies of - # scale stop at sizes above this, according to assumption in [1]. - if P_elec > 100 / 10**3: - P_elec_cost_per_unit_calc = 0.1 - else: - P_elec_cost_per_unit_calc = P_elec - - # Return the cost of a single electrolyzer of the specified capacity in millions of USD (or - # the supplied currency). - # MUSD = GW * MUSD/GW * - * GW * MW/GW / MW ** - - cost = ( - P_elec_cost_per_unit_calc - * RC_elec - * (1 + self.IF * self.OS) - * ((P_elec_cost_per_unit_calc * 10**3 / self.RP_elec) ** self.SF_elec) - ) - cost_per_unit = cost / P_elec_cost_per_unit_calc - - return cost_per_unit * P_elec - - def calc_opex( - self, - P_elec: float, - capex_elec: float, - RC_elec: float | None = None, - OH: float | None = None, - ) -> float: - """ - OpEx for a single electrolyzer, given the electrolyzer capacity and reference cost. - Equations from [1], Table B.1, OpEx_elec_eq and OpEx_elec_neq. - The returned OpEx cost include equipment and non-equipment costs, but excludes the stack - replacement cost. - - NOTE: If the single electrolyzer capacity exceeds 100MW, the OpEx becomes fixed at the cost - of a 100MW system, due to decreasing economies of scale (based on assumption from [1]). - As such, if you use the output to calculate a cost per unit of electrolyzer, you will need - to divide the cost by 100MW and not the user-specified size of the electrolyzer for sizes - above 100 MW. - - NOTE: Code for the stack replacement cost is included below, but does not currently match - results from [1]. DO NOT USE in the current form. - - Args: - P_elec (float): Nominal capacity of the electrolyzer [GW]. - capex_elec (float): CapEx for electrolyzer [MUSD]. - RC_elec (float, optional): Reference cost of the electrolyzer [MUSD/GW]. Defaults to - None. Not currently used. - OH (float, optional): Operating hours [h]. Defaults to None. Not currently used. - - Returns: - float: OpEx for electrolyzer [MUSD]. - """ - # If electrolyzer capacity is >100MW, fix unit cost to 100MW electrolyzer as economies of - # scale stop at sizes above this, according to assumption in [1]. - if P_elec > 100 / 10**3: - P_elec = 0.1 - - # Including material cost for planned and unplanned maintenance, labor cost in central - # Europe, which all depend on a system scale. Excluding the cost of electricity and the - # stack replacement, calculated separately. Scaled maximum to P_elec_bar = 1 GW. - # MUSD*MW MUSD * - * - * GW * MW/GW - opex_elec_eq = ( - capex_elec * (1 - self.IF * (1 + self.OS)) * 0.0344 * (P_elec * 10**3) ** -0.155 - ) - - # Covers the other operational expenditure related to the facility level. This includes site - # management, land rent and taxes, administrative fees (insurance, legal fees...), and site - # maintenance. - # MUSD MUSD - opex_elec_neq = 0.04 * capex_elec * self.IF * (1 + self.OS) - - # NOTE: The stack replacement costs below don't match the results in [1] supplementary - # materials. - # ***DO NOT USE*** stack replacement cost in its current form. - - # Choose the scale factor based on electrolyzer size, [1], Table B.2. - # if P_elec < 10 / 10**3: - # self.SF_elec = -0.21 # scale factor, -0.21 for <10MW, -0.14 for >10MW - # else: - # self.SF_elec = -0.14 # scale factor, -0.21 for <10MW, -0.14 for >10MW - - # Approximation of stack costs and replacement cost depending on the electrolyzer equipment - # costs. - # Paid only the year in which the replacement is needed. - # MUSD/GW % * MUSD/GW * - * MW / MW ** - - # RC_SR = self.RU_SR * RC_elec * (1 - self.IF) * (self.RP_SR / self.RP_elec) ** self.SF_elec - # # - - * MW / MW - # SF_SR = 1 - (1 - self.SF_SR_0) * np.exp(-self.P_elec_bar / self.P_stack_max_bar) - # # SF_SR = 1 - (1 - self.SF_SR_0) * np.exp(-P_elec * 10**3 / self.P_stack_max_bar) - # # MUSD GW * MUSD/GW * GW * MW/GW MW ** - * h / h - # opex_elec_sr = P_elec * RC_SR * (P_elec * 10**3 / self.RP_SR) ** SF_SR * OH / self.OH_max - - return opex_elec_eq + opex_elec_neq diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_tools.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_tools.py deleted file mode 100644 index efce5947f..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_tools.py +++ /dev/null @@ -1,59 +0,0 @@ -import numpy as np - -from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_H2_LT_electrolyzer_Clusters import ( # noqa: E501 - PEM_H2_Clusters as PEMClusters, -) - - -def create_1MW_reference_PEM(curve_coeff=None): - pem_param_dict = { - "eol_eff_percent_loss": 10, - "uptime_hours_until_eol": 77600, - "include_degradation_penalty": True, - "turndown_ratio": 0.1, - "curve_coeff": curve_coeff, - } - pem = PEMClusters(cluster_size_mw=1, plant_life=30, **pem_param_dict) - return pem - - -def get_electrolyzer_BOL_efficiency(): - pem_1MW = create_1MW_reference_PEM() - bol_eff = pem_1MW.output_dict["BOL Efficiency Curve Info"]["Efficiency [kWh/kg]"].values[-1] - - return np.round(bol_eff, 2) - - -def size_electrolyzer_for_hydrogen_demand( - hydrogen_production_capacity_required_kgphr, - size_for="BOL", - electrolyzer_degradation_power_increase=None, -): - electrolyzer_energy_kWh_per_kg_estimate_BOL = get_electrolyzer_BOL_efficiency() - if size_for == "BOL": - electrolyzer_capacity_MW = ( - hydrogen_production_capacity_required_kgphr - * electrolyzer_energy_kWh_per_kg_estimate_BOL - / 1000 - ) - elif size_for == "EOL": - electrolyzer_energy_kWh_per_kg_estimate_EOL = ( - electrolyzer_energy_kWh_per_kg_estimate_BOL - * (1 + electrolyzer_degradation_power_increase) - ) - electrolyzer_capacity_MW = ( - hydrogen_production_capacity_required_kgphr - * electrolyzer_energy_kWh_per_kg_estimate_EOL - / 1000 - ) - - return electrolyzer_capacity_MW - - -def check_capacity_based_on_clusters(electrolyzer_capacity_BOL_MW, cluster_cap_mw): - if electrolyzer_capacity_BOL_MW % cluster_cap_mw == 0: - n_pem_clusters_max = electrolyzer_capacity_BOL_MW // cluster_cap_mw - else: - n_pem_clusters_max = int(np.ceil(np.ceil(electrolyzer_capacity_BOL_MW) / cluster_cap_mw)) - electrolyzer_size_mw = n_pem_clusters_max * cluster_cap_mw - return electrolyzer_size_mw diff --git a/tests/h2integrate/test_hydrogen/test_PEM_costs_Singlitico_model.py b/tests/h2integrate/test_hydrogen/test_PEM_costs_Singlitico_model.py deleted file mode 100644 index 6919c9943..000000000 --- a/tests/h2integrate/test_hydrogen/test_PEM_costs_Singlitico_model.py +++ /dev/null @@ -1,79 +0,0 @@ -import numpy as np -from pytest import approx - -from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_costs_Singlitico_model import ( - PEMCostsSingliticoModel, -) - - -TOL = 1e-3 - -BASELINE = np.array( - [ - # onshore, [capex, opex] - [ - [50.7105172052493, 1.2418205567631722], - ], - # offshore, [capex, opex] - [ - [67.44498788298158, 2.16690312809502], - ], - ] -) - - -class TestPEMCostsSingliticoModel: - def test_calc_capex(self): - P_elec = 0.1 # [GW] - RC_elec = 700 # [MUSD/GW] - - # test onshore capex - pem_onshore = PEMCostsSingliticoModel(elec_location=0) - capex_onshore = pem_onshore.calc_capex(P_elec, RC_elec) - - assert capex_onshore == approx(BASELINE[0][0][0], TOL) - - # test offshore capex - pem_offshore = PEMCostsSingliticoModel(elec_location=1) - capex_offshore = pem_offshore.calc_capex(P_elec, RC_elec) - - assert capex_offshore == approx(BASELINE[1][0][0], TOL) - - def test_calc_opex(self): - P_elec = 0.1 # [GW] - capex_onshore = BASELINE[0][0][0] - capex_offshore = BASELINE[1][0][0] - - # test onshore opex - pem_onshore = PEMCostsSingliticoModel(elec_location=0) - opex_onshore = pem_onshore.calc_opex(P_elec, capex_onshore) - - assert opex_onshore == approx(BASELINE[0][0][1], TOL) - - # test offshore opex - pem_offshore = PEMCostsSingliticoModel(elec_location=1) - opex_offshore = pem_offshore.calc_opex(P_elec, capex_offshore) - - assert opex_offshore == approx(BASELINE[1][0][1], TOL) - - def test_run(self): - P_elec = 0.1 # [GW] - RC_elec = 700 # [MUSD/GW] - - # test onshore opex - pem_onshore = PEMCostsSingliticoModel(elec_location=0) - capex_onshore, opex_onshore = pem_onshore.run(P_elec, RC_elec) - - assert capex_onshore == approx(BASELINE[0][0][0], TOL) - assert opex_onshore == approx(BASELINE[0][0][1], TOL) - - # test offshore opex - pem_offshore = PEMCostsSingliticoModel(elec_location=1) - capex_offshore, opex_offshore = pem_offshore.run(P_elec, RC_elec) - - assert capex_offshore == approx(BASELINE[1][0][0], TOL) - assert opex_offshore == approx(BASELINE[1][0][1], TOL) - - -if __name__ == "__main__": - test_set = TestPEMCostsSingliticoModel() From 55666aad514ae2be0c591ff5e733024da5f348fb Mon Sep 17 00:00:00 2001 From: John Jasa Date: Thu, 27 Nov 2025 09:48:59 -0700 Subject: [PATCH 11/30] Removing unused electrolyzer files --- .../electrolysis/PEM_electrolyzer_IVcurve.py | 657 ------------------ .../electrolysis/pem_mass_and_footprint.py | 93 --- .../hydrogen/electrolysis/run_PEM_master.py | 220 ------ .../hydrogen/electrolysis/run_h2_PEM_eco.py | 110 --- .../hydrogen/electrolysis/run_h2_clusters.py | 233 ------- .../test_pem_mass_and_footprint.py | 18 - 6 files changed, 1331 deletions(-) delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_electrolyzer_IVcurve.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/pem_mass_and_footprint.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/run_h2_PEM_eco.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/run_h2_clusters.py delete mode 100644 tests/h2integrate/test_hydrogen/test_pem_mass_and_footprint.py diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_electrolyzer_IVcurve.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_electrolyzer_IVcurve.py deleted file mode 100644 index 79d575a6e..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_electrolyzer_IVcurve.py +++ /dev/null @@ -1,657 +0,0 @@ -## Low-Temperature PEM Electrolyzer Model -""" -Python model of H2 PEM low-temp electrolyzer. - -Quick Hydrogen Physics: - -1 kg H2 <-> 11.1 N-m3 <-> 33.3 kWh (LHV) <-> 39.4 kWh (HHV) - -High mass energy density (1 kg H2= 3,77 l gasoline) -Low volumetric density (1 Nm³ H2= 0,34 l gasoline - -Hydrogen production from water electrolysis (~5 kWh/Nm³ H2) - -Power:1 MW electrolyser <-> 200 Nm³/h H2 <-> ±18 kg/h H2 -Energy:+/-55 kWh of electricity --> 1 kg H2 <-> 11.1 Nm³ <-> ±10 liters -demineralized water - -Power production from a hydrogen PEM fuel cell from hydrogen (+/-50% -efficiency): -Energy: 1 kg H2 --> 16 kWh -""" - -import sys -import math - -import numpy as np -import scipy -import pandas as pd -from matplotlib import pyplot as plt - - -np.set_printoptions(threshold=sys.maxsize) - - -def calc_current( - P_T, p1, p2, p3, p4, p5, p6 -): # calculates i-v curve coefficients given the stack power and stack temp - pwr, tempc = P_T - i_stack = ( - p1 * (pwr**2) + p2 * (tempc**2) + (p3 * pwr * tempc) + (p4 * pwr) + (p5 * tempc) + (p6) - ) - return i_stack - - -class PEM_electrolyzer_LT: - """ - Create an instance of a low-temperature PEM Electrolyzer System. Each - stack in the electrolyzer system in this model is rated at 1 MW_DC. - - Parameters - _____________ - np_array P_input_external_kW - 1-D array of time-series external power supply - - string voltage_type - Nature of voltage supplied to electrolyzer from the external power - supply ['variable' or 'constant] - - float power_supply_rating_MW - Rated power of external power supply - - Returns - _____________ - - """ - - def __init__(self, input_dict, output_dict): - self.input_dict = input_dict - self.output_dict = output_dict - - # array of input power signal - self.input_dict["P_input_external_kW"] = input_dict["P_input_external_kW"] - self.electrolyzer_system_size_MW = input_dict["electrolyzer_system_size_MW"] - - # self.input_dict['voltage_type'] = 'variable' # not yet implemented - self.input_dict["voltage_type"] = "constant" - self.stack_input_voltage_DC = 250 - - # Assumptions: - self.min_V_cell = 1.62 # Only used in variable voltage scenario - self.p_s_h2_bar = 31 # H2 outlet pressure - - # any current below this amount (10% rated) will saturate the H2 production to zero, used to - # be 500 (12.5% of rated) - self.stack_input_current_lower_bound = 400 # [A] - self.stack_rating_kW = 1000 # 1 MW - self.cell_active_area = 1250 # [cm^2] - self.N_cells = 130 - - # PEM electrolyzers have a max current density of approx 2 A/cm^2 so max is 2*cell_area - self.max_cell_current = 2 * self.cell_active_area - - # Constants: - self.moles_per_g_h2 = 0.49606 # [1/weight_h2] - self.V_TN = 1.48 # Thermo-neutral Voltage (Volts) in standard conditions - self.F = 96485.34 # Faraday's Constant (C/mol) or [As/mol] - self.R = 8.314 # Ideal Gas Constant (J/mol/K) - - # Additional Constants - self.T_C = 80 # stack temperature in [C] - self.mmHg_2_Pa = 133.322 # convert between mmHg to Pa - self.patmo = 101325 # atmospheric pressure [Pa] - self.mmHg_2_atm = self.mmHg_2_Pa / self.patmo # convert from mmHg to atm - - self.curve_coeff = self.iv_curve() # this initializes the I-V curve to calculate current - self.external_power_supply() - - def external_power_supply(self): - """ - External power source (grid or REG) which will need to be stepped - down and converted to DC power for the electrolyzer. - - Please note, for a wind farm as the electrolyzer's power source, - the model assumes variable power supplied to the stack at fixed - voltage (fixed voltage, variable power and current) - - TODO: extend model to accept variable voltage, current, and power - This will replicate direct DC-coupled PV system operating at MPP - """ - power_converter_efficiency = ( - 1.0 # this used to be 0.95 but feel free to change as you'd like - ) - if self.input_dict["voltage_type"] == "constant": - self.input_dict["P_input_external_kW"] = np.where( - self.input_dict["P_input_external_kW"] > (self.electrolyzer_system_size_MW * 1000), - (self.electrolyzer_system_size_MW * 1000), - self.input_dict["P_input_external_kW"], - ) - - self.output_dict["curtailed_P_kW"] = np.where( - self.input_dict["P_input_external_kW"] > (self.electrolyzer_system_size_MW * 1000), - ( - self.input_dict["P_input_external_kW"] - - (self.electrolyzer_system_size_MW * 1000) - ), - 0, - ) - - # Current used to be calculated as Power/Voltage but now it uses the IV curve - # self.output_dict['current_input_external_Amps'] = \ - # (self.input_dict['P_input_external_kW'] * 1000 * - # power_converter_efficiency) / (self.stack_input_voltage_DC * - # self.system_design()) - - self.output_dict["current_input_external_Amps"] = calc_current( - ( - ( - (self.input_dict["P_input_external_kW"] * power_converter_efficiency) - / self.system_design() - ), - self.T_C, - ), - *self.curve_coeff, - ) - - self.output_dict["stack_current_density_A_cm2"] = ( - self.output_dict["current_input_external_Amps"] / self.cell_active_area - ) - - self.output_dict["current_input_external_Amps"] = np.where( - self.output_dict["current_input_external_Amps"] - < self.stack_input_current_lower_bound, - 0, - self.output_dict["current_input_external_Amps"], - ) - - else: - pass # TODO: extend model to variable voltage and current source - - def iv_curve(self): - """ - This is a new function that creates the I-V curve to calculate current based - on input power and electrolyzer temperature - - current range is 0: max_cell_current+10 -> PEM have current density approx = 2 A/cm^2 - - temperature range is 40 degC : rated_temp+5 -> temperatures for PEM are usually within - 60-80degC - - calls cell_design() which calculates the cell voltage - """ - current_range = np.arange(0, self.max_cell_current + 10, 10) - temp_range = np.arange(40, self.T_C + 5, 5) - idx = 0 - powers = np.zeros(len(current_range) * len(temp_range)) - currents = np.zeros(len(current_range) * len(temp_range)) - temps_C = np.zeros(len(current_range) * len(temp_range)) - for i in range(len(current_range)): - for t in range(len(temp_range)): - powers[idx] = ( - current_range[i] - * self.cell_design(temp_range[t], current_range[i]) - * self.N_cells - * (1e-3) - ) # stack power - currents[idx] = current_range[i] - temps_C[idx] = temp_range[t] - idx = idx + 1 - - curve_coeff, curve_cov = scipy.optimize.curve_fit( - calc_current, (powers, temps_C), currents, p0=(1.0, 1.0, 1.0, 1.0, 1.0, 1.0) - ) # updates IV curve coeff - return curve_coeff - - def system_design(self): - """ - For now, system design is solely a function of max. external power - supply; i.e., a rated power supply of 50 MW means that the electrolyzer - system developed by this model is also rated at 50 MW - - TODO: Extend model to include this capability. - Assume that a PEM electrolyzer behaves as a purely resistive load - in a circuit, and design the configuration of the entire electrolyzer - system - which may consist of multiple stacks connected together in - series, parallel, or a combination of both. - """ - h2_production_multiplier = (self.electrolyzer_system_size_MW * 1000) / self.stack_rating_kW - self.output_dict["electrolyzer_system_size_MW"] = self.electrolyzer_system_size_MW - return h2_production_multiplier - - def cell_design(self, Stack_T, Stack_Current): - """ - - Please note that this method is currently not used in the model. It - will be used once the electrolyzer model is expanded to variable - voltage supply as well as implementation of the self.system_design() - method - - Motivation: - - The most common representation of the electrolyzer performance is the - polarization curve that represents the relation between the current density - and the voltage (V): - Source: https://www.sciencedirect.com/science/article/pii/S0959652620312312 - - V = N_c(E_cell + V_Act,c + V_Act,a + iR_cell) - - where N_c is the number of electrolyzer cells,E_cell is the open circuit - voltage VAct,and V_Act,c are the anode and cathode activation over-potentials, - i is the current density and iRcell is the electrolyzer cell resistance - (ohmic losses). - - Use this to make a V vs. A (Amperes/cm2) graph which starts at 1.23V because - thermodynamic reaction of water formation/splitting dictates that standard - electrode potential has a ∆G of 237 kJ/mol (where: ∆H = ∆G + T∆S) - - 10/31/2022 - ESG: https://www.sciencedirect.com/science/article/pii/S0360319906000693 - -> calculates cell voltage to make IV curve (called by iv_curve) - Another good source for the equations used in this function: - https://www.sciencedirect.com/science/article/pii/S0360319918309017 - - """ - - # Cell level inputs: - - E_rev0 = ( - 1.229 # (in Volts) Reversible potential at 25degC - Nerst Equation (see Note below) - ) - # E_th = 1.48 # (in Volts) Thermoneutral potential at 25degC - No longer used - - T_K = Stack_T + 273.15 # in Kelvins - # E_cell == Open Circuit Voltage - used to be a static variable, now calculated - # NOTE: E_rev is unused right now, E_rev0 is the general nerst equation for operating at 25 - # deg C at atmospheric pressure (whereas we will be operating at higher temps). From the - # literature above, it appears that E_rev0 is more correct - # https://www.sciencedirect.com/science/article/pii/S0360319911021380 - ( - 1.5184 - - (1.5421 * (10 ** (-3)) * T_K) - + (9.523 * (10 ** (-5)) * T_K * math.log(T_K)) - + (9.84 * (10 ** (-8)) * (T_K**2)) - ) - - # Calculate partial pressure of H2 at the cathode: - # Uses Antoine formula (see link below) - # p_h2o_sat calculation taken from compression efficiency calculation - # https://www.omnicalculator.com/chemistry/vapour-pressure-of-water#antoine-equation - A = 8.07131 - B = 1730.63 - C = 233.426 - - p_h2o_sat_mmHg = 10 ** ( - A - (B / (C + Stack_T)) - ) # vapor pressure of water in [mmHg] using Antoine formula - p_h20_sat_atm = p_h2o_sat_mmHg * self.mmHg_2_atm # convert mmHg to atm - - # could also use Arden-Buck equation (see below). Arden Buck and Antoine equations give - # barely different pressures for the temperatures we're looking, however, the differences - # between the two become more substantial at higher temps - - # p_h20_sat_pa=((0.61121*math.exp((18.678-(Stack_T/234.5))*(Stack_T/(257.14+Stack_T))))*1e+3) #ARDEN BUCK # noqa: E501 - # p_h20_sat_atm=p_h20_sat_pa/self.patmo - - # Cell reversible voltage kind of explain in Equations (12)-(15) of below source - # https://www.sciencedirect.com/science/article/pii/S0360319906000693 - # OR see equation (8) in the source below - # https://www.sciencedirect.com/science/article/pii/S0360319917309278?via%3Dihub - E_cell = E_rev0 + ((self.R * T_K) / (2 * self.F)) * ( - np.log((1 - p_h20_sat_atm) * math.sqrt(1 - p_h20_sat_atm)) - ) # 1 value is atmoshperic pressure in atm - i = Stack_Current / self.cell_active_area # i is cell current density - - # Following coefficient values obtained from Yigit and Selamet (2016) - - # https://www.sciencedirect.com/science/article/pii/S0360319916318341?via%3Dihub - a_a = 2 # Anode charge transfer coefficient - a_c = 0.5 # Cathode charge transfer coefficient - i_o_a = 2 * (10 ** (-7)) # anode exchange current density - i_o_c = 2 * (10 ** (-3)) # cathode exchange current density - - # below is the activation energy for anode and cathode - see - # https://www.sciencedirect.com/science/article/pii/S0360319911021380 - V_act = (((self.R * T_K) / (a_a * self.F)) * np.arcsinh(i / (2 * i_o_a))) + ( - ((self.R * T_K) / (a_c * self.F)) * np.arcsinh(i / (2 * i_o_c)) - ) - - # equation 13 and 12 for lambda_water_content and sigma: - # from https://www.sciencedirect.com/science/article/pii/S0360319917309278?via%3Dihub - lambda_water_content = ((-2.89556 + (0.016 * T_K)) + 1.625) / 0.1875 - - # reasonable membrane thickness of 180-µm NOTE: this will likely decrease in the future - delta = 0.018 # [cm] - - sigma = ((0.005139 * lambda_water_content) - 0.00326) * math.exp( - 1268 * ((1 / 303) - (1 / T_K)) - ) # membrane proton conductivity [S/cm] - - R_cell = delta / sigma # ionic resistance [ohms] - R_elec = 3.5 * ( - 10 ** (-5) - ) # [ohms] from Table 1 in https://journals.utm.my/jurnalteknologi/article/view/5213/3557 - V_cell = E_cell + V_act + (i * (R_cell + R_elec)) # cell voltage [V] - # NOTE: R_elec is to account for the electronic resistance measured between stack terminals - # in open-circuit conditions - # Supposedly, removing it shouldn't lead to large errors - # calculation for it: http://www.electrochemsci.org/papers/vol7/7043314.pdf - - # V_stack = self.N_cells * V_cell # Stack operational voltage -> this is combined in iv_calc for power rather than here # noqa: E501 - - return V_cell - - def dynamic_operation(self): # UNUSED - """ - Model the electrolyzer's realistic response/operation under variable RE - - TODO: add this capability to the model - """ - # When electrolyzer is already at or near its optimal operation - # temperature (~80degC) - - def water_electrolysis_efficiency(self): # UNUSED - """ - https://www.sciencedirect.com/science/article/pii/S2589299119300035#b0500 - - According to the first law of thermodynamics energy is conserved. - Thus, the conversion efficiency calculated from the yields of - converted electrical energy into chemical energy. Typically, - water electrolysis efficiency is calculated by the higher heating - value (HHV) of hydrogen. Since the electrolysis process water is - supplied to the cell in liquid phase efficiency can be calculated by: - - n_T = V_TN / V_cell - - where, V_TN is the thermo-neutral voltage (min. required V to - electrolyze water) - - Parameters - ______________ - - Returns - ______________ - - """ - # From the source listed in this function ... - # n_T=V_TN/V_cell NOT what's below which is input voltage -> this should call cell_design() - n_T = self.V_TN / (self.stack_input_voltage_DC / self.N_cells) - return n_T - - def faradaic_efficiency(self): # ONLY EFFICIENCY CONSIDERED RIGHT NOW - """ - Text background from: - [https://www.researchgate.net/publication/344260178_Faraday%27s_ - Efficiency_Modeling_of_a_Proton_Exchange_Membrane_Electrolyzer_ - Based_on_Experimental_Data] - - In electrolyzers, Faraday's efficiency is a relevant parameter to - assess the amount of hydrogen generated according to the input - energy and energy efficiency. Faraday's efficiency expresses the - faradaic losses due to the gas crossover current. The thickness - of the membrane and operating conditions (i.e., temperature, gas - pressure) may affect the Faraday's efficiency. - - Equation for n_F obtained from: - https://www.sciencedirect.com/science/article/pii/S0360319917347237#bib27 - - Parameters - ______________ - float f_1 - Coefficient - value at operating temperature of 80degC (mA2/cm4) - - float f_2 - Coefficient - value at operating temp of 80 degC (unitless) - - np_array current_input_external_Amps - 1-D array of current supplied to electrolyzer stack from external - power source - - - Returns - ______________ - - float n_F - Faradaic efficiency (unitless) - - """ - f_1 = 250 # Coefficient (mA2/cm4) - f_2 = 0.996 # Coefficient (unitless) - I_cell = self.output_dict["current_input_external_Amps"] * 1000 - - # Faraday efficiency - n_F = ( - ((I_cell / self.cell_active_area) ** 2) - / (f_1 + ((I_cell / self.cell_active_area) ** 2)) - ) * f_2 - - return n_F - - def compression_efficiency(self): # UNUSED AND MAY HAVE ISSUES - # Should this only be used if we plan on storing H2? - """ - In industrial contexts, the remaining hydrogen should be stored at - certain storage pressures that vary depending on the intended - application. In the case of subsequent compression, pressure-volume - work, Wc, must be performed. The additional pressure-volume work can - be related to the heating value of storable hydrogen. Then, the total - efficiency reduces by the following factor: - https://www.mdpi.com/1996-1073/13/3/612/htm - - Due to reasons of material properties and operating costs, large - amounts of gaseous hydrogen are usually not stored at pressures - exceeding 100 bar in aboveground vessels and 200 bar in underground - storages - https://www.sciencedirect.com/science/article/pii/S0360319919310195 - - Partial pressure of H2(g) calculated using: - The hydrogen partial pressure is calculated as a difference between - the cathode pressure, 101,325 Pa, and the water saturation - pressure - [Source: Energies2018,11,3273; doi:10.3390/en11123273] - - """ - n_limC = 0.825 # Limited efficiency of gas compressors (unitless) - H_LHV = 241 # Lower heating value of H2 (kJ/mol) - K = 1.4 # Average heat capacity ratio (unitless) - C_c = 2.75 # Compression factor (ratio of pressure after and before compression) - n_F = self.faradaic_efficiency() - j = self.output_dict["stack_current_density_A_cm2"] - n_x = ((1 - n_F) * j) * self.cell_active_area - n_h2 = j * self.cell_active_area - Z = 1 # [Assumption] Average compressibility factor (unitless) - T_in = 273.15 + self.T_C # (Kelvins) Assuming electrolyzer operates at 80degC - W_1_C = ( - (K / (K - 1)) - * ((n_h2 - n_x) / self.F) - * self.R - * T_in - * Z - * ((C_c ** ((K - 1) / K)) - 1) - ) # Single stage compression - - # Calculate partial pressure of H2 at the cathode: This is the Antoine formula (see link - # below) - # https://www.omnicalculator.com/chemistry/vapour-pressure-of-water#antoine-equation - A = 8.07131 - B = 1730.63 - C = 233.426 - p_h2o_sat = 10 ** (A - (B / (C + self.T_C))) # [mmHg] - p_cat = 101325 # Cathode pressure (Pa) - # Fixed unit bug between mmHg and Pa - - p_h2_cat = p_cat - (p_h2o_sat * self.mmHg_2_Pa) # convert mmHg to Pa - p_s_h2_Pa = self.p_s_h2_bar * 1e5 - - s_C = math.log10(p_s_h2_Pa / p_h2_cat) / math.log10(C_c) - W_C = round(s_C) * W_1_C # Pressure-Volume work - energy reqd. for compression - net_energy_carrier = n_h2 - n_x # C/s - net_energy_carrier = np.where((n_h2 - n_x) == 0, 1, net_energy_carrier) - n_C = 1 - ((W_C / (((net_energy_carrier) / self.F) * H_LHV * 1000)) * (1 / n_limC)) - n_C = np.where((n_h2 - n_x) == 0, 0, n_C) - return n_C - - def total_efficiency(self): - """ - Aside from efficiencies accounted for in this model - (water_electrolysis_efficiency, faradaic_efficiency, and - compression_efficiency) all process steps such as gas drying above - 2 bar or water pumping can be assumed as negligible. Ultimately, the - total efficiency or system efficiency of a PEM electrolysis system is: - - n_T = n_p_h2 * n_F_h2 * n_c_h2 - https://www.mdpi.com/1996-1073/13/3/612/htm - """ - # n_p_h2 = self.water_electrolysis_efficiency() #no longer considered - n_F_h2 = self.faradaic_efficiency() - # n_c_h2 = self.compression_efficiency() #no longer considered - - # n_T = n_p_h2 * n_F_h2 * n_c_h2 #No longer considers these other efficiencies - n_T = n_F_h2 - self.output_dict["total_efficiency"] = n_T - return n_T - - def h2_production_rate(self): - """ - H2 production rate calculated using Faraday's Law of Electrolysis - (https://www.sciencedirect.com/science/article/pii/S0360319917347237#bib27) - - Parameters - _____________ - - float f_1 - Coefficient - value at operating temperature of 80degC (mA2/cm4) - - float f_2 - Coefficient - value at operating temp of 80 degC (unitless) - - np_array - 1-D array of current supplied to electrolyzer stack from external - power source - - - Returns - _____________ - - """ - # Single stack calculations: - n_Tot = self.total_efficiency() - h2_production_rate = n_Tot * ( - (self.N_cells * self.output_dict["current_input_external_Amps"]) / (2 * self.F) - ) # mol/s - h2_production_rate_g_s = h2_production_rate / self.moles_per_g_h2 - h2_produced_kg_hr = h2_production_rate_g_s * 3.6 # Fixed: no more manual scaling - self.output_dict["stack_h2_produced_g_s"] = h2_production_rate_g_s - self.output_dict["stack_h2_produced_kg_hr"] = h2_produced_kg_hr - - # Total electrolyzer system calculations: - h2_produced_kg_hr_system = self.system_design() * h2_produced_kg_hr - # h2_produced_kg_hr_system = h2_produced_kg_hr - self.output_dict["h2_produced_kg_hr_system"] = h2_produced_kg_hr_system - - return h2_produced_kg_hr_system, h2_production_rate_g_s - - def degradation(self): - """ - TODO - Add a time component to the model - for degradation -> - https://www.hydrogen.energy.gov/pdfs/progress17/ii_b_1_peters_2017.pdf - """ - pass - - def water_supply(self): - """ - Calculate water supply rate based system efficiency and H2 production - rate - TODO: Add this capability to the model - - The 10x multiple is likely too low. See Lampert, David J., Cai, Hao, Wang, Zhichao, - Keisman, Jennifer, Wu, May, Han, Jeongwoo, Dunn, Jennifer, Sullivan, John L., - Elgowainy, Amgad, Wang, Michael, & Keisman, Jennifer. Development of a Life Cycle Inventory - of Water Consumption Associated with the Production of Transportation Fuels. United States. - https://doi.org/10.2172/1224980 - """ - # ratio of water_used:h2_kg_produced depends on power source - # h20_kg:h2_kg with PV 22-126:1 or 18-25:1 without PV but considering water - # deminersalisation stoichometrically its just 9:1 but ... theres inefficiencies in the - # water purification process - h2_produced_kg_hr_system, h2_production_rate_g_s = self.h2_production_rate() - water_used_kg_hr_system = h2_produced_kg_hr_system * 10 - self.output_dict["water_used_kg_hr"] = water_used_kg_hr_system - self.output_dict["water_used_kg_annual"] = np.sum(water_used_kg_hr_system) - - def h2_storage(self): - """ - Model to estimate Ideal Isorthermal H2 compression at 70degC - https://www.sciencedirect.com/science/article/pii/S036031991733954X - - The amount of hydrogen gas stored under pressure can be estimated - using the van der Waals equation - - p = [(nRT)/(V-nb)] - [a * ((n^2) / (V^2))] - - where p is pressure of the hydrogen gas (Pa), n the amount of - substance (mol), T the temperature (K), and V the volume of storage - (m3). The constants a and b are called the van der Waals coefficients, - which for hydrogen are 2.45 x 10^-2 Pa m6mol^-2 and 26.61 x 10^-6 , - respectively. - """ - - pass - - -if __name__ == "__main__": - # Example on how to use this model: - in_dict = {} - in_dict["electrolyzer_system_size_MW"] = 15 - out_dict = {} - - electricity_profile = pd.read_csv("sample_wind_electricity_profile.csv") - in_dict["P_input_external_kW"] = electricity_profile.iloc[:, 1].to_numpy() - - el = PEM_electrolyzer_LT(in_dict, out_dict) - el.h2_production_rate() - print( - "Hourly H2 production by stack (kg/hr): ", - out_dict["stack_h2_produced_kg_hr"][0:50], - ) - print( - "Hourly H2 production by system (kg/hr): ", - out_dict["h2_produced_kg_hr_system"][0:50], - ) - fig, axs = plt.subplots(2, 2) - fig.suptitle( - "PEM H2 Electrolysis Results for " - + str(out_dict["electrolyzer_system_size_MW"]) - + " MW System" - ) - - axs[0, 0].plot(out_dict["stack_h2_produced_kg_hr"]) - axs[0, 0].set_title("Hourly H2 production by stack") - axs[0, 0].set_ylabel("kg_h2 / hr") - axs[0, 0].set_xlabel("Hour") - - axs[0, 1].plot(out_dict["h2_produced_kg_hr_system"]) - axs[0, 1].set_title("Hourly H2 production by system") - axs[0, 1].set_ylabel("kg_h2 / hr") - axs[0, 1].set_xlabel("Hour") - - axs[1, 0].plot(in_dict["P_input_external_kW"]) - axs[1, 0].set_title("Hourly Energy Supplied by Wind Farm (kWh)") - axs[1, 0].set_ylabel("kWh") - axs[1, 0].set_xlabel("Hour") - - total_efficiency = out_dict["total_efficiency"] - system_h2_eff = (1 / total_efficiency) * 33.3 - system_h2_eff = np.where(total_efficiency == 0, 0, system_h2_eff) - - axs[1, 1].plot(system_h2_eff) - axs[1, 1].set_title("Total Stack Energy Usage per mass net H2") - axs[1, 1].set_ylabel("kWh_e/kg_h2") - axs[1, 1].set_xlabel("Hour") - - plt.show() - print("Annual H2 production (kg): ", np.sum(out_dict["h2_produced_kg_hr_system"])) - print("Annual energy production (kWh): ", np.sum(in_dict["P_input_external_kW"])) - print( - "H2 generated (kg) per kWH of energy generated by wind farm: ", - np.sum(out_dict["h2_produced_kg_hr_system"]) / np.sum(in_dict["P_input_external_kW"]), - ) diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/pem_mass_and_footprint.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/pem_mass_and_footprint.py deleted file mode 100644 index 1c6c0fbbc..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/pem_mass_and_footprint.py +++ /dev/null @@ -1,93 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt -from scipy.optimize import curve_fit - - -def _electrolyzer_footprint_data(): - """ - References: - [1] Bolhui, 2017 https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2020/Dec/IRENA_Green_hydrogen_cost_2020.pdf - - appears to include BOS - [2] Bourne, 2017 - - - [3] McPHy, 2018 (https://mcphy.com/en/equipment-services/electrolyzers/) - [4] Air Liquide 2021, Becancour Quebec - """ - - rating_mw = np.array([300, 100, 100, 20]) # [1], [2], [3], [4] - footprint_sqft = np.array([161500, 37700, 48500, 465000]) # [1], [2], [3], [4] - sqft_to_m2 = 0.092903 - footprint_m2 = footprint_sqft * sqft_to_m2 - - return rating_mw, footprint_m2 - - -def footprint(rating_mw): - """ - Estimate the area required for the electrolyzer equipment using a linear scaling - """ - - # from Singlitico 2021, Table 1 (ratio is given in m2/GW, so a conversion is used here forMW) - footprint_m2 = rating_mw * 48000 * (1 / 1e3) - - return footprint_m2 - - -def _electrolyzer_mass_data(): - """ - References: - [1] https://www.h-tec.com/en/products/detail/h-tec-pem-electrolyser-me450/me450/ - [2] https://www.nrel.gov/docs/fy19osti/70380.pdf - """ - - rating_mw = np.array([1, 1.25, 0.25, 45e-3, 40e-3, 28e-3, 14e-3, 14.4e-3, 7.2e-7]) - mass_kg = np.array([36e3, 17e3, 260, 900, 908, 858, 682, 275, 250]) - - return rating_mw, mass_kg - - -def _electrolyzer_mass_fit(x, m, b): - y = m * x + b - - return y - - -def mass(rating_mw): - """ - Estimate the electorlyzer mass given the electrolyzer rating based on data. - - Note: the largest electrolyzer data available was for 1.25 MW. Also, given the current fit, the - mass goes negative for very small electrolysis systems - """ - - rating_mw_fit, mass_kg_fit = _electrolyzer_mass_data() - - (m, b), pcov = curve_fit(_electrolyzer_mass_fit, rating_mw_fit, mass_kg_fit) - - mass_kg = _electrolyzer_mass_fit(rating_mw, m, b) - - return mass_kg - - -if __name__ == "__main__": - fig, ax = plt.subplots(1, 2) - rating_mw, footprint_m2 = _electrolyzer_footprint_data() - ax[0].scatter(rating_mw, footprint_m2, label="Data points") - - ratings = np.arange(0, 1000) - footprints = footprint(ratings) - - ax[0].plot(ratings, footprints, label="Scaling Factor") - ax[0].set(xlabel="Electrolyzer Rating (MW)", ylabel="Footprint (m$^2$)") - ax[0].legend(frameon=False) - print(rating_mw, footprint_m2) - - rating_mw, mass_kg = _electrolyzer_mass_data() - ax[1].scatter(rating_mw, np.multiply(mass_kg, 1e-3), label="Data points") - - ax[1].plot(ratings, mass(ratings) * 1e-3, label="Linear Fit") - ax[1].set(xlabel="Electrolyzer Rating (MW)", ylabel="Mass (metric tons)") - ax[1].legend(frameon=False) - plt.tight_layout() - plt.show() - print(rating_mw, np.divide(mass_kg, rating_mw)) diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py deleted file mode 100644 index e0db3c726..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py +++ /dev/null @@ -1,220 +0,0 @@ -import time -import warnings - -import numpy as np -import pandas as pd -from pyomo.environ import * # FIXME: no * imports, delete whole comment when fixed # noqa: F403 - -from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_H2_LT_electrolyzer_Clusters import ( # noqa: E501 - PEM_H2_Clusters as PEMClusters, -) - - -# from PyOMO import ipOpt !! FOR SANJANA!! -warnings.filterwarnings("ignore") - -""" -Perform a LCOH analysis for an offshore wind + Hydrogen PEM system - -1. Offshore wind site locations and cost details (4 sites, $1300/kw capex + BOS cost which will come - from Orbit Runs)~ -2. Cost Scaling Based on Year (Have Weiser et. al report with cost scaling for fixed and floating - tech, will implement) -3. Cost Scaling Based on Plant Size (Shields et. Al report) -4. Future Model Development Required: -- Floating Electrolyzer Platform -""" - - -# -# --------------------------- -# -class run_PEM_clusters: - """Inputs: - `electrical_power_signal`: plant power signal in kWh - `system_size_mw`: total installed electrolyzer capacity (for green steel this is 1000 MW) - `num_clusters`: number of PEM clusters that can be run independently - ->ESG note: I have been using num_clusters = 8 for centralized cases - Nomenclature: - `cluster`: cluster is built up of 1MW stacks - `stack`: must be 1MW (because of current PEM model) - """ - - def __init__( - self, - electrical_power_signal, - system_size_mw, - num_clusters, - electrolyzer_direct_cost_kw, - useful_life, - user_defined_electrolyzer_params, - verbose=True, - ): - # nomen - self.cluster_cap_mw = np.round(system_size_mw / num_clusters) - # capacity of each cluster, must be a multiple of 1 MW - - self.num_clusters = num_clusters - self.user_params = user_defined_electrolyzer_params - self.plant_life_yrs = useful_life - # Do not modify stack_rating_kw or stack_min_power_kw - # these represent the hard-coded and unmodifiable - # PEM model basecode - turndown_ratio = user_defined_electrolyzer_params["turndown_ratio"] - self.stack_rating_kw = 1000 # single stack rating - DO NOT CHANGE - self.stack_min_power_kw = turndown_ratio * self.stack_rating_kw - # self.stack_min_power_kw = 0.1 * self.stack_rating_kw - self.input_power_kw = electrical_power_signal - self.cluster_min_power = self.stack_min_power_kw * self.cluster_cap_mw - self.cluster_max_power = self.stack_rating_kw * self.cluster_cap_mw - - # For the optimization problem: - self.T = len(self.input_power_kw) - self.farm_power = 1e9 - self.switching_cost = ( - (electrolyzer_direct_cost_kw * 0.15 * self.cluster_cap_mw * 1000) - * (1.48e-4) - / (0.26586) - ) - self.verbose = verbose - - def run_grid_connected_pem(self, system_size_mw, hydrogen_production_capacity_required_kgphr): - pem = PEMClusters( - system_size_mw, - self.plant_life_yrs, - **self.user_params, - ) - - power_timeseries, stack_current = pem.grid_connected_func( - hydrogen_production_capacity_required_kgphr - ) - h2_ts, h2_tot = pem.run_grid_connected_workaround(power_timeseries, stack_current) - # h2_ts, h2_tot = pem.run(power_timeseries) - h2_df_ts = pd.Series(h2_ts, name="Cluster #0") - h2_df_tot = pd.Series(h2_tot, name="Cluster #0") - # h2_df_ts = pd.DataFrame(h2_ts, index=list(h2_ts.keys()), columns=['Cluster #0']) - # h2_df_tot = pd.DataFrame(h2_tot, index=list(h2_tot.keys()), columns=['Cluster #0']) - return pd.DataFrame(h2_df_ts), pd.DataFrame(h2_df_tot) - - def run(self): - # TODO: add control type as input! - clusters = self.create_clusters() # initialize clusters - power_to_clusters = self.even_split_power() - h2_df_ts = pd.DataFrame() - h2_df_tot = pd.DataFrame() - - col_names = [] - start = time.perf_counter() - for ci in range(len(clusters)): - cl_name = f"Cluster #{ci}" - col_names.append(cl_name) - h2_ts, h2_tot = clusters[ci].run(power_to_clusters[ci]) - # h2_dict_ts['Cluster #{}'.format(ci)] = h2_ts - - h2_ts_temp = pd.Series(h2_ts, name=cl_name) - h2_tot_temp = pd.Series(h2_tot, name=cl_name) - if len(h2_df_tot) == 0: - # h2_df_ts=pd.concat([h2_df_ts,h2_ts_temp],axis=0,ignore_index=False) - h2_df_tot = pd.concat([h2_df_tot, h2_tot_temp], axis=0, ignore_index=False) - h2_df_tot.columns = col_names - - h2_df_ts = pd.concat([h2_df_ts, h2_ts_temp], axis=0, ignore_index=False) - h2_df_ts.columns = col_names - else: - # h2_df_ts = h2_df_ts.join(h2_ts_temp) - h2_df_tot = h2_df_tot.join(h2_tot_temp) - h2_df_tot.columns = col_names - - h2_df_ts = h2_df_ts.join(h2_ts_temp) - h2_df_ts.columns = col_names - - end = time.perf_counter() - self.clusters = clusters - if self.verbose: - print(f"Took {round(end - start, 3)} sec to run the RUN function") - return h2_df_ts, h2_df_tot - # return h2_dict_ts, h2_df_tot - - def even_split_power(self): - start = time.perf_counter() - # determine how much power to give each cluster - num_clusters_on = np.floor(self.input_power_kw / self.cluster_min_power) - num_clusters_on = np.where( - num_clusters_on > self.num_clusters, self.num_clusters, num_clusters_on - ) - power_per_cluster = [ - self.input_power_kw[ti] / num_clusters_on[ti] if num_clusters_on[ti] > 0 else 0 - for ti, pwr in enumerate(self.input_power_kw) - ] - - power_per_to_active_clusters = np.array(power_per_cluster) - power_to_clusters = np.zeros((len(self.input_power_kw), self.num_clusters)) - for i, cluster_power in enumerate( - power_per_to_active_clusters - ): # np.arange(0,self.n_stacks,1): - clusters_off = self.num_clusters - int(num_clusters_on[i]) - no_power = np.zeros(clusters_off) - with_power = cluster_power * np.ones(int(num_clusters_on[i])) - tot_power = np.concatenate((with_power, no_power)) - power_to_clusters[i] = tot_power - - # power_to_clusters = np.repeat([power_per_cluster],self.num_clusters,axis=0) - end = time.perf_counter() - - if self.verbose: - print(f"Took {round(end - start, 3)} sec to run even_split_power function") - # rows are power, columns are stacks [300 x n_stacks] - - return np.transpose(power_to_clusters) - - def max_h2_cntrl(self): - # run as many at lower power as possible - ... - - def min_deg_cntrl(self): - # run as few as possible - ... - - def create_clusters(self): - start = time.perf_counter() - # TODO fix the power input - don't make it required! - # in_dict={'dt':3600} - clusters = PEMClusters(self.cluster_cap_mw, self.plant_life_yrs, **self.user_params) - stacks = [clusters] * self.num_clusters - end = time.perf_counter() - if self.verbose: - print(f"Took {round(end - start, 3)} sec to run the create clusters") - return stacks - - -if __name__ == "__main__": - system_size_mw = 1000 - num_clusters = 20 - cluster_cap_mw = system_size_mw / num_clusters - stack_rating_kw = 1000 - cluster_min_power_kw = 0.1 * stack_rating_kw * cluster_cap_mw - num_steps = 200 - power_rampup = np.arange( - cluster_min_power_kw, system_size_mw * stack_rating_kw, cluster_min_power_kw - ) - - plant_life = 30 - electrolyzer_model_parameters = { - "eol_eff_percent_loss": 10, - "uptime_hours_until_eol": 77600, - "include_degradation_penalty": True, - "turndown_ratio": 0.1, - } - # power_rampup = np.linspace(cluster_min_power_kw,system_size_mw*1000,num_steps) - power_rampdown = np.flip(power_rampup) - power_in = np.concatenate((power_rampup, power_rampdown)) - pem = run_PEM_clusters( - power_in, - system_size_mw, - num_clusters, - plant_life, - electrolyzer_model_parameters, - ) - - h2_ts, h2_tot = pem.run() - # pem.clusters[0].cell_design(80,1920*2) diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_h2_PEM_eco.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/run_h2_PEM_eco.py deleted file mode 100644 index 74302b3ed..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_h2_PEM_eco.py +++ /dev/null @@ -1,110 +0,0 @@ -import numpy as np -import examples.H2_Analysis.H2AModel as H2AModel - -from h2integrate.hydrogen.electrolysis.PEM_electrolyzer_IVcurve import PEM_electrolyzer_LT - - -def run_h2_PEM( - electrical_generation_timeseries, - electrolyzer_size, - kw_continuous, - forced_electrolyzer_cost_kw, - lcoe, - adjusted_installed_cost, - useful_life, - net_capital_costs, - voltage_type="constant", - stack_input_voltage_DC=250, - min_V_cell=1.62, - p_s_h2_bar=31, - stack_input_current_lower_bound=500, - cell_active_area=1250, - N_cells=130, - total_system_electrical_usage=55.5, -): - in_dict = {} - out_dict = {} - in_dict["P_input_external_kW"] = electrical_generation_timeseries - in_dict["electrolyzer_system_size_MW"] = electrolyzer_size - el = PEM_electrolyzer_LT(in_dict, out_dict) - - # el.power_supply_rating_MW = electrolyzer_size - # el.power_supply_rating_MW = power_supply_rating_MW - # print("electrolyzer size: ", electrolyzer_size) - # el.electrolyzer_system_size_MW = electrolyzer_size - # el.input_dict['voltage_type'] = voltage_type - # el.stack_input_voltage_DC = stack_input_voltage_DC - # el.stack_input_voltage_DC = - # Assumptions: - # el.min_V_cell = min_V_cell # Only used in variable voltage scenario - # el.p_s_h2_bar = p_s_h2_bar # H2 outlet pressure - # el.stack_input_current_lower_bound = stack_input_current_lower_bound - # el.cell_active_area = cell_active_area - # el.N_cells = N_cells - # print("running production rate") - # el.h2_production_rate() - - el.h2_production_rate() - el.water_supply() - - avg_generation = np.mean(electrical_generation_timeseries) # Avg Generation - # print("avg_generation: ", avg_generation) - cap_factor = avg_generation / kw_continuous - - hydrogen_hourly_production = out_dict["h2_produced_kg_hr_system"] - water_hourly_usage = out_dict["water_used_kg_hr"] - water_annual_usage = out_dict["water_used_kg_annual"] - electrolyzer_total_efficiency = out_dict["total_efficiency"] - # print('water annual: ', water_annual_usage) - # print("cap_factor: ", cap_factor) - - # Get Daily Hydrogen Production - Add Every 24 hours - i = 0 - daily_H2_production = [] - while i <= 8760: - x = sum(hydrogen_hourly_production[i : i + 24]) - daily_H2_production.append(x) - i = i + 24 - - avg_daily_H2_production = np.mean(daily_H2_production) # kgH2/day - hydrogen_annual_output = sum(hydrogen_hourly_production) # kgH2/year - # elec_remainder_after_h2 = combined_hybrid_curtailment_hopp - - H2A_Results = H2AModel.H2AModel( - cap_factor, - avg_daily_H2_production, - hydrogen_annual_output, - force_system_size=True, - forced_system_size=electrolyzer_size, - force_electrolyzer_cost=True, - forced_electrolyzer_cost_kw=forced_electrolyzer_cost_kw, - useful_life=useful_life, - ) - - feedstock_cost_h2_levelized_hopp = lcoe * total_system_electrical_usage / 100 # $/kg - # Hybrid Plant - levelized H2 Cost - HOPP - feedstock_cost_h2_via_net_cap_cost_lifetime_h2_hopp = adjusted_installed_cost / ( - hydrogen_annual_output * useful_life - ) # $/kgH2 - - # Total Hydrogen Cost ($/kgH2) - h2a_costs = H2A_Results["Total Hydrogen Cost ($/kgH2)"] - total_unit_cost_of_hydrogen = h2a_costs + feedstock_cost_h2_levelized_hopp - feedstock_cost_h2_via_net_cap_cost_lifetime_h2_reopt = net_capital_costs / ( - (kw_continuous / total_system_electrical_usage) * (8760 * useful_life) - ) - - H2_Results = { - "hydrogen_annual_output": hydrogen_annual_output, - "feedstock_cost_h2_levelized_hopp": feedstock_cost_h2_levelized_hopp, - "feedstock_cost_h2_via_net_cap_cost_lifetime_h2_hopp": feedstock_cost_h2_via_net_cap_cost_lifetime_h2_hopp, # noqa: E501 - "feedstock_cost_h2_via_net_cap_cost_lifetime_h2_reopt": feedstock_cost_h2_via_net_cap_cost_lifetime_h2_reopt, # noqa: E501 - "total_unit_cost_of_hydrogen": total_unit_cost_of_hydrogen, - "cap_factor": cap_factor, - "hydrogen_hourly_production": hydrogen_hourly_production, - "water_hourly_usage": water_hourly_usage, - "water_annual_usage": water_annual_usage, - "electrolyzer_total_efficiency": electrolyzer_total_efficiency, - } - - return H2_Results, H2A_Results diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_h2_clusters.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/run_h2_clusters.py deleted file mode 100644 index 4fe540ddb..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_h2_clusters.py +++ /dev/null @@ -1,233 +0,0 @@ -import sys - - -sys.path.append("") -import time -import warnings - -import numpy as np -import pandas as pd -from scipy import interpolate - -from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_H2_LT_electrolyzer_Clusters import ( # noqa: E501 - PEM_H2_Clusters as PEMClusters, -) - - -# from PyOMO import ipOpt !! FOR SANJANA!! -warnings.filterwarnings("ignore") - -""" -Perform a LCOH analysis for an offshore wind + Hydrogen PEM system - -1. Offshore wind site locations and cost details (4 sites, $1300/kw capex + BOS cost which will - come from Orbit Runs)~ -2. Cost Scaling Based on Year (Have Weiser et. al report with cost scaling for fixed and floating - tech, will implement) -3. Cost Scaling Based on Plant Size (Shields et. Al report) -4. Future Model Development Required: -- Floating Electrolyzer Platform -""" - - -# -# --------------------------- -# -class run_PEM_clusters: - """Add description and stuff :)""" - - def __init__(self, electrical_power_signal, system_size_mw, num_clusters, verbose=True): - self.cluster_cap_mw = np.round(system_size_mw / num_clusters) - self.num_clusters = num_clusters - - self.stack_rating_kw = 1000 - self.stack_min_power_kw = 0.1 * self.stack_rating_kw - # self.num_available_pem=interconnection_size_mw - self.input_power_kw = electrical_power_signal - self.cluster_min_power = self.stack_min_power_kw * self.cluster_cap_mw - self.cluster_max_power = self.stack_rating_kw * self.cluster_cap_mw - - self.verbose = verbose - - def run(self): - clusters = self.create_clusters() - power_to_clusters = self.even_split_power() - h2_df_ts = pd.DataFrame() - h2_df_tot = pd.DataFrame() - # h2_dict_ts={} - # h2_dict_tot={} - - col_names = [] - start = time.perf_counter() - for ci in range(len(clusters)): - cl_name = f"Cluster #{ci}" - col_names.append(cl_name) - h2_ts, h2_tot = clusters[ci].run(power_to_clusters[ci]) - # h2_dict_ts['Cluster #{}'.format(ci)] = h2_ts - - h2_ts_temp = pd.Series(h2_ts, name=cl_name) - h2_tot_temp = pd.Series(h2_tot, name=cl_name) - if len(h2_df_tot) == 0: - # h2_df_ts=pd.concat([h2_df_ts,h2_ts_temp],axis=0,ignore_index=False) - h2_df_tot = pd.concat([h2_df_tot, h2_tot_temp], axis=0, ignore_index=False) - h2_df_tot.columns = col_names - - h2_df_ts = pd.concat([h2_df_ts, h2_ts_temp], axis=0, ignore_index=False) - h2_df_ts.columns = col_names - else: - # h2_df_ts = h2_df_ts.join(h2_ts_temp) - h2_df_tot = h2_df_tot.join(h2_tot_temp) - h2_df_tot.columns = col_names - - h2_df_ts = h2_df_ts.join(h2_ts_temp) - h2_df_ts.columns = col_names - - end = time.perf_counter() - if self.verbose: - print(f"Took {round(end - start, 3)} sec to run the RUN function") - return h2_df_ts, h2_df_tot - # return h2_dict_ts, h2_df_tot - - def optimize_power_split(self): - # Inputs: power signal, number of stacks, cost of switching (assumed constant) - # install PyOMO - #!!! Insert Sanjana's Code !!! - # - power_per_stack = [] - return power_per_stack # size - - def even_split_power(self): - start = time.perf_counter() - # determine how much power to give each cluster - num_clusters_on = np.floor(self.input_power_kw / self.cluster_min_power) - num_clusters_on = np.where( - num_clusters_on > self.num_clusters, self.num_clusters, num_clusters_on - ) - power_per_cluster = [ - self.input_power_kw[ti] / num_clusters_on[ti] if num_clusters_on[ti] > 0 else 0 - for ti, pwr in enumerate(self.input_power_kw) - ] - - power_per_to_active_clusters = np.array(power_per_cluster) - power_to_clusters = np.zeros((len(self.input_power_kw), self.num_clusters)) - for i, cluster_power in enumerate( - power_per_to_active_clusters - ): # np.arange(0,self.n_stacks,1): - clusters_off = self.num_clusters - int(num_clusters_on[i]) - no_power = np.zeros(clusters_off) - with_power = cluster_power * np.ones(int(num_clusters_on[i])) - tot_power = np.concatenate((with_power, no_power)) - power_to_clusters[i] = tot_power - - # power_to_clusters = np.repeat([power_per_cluster],self.num_clusters,axis=0) - end = time.perf_counter() - if self.verbose: - print(f"Took {round(end - start, 3)} sec to run basic_split_power function") - # rows are power, columns are stacks [300 x n_stacks] - - return np.transpose(power_to_clusters) - - def run_distributed_layout_power(self, wind_plant): - # need floris configuration! - x_load_percent = np.linspace(0.1, 1.0, 10) - - # ac2ac_transformer_eff = np.array( - # [90.63, 93.91, 95.63, 96.56, 97.19, 97.50, 97.66, 97.66, 97.66, 97.50] - # ) - ac2dc_rectification_eff = ( - np.array([96.54, 98.12, 98.24, 98.6, 98.33, 98.03, 97.91, 97.43, 97.04, 96.687]) / 100 - ) - dc2dc_rectification_eff = ( - np.array([91.46, 95.16, 96.54, 97.13, 97.43, 97.61, 97.61, 97.73, 97.67, 97.61]) / 100 - ) - rect_eff = ac2dc_rectification_eff * dc2dc_rectification_eff - f = interpolate.interp1d(x_load_percent, rect_eff) - start_idx = 0 - end_idx = 8760 - nTurbs = self.num_clusters - power_turbines = np.zeros((nTurbs, 8760)) - power_to_clusters = np.zeros((8760, self.num_clusters)) - ac2dc_rated_power_kw = wind_plant.turb_rating - - power_turbines[:, start_idx:end_idx] = ( - wind_plant._system_model.fi.get_turbine_powers().reshape((nTurbs, end_idx - start_idx)) - / 1000 - ) - power_to_clusters = (power_turbines) * (f(power_turbines / ac2dc_rated_power_kw)) - - # power_farm *((100 - 12.83)/100) / 1000 - - clusters = self.create_clusters() - - h2_df_ts = pd.DataFrame() - h2_df_tot = pd.DataFrame() - # h2_dict_ts={} - # h2_dict_tot={} - - col_names = [] - start = time.perf_counter() - for ci in range(len(clusters)): - cl_name = f"Cluster #{ci}" - col_names.append(cl_name) - h2_ts, h2_tot = clusters[ci].run(power_to_clusters[ci]) - # h2_dict_ts['Cluster #{}'.format(ci)] = h2_ts - - h2_ts_temp = pd.Series(h2_ts, name=cl_name) - h2_tot_temp = pd.Series(h2_tot, name=cl_name) - if len(h2_df_tot) == 0: - # h2_df_ts=pd.concat([h2_df_ts,h2_ts_temp],axis=0,ignore_index=False) - h2_df_tot = pd.concat([h2_df_tot, h2_tot_temp], axis=0, ignore_index=False) - h2_df_tot.columns = col_names - - h2_df_ts = pd.concat([h2_df_ts, h2_ts_temp], axis=0, ignore_index=False) - h2_df_ts.columns = col_names - else: - # h2_df_ts = h2_df_ts.join(h2_ts_temp) - h2_df_tot = h2_df_tot.join(h2_tot_temp) - h2_df_tot.columns = col_names - - h2_df_ts = h2_df_ts.join(h2_ts_temp) - h2_df_ts.columns = col_names - - end = time.perf_counter() - if self.verbose: - print(f"Took {round(end - start, 3)} sec to run the distributed PEM case function") - return h2_df_ts, h2_df_tot - - def max_h2_cntrl(self): - # run as many at lower power as possible - ... - - def min_deg_cntrl(self): - # run as few as possible - ... - - def create_clusters(self): - start = time.perf_counter() - # TODO fix the power input - don't make it required! - # in_dict={'dt':3600} - clusters = PEMClusters(cluster_size_mw=self.cluster_cap_mw) - stacks = [clusters] * self.num_clusters - end = time.perf_counter() - if self.verbose: - print(f"Took {round(end - start, 3)} sec to run the create clusters") - return stacks - - -if __name__ == "__main__": - system_size_mw = 1000 - num_clusters = 20 - cluster_cap_mw = system_size_mw / num_clusters - stack_rating_kw = 1000 - cluster_min_power_kw = 0.1 * stack_rating_kw * cluster_cap_mw - num_steps = 200 - power_rampup = np.arange( - cluster_min_power_kw, system_size_mw * stack_rating_kw, cluster_min_power_kw - ) - - # power_rampup = np.linspace(cluster_min_power_kw,system_size_mw*1000,num_steps) - power_rampdown = np.flip(power_rampup) - power_in = np.concatenate((power_rampup, power_rampdown)) - pem = run_PEM_clusters(power_in, system_size_mw, num_clusters) - - h2_ts, h2_tot = pem.run() diff --git a/tests/h2integrate/test_hydrogen/test_pem_mass_and_footprint.py b/tests/h2integrate/test_hydrogen/test_pem_mass_and_footprint.py deleted file mode 100644 index 6a6173300..000000000 --- a/tests/h2integrate/test_hydrogen/test_pem_mass_and_footprint.py +++ /dev/null @@ -1,18 +0,0 @@ -import pytest - -from h2integrate.simulation.technologies.hydrogen.electrolysis.pem_mass_and_footprint import ( - mass, - footprint, -) - - -def test_footprint_0mw(): - assert footprint(0.0) == 0.0 - - -def test_footprint_1mw(): - assert footprint(1) == 48 - - -def test_mass(): - assert mass(0.045) == pytest.approx(900.0, rel=1e-4) From df38966f8eeb907524d88244cc74712aa3be8942 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Thu, 27 Nov 2025 10:06:43 -0700 Subject: [PATCH 12/30] Added back pem master --- .../hydrogen/electrolysis/run_PEM_master.py | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py new file mode 100644 index 000000000..e0db3c726 --- /dev/null +++ b/h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py @@ -0,0 +1,220 @@ +import time +import warnings + +import numpy as np +import pandas as pd +from pyomo.environ import * # FIXME: no * imports, delete whole comment when fixed # noqa: F403 + +from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_H2_LT_electrolyzer_Clusters import ( # noqa: E501 + PEM_H2_Clusters as PEMClusters, +) + + +# from PyOMO import ipOpt !! FOR SANJANA!! +warnings.filterwarnings("ignore") + +""" +Perform a LCOH analysis for an offshore wind + Hydrogen PEM system + +1. Offshore wind site locations and cost details (4 sites, $1300/kw capex + BOS cost which will come + from Orbit Runs)~ +2. Cost Scaling Based on Year (Have Weiser et. al report with cost scaling for fixed and floating + tech, will implement) +3. Cost Scaling Based on Plant Size (Shields et. Al report) +4. Future Model Development Required: +- Floating Electrolyzer Platform +""" + + +# +# --------------------------- +# +class run_PEM_clusters: + """Inputs: + `electrical_power_signal`: plant power signal in kWh + `system_size_mw`: total installed electrolyzer capacity (for green steel this is 1000 MW) + `num_clusters`: number of PEM clusters that can be run independently + ->ESG note: I have been using num_clusters = 8 for centralized cases + Nomenclature: + `cluster`: cluster is built up of 1MW stacks + `stack`: must be 1MW (because of current PEM model) + """ + + def __init__( + self, + electrical_power_signal, + system_size_mw, + num_clusters, + electrolyzer_direct_cost_kw, + useful_life, + user_defined_electrolyzer_params, + verbose=True, + ): + # nomen + self.cluster_cap_mw = np.round(system_size_mw / num_clusters) + # capacity of each cluster, must be a multiple of 1 MW + + self.num_clusters = num_clusters + self.user_params = user_defined_electrolyzer_params + self.plant_life_yrs = useful_life + # Do not modify stack_rating_kw or stack_min_power_kw + # these represent the hard-coded and unmodifiable + # PEM model basecode + turndown_ratio = user_defined_electrolyzer_params["turndown_ratio"] + self.stack_rating_kw = 1000 # single stack rating - DO NOT CHANGE + self.stack_min_power_kw = turndown_ratio * self.stack_rating_kw + # self.stack_min_power_kw = 0.1 * self.stack_rating_kw + self.input_power_kw = electrical_power_signal + self.cluster_min_power = self.stack_min_power_kw * self.cluster_cap_mw + self.cluster_max_power = self.stack_rating_kw * self.cluster_cap_mw + + # For the optimization problem: + self.T = len(self.input_power_kw) + self.farm_power = 1e9 + self.switching_cost = ( + (electrolyzer_direct_cost_kw * 0.15 * self.cluster_cap_mw * 1000) + * (1.48e-4) + / (0.26586) + ) + self.verbose = verbose + + def run_grid_connected_pem(self, system_size_mw, hydrogen_production_capacity_required_kgphr): + pem = PEMClusters( + system_size_mw, + self.plant_life_yrs, + **self.user_params, + ) + + power_timeseries, stack_current = pem.grid_connected_func( + hydrogen_production_capacity_required_kgphr + ) + h2_ts, h2_tot = pem.run_grid_connected_workaround(power_timeseries, stack_current) + # h2_ts, h2_tot = pem.run(power_timeseries) + h2_df_ts = pd.Series(h2_ts, name="Cluster #0") + h2_df_tot = pd.Series(h2_tot, name="Cluster #0") + # h2_df_ts = pd.DataFrame(h2_ts, index=list(h2_ts.keys()), columns=['Cluster #0']) + # h2_df_tot = pd.DataFrame(h2_tot, index=list(h2_tot.keys()), columns=['Cluster #0']) + return pd.DataFrame(h2_df_ts), pd.DataFrame(h2_df_tot) + + def run(self): + # TODO: add control type as input! + clusters = self.create_clusters() # initialize clusters + power_to_clusters = self.even_split_power() + h2_df_ts = pd.DataFrame() + h2_df_tot = pd.DataFrame() + + col_names = [] + start = time.perf_counter() + for ci in range(len(clusters)): + cl_name = f"Cluster #{ci}" + col_names.append(cl_name) + h2_ts, h2_tot = clusters[ci].run(power_to_clusters[ci]) + # h2_dict_ts['Cluster #{}'.format(ci)] = h2_ts + + h2_ts_temp = pd.Series(h2_ts, name=cl_name) + h2_tot_temp = pd.Series(h2_tot, name=cl_name) + if len(h2_df_tot) == 0: + # h2_df_ts=pd.concat([h2_df_ts,h2_ts_temp],axis=0,ignore_index=False) + h2_df_tot = pd.concat([h2_df_tot, h2_tot_temp], axis=0, ignore_index=False) + h2_df_tot.columns = col_names + + h2_df_ts = pd.concat([h2_df_ts, h2_ts_temp], axis=0, ignore_index=False) + h2_df_ts.columns = col_names + else: + # h2_df_ts = h2_df_ts.join(h2_ts_temp) + h2_df_tot = h2_df_tot.join(h2_tot_temp) + h2_df_tot.columns = col_names + + h2_df_ts = h2_df_ts.join(h2_ts_temp) + h2_df_ts.columns = col_names + + end = time.perf_counter() + self.clusters = clusters + if self.verbose: + print(f"Took {round(end - start, 3)} sec to run the RUN function") + return h2_df_ts, h2_df_tot + # return h2_dict_ts, h2_df_tot + + def even_split_power(self): + start = time.perf_counter() + # determine how much power to give each cluster + num_clusters_on = np.floor(self.input_power_kw / self.cluster_min_power) + num_clusters_on = np.where( + num_clusters_on > self.num_clusters, self.num_clusters, num_clusters_on + ) + power_per_cluster = [ + self.input_power_kw[ti] / num_clusters_on[ti] if num_clusters_on[ti] > 0 else 0 + for ti, pwr in enumerate(self.input_power_kw) + ] + + power_per_to_active_clusters = np.array(power_per_cluster) + power_to_clusters = np.zeros((len(self.input_power_kw), self.num_clusters)) + for i, cluster_power in enumerate( + power_per_to_active_clusters + ): # np.arange(0,self.n_stacks,1): + clusters_off = self.num_clusters - int(num_clusters_on[i]) + no_power = np.zeros(clusters_off) + with_power = cluster_power * np.ones(int(num_clusters_on[i])) + tot_power = np.concatenate((with_power, no_power)) + power_to_clusters[i] = tot_power + + # power_to_clusters = np.repeat([power_per_cluster],self.num_clusters,axis=0) + end = time.perf_counter() + + if self.verbose: + print(f"Took {round(end - start, 3)} sec to run even_split_power function") + # rows are power, columns are stacks [300 x n_stacks] + + return np.transpose(power_to_clusters) + + def max_h2_cntrl(self): + # run as many at lower power as possible + ... + + def min_deg_cntrl(self): + # run as few as possible + ... + + def create_clusters(self): + start = time.perf_counter() + # TODO fix the power input - don't make it required! + # in_dict={'dt':3600} + clusters = PEMClusters(self.cluster_cap_mw, self.plant_life_yrs, **self.user_params) + stacks = [clusters] * self.num_clusters + end = time.perf_counter() + if self.verbose: + print(f"Took {round(end - start, 3)} sec to run the create clusters") + return stacks + + +if __name__ == "__main__": + system_size_mw = 1000 + num_clusters = 20 + cluster_cap_mw = system_size_mw / num_clusters + stack_rating_kw = 1000 + cluster_min_power_kw = 0.1 * stack_rating_kw * cluster_cap_mw + num_steps = 200 + power_rampup = np.arange( + cluster_min_power_kw, system_size_mw * stack_rating_kw, cluster_min_power_kw + ) + + plant_life = 30 + electrolyzer_model_parameters = { + "eol_eff_percent_loss": 10, + "uptime_hours_until_eol": 77600, + "include_degradation_penalty": True, + "turndown_ratio": 0.1, + } + # power_rampup = np.linspace(cluster_min_power_kw,system_size_mw*1000,num_steps) + power_rampdown = np.flip(power_rampup) + power_in = np.concatenate((power_rampup, power_rampdown)) + pem = run_PEM_clusters( + power_in, + system_size_mw, + num_clusters, + plant_life, + electrolyzer_model_parameters, + ) + + h2_ts, h2_tot = pem.run() + # pem.clusters[0].cell_design(80,1920*2) From ac8573b172a7055c2ac4ad63204e9910b82cd996 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Thu, 27 Nov 2025 10:14:38 -0700 Subject: [PATCH 13/30] Moving PEM model --- .../hydrogen/eco_tools_pem_electrolyzer.py | 2 +- .../PEM_H2_LT_electrolyzer_Clusters.py | 200 ------- .../converters/hydrogen/pem_model/__init__.py | 0 .../hydrogen/pem_model}/run_PEM_master.py | 33 -- .../hydrogen/pem_model}/run_h2_PEM.py | 0 .../electrolysis/PEM_H2_LT_electrolyzer.py | 516 ------------------ .../hydrogen/electrolysis/__init__.py | 13 - 7 files changed, 1 insertion(+), 763 deletions(-) rename h2integrate/{simulation/technologies/hydrogen/electrolysis => converters/hydrogen/pem_model}/PEM_H2_LT_electrolyzer_Clusters.py (87%) create mode 100644 h2integrate/converters/hydrogen/pem_model/__init__.py rename h2integrate/{simulation/technologies/hydrogen/electrolysis => converters/hydrogen/pem_model}/run_PEM_master.py (88%) rename h2integrate/{simulation/technologies/hydrogen/electrolysis => converters/hydrogen/pem_model}/run_h2_PEM.py (100%) delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_H2_LT_electrolyzer.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/electrolysis/__init__.py diff --git a/h2integrate/converters/hydrogen/eco_tools_pem_electrolyzer.py b/h2integrate/converters/hydrogen/eco_tools_pem_electrolyzer.py index 49b8b5ea8..e7533db7d 100644 --- a/h2integrate/converters/hydrogen/eco_tools_pem_electrolyzer.py +++ b/h2integrate/converters/hydrogen/eco_tools_pem_electrolyzer.py @@ -2,8 +2,8 @@ from h2integrate.core.utilities import BaseConfig, merge_shared_inputs from h2integrate.core.validators import gt_zero, contains +from h2integrate.converters.hydrogen.pem_model.run_h2_PEM import run_h2_PEM from h2integrate.converters.hydrogen.electrolyzer_baseclass import ElectrolyzerPerformanceBaseClass -from h2integrate.simulation.technologies.hydrogen.electrolysis.run_h2_PEM import run_h2_PEM def ceildiv(a, b): diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_H2_LT_electrolyzer_Clusters.py b/h2integrate/converters/hydrogen/pem_model/PEM_H2_LT_electrolyzer_Clusters.py similarity index 87% rename from h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_H2_LT_electrolyzer_Clusters.py rename to h2integrate/converters/hydrogen/pem_model/PEM_H2_LT_electrolyzer_Clusters.py index eeefa4b1c..cd09dc742 100644 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_H2_LT_electrolyzer_Clusters.py +++ b/h2integrate/converters/hydrogen/pem_model/PEM_H2_LT_electrolyzer_Clusters.py @@ -27,7 +27,6 @@ import pandas as pd import rainflow from scipy import interpolate -from matplotlib import pyplot as plt np.set_printoptions(threshold=sys.maxsize) @@ -992,202 +991,3 @@ def run_grid_connected_workaround(self, power_input_signal, current_signal): h2_results_aggregates["Time until replacement [hours]"] = time_until_replacement return h2_results, h2_results_aggregates - - -if __name__ == "__main__": - cluster_size_mw = 1 # MW - plant_life = 30 # years - electrolyzer_model_parameters = { - "eol_eff_percent_loss": 10, # percent - for calculating EOL voltage and steady deg rate - "uptime_hours_until_eol": 77600, # for calculating steady deg rate - "include_degradation_penalty": True, - "turndown_ratio": 0.1, - "curve_coeff": [ - 4.0519644766515644e-08, - -0.00026186723338675105, - 3.8985774154190334, - 7.615382921418666, - -20.075110413404484, - 1.0, - ], - } - # Create PEM and initialize parameters - pem = PEM_H2_Clusters(cluster_size_mw, plant_life, **electrolyzer_model_parameters) - # ----- Run off-grid case ----- - # Make a mock input power signal - hours_in_year = 8760 - power_signal_kW_ramp_up = np.arange(0, 1000 * cluster_size_mw, 20) - power_signal_kW_ramp_down = np.flip(power_signal_kW_ramp_up) - power_signal_kW_ramp_updown = np.concatenate( - (power_signal_kW_ramp_down, power_signal_kW_ramp_up) - ) - n_repeats = int(np.ceil(hours_in_year / len(power_signal_kW_ramp_updown))) - power_signal_kW = np.tile(power_signal_kW_ramp_updown, n_repeats) - # Run the electrolyzer and get outputs - h2_results, h2_results_aggregates = pem.run(power_signal_kW) - - # ----- Run grid-connected case ----- - h2_kg_hr_system_required = 12 # kg-H2/hr - target_h2_per_year = h2_kg_hr_system_required * hours_in_year - power_required_kW, stack_current_signal = pem.grid_connected_func(h2_kg_hr_system_required) - h2_results_grid, h2_results_aggregates_grid = pem.run_grid_connected_workaround( - power_required_kW, stack_current_signal - ) - - # Refurbishment Schedule (RS) [MW/year] and Year of Replacement (YOR) - offgrid_RS = np.array( - list( - h2_results_aggregates["Performance By Year"][ - "Refurbishment Schedule [MW replaced/year]" - ].values() - ) - ) - offgrid_YOR = np.argwhere(offgrid_RS > 0)[:, 0] - grid_RS = np.array( - list( - h2_results_aggregates_grid["Performance By Year"][ - "Refurbishment Schedule [MW replaced/year]" - ].values() - ) - ) - grid_YOR = np.argwhere(grid_RS > 0)[:, 0] - # ----- Plot and compare results ----- - year_of_operation = list( - h2_results_aggregates["Performance By Year"]["Capacity Factor [-]"].keys() - ) - fig, ax = plt.subplots(nrows=3, ncols=1, sharex=True, figsize=[6.4, 7.2]) - - # Annual Hydrogen Produced (AH2) [metric tons H2/year] - offgrid_AH2 = ( - np.array( - list( - h2_results_aggregates["Performance By Year"][ - "Annual H2 Production [kg/year]" - ].values() - ) - ) - / 1000 - ) - grid_AH2 = ( - np.array( - list( - h2_results_aggregates_grid["Performance By Year"][ - "Annual H2 Production [kg/year]" - ].values() - ) - ) - / 1000 - ) - - ax[0].plot(year_of_operation, offgrid_AH2, color="b", ls="-", lw=2, label="off-grid") - ax[0].plot(year_of_operation, grid_AH2, color="r", ls="--", lw=2, label="grid-connected") - ax[0].vlines( - offgrid_YOR, - np.zeros(len(offgrid_YOR)), - offgrid_AH2[offgrid_YOR], - colors="skyblue", - ls="-", - lw=0.5, - ) - ax[0].vlines( - grid_YOR, - np.zeros(len(grid_YOR)), - grid_AH2[grid_YOR], - colors="tomato", - ls="--", - lw=0.5, - ) - ax[0].set_ylabel("Annual Hydrogen Produced\n[metric tons/year]") - ax[0].legend(loc="upper right") - ax[0].set_ylim((0.8 * np.min([offgrid_AH2, grid_AH2]), 1.2 * np.max([offgrid_AH2, grid_AH2]))) - - # Annual Energy Used (AEU) [MWh/year] - offgrid_AEU = ( - np.array( - list( - h2_results_aggregates["Performance By Year"][ - "Annual Energy Used [kWh/year]" - ].values() - ) - ) - / 1000 - ) - grid_AEU = ( - np.array( - list( - h2_results_aggregates_grid["Performance By Year"][ - "Annual Energy Used [kWh/year]" - ].values() - ) - ) - / 1000 - ) - ax[1].plot(year_of_operation, offgrid_AEU, color="b", ls="-", lw=2, label="_off-grid") - ax[1].plot(year_of_operation, grid_AEU, color="r", ls="--", lw=2, label="_grid-connected") - ax[1].vlines( - offgrid_YOR, - np.zeros(len(offgrid_YOR)), - offgrid_AEU[offgrid_YOR], - colors="skyblue", - ls="-", - lw=0.5, - label="off-grid stack replacement", - ) - ax[1].vlines( - grid_YOR, - np.zeros(len(grid_YOR)), - grid_AEU[grid_YOR], - colors="tomato", - ls="--", - lw=0.5, - label="grid-connected stack replacement", - ) - ax[1].set_ylabel("Annual Energy Used\n[MWh/year]") - ax[1].legend(loc="upper right") - ax[1].set_ylim((0.8 * np.min([offgrid_AEU, grid_AEU]), 1.2 * np.max([offgrid_AEU, grid_AEU]))) - - # Annual Average Conversion Efficiency (AACE) [kWh/kg] - offgrid_AACE = np.array( - list( - h2_results_aggregates["Performance By Year"][ - "Annual Average Efficiency [kWh/kg]" - ].values() - ) - ) - grid_AACE = np.array( - list( - h2_results_aggregates_grid["Performance By Year"][ - "Annual Average Efficiency [kWh/kg]" - ].values() - ) - ) - ax[2].plot(year_of_operation, offgrid_AACE, color="b", ls="-", lw=2, label="_off-grid") - ax[2].plot(year_of_operation, grid_AACE, color="r", ls="--", lw=2, label="_grid-connected") - ax[2].vlines( - offgrid_YOR, - np.zeros(len(offgrid_YOR)), - offgrid_AACE[offgrid_YOR], - colors="skyblue", - ls="-", - lw=0.5, - label="off-grid stack replacement", - ) - ax[2].vlines( - grid_YOR, - np.zeros(len(grid_YOR)), - grid_AACE[grid_YOR], - colors="tomato", - ls="--", - lw=0.5, - label="grid-connected stack replacement", - ) - ax[2].set_ylabel("Annual Average Efficiency\n[kWh/kg]") - ax[2].set_xlabel("Year of Operation") - ax[2].set_xlim((0, plant_life)) - ax[2].set_ylim( - ( - 0.95 * np.min([offgrid_AACE, grid_AACE]), - 1.05 * np.max([offgrid_AACE, grid_AACE]), - ) - ) - fig.tight_layout() diff --git a/h2integrate/converters/hydrogen/pem_model/__init__.py b/h2integrate/converters/hydrogen/pem_model/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py b/h2integrate/converters/hydrogen/pem_model/run_PEM_master.py similarity index 88% rename from h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py rename to h2integrate/converters/hydrogen/pem_model/run_PEM_master.py index e0db3c726..df0176ec1 100644 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_PEM_master.py +++ b/h2integrate/converters/hydrogen/pem_model/run_PEM_master.py @@ -185,36 +185,3 @@ def create_clusters(self): if self.verbose: print(f"Took {round(end - start, 3)} sec to run the create clusters") return stacks - - -if __name__ == "__main__": - system_size_mw = 1000 - num_clusters = 20 - cluster_cap_mw = system_size_mw / num_clusters - stack_rating_kw = 1000 - cluster_min_power_kw = 0.1 * stack_rating_kw * cluster_cap_mw - num_steps = 200 - power_rampup = np.arange( - cluster_min_power_kw, system_size_mw * stack_rating_kw, cluster_min_power_kw - ) - - plant_life = 30 - electrolyzer_model_parameters = { - "eol_eff_percent_loss": 10, - "uptime_hours_until_eol": 77600, - "include_degradation_penalty": True, - "turndown_ratio": 0.1, - } - # power_rampup = np.linspace(cluster_min_power_kw,system_size_mw*1000,num_steps) - power_rampdown = np.flip(power_rampup) - power_in = np.concatenate((power_rampup, power_rampdown)) - pem = run_PEM_clusters( - power_in, - system_size_mw, - num_clusters, - plant_life, - electrolyzer_model_parameters, - ) - - h2_ts, h2_tot = pem.run() - # pem.clusters[0].cell_design(80,1920*2) diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/run_h2_PEM.py b/h2integrate/converters/hydrogen/pem_model/run_h2_PEM.py similarity index 100% rename from h2integrate/simulation/technologies/hydrogen/electrolysis/run_h2_PEM.py rename to h2integrate/converters/hydrogen/pem_model/run_h2_PEM.py diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_H2_LT_electrolyzer.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_H2_LT_electrolyzer.py deleted file mode 100644 index 2b964b6ec..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/PEM_H2_LT_electrolyzer.py +++ /dev/null @@ -1,516 +0,0 @@ -## Low-Temperature PEM Electrolyzer Model -""" -Python model of H2 PEM low-temp electrolyzer. - -Quick Hydrogen Physics: - -1 kg H2 <-> 11.1 N-m3 <-> 33.3 kWh (LHV) <-> 39.4 kWh (HHV) - -High mass energy density (1 kg H2= 3,77 l gasoline) -Low volumetric density (1 Nm³ H2= 0,34 l gasoline - -Hydrogen production from water electrolysis (~5 kWh/Nm³ H2) - -Power:1 MW electrolyser <-> 200 Nm³/h H2 <-> ±18 kg/h H2 -Energy:+/-55 kWh of electricity --> 1 kg H2 <-> 11.1 Nm³ <-> ±10 liters -demineralized water - -Power production from a hydrogen PEM fuel cell from hydrogen (+/-50% -efficiency): -Energy: 1 kg H2 --> 16 kWh -""" - -import sys -import math - -import numpy as np -import pandas as pd -from matplotlib import pyplot as plt - - -np.set_printoptions(threshold=sys.maxsize) - - -class PEM_electrolyzer_LT: - """ - Create an instance of a low-temperature PEM Electrolyzer System. Each - stack in the electrolyzer system in this model is rated at 1 MW_DC. - - Parameters - _____________ - np_array P_input_external_kW - 1-D array of time-series external power supply - - string voltage_type - Nature of voltage supplied to electrolyzer from the external power - supply ['variable' or 'constant] - - float power_supply_rating_MW - Rated power of external power supply - - Returns - _____________ - - """ - - def __init__(self, input_dict, output_dict): - self.input_dict = input_dict - self.output_dict = output_dict - - # array of input power signal - self.input_dict["P_input_external_kW"] = input_dict["P_input_external_kW"] - self.electrolyzer_system_size_MW = input_dict["electrolyzer_system_size_MW"] - - # self.input_dict['voltage_type'] = 'variable' # not yet implemented - self.input_dict["voltage_type"] = "constant" - self.stack_input_voltage_DC = 250 - - # Assumptions: - self.min_V_cell = 1.62 # Only used in variable voltage scenario - self.p_s_h2_bar = 31 # H2 outlet pressure - self.stack_input_current_lower_bound = 500 - self.stack_rating_kW = 1000 # 1 MW - self.cell_active_area = 1250 - self.N_cells = 130 - - # Constants: - self.moles_per_g_h2 = 0.49606 - self.V_TN = 1.48 # Thermo-neutral Voltage (Volts) - self.F = 96485 # Faraday's Constant (C/mol) - self.R = 8.314 # Ideal Gas Constant (J/mol/K) - - self.external_power_supply() - - def external_power_supply(self): - """ - External power source (grid or REG) which will need to be stepped - down and converted to DC power for the electrolyzer. - - Please note, for a wind farm as the electrolyzer's power source, - the model assumes variable power supplied to the stack at fixed - voltage (fixed voltage, variable power and current) - - TODO: extend model to accept variable voltage, current, and power - This will replicate direct DC-coupled PV system operating at MPP - """ - power_converter_efficiency = 0.95 - if self.input_dict["voltage_type"] == "constant": - self.input_dict["P_input_external_kW"] = np.where( - self.input_dict["P_input_external_kW"] > (self.electrolyzer_system_size_MW * 1000), - (self.electrolyzer_system_size_MW * 1000), - self.input_dict["P_input_external_kW"], - ) - - self.output_dict["curtailed_P_kW"] = np.where( - self.input_dict["P_input_external_kW"] > (self.electrolyzer_system_size_MW * 1000), - ( - self.input_dict["P_input_external_kW"] - - (self.electrolyzer_system_size_MW * 1000) - ), - 0, - ) - - self.output_dict["current_input_external_Amps"] = ( - self.input_dict["P_input_external_kW"] * 1000 * power_converter_efficiency - ) / (self.stack_input_voltage_DC * self.system_design()) - - self.output_dict["stack_current_density_A_cm2"] = ( - self.output_dict["current_input_external_Amps"] / self.cell_active_area - ) - - self.output_dict["current_input_external_Amps"] = np.where( - self.output_dict["current_input_external_Amps"] - < self.stack_input_current_lower_bound, - 0, - self.output_dict["current_input_external_Amps"], - ) - - else: - pass # TODO: extend model to variable voltage and current source - - def system_design(self): - """ - For now, system design is solely a function of max. external power - supply; i.e., a rated power supply of 50 MW means that the electrolyzer - system developed by this model is also rated at 50 MW - - TODO: Extend model to include this capability. - Assume that a PEM electrolyzer behaves as a purely resistive load - in a circuit, and design the configuration of the entire electrolyzer - system - which may consist of multiple stacks connected together in - series, parallel, or a combination of both. - """ - h2_production_multiplier = (self.electrolyzer_system_size_MW * 1000) / self.stack_rating_kW - self.output_dict["electrolyzer_system_size_MW"] = self.electrolyzer_system_size_MW - return h2_production_multiplier - - def cell_design(self): - """ - Creates an I-V (polarization) curve of each cell in a stack. - - Please note that this method is currently not used in the model. It - will be used once the electrolyzer model is expanded to variable - voltage supply as well as implementation of the self.system_design() - method - - Motivation: - - The most common representation of the electrolyzer performance is the - polarization curve that represents the relation between the current density - and the voltage (V): - Source: https://www.sciencedirect.com/science/article/pii/S0959652620312312 - - V = N_c(E_cell + V_Act,c + V_Act,a + iR_cell) - - where N_c is the number of electrolyzer cells,E_cell is the open circuit - voltage VAct,and V_Act,c are the anode and cathode activation over-potentials, - i is the current density and iRcell is the electrolyzer cell resistance - (ohmic losses). - - Use this to make a V vs. A (Amperes/cm2) graph which starts at 1.23V because - thermodynamic reaction of water formation/splitting dictates that standard - electrode potential has a ∆G of 237 kJ/mol (where: ∆H = ∆G + T∆S) - """ - - # Cell level inputs: - N_cells = 130 - self.cell_active_area / N_cells - (self.stack_rating_kW * 1000) / N_cells - - # V_cell_max = 3.0 #Volts - # V_cell_I_density_max = 2.50 #mA/cm2 - E_rev = 1.23 # (in Volts) Reversible potential at 25degC - T_C = 80 # Celsius - T_K = T_C + 273.15 # in Kelvins - # E_cell == Open Circuit Voltage - E_cell = ( - 1.5184 - - (1.5421 * (10 ** (-3)) * T_K) - + (9.523 * (10 ** (-5)) * T_K * math.log(T_K)) - + (9.84 * (10 ** (-8)) * (T_K**2)) - ) - # V_act = V_act_c + V_Act_a - R = 8.314 # Ideal Gas Constant (J/mol/K) - i = self.output_dict["stack_current_density_A_cm2"] - F = 96485 # Faraday's Constant (C/mol) - - # Following coefficient values obtained from Yigit and Selamet (2016) - - # https://www.sciencedirect.com/science/article/pii/S0360319916318341?via%3Dihub - a_a = 2 # Anode charge transfer coefficient - a_c = 0.5 # Cathode charge transfer coefficient - i_o_a = 2 * (10 ** (-7)) - i_o_c = 2 * (10 ** (-3)) - V_act = (((R * T_K) / (a_a * F)) * np.arcsinh(i / (2 * i_o_a))) + ( - ((R * T_K) / (a_c * F)) * np.arcsinh(i / (2 * i_o_c)) - ) - lambda_water_content = ((-2.89556 + (0.016 * T_K)) + 1.625) / 0.1875 - delta = 0.0003 # membrane thickness (cm) - assuming a 3-µm thick membrane - sigma = ((0.005139 * lambda_water_content) - 0.00326) * math.exp( - 1268 * ((1 / 303) - (1 / T_K)) - ) # Material thickness # material conductivity - R_cell = delta / sigma - V_cell = E_cell + V_act + (i * R_cell) - V_cell = np.where(V_cell < E_rev, E_rev, V_cell) - N_cells * V_cell # Stack operational voltage - - def dynamic_operation(self): - """ - Model the electrolyzer's realistic response/operation under variable RE - - TODO: add this capability to the model - """ - # When electrolyzer is already at or near its optimal operation - # temperature (~80degC) - - def water_electrolysis_efficiency(self): - """ - https://www.sciencedirect.com/science/article/pii/S2589299119300035#b0500 - - According to the first law of thermodynamics energy is conserved. - Thus, the conversion efficiency calculated from the yields of - converted electrical energy into chemical energy. Typically, - water electrolysis efficiency is calculated by the higher heating - value (HHV) of hydrogen. Since the electrolysis process water is - supplied to the cell in liquid phase efficiency can be calculated by: - - n_T = V_TN / V_cell - - where, V_TN is the thermo-neutral voltage (min. required V to - electrolyze water) - - Parameters - ______________ - - Returns - ______________ - - """ - - n_T = self.V_TN / (self.stack_input_voltage_DC / self.N_cells) - return n_T - - def faradaic_efficiency(self): - """ - Text background from: - [https://www.researchgate.net/publication/344260178_Faraday%27s_ - Efficiency_Modeling_of_a_Proton_Exchange_Membrane_Electrolyzer_ - Based_on_Experimental_Data] - - In electrolyzers, Faraday's efficiency is a relevant parameter to - assess the amount of hydrogen generated according to the input - energy and energy efficiency. Faraday's efficiency expresses the - faradaic losses due to the gas crossover current. The thickness - of the membrane and operating conditions (i.e., temperature, gas - pressure) may affect the Faraday's efficiency. - - Equation for n_F obtained from: - https://www.sciencedirect.com/science/article/pii/S0360319917347237#bib27 - - Parameters - ______________ - float f_1 - Coefficient - value at operating temperature of 80degC (mA2/cm4) - - float f_2 - Coefficient - value at operating temp of 80 degC (unitless) - - np_array current_input_external_Amps - 1-D array of current supplied to electrolyzer stack from external - power source - - - Returns - ______________ - - float n_F - Faradaic efficiency (unitless) - - """ - f_1 = 250 # Coefficient (mA2/cm4) - f_2 = 0.996 # Coefficient (unitless) - I_cell = self.output_dict["current_input_external_Amps"] * 1000 - - # Faraday efficiency - n_F = ( - ((I_cell / self.cell_active_area) ** 2) - / (f_1 + ((I_cell / self.cell_active_area) ** 2)) - ) * f_2 - - return n_F - - def compression_efficiency(self): - """ - In industrial contexts, the remaining hydrogen should be stored at - certain storage pressures that vary depending on the intended - application. In the case of subsequent compression, pressure-volume - work, Wc, must be performed. The additional pressure-volume work can - be related to the heating value of storable hydrogen. Then, the total - efficiency reduces by the following factor: - https://www.mdpi.com/1996-1073/13/3/612/htm - - Due to reasons of material properties and operating costs, large - amounts of gaseous hydrogen are usually not stored at pressures - exceeding 100 bar in aboveground vessels and 200 bar in underground - storages - https://www.sciencedirect.com/science/article/pii/S0360319919310195 - - Partial pressure of H2(g) calculated using: - The hydrogen partial pressure is calculated as a difference between - the cathode pressure, 101,325 Pa, and the water saturation - pressure - [Source: Energies2018,11,3273; doi:10.3390/en11123273] - - """ - n_limC = 0.825 # Limited efficiency of gas compressors (unitless) - H_LHV = 241 # Lower heating value of H2 (kJ/mol) - K = 1.4 # Average heat capacity ratio (unitless) - C_c = 2.75 # Compression factor (ratio of pressure after and before compression) - n_F = self.faradaic_efficiency() - j = self.output_dict["stack_current_density_A_cm2"] - n_x = ((1 - n_F) * j) * self.cell_active_area - n_h2 = j * self.cell_active_area - Z = 1 # [Assumption] Average compressibility factor (unitless) - T_in_C = 80 # Assuming electrolyzer operates at 80degC - T_in = 273.15 + T_in_C # (Kelvins) Assuming electrolyzer operates at 80degC - W_1_C = ( - (K / (K - 1)) - * ((n_h2 - n_x) / self.F) - * self.R - * T_in - * Z - * ((C_c ** ((K - 1) / K)) - 1) - ) # Single stage compression - - # Calculate partial pressure of H2 at the cathode: - A = 8.07131 - B = 1730.63 - C = 233.426 - p_h2o_sat = 10 ** (A - (B / (C + T_in_C))) # Pa - p_cat = 101325 # Cathode pressure (Pa) - p_h2_cat = p_cat - p_h2o_sat - p_s_h2_Pa = self.p_s_h2_bar * 1e5 - - s_C = math.log10(p_s_h2_Pa / p_h2_cat) / math.log10(C_c) - W_C = round(s_C) * W_1_C # Pressure-Volume work - energy reqd. for compression - net_energy_carrier = n_h2 - n_x # C/s - net_energy_carrier = np.where((n_h2 - n_x) == 0, 1, net_energy_carrier) - n_C = 1 - ((W_C / (((net_energy_carrier) / self.F) * H_LHV * 1000)) * (1 / n_limC)) - n_C = np.where((n_h2 - n_x) == 0, 0, n_C) - return n_C - - def total_efficiency(self): - """ - Aside from efficiencies accounted for in this model - (water_electrolysis_efficiency, faradaic_efficiency, and - compression_efficiency) all process steps such as gas drying above - 2 bar or water pumping can be assumed as negligible. Ultimately, the - total efficiency or system efficiency of a PEM electrolysis system is: - - n_T = n_p_h2 * n_F_h2 * n_c_h2 - https://www.mdpi.com/1996-1073/13/3/612/htm - """ - n_p_h2 = self.water_electrolysis_efficiency() - n_F_h2 = self.faradaic_efficiency() - n_c_h2 = self.compression_efficiency() - - n_T = n_p_h2 * n_F_h2 * n_c_h2 - self.output_dict["total_efficiency"] = n_T - return n_T - - def h2_production_rate(self): - """ - H2 production rate calculated using Faraday's Law of Electrolysis - (https://www.sciencedirect.com/science/article/pii/S0360319917347237#bib27) - - Parameters - _____________ - - float f_1 - Coefficient - value at operating temperature of 80degC (mA2/cm4) - - float f_2 - Coefficient - value at operating temp of 80 degC (unitless) - - np_array - 1-D array of current supplied to electrolyzer stack from external - power source - - - Returns - _____________ - - """ - # Single stack calculations: - n_Tot = self.total_efficiency() - h2_production_rate = n_Tot * ( - (self.N_cells * self.output_dict["current_input_external_Amps"]) / (2 * self.F) - ) # mol/s - h2_production_rate_g_s = h2_production_rate / self.moles_per_g_h2 - h2_produced_kg_hr = ( - h2_production_rate_g_s * 3.6 * 72.55 / 55.5 - ) ## TEMPORARY CORRECTION APPLIED FOR PEM EFFICIENCY to reach expected 55.5kwh/kg value - - self.output_dict["stack_h2_produced_kg_hr"] = h2_produced_kg_hr - - # Total electrolyzer system calculations: - h2_produced_kg_hr_system = self.system_design() * h2_produced_kg_hr - # h2_produced_kg_hr_system = h2_produced_kg_hr - self.output_dict["h2_produced_kg_hr_system"] = h2_produced_kg_hr_system - - return h2_produced_kg_hr_system - - def degradation(self): - """ - TODO - Add a time component to the model - for degradation -> - https://www.hydrogen.energy.gov/pdfs/progress17/ii_b_1_peters_2017.pdf - """ - pass - - def water_supply(self): - """ - Calculate water supply rate based system efficiency and H2 production - rate - TODO: Add this capability to the model - """ - water_used_kg_hr_system = self.h2_production_rate() * 10 - self.output_dict["water_used_kg_hr"] = water_used_kg_hr_system - self.output_dict["water_used_kg_annual"] = np.sum(water_used_kg_hr_system) - - def h2_storage(self): - """ - Model to estimate Ideal Isorthermal H2 compression at 70degC - https://www.sciencedirect.com/science/article/pii/S036031991733954X - - The amount of hydrogen gas stored under pressure can be estimated - using the van der Waals equation - - p = [(nRT)/(V-nb)] - [a * ((n^2) / (V^2))] - - where p is pressure of the hydrogen gas (Pa), n the amount of - substance (mol), T the temperature (K), and V the volume of storage - (m3). The constants a and b are called the van der Waals coefficients, - which for hydrogen are 2.45 x 10^2 Pa m6mol-2 and 26.61 x 10^6 , - respectively. - """ - - pass - - -if __name__ == "__main__": - # Example on how to use this model: - in_dict = {} - in_dict["electrolyzer_system_size_MW"] = 15 - out_dict = {} - - electricity_profile = pd.read_csv("sample_wind_electricity_profile.csv") - in_dict["P_input_external_kW"] = electricity_profile.iloc[:, 1].to_numpy() - - el = PEM_electrolyzer_LT(in_dict, out_dict) - el.h2_production_rate() - print( - "Hourly H2 production by stack (kg/hr): ", - out_dict["stack_h2_produced_kg_hr"][0:50], - ) - print( - "Hourly H2 production by system (kg/hr): ", - out_dict["h2_produced_kg_hr_system"][0:50], - ) - fig, axs = plt.subplots(2, 2) - fig.suptitle( - "PEM H2 Electrolysis Results for " - + str(out_dict["electrolyzer_system_size_MW"]) - + " MW System" - ) - - axs[0, 0].plot(out_dict["stack_h2_produced_kg_hr"]) - axs[0, 0].set_title("Hourly H2 production by stack") - axs[0, 0].set_ylabel("kg_h2 / hr") - axs[0, 0].set_xlabel("Hour") - - axs[0, 1].plot(out_dict["h2_produced_kg_hr_system"]) - axs[0, 1].set_title("Hourly H2 production by system") - axs[0, 1].set_ylabel("kg_h2 / hr") - axs[0, 1].set_xlabel("Hour") - - axs[1, 0].plot(in_dict["P_input_external_kW"]) - axs[1, 0].set_title("Hourly Energy Supplied by Wind Farm (kWh)") - axs[1, 0].set_ylabel("kWh") - axs[1, 0].set_xlabel("Hour") - - total_efficiency = out_dict["total_efficiency"] - system_h2_eff = (1 / total_efficiency) * 33.3 - system_h2_eff = np.where(total_efficiency == 0, 0, system_h2_eff) - - axs[1, 1].plot(system_h2_eff) - axs[1, 1].set_title("Total Stack Energy Usage per mass net H2") - axs[1, 1].set_ylabel("kWh_e/kg_h2") - axs[1, 1].set_xlabel("Hour") - - plt.show() - print("Annual H2 production (kg): ", np.sum(out_dict["h2_produced_kg_hr_system"])) - print("Annual energy production (kWh): ", np.sum(in_dict["P_input_external_kW"])) - print( - "H2 generated (kg) per kWH of energy generated by wind farm: ", - np.sum(out_dict["h2_produced_kg_hr_system"]) / np.sum(in_dict["P_input_external_kW"]), - ) diff --git a/h2integrate/simulation/technologies/hydrogen/electrolysis/__init__.py b/h2integrate/simulation/technologies/hydrogen/electrolysis/__init__.py deleted file mode 100644 index 1b79c00b3..000000000 --- a/h2integrate/simulation/technologies/hydrogen/electrolysis/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_H2_LT_electrolyzer_Clusters -import h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_tools -from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_costs_Singlitico_model import ( - PEMCostsSingliticoModel, -) - -# FIXME: duplicative imports -from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_electrolyzer_IVcurve import ( - PEM_electrolyzer_LT, -) -from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_H2_LT_electrolyzer import ( - PEM_electrolyzer_LT, # FIXME: duplicative import, delete whole comment when fixed # noqa: F811 -) From 562df9ed2f9e012251e33bc8a6028e12c5658c98 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Thu, 27 Nov 2025 11:00:00 -0700 Subject: [PATCH 14/30] Big refactor and removal of hydrogen storage from simulation/technologies --- .../hydrogen/pem_model/run_PEM_master.py | 2 +- .../hydrogen/pem_model/run_h2_PEM.py | 8 +- .../technologies/hydrogen/__init__.py | 2 +- ...Hydrogen Cost as Function of Capacity.docx | Bin 355070 -> 0 bytes .../hydrogen/h2_storage/__init__.py | 0 .../h2_storage/lined_rock_cavern/__init__.py | 0 .../lined_rock_cavern/lined_rock_cavern.py | 168 ------ .../hydrogen/h2_storage/mch/__init__.py | 0 .../hydrogen/h2_storage/mch/mch_cost.py | 142 ----- .../hydrogen/h2_storage/on_turbine/README.md | 117 ---- .../h2_storage/on_turbine/__init__.py | 3 - .../on_turbine/on_turbine_hydrogen_storage.py | 527 ------------------ .../h2_storage/pipe_storage/__init__.py | 3 - .../pipe_storage/underground_pipe_storage.py | 177 ------ .../h2_storage/salt_cavern/__init__.py | 0 .../h2_storage/salt_cavern/salt_cavern.py | 169 ------ .../hydrogen/h2_storage/storage_sizing.py | 84 --- .../hydrogen/h2_transport/h2_pipe_array.py | 4 +- h2integrate/storage/hydrogen/eco_storage.py | 223 -------- .../storage/hydrogen/h2_storage_cost.py | 227 +++++++- .../hydrogen/test/test_hydrogen_storage.py | 208 ++++++- .../hydrogen/test/test_tol_mch_storage.py | 2 +- .../test_h2_transport/test_h2_compressor.py | 2 +- .../test_h2_export_pipeline.py | 4 +- .../test_h2_transport/test_h2_pipe_array.py | 2 +- .../test_hydrogen/test_lined_rock_storage.py | 2 +- .../test_hydrogen/test_pressurized_turbine.py | 2 +- .../test_hydrogen/test_salt_cavern_storage.py | 2 +- .../test_underground_pipe_storage.py | 2 +- 29 files changed, 415 insertions(+), 1667 deletions(-) delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/Bulk Hydrogen Cost as Function of Capacity.docx delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/__init__.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/lined_rock_cavern/__init__.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/lined_rock_cavern/lined_rock_cavern.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/mch/__init__.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/mch/mch_cost.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/on_turbine/README.md delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/on_turbine/__init__.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/on_turbine/on_turbine_hydrogen_storage.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/pipe_storage/__init__.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/pipe_storage/underground_pipe_storage.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/salt_cavern/__init__.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/salt_cavern/salt_cavern.py delete mode 100644 h2integrate/simulation/technologies/hydrogen/h2_storage/storage_sizing.py delete mode 100644 h2integrate/storage/hydrogen/eco_storage.py diff --git a/h2integrate/converters/hydrogen/pem_model/run_PEM_master.py b/h2integrate/converters/hydrogen/pem_model/run_PEM_master.py index df0176ec1..3873ca07b 100644 --- a/h2integrate/converters/hydrogen/pem_model/run_PEM_master.py +++ b/h2integrate/converters/hydrogen/pem_model/run_PEM_master.py @@ -5,7 +5,7 @@ import pandas as pd from pyomo.environ import * # FIXME: no * imports, delete whole comment when fixed # noqa: F403 -from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_H2_LT_electrolyzer_Clusters import ( # noqa: E501 +from h2integrate.converters.hydrogen.pem_model.PEM_H2_LT_electrolyzer_Clusters import ( PEM_H2_Clusters as PEMClusters, ) diff --git a/h2integrate/converters/hydrogen/pem_model/run_h2_PEM.py b/h2integrate/converters/hydrogen/pem_model/run_h2_PEM.py index 96957a54b..222622a85 100644 --- a/h2integrate/converters/hydrogen/pem_model/run_h2_PEM.py +++ b/h2integrate/converters/hydrogen/pem_model/run_h2_PEM.py @@ -1,12 +1,8 @@ import numpy as np import pandas as pd -from h2integrate.simulation.technologies.hydrogen.electrolysis.run_PEM_master import ( - run_PEM_clusters, -) -from h2integrate.simulation.technologies.hydrogen.electrolysis.PEM_H2_LT_electrolyzer_Clusters import ( # noqa: E501 - eta_h2_hhv, -) +from h2integrate.converters.hydrogen.pem_model.run_PEM_master import run_PEM_clusters +from h2integrate.converters.hydrogen.pem_model.PEM_H2_LT_electrolyzer_Clusters import eta_h2_hhv def clean_up_final_outputs(h2_tot, h2_ts): diff --git a/h2integrate/simulation/technologies/hydrogen/__init__.py b/h2integrate/simulation/technologies/hydrogen/__init__.py index 0f6efc5fb..97ac95ab5 100644 --- a/h2integrate/simulation/technologies/hydrogen/__init__.py +++ b/h2integrate/simulation/technologies/hydrogen/__init__.py @@ -1 +1 @@ -from h2integrate.simulation.technologies.hydrogen.h2_transport.h2_compression import Compressor +from h2integrate.converters.hydrogen.pem_model.h2_transport.h2_compression import Compressor diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/Bulk Hydrogen Cost as Function of Capacity.docx b/h2integrate/simulation/technologies/hydrogen/h2_storage/Bulk Hydrogen Cost as Function of Capacity.docx deleted file mode 100644 index 4f103cdcdfaed8d872e181dcca7b8d83fe74460a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 355070 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

Q9gEw>9=lf&?LTZ3IALpfOc^El>xKx91+PlL(O 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�(A%gVlBL4k@a8_ZZK*lI}YAhVkz6WVef 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 CBRjqT diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/__init__.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/lined_rock_cavern/__init__.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/lined_rock_cavern/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/lined_rock_cavern/lined_rock_cavern.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/lined_rock_cavern/lined_rock_cavern.py deleted file mode 100644 index 92cc16ea0..000000000 --- a/h2integrate/simulation/technologies/hydrogen/h2_storage/lined_rock_cavern/lined_rock_cavern.py +++ /dev/null @@ -1,168 +0,0 @@ -""" -Author: Kaitlin Brunik -Created: 7/20/2023 -Institution: National Renewable Energy Lab -Description: This file outputs capital and operational costs of lined rock cavern hydrogen storage. -It needs to be updated to with operational dynamics. -Costs are in 2018 USD - -Sources: - - [1] Papadias 2021: https://www.sciencedirect.com/science/article/pii/S0360319921030834?via%3Dihub - - [2] Papadias 2021: Bulk Hydrogen as Function of Capacity.docx documentation at - hopp/hydrogen/h2_storage - - [3] HDSAM V4.0 Gaseous H2 Geologic Storage sheet -""" - -import numpy as np - -from h2integrate.simulation.technologies.hydrogen.h2_transport.h2_compression import Compressor - - -class LinedRockCavernStorage: - """ - - Costs are in 2018 USD - """ - - def __init__(self, input_dict): - """ - Initialize LinedRockCavernStorage. - - Args: - input_dict (dict): - - h2_storage_kg (float): total capacity of hydrogen storage [kg] - - storage_duration_hrs (float): (optional if h2_storage_kg set) [hrs] - - flow_rate_kg_hr (float): (optional if h2_storage_kg set) [kg/hr] - - system_flow_rate (float): [kg/day] - - labor_rate (float): (optional, default: 37.40) [$2018/hr] - - insurance (float): (optional, default: 1%) [decimal percent] - - property_taxes (float): (optional, default: 1%) [decimal percent] - - licensing_permits (float): (optional, default: 0.01%) [decimal percent] - Returns: - - lined_rock_cavern_storage_capex_per_kg (float): the installed capital cost per kg h2 - in 2018 [USD/kg] - - installed_capex (float): the installed capital cost in 2018 [USD] (including - compressor) - - storage_compressor_capex (float): the installed capital cost in 2018 for the - compressor [USD] - - total_opex (float): the OPEX (annual, fixed) in 2018 excluding electricity costs - [USD/kg-yr] - - output_dict (dict): - - lined_rock_cavern_storage_capex (float): installed capital cost in 2018 [USD] - - lined_rock_cavern_storage_opex (float): OPEX (annual, fixed) in 2018 [USD/yr] - """ - self.input_dict = input_dict - self.output_dict = {} - - # inputs - if "h2_storage_kg" in input_dict: - self.h2_storage_kg = input_dict["h2_storage_kg"] # [kg] - elif "storage_duration_hrs" and "flow_rate_kg_hr" in input_dict: - self.h2_storage_kg = input_dict["storage_duration_hrs"] * input_dict["flow_rate_kg_hr"] - else: - raise Exception( - "input_dict must contain h2_storage_kg or storage_duration_hrs and flow_rate_kg_hr" - ) - - if "system_flow_rate" not in input_dict.keys(): - raise ValueError("system_flow_rate required for lined rock cavern storage model.") - else: - self.system_flow_rate = input_dict["system_flow_rate"] - - self.labor_rate = input_dict.get("labor_rate", 37.39817) # $(2018)/hr - self.insurance = input_dict.get("insurance", 1 / 100) # % of total capital investment - self.property_taxes = input_dict.get( - "property_taxes", 1 / 100 - ) # % of total capital investment - self.licensing_permits = input_dict.get( - "licensing_permits", 0.1 / 100 - ) # % of total capital investment - self.comp_om = input_dict.get( - "compressor_om", 4 / 100 - ) # % of compressor capital investment - self.facility_om = input_dict.get( - "facility_om", 1 / 100 - ) # % of facility capital investment minus compressor capital investment - - def lined_rock_cavern_capex(self): - """ - Calculates the installed capital cost of lined rock cavern hydrogen storage - Returns: - - lined_rock_cavern_storage_capex_per_kg (float): the installed capital cost per kg h2 - in 2018 [USD/kg] - - installed_capex (float): the installed capital cost in 2018 [USD] (including - compressor) - - storage_compressor_capex (float): the installed capital cost in 2018 for the - compressor [USD] - - output_dict (dict): - - lined_rock_cavern_storage_capex (float): installed capital cost in 2018 [USD] - """ - - # Installed capital cost - a = 0.095803 - b = 1.5868 - c = 10.332 - self.lined_rock_cavern_storage_capex_per_kg = np.exp( - a * (np.log(self.h2_storage_kg / 1000)) ** 2 - b * np.log(self.h2_storage_kg / 1000) + c - ) # 2019 [USD] from Papadias [2] - self.installed_capex = self.lined_rock_cavern_storage_capex_per_kg * self.h2_storage_kg - cepci_overall = 1.29 / 1.30 # Convert from $2019 to $2018 - self.installed_capex = cepci_overall * self.installed_capex - self.output_dict["lined_rock_cavern_storage_capex"] = self.installed_capex - - outlet_pressure = 200 # Max outlet pressure of lined rock cavern in [1] - n_compressors = 2 - storage_compressor = Compressor( - outlet_pressure, self.system_flow_rate, n_compressors=n_compressors - ) - storage_compressor.compressor_power() - motor_rating, power = storage_compressor.compressor_system_power() - if motor_rating > 1600: - n_compressors += 1 - storage_compressor = Compressor( - outlet_pressure, self.system_flow_rate, n_compressors=n_compressors - ) - storage_compressor.compressor_power() - motor_rating, power = storage_compressor.compressor_system_power() - comp_capex, comp_OM = storage_compressor.compressor_costs() - cepci = 1.36 / 1.29 # convert from $2016 to $2018 - self.comp_capex = comp_capex * cepci - return ( - self.lined_rock_cavern_storage_capex_per_kg, - self.installed_capex, - self.comp_capex, - ) - - def lined_rock_cavern_opex(self): - """ - Calculates the operation and maintenance costs excluding electricity costs for the lined - rock cavern hydrogen storage - - Returns: - - total_opex (float): the OPEX (annual, fixed) in 2018 excluding electricity costs - [USD/kg-yr] - - output_dict (dict): - - lined_rock_cavern_storage_opex (float): OPEX (annual, fixed) in 2018 [USD/yr] - """ - # Operations and Maintenace costs [3] - # Labor - # Base case is 1 operator, 24 hours a day, 7 days a week for a 100,000 kg/day average - # capacity facility. Scaling factor of 0.25 is used for other sized facilities - annual_hours = 8760 * (self.system_flow_rate / 100000) ** 0.25 - self.overhead = 0.5 - labor = (annual_hours * self.labor_rate) * (1 + self.overhead) # Burdened labor cost - insurance = self.insurance * self.installed_capex - property_taxes = self.property_taxes * self.installed_capex - licensing_permits = self.licensing_permits * self.installed_capex - comp_op_maint = self.comp_om * self.comp_capex - facility_op_maint = self.facility_om * (self.installed_capex - self.comp_capex) - - # O&M excludes electricity requirements - total_om = ( - labor - + insurance - + licensing_permits - + property_taxes - + comp_op_maint - + facility_op_maint - ) - self.output_dict["lined_rock_cavern_storage_opex"] = total_om - return total_om diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/mch/__init__.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/mch/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/mch/mch_cost.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/mch/mch_cost.py deleted file mode 100644 index 29127da91..000000000 --- a/h2integrate/simulation/technologies/hydrogen/h2_storage/mch/mch_cost.py +++ /dev/null @@ -1,142 +0,0 @@ -from attrs import field, define - - -@define -class MCHStorage: - """ - Cost model representing a toluene/methylcyclohexane (TOL/MCH) hydrogen storage system. - - Costs are in 2024 USD. - - Sources: - Breunig, H., Rosner, F., Saqline, S. et al. "Achieving gigawatt-scale green hydrogen - production and seasonal storage at industrial locations across the U.S." *Nat Commun* - **15**, 9049 (2024). https://doi.org/10.1038/s41467-024-53189-2 - - Args: - max_H2_production_kg_pr_hr (float): Maximum amount of hydrogen that may be - used to fill storage in kg/hr. - hydrogen_storage_capacity_kg (float): Hydrogen storage capacity in kilograms. - hydrogen_demand_kg_pr_hr (float): Hydrogen demand in kg/hr. - annual_hydrogen_stored_kg_pr_yr (float): Sum of hydrogen used to fill storage - in kg/year. - - Note: - Hydrogenation capacity (HC) should be sized to allow for peak hydrogen charge rate. - Dehydrogenation capacity (DC) sized to assume that end-use requires a consistent H2 supply. - Maximum storage capacity (MS) is the maximum consecutive quantity of H2 stored - with lowest frequency of discharge. - Annual hydrogen storage (AS) is the hydrogen curtailed from production into storage. - - """ - - max_H2_production_kg_pr_hr: float - hydrogen_storage_capacity_kg: float - hydrogen_demand_kg_pr_hr: float - annual_hydrogen_stored_kg_pr_yr: float - - #: dehydrogenation capacity [metric tonnes/day] - Dc: float = field(init=False) - - #: hydrogenation capacity [metric tonnes/day] - Hc: float = field(init=False) - - #: maximum storage capacity [metric tonnes] - Ms: float = field(init=False) - - #: annual hydrogen into storage [metric tonnes] - As: float = field(init=False) - - # overnight capital cost coefficients - occ_coeff = (54706639.43, 147074.25, 588779.05, 20825.39, 10.31) - - #: fixed O&M cost coefficients - foc_coeff = (3419384.73, 3542.79, 13827.02, 61.22, 0.0) - - #: variable O&M cost coefficients - voc_coeff = (711326.78, 1698.76, 6844.86, 36.04, 376.31) - - #: lcos cost coefficients for a capital charge factor of 0.0710 - lcos_coeff = (8014882.91, 15683.82, 62475.19, 1575.86, 377.04) - - #: hydrogen storage efficiency - eta = 0.9984 - - #: cost year associated with the costs in this model - cost_year = 2024 - - def __attrs_post_init__(self): - # Equation (3): DC = P_avg - self.Dc = self.hydrogen_demand_kg_pr_hr * 24 / 1e3 - - # Equation (2): HC = P_nameplate - P_avg - P_nameplate = self.max_H2_production_kg_pr_hr * 24 / 1e3 - self.Hc = P_nameplate - self.Dc - - # Equation (1): AS = sum(curtailed_h2) - self.As = self.annual_hydrogen_stored_kg_pr_yr / 1e3 - - # Defined in paragraph between Equation (2) and (3) - self.Ms = self.hydrogen_storage_capacity_kg / 1e3 - - def calc_cost_value(self, b0, b1, b2, b3, b4): - """ - Calculate the value of the cost function for the given coefficients. - - Args: - b0 (float): Coefficient representing the base cost. - b1 (float): Coefficient for the Hc (hydrogenation capacity) term. - b2 (float): Coefficient for the Dc (dehydrogenation capacity) term. - b3 (float): Coefficient for the Ms (maximum storage) term. - b4 (float): Coefficient for the As (annual hydrogen into storage) term. - Returns: - float: The calculated cost value based on the provided coefficients and attributes. - - """ - return b0 + (b1 * self.Hc) + (b2 * self.Dc) + (b3 * self.Ms) + b4 * self.As - - def run_costs(self): - """Calculate the costs of TOL/MCH hydrogen storage. - - Returns: - dict: dictionary of costs for TOL/MCH storage - """ - cost_results = { - "mch_capex": self.calc_cost_value(*self.occ_coeff), - "mch_opex": self.calc_cost_value(*self.foc_coeff), - "mch_variable_om": self.calc_cost_value(*self.voc_coeff), - "mch_cost_year": self.cost_year, - } - return cost_results - - def estimate_lcos(self): - """Estimate the levelized cost of hydrogen storage. Based on Equation (7) of the - reference article. - - Returns: - float: levelized cost of storage in $2024/kg-H2 stored - """ - - lcos_numerator = self.calc_cost_value(*self.lcos_coeff) - lcos_denom = self.As * self.eta * 1e3 - lcos_est = lcos_numerator / lcos_denom - return lcos_est - - def estimate_lcos_from_costs(self, ccf=0.0710): - """Estimate the levelized cost of hydrogen storage. Based on Equation (5) of the - reference article. - - Args: - ccf (float, optional): Capital charge factor. Defaults to 0.0710. - - Returns: - float: levelized cost of storage in $2024/kg-H2 stored - """ - - toc = self.calc_cost_value(*self.occ_coeff) - voc = self.calc_cost_value(*self.voc_coeff) - foc = self.calc_cost_value(*self.foc_coeff) - costs = (toc * ccf) + voc + foc - denom = self.As * self.eta * 1e3 - lcos_est = costs / denom - return lcos_est diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/on_turbine/README.md b/h2integrate/simulation/technologies/hydrogen/h2_storage/on_turbine/README.md deleted file mode 100644 index 1a042fde2..000000000 --- a/h2integrate/simulation/technologies/hydrogen/h2_storage/on_turbine/README.md +++ /dev/null @@ -1,117 +0,0 @@ - -# On-turbine hydrogen storage modeling - -## Implementation - -In this module, we create a model for storing hydrogen in a wind turbine tower -We follow, largely, the work of Kottenstette (see NREL/TP-500-34656), although -various assumptions in their study are not marked, and our goal is to flesh out -some of their assumptions. - -`PressurizedTower` is an object model that represents a pressurized wind turbine -tower with geometry specified on input. The Kottenstette work assumes a wind -turbine tower whose thickness is set by a constant diameter-thickness ratio, -which defaults to 320. The tower is specified by diameter/height pairs, between -which a linear taper is assumed. The material of the tower is assumed to be -steel with the following properties: - -- ultimate tensile strength: 636 MPa -- yield strength: 350 MPa -- welded joint efficiency: 0.80 (see ASME Boiler and Pressure Vessel Code for details) -- density: 7817 kg/m3 -- cost per kg: $1.50 - -These can be modified for alternative studies by variable access on the `PressurizedTower` object before running an analysis. Refer to the `__init__()` -function for definitions. Inner volume of the tower is computed by conic frustum -volume according to each section, assuming thin walls (s.t. $d \gg t$). Wall -material is computed by assuming the wall thickness is centered at the diameter -dimension (outer or inner thickness placement is available by specification). - -## Hydrogen storage - -### Wall increment - -When hydrogen is stored, a Goodman's equation thickness increment is assumed for -the vertical tower walls (see Kottenstette) in order to handle the additional -pressure stress contribution which is a zero-intercept linear function of -diameter, see `PressurizedTower.get_thickness_increment_const` for the leading -coefficient calculation. Hydrogen is assumed to be stored at the crossover -pressure where pressurized burst strength and aerodynamic moment fatigue are -balanced, see Kottenstette for theory and -`PressureizedTower.get_crossover_pressure` for implementation. - -### End cap sizing - -End caps are necessary for a pressure vessel tower, which are sized according to -the ASME Boiler and Pressure Vessel code. Caps are assumed to be welded with -flat pressure heads affixed by a rounded corner. Implementation in -`PressurizedTower.compute_cap_thickness` contains details on thickness -computation. Following Kottenstette, we use 2.66 \$/kg to cost the endcap -material. - -### Hydrogen - -A pressurized tower, then, is assumed to hold a volume of $\mathrm{H}_2$, stored -at pressure and the ambient temperature, and the ideal gas law is used to relate -the resulting mass of the stored hydrogen. - -### Summary and additional costs - -Above the baseline (non-pressurized) tower, hydrogen storage entails, then: -- increased wall thickness -- the addition of 2 pressure vessel endcaps -- additional fixed non-tower expenses, given in Kottenstette - - additional ladder - - conduit for weatherized wiring - - mainframe extension for safe addition of external ladder - - additional interior access door - - access mainway & nozzles for pressure vessel - -## Expenditure models - -### Capital expenditure (CapEx) model - -Capital expenses in addition to the baseline tower are given by: -- additional steel costs above baseline tower for wall reinforcement & pressure - end caps -- additional hardware requirements for/to facilitate hydrogen storage - -### Operational expenditure (OpEx) model - -Operational expenditure on pressure vessel is modeled roughly following the -relevant costs from `hopp/hydrogen/h2_storage/pressure_vessel`. The resulting -estimates are _rough_. Basically the annual operational expenditures are modeled -as: -$$ -OPEX= R_{\mathrm{maint}} \times CAPEX + \mathrm{Hours}_{\mathrm{staff}} \times \mathrm{Wage} -$$ -where $R_{\mathrm{maint}}$ is a maintenance rate. We assume: -- $R_{\mathrm{maint}}= 0.03$; i.e.: 3\% of the capital costs must be re-invested each year to cover maintenance -- $\mathrm{Wage}= \$36/\mathrm{hr}$ -- $\mathrm{Hours}= 60$: 60 man-hours of maintenance on the pressure vessel per year; this number very roughly derived from the other code - -## Unit testing - -Unit testing of this module consists of three tests under two approaches: -- comparison with simple geometries - - cylindrical tower - - conical tower -- comparison with Kottenstette results - - issue: Kottenstette results have some assumptions secreted away, so these - _at best_ are just to ensure the model remains in the ballpark of - the Kottenstette results - - specifically, Kottenstette's results table (Table 3 in NREL report) - imply a partial tower pressure vessel, inducing various - differences between this code and their results - - I can't figure out how to size the pressure vessel caps (heads) to get the - costs that are reported in Kottenstette - - tests against Table 3: - - traditional/non-pressurized tower: - - tower costs within 5% - - non-tower costs within 5% - - pressurized tower: - - wall costs within 10% - - _top cap within 100%_ - - _bottom cap within 200%_ - - non-tower cost within 10% - - capacity within 10% diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/on_turbine/__init__.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/on_turbine/__init__.py deleted file mode 100644 index 832c8d2d1..000000000 --- a/h2integrate/simulation/technologies/hydrogen/h2_storage/on_turbine/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from h2integrate.simulation.technologies.hydrogen.h2_storage.on_turbine.on_turbine_hydrogen_storage import ( - PressurizedTower, -) diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/on_turbine/on_turbine_hydrogen_storage.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/on_turbine/on_turbine_hydrogen_storage.py deleted file mode 100644 index d8fca53f7..000000000 --- a/h2integrate/simulation/technologies/hydrogen/h2_storage/on_turbine/on_turbine_hydrogen_storage.py +++ /dev/null @@ -1,527 +0,0 @@ -""" -Author: Cory Frontin -Date: 23 Jan 2023 -Institution: National Renewable Energy Lab -Description: This file handles the cost, sizing, and pressure of on-turbine H2 storage - -To use this class, specify a turbine - -Costs are assumed to be in 2003 dollars [1] - -Sources: - - [1] Kottenstette 2003 (use their chosen favorite design) -Args: - - year (int): construction year - - turbine (dict): contains various information about the turbine, including tower_length, - section_diameters, and section_heights -API member functions: - - get_capex(): return the total additional capex necessary for H2 production, in 2003 dollars - - get_opex(): return the result of a simple model for operational expenditures for pressure - vessel, in 2003 dollars - - get_mass_empty(): return the total additional empty mass necessary for H2 production, in kg - - get_capacity_H2(): return the capacity mass of hydrogen @ operating pressure, ambient temp., - in kg - - get_pressure_H2() return the operating hydrogen pressure, in Pa -""" - -from __future__ import annotations - -import numpy as np - - -class PressurizedTower: - def __init__(self, year: int, turbine: dict): - # key inputs - self.year = year - self.turbine = turbine - - self.tower_length = turbine["tower_length"] # m - self.section_diameters = turbine["section_diameters"] # m - self.section_heights = turbine["section_heights"] # m - - # calculation settings - self.setting_volume_thickness_calc = "centered" # ['centered', 'outer', 'inner'] - - # constants/parameters - self.d_t_ratio = 320.0 # Kottenstette 2003 - self.thickness_top = self.section_diameters[-1] / self.d_t_ratio # m - self.thickness_bot = self.section_diameters[0] / self.d_t_ratio # m - self.ultimate_tensile_strength = 636e6 # Pa, Kottenstette 2003 - self.yield_strength = 350e6 # Pa, Kottenstette 2003 - self.welded_joint_efficiency = 0.80 # journal edition - # self.welded_joint_efficiency= 0.85 # double-welded butt joint w/ spot inspection (ASME) - self.density_steel = 7817.0 # kg/m^3 - self.gasconstant_H2 = 4126.0 # J/(kg K) - self.operating_temp = 25.0 # degC - - self.costrate_steel = 1.50 # $/kg - self.costrate_endcap = 2.66 # $/kg - - self.costrate_ladder = 32.80 # $/m - self.cost_door = 2000 # $ - self.cost_mainframe_extension = 6300 # $ - self.cost_nozzles_manway = 16000 # $ - self.costrate_conduit = 35 # $/m - - # based on pressure_vessel maintenance costs - self.wage = 36 # 2003 dollars (per hour worked) - self.staff_hours = 60 # hours - self.maintenance_rate = 0.03 # factor - - # set the operating pressure @ the crossover pressure - self.operating_pressure = PressurizedTower.get_crossover_pressure( - self.welded_joint_efficiency, self.ultimate_tensile_strength, self.d_t_ratio - ) - - def run(self): - # get the inner volume and traditional material volume, mass, cost - self.tower_inner_volume = self.get_volume_tower_inner() - ( - self.wall_material_volume_trad, - self.cap_bot_material_volume_trad, - self.cap_top_material_volume_trad, - ) = self.get_volume_tower_material(pressure=0.0) - self.wall_material_mass_trad = self.wall_material_volume_trad * self.density_steel - self.wall_material_cost_trad = self.wall_material_mass_trad * self.costrate_steel - self.cap_material_mass_trad = ( - self.cap_top_material_volume_trad + self.cap_bot_material_volume_trad - ) * self.density_steel - self.cap_material_cost_trad = self.cap_material_mass_trad * self.costrate_steel - self.nonwall_cost_trad = self.get_cost_nontower(traditional=True) - - ( - self.wall_material_volume, - self.cap_bot_material_volume, - self.cap_top_material_volume, - ) = self.get_volume_tower_material() - self.wall_material_mass = self.wall_material_volume * self.density_steel - self.wall_material_cost = self.wall_material_mass * self.costrate_steel - self.wall_material_mass = self.wall_material_volume * self.density_steel - self.cap_material_mass = ( - self.cap_bot_material_volume + self.cap_top_material_volume - ) * self.density_steel - self.cap_material_cost = self.cap_material_mass * self.costrate_endcap - self.nonwall_cost = self.get_cost_nontower() - - if False: - # print the inner volume and pressure-free material properties - print("operating pressure:", self.operating_pressure) - print("tower inner volume:", self.tower_inner_volume) - print() - print( - "tower wall material volume (non-pressurized):", - self.wall_material_volume_trad, - ) - print( - "tower wall material mass (non-pressurized):", - self.wall_material_mass_trad, - ) - print( - "tower wall material cost (non-pressurized):", - self.wall_material_cost_trad, - ) - print( - "tower cap material volume (non-pressurized):", - self.cap_top_material_volume_trad + self.cap_bot_material_volume_trad, - ) - print( - "tower cap material mass (non-pressurized):", - self.cap_material_mass_trad, - ) - print( - "tower cap material cost (non-pressurized):", - self.cap_material_cost_trad, - ) - print( - "tower total material cost (non-pressurized):", - self.wall_material_cost_trad + self.cap_material_cost_trad, - ) - - # print the changes to the structure - print() - print("tower wall material volume (pressurized):", self.wall_material_volume) - print("tower wall material mass (pressurized):", self.wall_material_mass) - print("tower wall material cost (pressurized):", self.wall_material_cost) - print() - print( - "tower cap material volume (pressurized):", - self.cap_bot_material_volume + self.cap_top_material_volume, - ) - print("tower cap material mass (pressurized):", self.cap_material_mass) - print( - "tower top cap material cost (pressurized):", - self.cap_top_material_volume * self.density_steel * self.costrate_endcap, - ) - print( - "tower bot cap material cost (pressurized):", - self.cap_bot_material_volume * self.density_steel * self.costrate_endcap, - ) - print("tower cap material cost (pressurized):", self.cap_material_cost) - print() - print("operating mass fraction:", self.get_operational_mass_fraction()) - print("nonwall cost (non-pressurized):", self.nonwall_cost_trad) - print("nonwall cost (pressurized):", self.nonwall_cost) - print( - "tower total material cost (pressurized):", - self.wall_material_cost + self.cap_material_cost, - ) - - print() - print( - "delta tower wall material cost:", - self.wall_material_cost - self.wall_material_cost_trad, - ) - print("empty mass:", self.get_mass_empty()) - print() - print("capex:", self.get_capex()) - print("opex:", self.get_opex()) - print("capacity (H2):", self.get_capacity_H2()) - - def get_volume_tower_inner(self): - """ - get the inner volume of the tower in m^3 - - assume t << d - """ - - # count the sections, all assumed conic frustum - Nsection = len(self.section_diameters) - 1 - - # loop over sections, calclulating volume of each - vol_section = np.zeros((Nsection,)) - for i_section in range(Nsection): - diameter_bot = self.section_diameters[i_section] # m - height_bot = self.section_heights[i_section] # m - diameter_top = self.section_diameters[i_section + 1] # m - height_top = self.section_heights[i_section + 1] # m - dh = np.abs(height_top - height_bot) # height of section, m - - vol_section[i_section] = PressurizedTower.compute_frustum_volume( - dh, diameter_bot, diameter_top - ) - - # total volume: sum of sections - return np.sum(vol_section) # m^3 - - def get_volume_tower_material(self, pressure: float | None = None): - """ - get the material volume of the tower in m^3 - - if pressurized, use pressure to set thickness increment due to pressurization - - assume t << d - - params: - - pressure: gauge pressure of H2 (defaults to design op. pressure) - returns: - - Vmat_wall: material volume of vertical tower - - Vmat_bot: material volume of bottom cap (nonzero only if pressurized) - - Vmat_top: material volume of top cap (nonzero only if pressurized) - """ - - # override pressure iff requested - if pressure is None: - pressure = self.operating_pressure - - # this is the linear constant s.t. delta t ~ alpha * d - alpha_dtp = PressurizedTower.get_thickness_increment_const( - pressure, self.ultimate_tensile_strength - ) # - - # loop over the sections of the tower - Nsection = len(self.section_diameters) - 1 - matvol_section = np.zeros((Nsection,)) - for i_section in range(Nsection): - d1 = self.section_diameters[i_section] - h1 = self.section_heights[i_section] - d2 = self.section_diameters[i_section + 1] - h2 = self.section_heights[i_section + 1] - - if self.setting_volume_thickness_calc == "centered": - Vouter = PressurizedTower.compute_frustum_volume( - h2 - h1, - d1 * (1 + (1 / self.d_t_ratio + alpha_dtp)), - d2 * (1 + (1 / self.d_t_ratio + alpha_dtp)), - ) - Vinner = PressurizedTower.compute_frustum_volume( - h2 - h1, - d1 * (1 - (1 / self.d_t_ratio + alpha_dtp)), - d2 * (1 - (1 / self.d_t_ratio + alpha_dtp)), - ) - elif self.setting_volume_thickness_calc == "outer": - Vouter = PressurizedTower.compute_frustum_volume( - h2 - h1, - d1 * (1 + 2 * (1 / self.d_t_ratio + alpha_dtp)), - d2 * (1 + 2 * (1 / self.d_t_ratio + alpha_dtp)), - ) - Vinner = PressurizedTower.compute_frustum_volume(h2 - h1, d1, d2) - elif self.setting_volume_thickness_calc == "inner": - Vouter = PressurizedTower.compute_frustum_volume(h2 - h1, d1, d2) - Vinner = PressurizedTower.compute_frustum_volume( - h2 - h1, - d1 * (1 - 2 * (1 / self.d_t_ratio + alpha_dtp)), - d2 * (1 - 2 * (1 / self.d_t_ratio + alpha_dtp)), - ) - - matvol_section[i_section] = Vouter - Vinner - - # compute wall volume by summing sections - Vmat_wall = np.sum(matvol_section) # m^3 - - if pressure == 0: - Vmat_bot = 0.0 - Vmat_top = 0.0 - else: - # compute caps as well: area by thickness - Vmat_bot = (np.pi / 4 * self.section_diameters[0] ** 2) * ( - PressurizedTower.compute_cap_thickness( - pressure, - self.section_diameters[0], # assume first is bottom - self.yield_strength, - efficiency_weld=self.welded_joint_efficiency, - ) - ) # m^3 - Vmat_top = ( - np.pi - / 4 - * self.section_diameters[-1] ** 2 - * ( - PressurizedTower.compute_cap_thickness( - pressure, - self.section_diameters[-1], # assume last is top - self.yield_strength, - efficiency_weld=self.welded_joint_efficiency, - ) - ) - ) # m^3 - - # total material volume - return (Vmat_wall, Vmat_bot, Vmat_top) # m^3 - - def get_mass_tower_material(self, pressure: float | None = None): - """ - get the material mass of the tower in m^3 - - if pressurized, use pressure to set thickness increment due to pressurization - - assume t << d - - params: - - pressure: gauge pressure of H2 (defaults to design op. pressure) - returns: - - Mmat_wall: material mass of vertical tower - - Mmat_bot: material mass of bottom cap - - Mmat_top: material mass of top cap - """ - - # pass through to volume calculator, multiplying by steel density - return [self.density_steel * x for x in self.get_volume_tower_material(pressure)] # kg - - def get_cost_tower_material(self, pressure: float | None = None): - """ - get the material cost of the tower in m^3 - - if pressurized, use pressure to set thickness increment due to pressurization - - assume t << d - - params: - - pressure: gauge pressure of H2 (defaults to design op. pressure) - returns: - - Vmat_wall: material cost of vertical tower - - Vmat_bot: material cost of bottom cap - - Vmat_top: material cost of top cap - """ - - if pressure == 0: - return [ - self.costrate_steel * x for x in self.get_mass_tower_material(pressure=pressure) - ] # 2003 dollars - else: - Mmat_wall, Mmat_bot, Mmat_top = self.get_mass_tower_material(pressure=pressure) - # use adjusted pressure cap cost - return [ - self.costrate_steel * Mmat_wall, - self.costrate_endcap * Mmat_bot, - self.costrate_endcap * Mmat_top, - ] # 2003 dollars - - def get_operational_mass_fraction(self): - """ - get the fraction of stored hydrogen to tower mass - - following Kottenstette - """ - - Sut = self.ultimate_tensile_strength - rho = self.density_steel - R = self.gasconstant_H2 - T = self.operating_temp + 273.15 # convert to K - - frac = Sut / (rho * R * T) - - return frac # nondim. - - def get_cost_nontower(self, traditional: bool = False, naive: bool = True): - nonwall_cost = 0 - if traditional: - nonwall_cost += self.tower_length * self.costrate_ladder # add ladder cost - nonwall_cost += self.cost_door # add door cost - else: - naive = True - if naive: - nonwall_cost += self.cost_mainframe_extension - nonwall_cost += 2 * self.cost_door - nonwall_cost += 2 * self.tower_length * self.costrate_ladder - nonwall_cost += self.cost_nozzles_manway - nonwall_cost += self.tower_length * self.costrate_conduit - else: - # adjust length b.c. conduit, one ladder must ride outside pressure vessel - adj_length = np.sqrt( - (self.section_diameters[0] - self.section_diameters[-1]) ** 2 - + self.tower_length**2 - ) - nonwall_cost += self.cost_mainframe_extension - nonwall_cost += 2 * self.cost_door - nonwall_cost += self.tower_length * self.costrate_ladder - nonwall_cost += adj_length * self.costrate_ladder - nonwall_cost += self.cost_nozzles_manway - nonwall_cost += adj_length * self.costrate_conduit - return nonwall_cost # 2003 dollars - - ### OFFICIAL OUTPUT INTERFACE - - def get_capex(self): - """return the total additional capex necessary for H2 production""" - capex_withH2 = self.get_cost_nontower() + np.sum(self.get_cost_tower_material()) - capex_without = self.get_cost_nontower(traditional=True) + np.sum( - self.get_cost_tower_material(pressure=0) - ) - return capex_withH2 - capex_without # 2003 dollars - - def get_opex(self): - """ - a simple model for operational expenditures for PV - - maintenance for pressure vessel based on an annual maintenance rate - against the vessel-specific capital expenditure, plus wages times staff - hours per year - """ - - return ( - self.get_capex() * self.maintenance_rate + self.wage * self.staff_hours - ) # 2003 dollars - - def get_mass_empty(self): - """return the total additional empty mass necessary for H2 production in kg""" - - Mtower_withH2 = np.sum(self.get_mass_tower_material()) - Mnontower_withH2 = 0.0 # not specified - - Mtower_without = np.sum(self.get_mass_tower_material(pressure=0)) - Mnontower_without = 0.0 # not specified - - Mtotal_withH2 = Mtower_withH2 + Mnontower_withH2 - Mtotal_without = Mtower_without + Mnontower_without - - return Mtotal_withH2 - Mtotal_without # kg - - def get_capacity_H2(self): - """get the ideal gas H2 capacity in kg""" - - Tabs = self.operating_temp + 273.15 - R = self.gasconstant_H2 - p = self.operating_pressure - V = self.get_volume_tower_inner() - - # ideal gas law - m_H2 = p * V / (R * Tabs) - - return m_H2 # kg - - def get_pressure_H2(self): - return self.operating_pressure # Pa, trivial, but for delivery - - ### STATIC FUNCTIONS - - @staticmethod - def compute_cap_thickness( - pressure, - diameter, - strength_yield, - safetyfactor_Sy=1.5, - efficiency_weld=0.80, - constant=0.10, - ): - """ - compute the necessary thickness for a pressure vessel cap - - $$ - t= d \\sqrt{\\frac{C P}{S E}} - $$ - with weld joint efficiency E, allowable stress S, pressure P, diameter - of pressure action d, edge restraint factor C - - assumed: - - C= 0.10: Fig-UG-34 of ASME Code S VII, div. 1, via Rao's _Companion - Guide to the ASME Boiler and Pressure Vessel Code_ (2009), - fig. 21.3. type of sketch (a) assumed - - E= 0.80: conservatively butt weld, inspected - - using the ASME pressure vessel code definitions, and values given in - Rao _Companion Guide to the ASME Boiler and Pressure Vessel Code_ (2009) - """ - - return diameter * np.sqrt( - constant * pressure / (efficiency_weld * strength_yield / safetyfactor_Sy) - ) - - @staticmethod - def compute_frustum_volume(height, base_diameter, top_diameter): - """ - return the volume of a frustum (truncated cone) - """ - return ( - np.pi - / 12.0 - * height - * (base_diameter**2 + base_diameter * top_diameter + top_diameter**2) - ) # volume units - - @staticmethod - def get_crossover_pressure( - welded_joint_efficiency: float, - ultimate_tensile_strength: float, - d_t_ratio: float, - ): - """ - get burst/fatigue crossover pressure - - following Kottenstette 2003 - """ - - # convert to nice variables - E = welded_joint_efficiency # nondim. - Sut = ultimate_tensile_strength # pressure units - d_over_t = d_t_ratio # length-per-length; assumed fixed in this study - - p_crossover = 4 * E * Sut / (7 * d_over_t * (1 - E / 7.0)) # pressure units - - return p_crossover # pressure units - - @staticmethod - def get_thickness_increment_const(pressure: float, ultimate_tensile_strength: float): - """ - compute Goodman equation-based thickness increment in m - - following Kottenstette 2003 - """ - - # convert to text variables - p = pressure - # r= diameter/2 - Sut = ultimate_tensile_strength - - alpha_dtp = 0.25 * p / Sut - - return alpha_dtp # length per length diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/pipe_storage/__init__.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/pipe_storage/__init__.py deleted file mode 100644 index 4d3cf776e..000000000 --- a/h2integrate/simulation/technologies/hydrogen/h2_storage/pipe_storage/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from h2integrate.simulation.technologies.hydrogen.h2_storage.pipe_storage.underground_pipe_storage import ( - UndergroundPipeStorage, -) diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/pipe_storage/underground_pipe_storage.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/pipe_storage/underground_pipe_storage.py deleted file mode 100644 index 5d9597f59..000000000 --- a/h2integrate/simulation/technologies/hydrogen/h2_storage/pipe_storage/underground_pipe_storage.py +++ /dev/null @@ -1,177 +0,0 @@ -""" -Author: Kaitlin Brunik -Updated: 7/20/2023 -Institution: National Renewable Energy Lab -Description: This file outputs capital and operational costs of underground pipeline hydrogen -storage. It needs to be updated to with operational dynamics and physical size (footprint and mass). -Oversize pipe: pipe OD = 24'' schedule 60 [1] -Max pressure: 100 bar -Costs are in 2018 USD -Sources: - - [1] Papadias 2021: https://www.sciencedirect.com/science/article/pii/S0360319921030834?via%3Dihub - - [2] Papadias 2021: Bulk Hydrogen as Function of Capacity.docx documentation at - hopp/hydrogen/h2_storage - - [3] HDSAM V4.0 Gaseous H2 Geologic Storage sheet -""" - -import numpy as np - -from h2integrate.simulation.technologies.hydrogen.h2_transport.h2_compression import Compressor - - -class UndergroundPipeStorage: - """ - - Oversize pipe: pipe OD = 24'' schedule 60 - - Max pressure: 100 bar - - Costs are in 2018 USD - """ - - def __init__(self, input_dict): - """ - Initialize UndergroundPipeStorage. - - Args: - input_dict (dict): - - h2_storage_kg (float): total capacity of hydrogen storage [kg] - - storage_duration_hrs (float): (optional if h2_storage_kg set) [hrs] - - flow_rate_kg_hr (float): (optional if h2_storage_kg set) [kg/hr] - - compressor_output_pressure (float): 100 bar required [bar] - - system_flow_rate (float): [kg/day] - - labor_rate (float): (optional, default: 37.40) [$2018/hr] - - insurance (float): (optional, default: 1%) [decimal percent] - - property_taxes (float): (optional, default: 1%) [decimal percent] - - licensing_permits (float): (optional, default: 0.01%) [decimal percent] - Returns: - - pipe_storage_capex_per_kg (float): the installed capital cost per kg h2 in 2018 - [USD/kg] - - installed_capex (float): the installed capital cost in 2018 [USD] (including - compressor) - - storage_compressor_capex (float): the installed capital cost in 2018 for the - compressor [USD] - - total_opex (float): the OPEX (annual, fixed) in 2018 excluding electricity costs - [USD/kg-yr] - - output_dict (dict): - - pipe_storage_capex (float): installed capital cost in 2018 [USD] - - pipe_storage_opex (float): OPEX (annual, fixed) in 2018 [USD/yr] - """ - self.input_dict = input_dict - self.output_dict = {} - """""" - # inputs - if input_dict["compressor_output_pressure"] == 100: - self.compressor_output_pressure = input_dict["compressor_output_pressure"] # [bar] - else: - raise Exception( - "Error. compressor_output_pressure must = 100bar for pressure vessel storage." - ) - if "h2_storage_kg" in input_dict: - self.h2_storage_kg = input_dict["h2_storage_kg"] # [kg] - elif "storage_duration_hrs" and "flow_rate_kg_hr" in input_dict: - self.h2_storage_kg = input_dict["storage_duration_hrs"] * input_dict["flow_rate_kg_hr"] - else: - raise Exception( - "input_dict must contain h2_storage_kg or storage_duration_hrs and flow_rate_kg_hr" - ) - - if "system_flow_rate" not in input_dict.keys(): - raise ValueError("system_flow_rate required for underground pipe storage model.") - else: - self.system_flow_rate = input_dict["system_flow_rate"] - - self.labor_rate = input_dict.get("labor_rate", 37.39817) # $(2018)/hr - self.insurance = input_dict.get("insurance", 1 / 100) # % of total capital investment - self.property_taxes = input_dict.get( - "property_taxes", 1 / 100 - ) # % of total capital investment - self.licensing_permits = input_dict.get( - "licensing_permits", 0.1 / 100 - ) # % of total capital investment - self.comp_om = input_dict.get( - "compressor_om", 4 / 100 - ) # % of compressor capital investment - self.facility_om = input_dict.get( - "facility_om", 1 / 100 - ) # % of facility capital investment minus compressor capital investment - - def pipe_storage_capex(self): - """ - Calculates the installed capital cost of underground pipe hydrogen storage - Returns: - - pipe_storage_capex_per_kg (float): the installed capital cost per kg h2 in 2018 - [USD/kg] - - installed_capex (float): the installed capital cost in 2018 [USD] (including - compressor) - - storage_compressor_capex (float): the installed capital cost in 2018 for the - compressor [USD] - - output_dict (dict): - - pipe_storage_capex (float): installed capital cost in 2018 [USD] - """ - - # Installed capital cost - a = 0.0041617 - b = 0.060369 - c = 6.4581 - self.pipe_storage_capex_per_kg = np.exp( - a * (np.log(self.h2_storage_kg / 1000)) ** 2 - b * np.log(self.h2_storage_kg / 1000) + c - ) # 2019 [USD] from Papadias [2] - self.installed_capex = self.pipe_storage_capex_per_kg * self.h2_storage_kg - cepci_overall = 1.29 / 1.30 # Convert from $2019 to $2018 - self.installed_capex = cepci_overall * self.installed_capex - self.output_dict["pipe_storage_capex"] = self.installed_capex - - outlet_pressure = ( - self.compressor_output_pressure - ) # Max outlet pressure of underground pipe storage [1] - n_compressors = 2 - storage_compressor = Compressor( - outlet_pressure, self.system_flow_rate, n_compressors=n_compressors - ) - storage_compressor.compressor_power() - motor_rating, power = storage_compressor.compressor_system_power() - if motor_rating > 1600: - n_compressors += 1 - storage_compressor = Compressor( - outlet_pressure, self.system_flow_rate, n_compressors=n_compressors - ) - storage_compressor.compressor_power() - motor_rating, power = storage_compressor.compressor_system_power() - comp_capex, comp_OM = storage_compressor.compressor_costs() - cepci = 1.36 / 1.29 # convert from $2016 to $2018 - self.comp_capex = comp_capex * cepci - - return self.pipe_storage_capex_per_kg, self.installed_capex, self.comp_capex - - def pipe_storage_opex(self): - """ - Calculates the operation and maintenance costs excluding electricity costs for the - underground pipe hydrogen storage - - Returns: - - total_opex (float): the OPEX (annual, fixed) in 2018 excluding electricity costs - [USD/kg-yr] - - output_dict (dict): - - pipe_storage_opex (float): OPEX (annual, fixed) in 2018 [USD/yr] - """ - # Operations and Maintenace costs [3] - # Labor - # Base case is 1 operator, 24 hours a day, 7 days a week for a 100,000 kg/day average - # capacity facility. Scaling factor of 0.25 is used for other sized facilities - annual_hours = 8760 * (self.system_flow_rate / 100000) ** 0.25 - self.overhead = 0.5 - labor = (annual_hours * self.labor_rate) * (1 + self.overhead) # Burdened labor cost - insurance = self.insurance * self.installed_capex - property_taxes = self.property_taxes * self.installed_capex - licensing_permits = self.licensing_permits * self.installed_capex - comp_op_maint = self.comp_om * self.comp_capex - facility_op_maint = self.facility_om * (self.installed_capex - self.comp_capex) - - # O&M excludes electricity requirements - total_om = ( - labor - + insurance - + licensing_permits - + property_taxes - + comp_op_maint - + facility_op_maint - ) - self.output_dict["pipe_storage_opex"] = total_om - return total_om diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/salt_cavern/__init__.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/salt_cavern/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/salt_cavern/salt_cavern.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/salt_cavern/salt_cavern.py deleted file mode 100644 index 197ba41ef..000000000 --- a/h2integrate/simulation/technologies/hydrogen/h2_storage/salt_cavern/salt_cavern.py +++ /dev/null @@ -1,169 +0,0 @@ -""" -Author: Kaitlin Brunik -Created: 7/20/2023 -Institution: National Renewable Energy Lab -Description: This file outputs capital and operational costs of salt cavern hydrogen storage. -It needs to be updated to with operational dynamics. -Costs are in 2018 USD - -Sources: - - [1] Papadias 2021: https://www.sciencedirect.com/science/article/pii/S0360319921030834?via%3Dihub - - [2] Papadias 2021: Bulk Hydrogen as Function of Capacity.docx documentation at - hopp/hydrogen/h2_storage - - [3] HDSAM V4.0 Gaseous H2 Geologic Storage sheet -""" - -import numpy as np - -from h2integrate.simulation.technologies.hydrogen.h2_transport.h2_compression import Compressor - - -class SaltCavernStorage: - """ - - Costs are in 2018 USD - """ - - def __init__(self, input_dict): - """ - Initialize SaltCavernStorage. - - Args: - input_dict (dict): - - h2_storage_kg (float): total capacity of hydrogen storage [kg] - - storage_duration_hrs (float): (optional if h2_storage_kg set) [hrs] - - flow_rate_kg_hr (float): (optional if h2_storage_kg set) [kg/hr] - - system_flow_rate (float): [kg/day] - - labor_rate (float): (optional, default: 37.40) [$2018/hr] - - insurance (float): (optional, default: 1%) [decimal percent] - - property_taxes (float): (optional, default: 1%) [decimal percent] - - licensing_permits (float): (optional, default: 0.01%) [decimal percent] - Returns: - - salt_cavern_storage_capex_per_kg (float): the installed capital cost per kg h2 in - 2018 [USD/kg] - - installed_capex (float): the installed capital cost in 2018 [USD] (including - compressor) - - storage_compressor_capex (float): the installed capital cost in 2018 for the - compressor [USD] - - total_opex (float): the OPEX (annual, fixed) in 2018 excluding electricity costs - [USD/kg-yr] - - output_dict (dict): - - salt_cavern_storage_capex (float): installed capital cost in 2018 [USD] - - salt_cavern_storage_opex (float): OPEX (annual, fixed) in 2018 [USD/yr] - """ - self.input_dict = input_dict - self.output_dict = {} - - # inputs - if "h2_storage_kg" in input_dict: - self.h2_storage_kg = input_dict["h2_storage_kg"] # [kg] - elif "storage_duration_hrs" and "flow_rate_kg_hr" in input_dict: - self.h2_storage_kg = input_dict["storage_duration_hrs"] * input_dict["flow_rate_kg_hr"] - else: - raise Exception( - "input_dict must contain h2_storage_kg or storage_duration_hrs and flow_rate_kg_hr" - ) - - if "system_flow_rate" not in input_dict.keys(): - raise ValueError("system_flow_rate required for salt cavern storage model.") - else: - self.system_flow_rate = input_dict["system_flow_rate"] - - self.labor_rate = input_dict.get("labor_rate", 37.39817) # $(2018)/hr - self.insurance = input_dict.get("insurance", 1 / 100) # % of total capital investment - self.property_taxes = input_dict.get( - "property_taxes", 1 / 100 - ) # % of total capital investment - self.licensing_permits = input_dict.get( - "licensing_permits", 0.1 / 100 - ) # % of total capital investment - self.comp_om = input_dict.get( - "compressor_om", 4 / 100 - ) # % of compressor capital investment - self.facility_om = input_dict.get( - "facility_om", 1 / 100 - ) # % of facility capital investment minus compressor capital investment - - def salt_cavern_capex(self): - """ - Calculates the installed capital cost of salt cavern hydrogen storage - Returns: - - salt_cavern_capex_per_kg (float): the installed capital cost per kg h2 in 2018 - [USD/kg] - - installed_capex (float): the installed capital cost in 2018 [USD] (including - compressor) - - storage_compressor_capex (float): the installed capital cost in 2018 for the - compressor [USD] - - output_dict (dict): - - salt_cavern_capex (float): installed capital cost in 2018 [USD] - """ - - # Installed capital cost - a = 0.092548 - b = 1.6432 - c = 10.161 - self.salt_cavern_storage_capex_per_kg = np.exp( - a * (np.log(self.h2_storage_kg / 1000)) ** 2 - b * np.log(self.h2_storage_kg / 1000) + c - ) # 2019 [USD] from Papadias [2] - self.installed_capex = self.salt_cavern_storage_capex_per_kg * self.h2_storage_kg - cepci_overall = 1.29 / 1.30 # Convert from $2019 to $2018 - self.installed_capex = cepci_overall * self.installed_capex - self.output_dict["salt_cavern_storage_capex"] = self.installed_capex - - outlet_pressure = 120 # Max outlet pressure of salt cavern in [1] - n_compressors = 2 - storage_compressor = Compressor( - outlet_pressure, self.system_flow_rate, n_compressors=n_compressors - ) - storage_compressor.compressor_power() - motor_rating, power = storage_compressor.compressor_system_power() - if motor_rating > 1600: - n_compressors += 1 - storage_compressor = Compressor( - outlet_pressure, self.system_flow_rate, n_compressors=n_compressors - ) - storage_compressor.compressor_power() - motor_rating, power = storage_compressor.compressor_system_power() - comp_capex, comp_OM = storage_compressor.compressor_costs() - cepci = 1.36 / 1.29 # convert from $2016 to $2018 - self.comp_capex = comp_capex * cepci - - return ( - self.salt_cavern_storage_capex_per_kg, - self.installed_capex, - self.comp_capex, - ) - - def salt_cavern_opex(self): - """ - Calculates the operation and maintenance costs excluding electricity costs for the salt - cavern hydrogen storage - - Returns: - - total_opex (float): the OPEX (annual, fixed) in 2018 excluding electricity costs - [USD/kg-yr] - - output_dict (dict): - - salt_cavern_storage_opex (float): OPEX (annual, fixed) in 2018 [USD/yr] - """ - # Operations and Maintenace costs [3] - # Labor - # Base case is 1 operator, 24 hours a day, 7 days a week for a 100,000 kg/day average - # capacity facility. Scaling factor of 0.25 is used for other sized facilities - annual_hours = 8760 * (self.system_flow_rate / 100000) ** 0.25 - self.overhead = 0.5 - labor = (annual_hours * self.labor_rate) * (1 + self.overhead) # Burdened labor cost - insurance = self.insurance * self.installed_capex - property_taxes = self.property_taxes * self.installed_capex - licensing_permits = self.licensing_permits * self.installed_capex - comp_op_maint = self.comp_om * self.comp_capex - facility_op_maint = self.facility_om * (self.installed_capex - self.comp_capex) - - # O&M excludes electricity requirements - total_om = ( - labor - + insurance - + licensing_permits - + property_taxes - + comp_op_maint - + facility_op_maint - ) - self.output_dict["salt_cavern_storage_opex"] = total_om - return total_om diff --git a/h2integrate/simulation/technologies/hydrogen/h2_storage/storage_sizing.py b/h2integrate/simulation/technologies/hydrogen/h2_storage/storage_sizing.py deleted file mode 100644 index 4fa94b4ea..000000000 --- a/h2integrate/simulation/technologies/hydrogen/h2_storage/storage_sizing.py +++ /dev/null @@ -1,84 +0,0 @@ -import numpy as np - - -def hydrogen_storage_capacity(H2_Results, electrolyzer_size_mw, hydrogen_demand_kgphr): - """Calculate storage capacity based on hydrogen demand and production. - - Args: - H2_Results (dict): Dictionary including electrolyzer physics results. - electrolyzer_size_mw (float): Electrolyzer size in MW. - hydrogen_demand_kgphr (float): Hydrogen demand in kg/hr. - - Returns: - hydrogen_demand_kgphr (list): Hydrogen hourly demand in kilograms per hour. - hydrogen_storage_capacity_kg (float): Hydrogen storage capacity in kilograms. - hydrogen_storage_duration_hr (float): Hydrogen storage duration in hours using HHV/LHV. - hydrogen_storage_soc (list): Timeseries of the hydrogen storage state of charge. - """ - - hydrogen_production_kgphr = H2_Results["Hydrogen Hourly Production [kg/hr]"] - - hydrogen_demand_kgphr = max( - hydrogen_demand_kgphr, np.mean(hydrogen_production_kgphr) - ) # TODO: potentially add buffer No buffer needed since we are already oversizing - - # TODO: SOC is just an absolute value and is not a percentage. Ideally would calculate as shortfall in future. - hydrogen_storage_soc = [] - for j in range(len(hydrogen_production_kgphr)): - if j == 0: - hydrogen_storage_soc.append(hydrogen_production_kgphr[j] - hydrogen_demand_kgphr) - else: - hydrogen_storage_soc.append( - hydrogen_storage_soc[j - 1] + hydrogen_production_kgphr[j] - hydrogen_demand_kgphr - ) - - minimum_soc = np.min(hydrogen_storage_soc) - - # adjust soc so it's not negative. - if minimum_soc < 0: - hydrogen_storage_soc = [x + np.abs(minimum_soc) for x in hydrogen_storage_soc] - - hydrogen_storage_capacity_kg = np.max(hydrogen_storage_soc) - np.min(hydrogen_storage_soc) - h2_LHV = 119.96 # MJ/kg - h2_HHV = 141.88 # MJ/kg - hydrogen_storage_capacity_MWh_LHV = hydrogen_storage_capacity_kg * h2_LHV / 3600 - hydrogen_storage_capacity_kg * h2_HHV / 3600 - - # # Get max injection/withdrawal rate - # hydrogen_injection_withdrawal_rate = [] - # for j in range(len(hydrogen_production_kgphr)): - # hydrogen_injection_withdrawal_rate.append( - # hydrogen_production_kgphr[j] - hydrogen_demand_kgphr - # ) - # max_h2_injection_rate_kgphr = max(hydrogen_injection_withdrawal_rate) - - # # Get storage compressor capacity. TODO: sync compressor calculation here with H2Integrate - # compressor model - # compressor_total_capacity_kW = ( - # max_h2_injection_rate_kgphr / 3600 / 2.0158 * 8641.678424 - # ) - - # compressor_max_capacity_kw = 16000 - # n_comps = math.ceil(compressor_total_capacity_kW / compressor_max_capacity_kw) - - # small_positive = 1e-6 - # compressor_avg_capacity_kw = compressor_total_capacity_kW / ( - # n_comps + small_positive - # ) - - # Get average electrolyzer efficiency - electrolyzer_average_efficiency_HHV = H2_Results["Sim: Average Efficiency [%-HHV]"] - - # Calculate storage durationhyd - hydrogen_storage_duration_hr = ( - hydrogen_storage_capacity_MWh_LHV - / electrolyzer_size_mw - / electrolyzer_average_efficiency_HHV - ) - - return ( - np.ones_like(hydrogen_storage_capacity_kg) * hydrogen_demand_kgphr, - hydrogen_storage_capacity_kg, - hydrogen_storage_duration_hr, - hydrogen_storage_soc, - ) diff --git a/h2integrate/simulation/technologies/hydrogen/h2_transport/h2_pipe_array.py b/h2integrate/simulation/technologies/hydrogen/h2_transport/h2_pipe_array.py index a0ff76e79..3b7cae9d8 100644 --- a/h2integrate/simulation/technologies/hydrogen/h2_transport/h2_pipe_array.py +++ b/h2integrate/simulation/technologies/hydrogen/h2_transport/h2_pipe_array.py @@ -1,8 +1,6 @@ from numpy import flip, isnan, nansum -from h2integrate.simulation.technologies.hydrogen.h2_transport.h2_export_pipe import ( - run_pipe_analysis, -) +from h2integrate.converters.hydrogen.pem_model.h2_transport.h2_export_pipe import run_pipe_analysis """ diff --git a/h2integrate/storage/hydrogen/eco_storage.py b/h2integrate/storage/hydrogen/eco_storage.py deleted file mode 100644 index c99261d18..000000000 --- a/h2integrate/storage/hydrogen/eco_storage.py +++ /dev/null @@ -1,223 +0,0 @@ -import numpy as np -from attrs import field, define - -from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains -from h2integrate.core.model_baseclasses import CostModelBaseClass -from h2integrate.simulation.technologies.hydrogen.h2_storage.mch.mch_cost import MCHStorage - - -# TODO: fix import structure in future refactor - - -from h2integrate.simulation.technologies.hydrogen.h2_storage.storage_sizing import hydrogen_storage_capacity # noqa: E501 # fmt: skip # isort:skip -from h2integrate.simulation.technologies.hydrogen.h2_storage.salt_cavern.salt_cavern import SaltCavernStorage # noqa: E501 # fmt: skip # isort:skip -from h2integrate.simulation.technologies.hydrogen.h2_storage.lined_rock_cavern.lined_rock_cavern import LinedRockCavernStorage # noqa: E501 # fmt: skip # isort:skip - - -@define -class H2StorageModelConfig(BaseConfig): - commodity_name: str = field(default="hydrogen") - commodity_units: str = field(default="kg") - electrolyzer_rating_mw_for_h2_storage_sizing: float | None = field(default=None) - size_capacity_from_demand: dict = field(default={"flag": True}) - capacity_from_max_on_turbine_storage: bool = field(default=False) - type: str = field( - default="salt_cavern", - validator=contains(["salt_cavern", "lined_rock_cavern", "none", "mch"]), - ) - days: int = field(default=0) - cost_year: int = field(default=2018, converter=int, validator=contains([2018, 2021])) - - def __attrs_post_init__(self): - if self.type == "mch": - self.cost_year = 2024 - else: - self.cost_year = 2018 - - -class H2Storage(CostModelBaseClass): - def initialize(self): - super().initialize() - self.options.declare("verbose", types=bool, default=False) - - def setup(self): - self.config = H2StorageModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") - ) - - super().setup() - - self.add_input( - "hydrogen_in", - val=0.0, - shape_by_conn=True, - units="kg/h", - ) - - self.add_input( - "rated_h2_production_kg_pr_hr", - val=0.0, - units="kg/h", - desc="Rated hydrogen production of electrolyzer", - ) - self.add_input("efficiency", val=0.0, desc="Average efficiency of the electrolyzer") - - def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): - ########### initialize output dictionary ########### - h2_storage_results = {} - - storage_max_fill_rate = np.max(inputs["hydrogen_in"]) - - ########### get hydrogen storage size in kilograms ########### - ##################### no hydrogen storage - if self.config.type == "none": - h2_storage_capacity_kg = 0.0 - storage_max_fill_rate = 0.0 - - ##################### get storage capacity from hydrogen storage demand - elif self.config.size_capacity_from_demand["flag"]: - if self.config.electrolyzer_rating_mw_for_h2_storage_sizing is None: - raise ( - ValueError( - "h2 storage input battery_electricity_discharge must be specified \ - if size_capacity_from_demand is True." - ) - ) - hydrogen_storage_demand = np.mean( - inputs["hydrogen_in"] - ) # TODO: update demand based on end-use needs - results_dict = { - "Hydrogen Hourly Production [kg/hr]": inputs["hydrogen_in"], - "Sim: Average Efficiency [%-HHV]": inputs["efficiency"], - } - ( - hydrogen_demand_kgphr, - hydrogen_storage_capacity_kg, - hydrogen_storage_duration_hr, - hydrogen_storage_soc, - ) = hydrogen_storage_capacity( - results_dict, - self.config.electrolyzer_rating_mw_for_h2_storage_sizing, - hydrogen_storage_demand, - ) - h2_storage_capacity_kg = hydrogen_storage_capacity_kg - h2_storage_results["hydrogen_storage_duration_hr"] = hydrogen_storage_duration_hr - h2_storage_results["hydrogen_storage_soc"] = hydrogen_storage_soc - - ##################### get storage capacity based on storage days in config - else: - storage_hours = self.config.days * 24 - h2_storage_capacity_kg = round(storage_hours * storage_max_fill_rate) - - h2_storage_results["h2_storage_capacity_kg"] = h2_storage_capacity_kg - h2_storage_results["h2_storage_max_fill_rate_kg_hr"] = storage_max_fill_rate - - ########### run specific hydrogen storage models for costs and energy use ########### - if self.config.type == "none": - h2_storage_results["storage_capex"] = 0.0 - h2_storage_results["storage_opex"] = 0.0 - h2_storage_results["storage_energy"] = 0.0 - h2_storage_results["cost_year"] = 2018 - - h2_storage = None - - elif self.config.type == "salt_cavern": - # initialize dictionary for salt cavern storage parameters - storage_input = {} - - # pull parameters from plant_config file - storage_input["h2_storage_kg"] = h2_storage_capacity_kg - storage_input["system_flow_rate"] = storage_max_fill_rate - storage_input["model"] = "papadias" - - # run salt cavern storage model - h2_storage = SaltCavernStorage(storage_input) - - h2_storage.salt_cavern_capex() - h2_storage.salt_cavern_opex() - - h2_storage_results["storage_capex"] = h2_storage.output_dict[ - "salt_cavern_storage_capex" - ] - h2_storage_results["storage_opex"] = h2_storage.output_dict["salt_cavern_storage_opex"] - h2_storage_results["storage_energy"] = 0.0 - h2_storage_results["cost_year"] = 2018 - - elif self.config.type == "lined_rock_cavern": - # initialize dictionary for salt cavern storage parameters - storage_input = {} - - # pull parameters from plat_config file - storage_input["h2_storage_kg"] = h2_storage_capacity_kg - storage_input["system_flow_rate"] = storage_max_fill_rate - storage_input["model"] = "papadias" - - # run salt cavern storage model - h2_storage = LinedRockCavernStorage(storage_input) - - h2_storage.lined_rock_cavern_capex() - h2_storage.lined_rock_cavern_opex() - - h2_storage_results["storage_capex"] = h2_storage.output_dict[ - "lined_rock_cavern_storage_capex" - ] - h2_storage_results["storage_opex"] = h2_storage.output_dict[ - "lined_rock_cavern_storage_opex" - ] - h2_storage_results["storage_energy"] = 0.0 - h2_storage_results["cost_year"] = 2018 # TODO: check - - elif self.config.type == "mch": - if not self.config.size_capacity_from_demand["flag"]: - msg = ( - "To use MCH hydrogen storage, the size_capacity_from_demand " - "flag must be True." - ) - raise ValueError(msg) - - max_rated_h2 = np.max( - [inputs["rated_h2_production_kg_pr_hr"][0], storage_max_fill_rate] - ) - h2_charge_discharge = np.diff(hydrogen_storage_soc, prepend=False) - h2_charged_idx = np.argwhere(h2_charge_discharge > 0).flatten() - annual_h2_stored = sum([h2_charge_discharge[i] for i in h2_charged_idx]) - - h2_storage = MCHStorage( - max_H2_production_kg_pr_hr=max_rated_h2, - hydrogen_storage_capacity_kg=h2_storage_capacity_kg, - hydrogen_demand_kg_pr_hr=hydrogen_demand_kgphr, - annual_hydrogen_stored_kg_pr_yr=annual_h2_stored, - ) - h2_storage_costs = h2_storage.run_costs() - h2_storage_results["storage_capex"] = h2_storage_costs["mch_capex"] - h2_storage_results["storage_opex"] = ( - h2_storage_costs["mch_variable_om"] + h2_storage_costs["mch_opex"] - ) - h2_storage_results["storage_energy"] = 0.0 - h2_storage_results["cost_year"] = 2024 - - else: - msg = ( - "H2 storage type %s was given, but must be one of ['none', 'turbine', 'pipe'," - " 'salt_cavern', 'lined_rock_cavern', 'mch']" - ) - raise ValueError(msg) - - if self.options["verbose"]: - print("\nH2 Storage Results:") - print("H2 storage capex: ${:,.0f}".format(h2_storage_results["storage_capex"])) - print("H2 storage annual opex: ${:,.0f}/yr".format(h2_storage_results["storage_opex"])) - print( - "H2 storage capacity (metric tons): ", - h2_storage_results["h2_storage_capacity_kg"] / 1000, - ) - if h2_storage_results["h2_storage_capacity_kg"] > 0: - print( - "H2 storage cost $/kg of H2: ", - h2_storage_results["storage_capex"] - / h2_storage_results["h2_storage_capacity_kg"], - ) - - outputs["CapEx"] = h2_storage_results["storage_capex"] - outputs["OpEx"] = h2_storage_results["storage_opex"] diff --git a/h2integrate/storage/hydrogen/h2_storage_cost.py b/h2integrate/storage/hydrogen/h2_storage_cost.py index 20c693cfb..3c9b1af5a 100644 --- a/h2integrate/storage/hydrogen/h2_storage_cost.py +++ b/h2integrate/storage/hydrogen/h2_storage_cost.py @@ -1,15 +1,11 @@ +import numpy as np from attrs import field, define from openmdao.utils import units from h2integrate.core.utilities import BaseConfig, merge_shared_inputs from h2integrate.core.validators import contains, gte_zero, range_val from h2integrate.core.model_baseclasses import CostModelBaseClass - - -# TODO: fix import structure in future refactor -from h2integrate.simulation.technologies.hydrogen.h2_storage.lined_rock_cavern.lined_rock_cavern import LinedRockCavernStorage # noqa: E501 # fmt: skip # isort:skip -from h2integrate.simulation.technologies.hydrogen.h2_storage.salt_cavern.salt_cavern import SaltCavernStorage # noqa: E501 # fmt: skip # isort:skip -from h2integrate.simulation.technologies.hydrogen.h2_storage.pipe_storage import UndergroundPipeStorage # noqa: E501 # fmt: skip # isort:skip +from h2integrate.simulation.technologies.hydrogen.h2_transport.h2_compression import Compressor @define @@ -129,15 +125,14 @@ def make_storage_input_dict(self, inputs): inputs["max_capacity"], f"({self.config.commodity_units})*h", "kg" ) - # convert charge rate to kg/h - # TODO: update to kg/day as a bug-fix + # convert charge rate to kg/day (required for storage models) storage_max_fill_rate = units.convert_units( - inputs["max_charge_rate"], f"{self.config.commodity_units}", "kg/h" + inputs["max_charge_rate"], f"{self.config.commodity_units}", "kg/d" ) storage_input["h2_storage_kg"] = max_capacity_kg[0] - # below should be in kg/day + # system_flow_rate must be in kg/day storage_input["system_flow_rate"] = storage_max_fill_rate[0] return storage_input @@ -157,13 +152,71 @@ def setup(self): def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): storage_input = self.make_storage_input_dict(inputs) - h2_storage = LinedRockCavernStorage(storage_input) - h2_storage.lined_rock_cavern_capex() - h2_storage.lined_rock_cavern_opex() + h2_storage_kg = storage_input["h2_storage_kg"] + system_flow_rate = storage_input["system_flow_rate"] + labor_rate = storage_input.get("labor_rate", 37.39817) + insurance = storage_input.get("insurance", 1 / 100) + property_taxes = storage_input.get("property_taxes", 1 / 100) + licensing_permits = storage_input.get("licensing_permits", 0.1 / 100) + comp_om = storage_input.get("compressor_om", 4 / 100) + facility_om = storage_input.get("facility_om", 1 / 100) + + # Calculate CAPEX + # Installed capital cost per kg from Papadias [2] + a = 0.095803 + b = 1.5868 + c = 10.332 + lined_rock_cavern_storage_capex_per_kg = np.exp( + a * (np.log(h2_storage_kg / 1000)) ** 2 - b * np.log(h2_storage_kg / 1000) + c + ) # 2019 [USD] from Papadias [2] + installed_capex = lined_rock_cavern_storage_capex_per_kg * h2_storage_kg + cepci_overall = 1.29 / 1.30 # Convert from $2019 to $2018 + installed_capex = cepci_overall * installed_capex + + # Calculate compressor costs + outlet_pressure = 200 # Max outlet pressure of lined rock cavern in [1] + n_compressors = 2 + storage_compressor = Compressor( + outlet_pressure, system_flow_rate, n_compressors=n_compressors + ) + storage_compressor.compressor_power() + motor_rating, power = storage_compressor.compressor_system_power() + if motor_rating > 1600: + n_compressors += 1 + storage_compressor = Compressor( + outlet_pressure, system_flow_rate, n_compressors=n_compressors + ) + storage_compressor.compressor_power() + motor_rating, power = storage_compressor.compressor_system_power() + comp_capex, comp_OM = storage_compressor.compressor_costs() + cepci = 1.36 / 1.29 # convert from $2016 to $2018 + comp_capex = comp_capex * cepci + + # Calculate OPEX + # Labor - Base case is 1 operator, 24 hours a day, 7 days a week for a 100,000 kg/day + # average capacity facility. Scaling factor of 0.25 is used for other sized facilities + annual_hours = 8760 * (system_flow_rate / 100000) ** 0.25 + overhead = 0.5 + labor = (annual_hours * labor_rate) * (1 + overhead) # Burdened labor cost + insurance_cost = insurance * installed_capex + property_taxes_cost = property_taxes * installed_capex + licensing_permits_cost = licensing_permits * installed_capex + comp_op_maint = comp_om * comp_capex + facility_op_maint = facility_om * (installed_capex - comp_capex) + + # O&M excludes electricity requirements + total_om = ( + labor + + insurance_cost + + licensing_permits_cost + + property_taxes_cost + + comp_op_maint + + facility_op_maint + ) - outputs["CapEx"] = h2_storage.output_dict["lined_rock_cavern_storage_capex"] - outputs["OpEx"] = h2_storage.output_dict["lined_rock_cavern_storage_opex"] + outputs["CapEx"] = installed_capex + outputs["OpEx"] = total_om class SaltCavernStorageCostModel(HydrogenStorageBaseCostModel): @@ -175,13 +228,71 @@ def setup(self): def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): storage_input = self.make_storage_input_dict(inputs) - h2_storage = SaltCavernStorage(storage_input) - h2_storage.salt_cavern_capex() - h2_storage.salt_cavern_opex() + h2_storage_kg = storage_input["h2_storage_kg"] + system_flow_rate = storage_input["system_flow_rate"] + labor_rate = storage_input.get("labor_rate", 37.39817) + insurance = storage_input.get("insurance", 1 / 100) + property_taxes = storage_input.get("property_taxes", 1 / 100) + licensing_permits = storage_input.get("licensing_permits", 0.1 / 100) + comp_om = storage_input.get("compressor_om", 4 / 100) + facility_om = storage_input.get("facility_om", 1 / 100) + + # Calculate CAPEX + # Installed capital cost per kg from Papadias [2] + a = 0.092548 + b = 1.6432 + c = 10.161 + salt_cavern_storage_capex_per_kg = np.exp( + a * (np.log(h2_storage_kg / 1000)) ** 2 - b * np.log(h2_storage_kg / 1000) + c + ) # 2019 [USD] from Papadias [2] + installed_capex = salt_cavern_storage_capex_per_kg * h2_storage_kg + cepci_overall = 1.29 / 1.30 # Convert from $2019 to $2018 + installed_capex = cepci_overall * installed_capex + + # Calculate compressor costs + outlet_pressure = 120 # Max outlet pressure of salt cavern in [1] + n_compressors = 2 + storage_compressor = Compressor( + outlet_pressure, system_flow_rate, n_compressors=n_compressors + ) + storage_compressor.compressor_power() + motor_rating, power = storage_compressor.compressor_system_power() + if motor_rating > 1600: + n_compressors += 1 + storage_compressor = Compressor( + outlet_pressure, system_flow_rate, n_compressors=n_compressors + ) + storage_compressor.compressor_power() + motor_rating, power = storage_compressor.compressor_system_power() + comp_capex, comp_OM = storage_compressor.compressor_costs() + cepci = 1.36 / 1.29 # convert from $2016 to $2018 + comp_capex = comp_capex * cepci + + # Calculate OPEX + # Labor - Base case is 1 operator, 24 hours a day, 7 days a week for a 100,000 kg/day + # average capacity facility. Scaling factor of 0.25 is used for other sized facilities + annual_hours = 8760 * (system_flow_rate / 100000) ** 0.25 + overhead = 0.5 + labor = (annual_hours * labor_rate) * (1 + overhead) # Burdened labor cost + insurance_cost = insurance * installed_capex + property_taxes_cost = property_taxes * installed_capex + licensing_permits_cost = licensing_permits * installed_capex + comp_op_maint = comp_om * comp_capex + facility_op_maint = facility_om * (installed_capex - comp_capex) + + # O&M excludes electricity requirements + total_om = ( + labor + + insurance_cost + + licensing_permits_cost + + property_taxes_cost + + comp_op_maint + + facility_op_maint + ) - outputs["CapEx"] = h2_storage.output_dict["salt_cavern_storage_capex"] - outputs["OpEx"] = h2_storage.output_dict["salt_cavern_storage_opex"] + outputs["CapEx"] = installed_capex + outputs["OpEx"] = total_om class PipeStorageCostModel(HydrogenStorageBaseCostModel): @@ -194,12 +305,72 @@ def setup(self): def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): storage_input = self.make_storage_input_dict(inputs) - # compressor_output_pressure must be 100 bar or else an error will be thrown - storage_input.update({"compressor_output_pressure": 100}) - h2_storage = UndergroundPipeStorage(storage_input) - - h2_storage.pipe_storage_capex() - h2_storage.pipe_storage_opex() + h2_storage_kg = storage_input["h2_storage_kg"] + system_flow_rate = storage_input["system_flow_rate"] + labor_rate = storage_input.get("labor_rate", 37.39817) + insurance = storage_input.get("insurance", 1 / 100) + property_taxes = storage_input.get("property_taxes", 1 / 100) + licensing_permits = storage_input.get("licensing_permits", 0.1 / 100) + comp_om = storage_input.get("compressor_om", 4 / 100) + facility_om = storage_input.get("facility_om", 1 / 100) + + # compressor_output_pressure must be 100 bar for underground pipe storage + compressor_output_pressure = 100 + + # Calculate CAPEX + # Installed capital cost per kg from Papadias [2] + a = 0.0041617 + b = 0.060369 + c = 6.4581 + pipe_storage_capex_per_kg = np.exp( + a * (np.log(h2_storage_kg / 1000)) ** 2 - b * np.log(h2_storage_kg / 1000) + c + ) # 2019 [USD] from Papadias [2] + installed_capex = pipe_storage_capex_per_kg * h2_storage_kg + cepci_overall = 1.29 / 1.30 # Convert from $2019 to $2018 + installed_capex = cepci_overall * installed_capex + + # Calculate compressor costs + outlet_pressure = ( + compressor_output_pressure # Max outlet pressure of underground pipe storage [1] + ) + n_compressors = 2 + storage_compressor = Compressor( + outlet_pressure, system_flow_rate, n_compressors=n_compressors + ) + storage_compressor.compressor_power() + motor_rating, power = storage_compressor.compressor_system_power() + if motor_rating > 1600: + n_compressors += 1 + storage_compressor = Compressor( + outlet_pressure, system_flow_rate, n_compressors=n_compressors + ) + storage_compressor.compressor_power() + motor_rating, power = storage_compressor.compressor_system_power() + comp_capex, comp_OM = storage_compressor.compressor_costs() + cepci = 1.36 / 1.29 # convert from $2016 to $2018 + comp_capex = comp_capex * cepci + + # Calculate OPEX + # Labor - Base case is 1 operator, 24 hours a day, 7 days a week for a 100,000 kg/day + # average capacity facility. Scaling factor of 0.25 is used for other sized facilities + annual_hours = 8760 * (system_flow_rate / 100000) ** 0.25 + overhead = 0.5 + labor = (annual_hours * labor_rate) * (1 + overhead) # Burdened labor cost + insurance_cost = insurance * installed_capex + property_taxes_cost = property_taxes * installed_capex + licensing_permits_cost = licensing_permits * installed_capex + comp_op_maint = comp_om * comp_capex + facility_op_maint = facility_om * (installed_capex - comp_capex) + + # O&M excludes electricity requirements + total_om = ( + labor + + insurance_cost + + licensing_permits_cost + + property_taxes_cost + + comp_op_maint + + facility_op_maint + ) - outputs["CapEx"] = h2_storage.output_dict["pipe_storage_capex"] - outputs["OpEx"] = h2_storage.output_dict["pipe_storage_opex"] + outputs["CapEx"] = installed_capex + outputs["OpEx"] = total_om diff --git a/h2integrate/storage/hydrogen/test/test_hydrogen_storage.py b/h2integrate/storage/hydrogen/test/test_hydrogen_storage.py index ffdd91ca1..fad61bb5e 100644 --- a/h2integrate/storage/hydrogen/test/test_hydrogen_storage.py +++ b/h2integrate/storage/hydrogen/test/test_hydrogen_storage.py @@ -45,7 +45,7 @@ def test_salt_cavern_ex_2(plant_config, subtests): with subtests.test("CapEx"): assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-6) == 65337437.17944019 with subtests.test("OpEx"): - assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 2358794.1150327 + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 3149096.037312646 with subtests.test("VarOpEx"): assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 with subtests.test("Cost year"): @@ -75,7 +75,7 @@ def test_lined_rock_cavern_ex_12_small_case(plant_config, subtests): with subtests.test("CapEx"): assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-6) == 18693728.23242369 with subtests.test("OpEx"): - assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 767466.36227705 + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 1099582.4333529277 with subtests.test("VarOpEx"): assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 with subtests.test("Cost year"): @@ -104,7 +104,7 @@ def test_lined_rock_cavern_ex_1(plant_config, subtests): with subtests.test("CapEx"): assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-6) == 92392496.03198986 with subtests.test("OpEx"): - assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 3228373.63748516 + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 4292680.718474801 with subtests.test("VarOpEx"): assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 with subtests.test("Cost year"): @@ -133,7 +133,7 @@ def test_lined_rock_cavern_ex_14(plant_config, subtests): with subtests.test("CapEx"): assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-6) == 1.28437699 * 1e8 with subtests.test("OpEx"): - assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 4330693.49125131 + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 5315184.827689768 with subtests.test("VarOpEx"): assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 with subtests.test("Cost year"): @@ -162,7 +162,205 @@ def test_buried_pipe_storage(plant_config, subtests): with subtests.test("CapEx"): assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-6) == 1827170156.1390543 with subtests.test("OpEx"): - assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 56972128.44322313 + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 57720829.60694359 + with subtests.test("VarOpEx"): + assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 + with subtests.test("Cost year"): + assert prob.get_val("sys.cost_year") == 2018 + + +def test_lined_rock_cavern_capex_per_kg(plant_config): + """Test based on original test_lined_rock_storage.py with 1M kg storage capacity.""" + tech_config_dict = { + "model_inputs": { + "shared_parameters": { + "max_capacity": 1000000, + "max_charge_rate": 100000 / 24, # 100000 kg/day converted to kg/h + } + } + } + prob = om.Problem() + comp = LinedRockCavernStorageCostModel( + plant_config=plant_config, + tech_config=tech_config_dict, + driver_config={}, + ) + prob.model.add_subsystem("sys", comp) + prob.setup() + prob.run_model() + + # Calculate expected capex per kg + h2_storage_kg = 1000000 + a = 0.095803 + b = 1.5868 + c = 10.332 + capex_per_kg = np.exp( + a * (np.log(h2_storage_kg / 1000)) ** 2 - b * np.log(h2_storage_kg / 1000) + c + ) + cepci_overall = 1.29 / 1.30 + expected_capex = cepci_overall * capex_per_kg * h2_storage_kg + + assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-6) == expected_capex + + +def test_lined_rock_cavern_1M_kg(plant_config, subtests): + """Test lined rock cavern with 1M kg capacity matching original unit tests.""" + tech_config_dict = { + "model_inputs": { + "shared_parameters": { + "max_capacity": 1000000, + "max_charge_rate": 100000 / 24, # 100000 kg/day converted to kg/h + } + } + } + prob = om.Problem() + comp = LinedRockCavernStorageCostModel( + plant_config=plant_config, + tech_config=tech_config_dict, + driver_config={}, + ) + prob.model.add_subsystem("sys", comp) + prob.setup() + prob.run_model() + + with subtests.test("CapEx"): + # Expected: 51136144.673 (from original test) + assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-5) == 51136144.673 + with subtests.test("OpEx"): + # Expected: 2359700 (from original test) + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-5) == 2359700 + with subtests.test("VarOpEx"): + assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 + with subtests.test("Cost year"): + assert prob.get_val("sys.cost_year") == 2018 + + +def test_salt_cavern_capex_per_kg(plant_config): + """Test based on original test_salt_cavern_storage.py with 1M kg storage capacity.""" + tech_config_dict = { + "model_inputs": { + "shared_parameters": { + "max_capacity": 1000000, + "max_charge_rate": 100000 / 24, # 100000 kg/day converted to kg/h + } + } + } + prob = om.Problem() + comp = SaltCavernStorageCostModel( + plant_config=plant_config, + tech_config=tech_config_dict, + driver_config={}, + ) + prob.model.add_subsystem("sys", comp) + prob.setup() + prob.run_model() + + # Calculate expected capex per kg + h2_storage_kg = 1000000 + a = 0.092548 + b = 1.6432 + c = 10.161 + capex_per_kg = np.exp( + a * (np.log(h2_storage_kg / 1000)) ** 2 - b * np.log(h2_storage_kg / 1000) + c + ) + cepci_overall = 1.29 / 1.30 + expected_capex = cepci_overall * capex_per_kg * h2_storage_kg + + assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-6) == expected_capex + + +def test_salt_cavern_1M_kg(plant_config, subtests): + """Test salt cavern with 1M kg capacity matching original unit tests.""" + tech_config_dict = { + "model_inputs": { + "shared_parameters": { + "max_capacity": 1000000, + "max_charge_rate": 100000 / 24, # 100000 kg/day converted to kg/h + } + } + } + prob = om.Problem() + comp = SaltCavernStorageCostModel( + plant_config=plant_config, + tech_config=tech_config_dict, + driver_config={}, + ) + prob.model.add_subsystem("sys", comp) + prob.setup() + prob.run_model() + + with subtests.test("CapEx"): + # Expected: 24992482.4198 (from original test) + assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-5) == 24992482.4198 + with subtests.test("OpEx"): + # Expected: 1461664 (from original test) + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-5) == 1461664 + with subtests.test("VarOpEx"): + assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 + with subtests.test("Cost year"): + assert prob.get_val("sys.cost_year") == 2018 + + +def test_pipe_storage_capex_per_kg(plant_config): + """Test based on original test_underground_pipe_storage.py with 1M kg storage capacity.""" + tech_config_dict = { + "model_inputs": { + "shared_parameters": { + "max_capacity": 1000000, + "max_charge_rate": 100000 / 24, # 100000 kg/day converted to kg/h + } + } + } + prob = om.Problem() + comp = PipeStorageCostModel( + plant_config=plant_config, + tech_config=tech_config_dict, + driver_config={}, + ) + prob.model.add_subsystem("sys", comp) + prob.setup() + prob.run_model() + + # Calculate expected capex per kg + h2_storage_kg = 1000000 + a = 0.0041617 + b = 0.060369 + c = 6.4581 + capex_per_kg = np.exp( + a * (np.log(h2_storage_kg / 1000)) ** 2 - b * np.log(h2_storage_kg / 1000) + c + ) + cepci_overall = 1.29 / 1.30 + expected_capex = cepci_overall * capex_per_kg * h2_storage_kg + + assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-6) == expected_capex + + +def test_pipe_storage_1M_kg(plant_config, subtests): + """Test underground pipe storage with 1M kg capacity matching original unit tests.""" + tech_config_dict = { + "model_inputs": { + "shared_parameters": { + "max_capacity": 1000000, + "max_charge_rate": 100000 / 24, # 100000 kg/day converted to kg/h + } + } + } + prob = om.Problem() + comp = PipeStorageCostModel( + plant_config=plant_config, + tech_config=tech_config_dict, + driver_config={}, + ) + prob.model.add_subsystem("sys", comp) + prob.setup() + prob.run_model() + + with subtests.test("CapEx"): + # Expected: 508745483.851 (from original test) + assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-5) == 508745483.851 + with subtests.test("OpEx"): + # Expected: 16439748 (from original test) + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-5) == 16439748 with subtests.test("VarOpEx"): assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 with subtests.test("Cost year"): diff --git a/h2integrate/storage/hydrogen/test/test_tol_mch_storage.py b/h2integrate/storage/hydrogen/test/test_tol_mch_storage.py index 7f703ae93..862126402 100644 --- a/h2integrate/storage/hydrogen/test/test_tol_mch_storage.py +++ b/h2integrate/storage/hydrogen/test/test_tol_mch_storage.py @@ -4,7 +4,7 @@ from pytest import approx, fixture from h2integrate.storage.hydrogen.mch_storage import MCHTOLStorageCostModel -from h2integrate.simulation.technologies.hydrogen.h2_storage.mch.mch_cost import MCHStorage +from h2integrate.converters.hydrogen.pem_model.h2_storage.mch.mch_cost import MCHStorage @fixture diff --git a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_compressor.py b/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_compressor.py index 7ce0cb9ab..7727d3131 100644 --- a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_compressor.py +++ b/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_compressor.py @@ -1,6 +1,6 @@ from pytest import approx, raises -from h2integrate.simulation.technologies.hydrogen.h2_transport.h2_compression import Compressor +from h2integrate.converters.hydrogen.pem_model.h2_transport.h2_compression import Compressor # test that we get the results we got when the code was received diff --git a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_export_pipeline.py b/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_export_pipeline.py index 6bd8bb0f4..77fe8b68a 100644 --- a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_export_pipeline.py +++ b/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_export_pipeline.py @@ -1,6 +1,4 @@ -from h2integrate.simulation.technologies.hydrogen.h2_transport.h2_export_pipe import ( - run_pipe_analysis, -) +from h2integrate.converters.hydrogen.pem_model.h2_transport.h2_export_pipe import run_pipe_analysis # test that we the results we got when the code was recieved diff --git a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_pipe_array.py b/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_pipe_array.py index 47d18c782..842e7fd9c 100644 --- a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_pipe_array.py +++ b/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_pipe_array.py @@ -1,4 +1,4 @@ -from h2integrate.simulation.technologies.hydrogen.h2_transport.h2_pipe_array import ( +from h2integrate.converters.hydrogen.pem_model.h2_transport.h2_pipe_array import ( run_pipe_array, run_pipe_array_const_diam, ) diff --git a/tests/h2integrate/test_hydrogen/test_lined_rock_storage.py b/tests/h2integrate/test_hydrogen/test_lined_rock_storage.py index 503ce914e..e3b86920a 100644 --- a/tests/h2integrate/test_hydrogen/test_lined_rock_storage.py +++ b/tests/h2integrate/test_hydrogen/test_lined_rock_storage.py @@ -1,7 +1,7 @@ import pytest from pytest import fixture -from h2integrate.simulation.technologies.hydrogen.h2_storage.lined_rock_cavern.lined_rock_cavern import ( # noqa: E501 +from h2integrate.converters.hydrogen.pem_model.h2_storage.lined_rock_cavern.lined_rock_cavern import ( # noqa: E501 LinedRockCavernStorage, ) diff --git a/tests/h2integrate/test_hydrogen/test_pressurized_turbine.py b/tests/h2integrate/test_hydrogen/test_pressurized_turbine.py index be6eb165d..796cd482e 100644 --- a/tests/h2integrate/test_hydrogen/test_pressurized_turbine.py +++ b/tests/h2integrate/test_hydrogen/test_pressurized_turbine.py @@ -1,7 +1,7 @@ import numpy as np from pytest import approx -from h2integrate.simulation.technologies.hydrogen.h2_storage.on_turbine.on_turbine_hydrogen_storage import ( # noqa: E501 +from h2integrate.converters.hydrogen.pem_model.h2_storage.on_turbine.on_turbine_hydrogen_storage import ( # noqa: E501 PressurizedTower, ) diff --git a/tests/h2integrate/test_hydrogen/test_salt_cavern_storage.py b/tests/h2integrate/test_hydrogen/test_salt_cavern_storage.py index 821ee33e2..87b93e44a 100644 --- a/tests/h2integrate/test_hydrogen/test_salt_cavern_storage.py +++ b/tests/h2integrate/test_hydrogen/test_salt_cavern_storage.py @@ -1,7 +1,7 @@ import pytest from pytest import fixture -from h2integrate.simulation.technologies.hydrogen.h2_storage.salt_cavern.salt_cavern import ( +from h2integrate.converters.hydrogen.pem_model.h2_storage.salt_cavern.salt_cavern import ( SaltCavernStorage, ) diff --git a/tests/h2integrate/test_hydrogen/test_underground_pipe_storage.py b/tests/h2integrate/test_hydrogen/test_underground_pipe_storage.py index 68ded360f..a0e516f0d 100644 --- a/tests/h2integrate/test_hydrogen/test_underground_pipe_storage.py +++ b/tests/h2integrate/test_hydrogen/test_underground_pipe_storage.py @@ -1,7 +1,7 @@ import pytest from pytest import fixture -from h2integrate.simulation.technologies.hydrogen.h2_storage.pipe_storage.underground_pipe_storage import ( # noqa: E501 +from h2integrate.converters.hydrogen.pem_model.h2_storage.pipe_storage.underground_pipe_storage import ( # noqa: E501 UndergroundPipeStorage, ) From 227126ae115fe049fcf5006ffb0b14306cb6a342 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Thu, 27 Nov 2025 17:35:45 -0700 Subject: [PATCH 15/30] Shifting more electrolyzer files --- .../technologies/hydrogen/__init__.py | 1 - .../storage/hydrogen/h2_storage_cost.py | 2 +- .../hydrogen/h2_transport/__init__.py | 0 .../data_tables/pipe_dimensions_metric.csv | 88 ++-- .../data_tables/steel_costs_per_kg.csv | 20 +- .../data_tables/steel_mechanical_props.csv | 22 +- .../hydrogen/h2_transport/h2_compression.py | 0 .../hydrogen/h2_transport/h2_export_pipe.py | 0 .../hydrogen/h2_transport/h2_pipe_array.py | 2 +- .../hydrogen/test/test_tol_mch_storage.py | 132 ----- .../test_h2_transport/test_h2_compressor.py | 2 +- .../test_h2_export_pipeline.py | 2 +- .../test_h2_transport/test_h2_pipe_array.py | 2 +- .../test_hydrogen/test_lined_rock_storage.py | 64 --- .../test_hydrogen/test_pressurized_turbine.py | 485 ------------------ .../test_hydrogen/test_salt_cavern_storage.py | 62 --- .../test_underground_pipe_storage.py | 65 --- 17 files changed, 70 insertions(+), 879 deletions(-) delete mode 100644 h2integrate/simulation/technologies/hydrogen/__init__.py rename h2integrate/{simulation/technologies => storage}/hydrogen/h2_transport/__init__.py (100%) rename h2integrate/{simulation/technologies => storage}/hydrogen/h2_transport/data_tables/pipe_dimensions_metric.csv (98%) rename h2integrate/{simulation/technologies => storage}/hydrogen/h2_transport/data_tables/steel_costs_per_kg.csv (94%) rename h2integrate/{simulation/technologies => storage}/hydrogen/h2_transport/data_tables/steel_mechanical_props.csv (96%) rename h2integrate/{simulation/technologies => storage}/hydrogen/h2_transport/h2_compression.py (100%) rename h2integrate/{simulation/technologies => storage}/hydrogen/h2_transport/h2_export_pipe.py (100%) rename h2integrate/{simulation/technologies => storage}/hydrogen/h2_transport/h2_pipe_array.py (97%) delete mode 100644 tests/h2integrate/test_hydrogen/test_lined_rock_storage.py delete mode 100644 tests/h2integrate/test_hydrogen/test_pressurized_turbine.py delete mode 100644 tests/h2integrate/test_hydrogen/test_salt_cavern_storage.py delete mode 100644 tests/h2integrate/test_hydrogen/test_underground_pipe_storage.py diff --git a/h2integrate/simulation/technologies/hydrogen/__init__.py b/h2integrate/simulation/technologies/hydrogen/__init__.py deleted file mode 100644 index 97ac95ab5..000000000 --- a/h2integrate/simulation/technologies/hydrogen/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from h2integrate.converters.hydrogen.pem_model.h2_transport.h2_compression import Compressor diff --git a/h2integrate/storage/hydrogen/h2_storage_cost.py b/h2integrate/storage/hydrogen/h2_storage_cost.py index 3c9b1af5a..8cca14a30 100644 --- a/h2integrate/storage/hydrogen/h2_storage_cost.py +++ b/h2integrate/storage/hydrogen/h2_storage_cost.py @@ -5,7 +5,7 @@ from h2integrate.core.utilities import BaseConfig, merge_shared_inputs from h2integrate.core.validators import contains, gte_zero, range_val from h2integrate.core.model_baseclasses import CostModelBaseClass -from h2integrate.simulation.technologies.hydrogen.h2_transport.h2_compression import Compressor +from h2integrate.storage.hydrogen.h2_transport.h2_compression import Compressor @define diff --git a/h2integrate/simulation/technologies/hydrogen/h2_transport/__init__.py b/h2integrate/storage/hydrogen/h2_transport/__init__.py similarity index 100% rename from h2integrate/simulation/technologies/hydrogen/h2_transport/__init__.py rename to h2integrate/storage/hydrogen/h2_transport/__init__.py diff --git a/h2integrate/simulation/technologies/hydrogen/h2_transport/data_tables/pipe_dimensions_metric.csv b/h2integrate/storage/hydrogen/h2_transport/data_tables/pipe_dimensions_metric.csv similarity index 98% rename from h2integrate/simulation/technologies/hydrogen/h2_transport/data_tables/pipe_dimensions_metric.csv rename to h2integrate/storage/hydrogen/h2_transport/data_tables/pipe_dimensions_metric.csv index 409fda019..9a1ded5e1 100644 --- a/h2integrate/simulation/technologies/hydrogen/h2_transport/data_tables/pipe_dimensions_metric.csv +++ b/h2integrate/storage/hydrogen/h2_transport/data_tables/pipe_dimensions_metric.csv @@ -1,44 +1,44 @@ -DN,Outer diameter [mm],S 5S,S 10,S 10S,S 20,S 30,S Std,S 40,S 40S,S60,S XS,S 80,S 80S,S 100,S 120,S 140,S 160,S XXS -6,10.29,0.889,,1.245,1.245,1.448,1.727,1.727,1.727,,2.413,2.413,2.413,,,,, -8,13.72,1.245,,1.651,1.651,1.854,2.235,2.235,2.235,,3.023,3.023,3.023,,,,, -10,17.15,1.245,,1.651,1.651,1.854,2.311,2.311,2.311,,3.2,3.2,3.2,,,,, -15,21.34,1.651,,2.108,2.108,2.413,2.769,2.769,2.769,,3.734,3.734,3.734,,,,4.775,7.468 -20,26.67,1.651,,2.108,2.108,2.413,2.87,2.87,2.87,,3.912,3.912,3.912,,,,5.563,7.823 -25,33.4,1.651,,2.769,2.769,2.896,3.378,3.378,3.378,,4.547,4.547,4.547,,,,6.35,9.093 -32,42.16,1.651,,2.769,2.769,2.972,3.556,3.556,3.556,,4.851,4.851,4.851,,,,6.35,9.703 -40,48.26,1.651,,2.769,2.769,3.175,3.683,3.683,3.683,,5.08,5.08,5.08,,,,7.137,10.16 -50,60.33,1.651,,2.769,2.769,3.175,3.912,3.912,3.912,,5.537,5.537,5.537,,,,8.738,11.074 -65,73.03,2.108,,3.048,3.048,4.775,5.156,5.156,5.156,,7.01,7.01,7.01,,,,9.525,14.021 -80,88.9,2.108,,3.048,3.048,4.775,5.486,5.486,5.486,,7.62,7.62,7.62,,,,8.839,15.24 -90,101.6,2.108,,3.048,3.048,4.775,5.74,5.74,5.74,,8.077,8.077,8.077,,,,,16.154 -100,114.3,2.11,3.05,3.05,0,4.78,6.02,6.02,6.02,0,8.56,8.56,8.56,0,0,0,13.03,17.12 -125,141.3,2.77,3.4,3.4,0,0,6.55,6.55,6.55,0,9.53,9.53,9.53,0,0,0,15.88,19.05 -150,168.28,2.77,3.4,3.4,0,0,7.11,7.11,7.11,0,10.97,10.97,10.97,0,0,0,18.26,21.95 -,193.68,,,,,,7.65,7.65,7.65,,12.7,12.7,12.7,,,,,22.23 -200,219.08,2.77,3.76,3.76,6.35,7.04,8.18,8.18,8.18,10.31,12.7,12.7,12.7,15.06,18.26,20.62,23.01,22.23 -,,,,,,,,,,,,,,,,,, -250,273.05,3.404,4.191,4.191,6.35,7.798,9.271,9.271,9.271,12.7,12.7,15.088,12.7,18.237,21.412,25.4,28.575,25.4 -300,323.85,3.962,4.572,4.572,6.35,8.382,9.525,10.312,9.525,14.275,12.7,17.45,12.7,21.412,25.4,28.575,33.325,25.4 -350,355.6,3.962,6.35,4.775,7.925,9.525,9.525,11.1,9.525,15.062,12.7,19.05,12.7,23.8,27.762,31.75,35.712, -400,406.4,4.191,6.35,4.775,7.925,9.525,9.525,12.7,9.525,16.662,12.7,21.412,12.7,26.187,30.937,36.5,40.488, -450,457.2,4.191,6.35,4.775,7.925,11.1,9.525,14.275,9.525,19.05,12.7,23.8,12.7,29.362,34.925,39.675,45.237, -500,508,4.775,6.35,5.537,9.525,12.7,9.525,15.062,9.525,20.625,12.7,26.187,12.7,32.512,38.1,44.45,49.987, -550,558.8,4.775,6.35,5.537,9.525,12.7,9.525,,9.525,22.225,12.7,28.575,12.7,34.925,41.275,47.625,53.975, -600,609.6,5.537,6.35,6.35,9.525,14.275,9.525,17.45,9.525,24.587,12.7,30.937,12.7,38.887,46.025,52.375,59.512, -650,660.4,,7.925,,12.7,,9.525,,9.525,,12.7,,,,,,, -700,711.2,,7.925,,12.7,15.875,9.525,,9.525,,12.7,,,,,,, -750,762,6.35,7.925,7.925,12.7,15.875,9.525,,9.525,,12.7,,,,,,, -800,812.8,,7.925,,12.7,15.875,9.525,17.475,9.525,,12.7,,,,,,, -850,863.6,,7.925,,12.7,15.875,9.525,17.475,9.525,,12.7,,,,,,, -900,914.4,,7.925,,12.7,15.875,9.525,19.05,9.525,,12.7,,,,,,, -1000,1016,,,,,,9.525,,,,12.7,,,,,,,25.4 -1050,1066.8,,,,,,9.525,,,,12.7,,,,,,,25.4 -1100,1117.6,,,,,,9.525,,,,12.7,,,,,,,25.4 -1150,1168.4,,,,,,9.525,,,,12.7,,,,,,,25.4 -1200,1219.2,,,,,,9.525,,,,12.7,,,,,,,25.4 -1300,1320.8,,,,,,9.525,,,,12.7,,,,,,,25.4 -1400,1422.4,,,,,,9.525,,,,12.7,,,,,,,25.4 -1500,1524,,,,,,9.525,,,,12.7,,,,,,,25.4 -1600,1625.6,,,,,,9.525,,,,12.7,,,,,,,25.4 -1700,1727.2,,,,,,9.525,,,,12.7,,,,,,,25.4 -1800,1828.8,,,,,,9.525,,,,12.7,,,,,,,25.4 +DN,Outer diameter [mm],S 5S,S 10,S 10S,S 20,S 30,S Std,S 40,S 40S,S60,S XS,S 80,S 80S,S 100,S 120,S 140,S 160,S XXS +6,10.29,0.889,,1.245,1.245,1.448,1.727,1.727,1.727,,2.413,2.413,2.413,,,,, +8,13.72,1.245,,1.651,1.651,1.854,2.235,2.235,2.235,,3.023,3.023,3.023,,,,, +10,17.15,1.245,,1.651,1.651,1.854,2.311,2.311,2.311,,3.2,3.2,3.2,,,,, +15,21.34,1.651,,2.108,2.108,2.413,2.769,2.769,2.769,,3.734,3.734,3.734,,,,4.775,7.468 +20,26.67,1.651,,2.108,2.108,2.413,2.87,2.87,2.87,,3.912,3.912,3.912,,,,5.563,7.823 +25,33.4,1.651,,2.769,2.769,2.896,3.378,3.378,3.378,,4.547,4.547,4.547,,,,6.35,9.093 +32,42.16,1.651,,2.769,2.769,2.972,3.556,3.556,3.556,,4.851,4.851,4.851,,,,6.35,9.703 +40,48.26,1.651,,2.769,2.769,3.175,3.683,3.683,3.683,,5.08,5.08,5.08,,,,7.137,10.16 +50,60.33,1.651,,2.769,2.769,3.175,3.912,3.912,3.912,,5.537,5.537,5.537,,,,8.738,11.074 +65,73.03,2.108,,3.048,3.048,4.775,5.156,5.156,5.156,,7.01,7.01,7.01,,,,9.525,14.021 +80,88.9,2.108,,3.048,3.048,4.775,5.486,5.486,5.486,,7.62,7.62,7.62,,,,8.839,15.24 +90,101.6,2.108,,3.048,3.048,4.775,5.74,5.74,5.74,,8.077,8.077,8.077,,,,,16.154 +100,114.3,2.11,3.05,3.05,0,4.78,6.02,6.02,6.02,0,8.56,8.56,8.56,0,0,0,13.03,17.12 +125,141.3,2.77,3.4,3.4,0,0,6.55,6.55,6.55,0,9.53,9.53,9.53,0,0,0,15.88,19.05 +150,168.28,2.77,3.4,3.4,0,0,7.11,7.11,7.11,0,10.97,10.97,10.97,0,0,0,18.26,21.95 +,193.68,,,,,,7.65,7.65,7.65,,12.7,12.7,12.7,,,,,22.23 +200,219.08,2.77,3.76,3.76,6.35,7.04,8.18,8.18,8.18,10.31,12.7,12.7,12.7,15.06,18.26,20.62,23.01,22.23 +,,,,,,,,,,,,,,,,,, +250,273.05,3.404,4.191,4.191,6.35,7.798,9.271,9.271,9.271,12.7,12.7,15.088,12.7,18.237,21.412,25.4,28.575,25.4 +300,323.85,3.962,4.572,4.572,6.35,8.382,9.525,10.312,9.525,14.275,12.7,17.45,12.7,21.412,25.4,28.575,33.325,25.4 +350,355.6,3.962,6.35,4.775,7.925,9.525,9.525,11.1,9.525,15.062,12.7,19.05,12.7,23.8,27.762,31.75,35.712, +400,406.4,4.191,6.35,4.775,7.925,9.525,9.525,12.7,9.525,16.662,12.7,21.412,12.7,26.187,30.937,36.5,40.488, +450,457.2,4.191,6.35,4.775,7.925,11.1,9.525,14.275,9.525,19.05,12.7,23.8,12.7,29.362,34.925,39.675,45.237, +500,508,4.775,6.35,5.537,9.525,12.7,9.525,15.062,9.525,20.625,12.7,26.187,12.7,32.512,38.1,44.45,49.987, +550,558.8,4.775,6.35,5.537,9.525,12.7,9.525,,9.525,22.225,12.7,28.575,12.7,34.925,41.275,47.625,53.975, +600,609.6,5.537,6.35,6.35,9.525,14.275,9.525,17.45,9.525,24.587,12.7,30.937,12.7,38.887,46.025,52.375,59.512, +650,660.4,,7.925,,12.7,,9.525,,9.525,,12.7,,,,,,, +700,711.2,,7.925,,12.7,15.875,9.525,,9.525,,12.7,,,,,,, +750,762,6.35,7.925,7.925,12.7,15.875,9.525,,9.525,,12.7,,,,,,, +800,812.8,,7.925,,12.7,15.875,9.525,17.475,9.525,,12.7,,,,,,, +850,863.6,,7.925,,12.7,15.875,9.525,17.475,9.525,,12.7,,,,,,, +900,914.4,,7.925,,12.7,15.875,9.525,19.05,9.525,,12.7,,,,,,, +1000,1016,,,,,,9.525,,,,12.7,,,,,,,25.4 +1050,1066.8,,,,,,9.525,,,,12.7,,,,,,,25.4 +1100,1117.6,,,,,,9.525,,,,12.7,,,,,,,25.4 +1150,1168.4,,,,,,9.525,,,,12.7,,,,,,,25.4 +1200,1219.2,,,,,,9.525,,,,12.7,,,,,,,25.4 +1300,1320.8,,,,,,9.525,,,,12.7,,,,,,,25.4 +1400,1422.4,,,,,,9.525,,,,12.7,,,,,,,25.4 +1500,1524,,,,,,9.525,,,,12.7,,,,,,,25.4 +1600,1625.6,,,,,,9.525,,,,12.7,,,,,,,25.4 +1700,1727.2,,,,,,9.525,,,,12.7,,,,,,,25.4 +1800,1828.8,,,,,,9.525,,,,12.7,,,,,,,25.4 diff --git a/h2integrate/simulation/technologies/hydrogen/h2_transport/data_tables/steel_costs_per_kg.csv b/h2integrate/storage/hydrogen/h2_transport/data_tables/steel_costs_per_kg.csv similarity index 94% rename from h2integrate/simulation/technologies/hydrogen/h2_transport/data_tables/steel_costs_per_kg.csv rename to h2integrate/storage/hydrogen/h2_transport/data_tables/steel_costs_per_kg.csv index f2da36654..3d5700fef 100644 --- a/h2integrate/simulation/technologies/hydrogen/h2_transport/data_tables/steel_costs_per_kg.csv +++ b/h2integrate/storage/hydrogen/h2_transport/data_tables/steel_costs_per_kg.csv @@ -1,10 +1,10 @@ -Specification No.,Grade,Price [$/kg] -API 5L,B,1.8 -API 5L,X42,2.2 -API 5L,X46,2.4 -API 5L,X52,2.8 -API 5L,X56,2.9 -API 5L,X60,3.2 -API 5L,X65,4.3 -API 5L,X70,6.5 -API 5L,X80,10 +Specification No.,Grade,Price [$/kg] +API 5L,B,1.8 +API 5L,X42,2.2 +API 5L,X46,2.4 +API 5L,X52,2.8 +API 5L,X56,2.9 +API 5L,X60,3.2 +API 5L,X65,4.3 +API 5L,X70,6.5 +API 5L,X80,10 diff --git a/h2integrate/simulation/technologies/hydrogen/h2_transport/data_tables/steel_mechanical_props.csv b/h2integrate/storage/hydrogen/h2_transport/data_tables/steel_mechanical_props.csv similarity index 96% rename from h2integrate/simulation/technologies/hydrogen/h2_transport/data_tables/steel_mechanical_props.csv rename to h2integrate/storage/hydrogen/h2_transport/data_tables/steel_mechanical_props.csv index 9ebe6366a..8ef7f6a9e 100644 --- a/h2integrate/simulation/technologies/hydrogen/h2_transport/data_tables/steel_mechanical_props.csv +++ b/h2integrate/storage/hydrogen/h2_transport/data_tables/steel_mechanical_props.csv @@ -1,11 +1,11 @@ -Specification No.,Grade,SMYS [Mpa],SMYS [ksi],SMTS [Mpa],SMTS [ksi] -API 5L,A25,172,25,310,45 -API 5L,A,207,30,331,48 -API 5L,B,241,35,414,60 -API 5L,X42,290,42,414,60 -API 5L,X52,317,52,455,66 -API 5L,X56,386,56,490,71 -API 5L,X60,414,60,517,75 -API 5L,X65,448,65,531,77 -API 5L,X70,483,70,565,82 -API 5L,X80,552,80,621,90 +Specification No.,Grade,SMYS [Mpa],SMYS [ksi],SMTS [Mpa],SMTS [ksi] +API 5L,A25,172,25,310,45 +API 5L,A,207,30,331,48 +API 5L,B,241,35,414,60 +API 5L,X42,290,42,414,60 +API 5L,X52,317,52,455,66 +API 5L,X56,386,56,490,71 +API 5L,X60,414,60,517,75 +API 5L,X65,448,65,531,77 +API 5L,X70,483,70,565,82 +API 5L,X80,552,80,621,90 diff --git a/h2integrate/simulation/technologies/hydrogen/h2_transport/h2_compression.py b/h2integrate/storage/hydrogen/h2_transport/h2_compression.py similarity index 100% rename from h2integrate/simulation/technologies/hydrogen/h2_transport/h2_compression.py rename to h2integrate/storage/hydrogen/h2_transport/h2_compression.py diff --git a/h2integrate/simulation/technologies/hydrogen/h2_transport/h2_export_pipe.py b/h2integrate/storage/hydrogen/h2_transport/h2_export_pipe.py similarity index 100% rename from h2integrate/simulation/technologies/hydrogen/h2_transport/h2_export_pipe.py rename to h2integrate/storage/hydrogen/h2_transport/h2_export_pipe.py diff --git a/h2integrate/simulation/technologies/hydrogen/h2_transport/h2_pipe_array.py b/h2integrate/storage/hydrogen/h2_transport/h2_pipe_array.py similarity index 97% rename from h2integrate/simulation/technologies/hydrogen/h2_transport/h2_pipe_array.py rename to h2integrate/storage/hydrogen/h2_transport/h2_pipe_array.py index 3b7cae9d8..9e48f2f38 100644 --- a/h2integrate/simulation/technologies/hydrogen/h2_transport/h2_pipe_array.py +++ b/h2integrate/storage/hydrogen/h2_transport/h2_pipe_array.py @@ -1,6 +1,6 @@ from numpy import flip, isnan, nansum -from h2integrate.converters.hydrogen.pem_model.h2_transport.h2_export_pipe import run_pipe_analysis +from h2integrate.storage.hydrogen.h2_transport.h2_export_pipe import run_pipe_analysis """ diff --git a/h2integrate/storage/hydrogen/test/test_tol_mch_storage.py b/h2integrate/storage/hydrogen/test/test_tol_mch_storage.py index 862126402..79dd90862 100644 --- a/h2integrate/storage/hydrogen/test/test_tol_mch_storage.py +++ b/h2integrate/storage/hydrogen/test/test_tol_mch_storage.py @@ -4,120 +4,6 @@ from pytest import approx, fixture from h2integrate.storage.hydrogen.mch_storage import MCHTOLStorageCostModel -from h2integrate.converters.hydrogen.pem_model.h2_storage.mch.mch_cost import MCHStorage - - -@fixture -def tol_mch_storage(): - # Test values are based on Supplementary Table 3 of - # https://doi.org/10.1038/s41467-024-53189-2 - Dc_tpd = 304 - Hc_tpd = 304 - As_tpy = 35000 - Ms_tpy = 16200 - in_dict = { - "max_H2_production_kg_pr_hr": (Hc_tpd + Dc_tpd) * 1e3 / 24, - "hydrogen_storage_capacity_kg": Ms_tpy * 1e3, - "hydrogen_demand_kg_pr_hr": Dc_tpd * 1e3 / 24, - "annual_hydrogen_stored_kg_pr_yr": As_tpy * 1e3, - } - - mch_storage = MCHStorage(**in_dict) - - return mch_storage - - -def test_init(): - # Test values are based on Supplementary Table 3 of - # https://doi.org/10.1038/s41467-024-53189-2 - Dc_tpd = 304 - Hc_tpd = 304 - As_tpy = 35000 - Ms_tpy = 16200 - in_dict = { - "max_H2_production_kg_pr_hr": (Hc_tpd + Dc_tpd) * 1e3 / 24, - "hydrogen_storage_capacity_kg": Ms_tpy * 1e3, - "hydrogen_demand_kg_pr_hr": Dc_tpd * 1e3 / 24, - "annual_hydrogen_stored_kg_pr_yr": As_tpy * 1e3, - } - mch_storage = MCHStorage(**in_dict) - - assert mch_storage.cost_year is not None - assert mch_storage.occ_coeff is not None - - -def test_sizing(tol_mch_storage, subtests): - # Test values are based on Supplementary Table 3 of - # https://doi.org/10.1038/s41467-024-53189-2 - Dc_tpd = 304 - Hc_tpd = 304 - As_tpy = 35000 - Ms_tpy = 16200 - with subtests.test("Dehydrogenation capacity"): - assert tol_mch_storage.Dc == approx(Dc_tpd, rel=1e-6) - with subtests.test("Hydrogenation capacity"): - assert tol_mch_storage.Hc == approx(Hc_tpd, rel=1e-6) - with subtests.test("Annual storage capacity"): - assert tol_mch_storage.As == approx(As_tpy, rel=1e-6) - with subtests.test("Maximum storage capacity"): - assert tol_mch_storage.Ms == approx(Ms_tpy, rel=1e-6) - - -def test_cost_calculation_methods(tol_mch_storage, subtests): - # Supplementary Table 3 - toc_actual = 639375591 - foc_actual = 10239180 - voc_actual = 17332229 - - max_cost_error_rel = 0.06 - capex = tol_mch_storage.calc_cost_value(*tol_mch_storage.occ_coeff) - fixed_om = tol_mch_storage.calc_cost_value(*tol_mch_storage.foc_coeff) - var_om = tol_mch_storage.calc_cost_value(*tol_mch_storage.voc_coeff) - with subtests.test("CapEx"): - assert capex == approx(toc_actual, rel=max_cost_error_rel) - with subtests.test("Fixed O&M"): - assert fixed_om == approx(foc_actual, rel=max_cost_error_rel) - with subtests.test("Variable O&M"): - assert var_om == approx(voc_actual, rel=max_cost_error_rel) - - -def test_run_costs(tol_mch_storage, subtests): - # Supplementary Table 3 - toc_actual = 639375591 - foc_actual = 10239180 - voc_actual = 17332229 - - max_cost_error_rel = 0.06 - cost_res = tol_mch_storage.run_costs() - - with subtests.test("CapEx"): - assert cost_res["mch_capex"] == approx(toc_actual, rel=max_cost_error_rel) - with subtests.test("Fixed O&M"): - assert cost_res["mch_opex"] == approx(foc_actual, rel=max_cost_error_rel) - with subtests.test("Variable O&M"): - assert cost_res["mch_variable_om"] == approx(voc_actual, rel=max_cost_error_rel) - - -def test_run_lcos(tol_mch_storage, subtests): - """ - This test is to highlight the difference between the LCOS when computed - using different methods from the same reference. - Specifically, the estimate_lcos and estimate_lcos_from_costs methods which - use Eq. 7 and Eq. 5 respectively from the source. - - Sources: - Breunig, H., Rosner, F., Saqline, S. et al. "Achieving gigawatt-scale green hydrogen - production and seasonal storage at industrial locations across the U.S." *Nat Commun* - **15**, 9049 (2024). https://doi.org/10.1038/s41467-024-53189-2 - """ - max_cost_error_rel = 0.06 - lcos_est = tol_mch_storage.estimate_lcos() - lcos_est_from_costs = tol_mch_storage.estimate_lcos_from_costs() - - with subtests.test("lcos equation"): - assert lcos_est == approx(2.05, rel=max_cost_error_rel) - with subtests.test("lcos equation from costs"): - assert lcos_est_from_costs == approx(2.05, rel=max_cost_error_rel) @fixture @@ -191,24 +77,6 @@ def test_mch_wrapper(plant_config, subtests): assert prob.get_val("sys.cost_year") == 2024 -def test_mch_ex1(subtests): - in_dict = { - "max_H2_production_kg_pr_hr": 14118.146788766157, - "hydrogen_storage_capacity_kg": 2081385.9326778147, - "hydrogen_demand_kg_pr_hr": 10775.824040553021, - "annual_hydrogen_stored_kg_pr_yr": 17878378.49459929, - } - mch_storage = MCHStorage(**in_dict) - cost_res = mch_storage.run_costs() - - with subtests.test("CapEx"): - assert pytest.approx(cost_res["mch_capex"], rel=1e-6) == 2.62304217 * 1e8 - with subtests.test("Fixed O&M"): - assert pytest.approx(cost_res["mch_opex"], rel=1e-6) == 7406935.548022923 - with subtests.test("Variable O&M"): - assert pytest.approx(cost_res["mch_variable_om"], rel=1e-6) == 9420636.00753175 - - def test_mch_wrapper_ex1(plant_config, subtests): # Ran Example 1 with MCH storage # Annual H2 Stored: 17878378.49459929 diff --git a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_compressor.py b/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_compressor.py index 7727d3131..d05b7b927 100644 --- a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_compressor.py +++ b/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_compressor.py @@ -1,6 +1,6 @@ from pytest import approx, raises -from h2integrate.converters.hydrogen.pem_model.h2_transport.h2_compression import Compressor +from h2integrate.storage.hydrogen.h2_transport.h2_compression import Compressor # test that we get the results we got when the code was received diff --git a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_export_pipeline.py b/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_export_pipeline.py index 77fe8b68a..ab8b9f5d7 100644 --- a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_export_pipeline.py +++ b/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_export_pipeline.py @@ -1,4 +1,4 @@ -from h2integrate.converters.hydrogen.pem_model.h2_transport.h2_export_pipe import run_pipe_analysis +from h2integrate.storage.hydrogen.h2_transport.h2_export_pipe import run_pipe_analysis # test that we the results we got when the code was recieved diff --git a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_pipe_array.py b/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_pipe_array.py index 842e7fd9c..ae8b96619 100644 --- a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_pipe_array.py +++ b/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_pipe_array.py @@ -1,4 +1,4 @@ -from h2integrate.converters.hydrogen.pem_model.h2_transport.h2_pipe_array import ( +from h2integrate.storage.hydrogen.h2_transport.h2_pipe_array import ( run_pipe_array, run_pipe_array_const_diam, ) diff --git a/tests/h2integrate/test_hydrogen/test_lined_rock_storage.py b/tests/h2integrate/test_hydrogen/test_lined_rock_storage.py deleted file mode 100644 index e3b86920a..000000000 --- a/tests/h2integrate/test_hydrogen/test_lined_rock_storage.py +++ /dev/null @@ -1,64 +0,0 @@ -import pytest -from pytest import fixture - -from h2integrate.converters.hydrogen.pem_model.h2_storage.lined_rock_cavern.lined_rock_cavern import ( # noqa: E501 - LinedRockCavernStorage, -) - - -# Test values are based on conclusions of Papadias 2021 and are in 2019 USD -in_dict = {"h2_storage_kg": 1000000, "system_flow_rate": 100000} - - -@fixture -def lined_rock_cavern_storage(): - lined_rock_cavern_storage = LinedRockCavernStorage(in_dict) - - return lined_rock_cavern_storage - - -def test_init(): - lined_rock_cavern_storage = LinedRockCavernStorage(in_dict) - - assert lined_rock_cavern_storage.input_dict is not None - assert lined_rock_cavern_storage.output_dict is not None - - -def test_capex_per_kg(lined_rock_cavern_storage): - lined_rock_cavern_storage_capex_per_kg, _installed_capex, _compressor_capex = ( - lined_rock_cavern_storage.lined_rock_cavern_capex() - ) - assert lined_rock_cavern_storage_capex_per_kg == pytest.approx(51.532548895265045) - - -def test_capex(lined_rock_cavern_storage): - _lined_rock_cavern_storage_capex_per_kg, installed_capex, _compressor_capex = ( - lined_rock_cavern_storage.lined_rock_cavern_capex() - ) - assert installed_capex == pytest.approx(51136144.673) - - -def test_compressor_capex(lined_rock_cavern_storage): - _lined_rock_cavern_storage_capex_per_kg, _installed_capex, compressor_capex = ( - lined_rock_cavern_storage.lined_rock_cavern_capex() - ) - assert compressor_capex == pytest.approx(9435600.2555) - - -def test_capex_output_dict(lined_rock_cavern_storage): - _lined_rock_cavern_storage_capex_per_kg, _installed_capex, _compressor_capex = ( - lined_rock_cavern_storage.lined_rock_cavern_capex() - ) - assert lined_rock_cavern_storage.output_dict[ - "lined_rock_cavern_storage_capex" - ] == pytest.approx(51136144.673) - - -def test_opex(lined_rock_cavern_storage): - _lined_rock_cavern_storage_capex_per_kg, _installed_capex, _compressor_capex = ( - lined_rock_cavern_storage.lined_rock_cavern_capex() - ) - lined_rock_cavern_storage.lined_rock_cavern_opex() - assert lined_rock_cavern_storage.output_dict["lined_rock_cavern_storage_opex"] == pytest.approx( - 2359700 - ) diff --git a/tests/h2integrate/test_hydrogen/test_pressurized_turbine.py b/tests/h2integrate/test_hydrogen/test_pressurized_turbine.py deleted file mode 100644 index 796cd482e..000000000 --- a/tests/h2integrate/test_hydrogen/test_pressurized_turbine.py +++ /dev/null @@ -1,485 +0,0 @@ -import numpy as np -from pytest import approx - -from h2integrate.converters.hydrogen.pem_model.h2_storage.on_turbine.on_turbine_hydrogen_storage import ( # noqa: E501 - PressurizedTower, -) - - -class TestPressurizedTower: - def test_frustum(self): - """ - test static methods for geometry of a frustum - """ - - # try a cone - D_ref = 15.0 - h_ref = 2.1 - V_cone_ref = np.pi / 3.0 * (D_ref / 2) ** 2 * h_ref - - V_cone = PressurizedTower.compute_frustum_volume(h_ref, D_ref, 0.0) - - assert V_cone == approx(V_cone_ref) - - # try a cylinder - D_ref = 45.0 - h_ref = 612.3 - V_cyl_ref = np.pi / 4.0 * D_ref**2 * h_ref - - V_cyl = PressurizedTower.compute_frustum_volume(h_ref, D_ref, D_ref) - - assert V_cyl == approx(V_cyl_ref) - - # try a frustum by delta of two cones - D0_ref = 5.7 - h0_ref = 0.0 + 1.0 - D1_ref = 1.9 - h1_ref = 2.4 + 1.0 - D2_ref = 0.0 - h2_ref = 2.4 + 1.2 + 1.0 - V_2_ref = np.pi / 3.0 * (D1_ref / 2) ** 2 * (h2_ref - h1_ref) - V_12_ref = np.pi / 3.0 * (D0_ref / 2) ** 2 * (h2_ref - h0_ref) - V_1_ref = V_12_ref - V_2_ref - - V_1 = PressurizedTower.compute_frustum_volume(h1_ref - h0_ref, D0_ref, D1_ref) - V_12 = PressurizedTower.compute_frustum_volume(h2_ref - h0_ref, D0_ref, D2_ref) - V_2 = PressurizedTower.compute_frustum_volume(h2_ref - h1_ref, D1_ref, D2_ref) - - assert V_1 == approx(V_1_ref) - assert V_12 == approx(V_12_ref) - assert V_2 == approx(V_2_ref) - - def test_crossover_pressure(self): - # random plausible values - E = 0.8 - Sut = 1200e3 - d_t_ratio = 321.0 - - p_ref = 4 * E * Sut / (7 * d_t_ratio * (1 - E / 7.0)) - - p = PressurizedTower.get_crossover_pressure(E, Sut, d_t_ratio) - - assert p == approx(p_ref) - - if False: # paper values are untrustworthy - - def test_thickness_increment_const(self): - # plot values - p = 600.0e3 - Sut = 636.0e6 - - alpha_dtp = PressurizedTower.get_thickness_increment_const(p, Sut) - - # values from graph seem to be off by a factor of ten... jacked up!!! - # these are the values as I read them off the graph assuming that factor - # is in fact erroneous - assert 2 * 1.41 * alpha_dtp == approx(0.675e-3, rel=0.05) - assert 2 * 2.12 * alpha_dtp == approx(0.900e-3, abs=0.05) - assert 2 * 2.82 * alpha_dtp == approx(1.250e-3, abs=0.05) - - def test_cylinder(self): - """ - a hypothetical (nonsensical) cylindical tower -> easy to compute - """ - - ### SETUP REFERENCE VALUES - - # input reference values - h_ref = 100.0 - D_ref = 10.0 - d_t_ratio_ref = 320.0 - density_steel_ref = 7817.0 # kg/m^3 - strength_ultimate_steel_ref = 636e6 # Pa - strength_yield_steel_ref = 350e6 # Pa - Eweld_ref = 0.80 - costrate_steel_ref = 1.50 - costrate_cap_ref = 2.66 - costrate_ladder_ref = 32.80 - cost_door_ref = 2000.0 - cost_mainframe_ref = 6300 - cost_nozzlesmanway_ref = 16000 - costrate_conduit_ref = 35 - temp_ref = 25.0 # degC - R_H2_ref = 4126.0 # J/(kg K) - maintenance_rate_ref = 0.03 - staff_hours_ref = 60 - wage_ref = 36 - - # geometric reference values - thickness_wall_trad_ref = D_ref / d_t_ratio_ref - surfacearea_wall_ref = np.pi * D_ref * h_ref - surfacearea_cap_ref = np.pi / 4.0 * D_ref**2 - - # non-pressurized/traditional geometry values - volume_wall_trad_ref = surfacearea_wall_ref * thickness_wall_trad_ref - volume_inner_ref = h_ref * surfacearea_cap_ref - volume_cap_top_trad_ref = 0.0 # surfacearea_cap_ref*thickness_top_ref - volume_cap_bot_trad_ref = 0.0 # surfacearea_cap_ref*thickness_bot_ref - - # non-pressurized/traditional mass/cost values - mass_wall_trad_ref = density_steel_ref * volume_wall_trad_ref - mass_cap_top_trad_ref = density_steel_ref * volume_cap_top_trad_ref - mass_cap_bot_trad_ref = density_steel_ref * volume_cap_bot_trad_ref - cost_tower_trad_ref = costrate_steel_ref * ( - mass_wall_trad_ref + mass_cap_top_trad_ref + mass_cap_bot_trad_ref - ) - cost_nontower_trad_ref = h_ref * costrate_ladder_ref + cost_door_ref - - # pressurization info - p_crossover_ref = ( - 4 * Eweld_ref * strength_ultimate_steel_ref / (7 * d_t_ratio_ref * (1 - Eweld_ref / 7)) - ) - delta_t_ref = p_crossover_ref * (D_ref / 2) / (2 * strength_ultimate_steel_ref) - thickness_wall_ref = D_ref / d_t_ratio_ref + delta_t_ref - thickness_cap_top_ref = D_ref * np.sqrt( - 0.10 * p_crossover_ref / (Eweld_ref * strength_yield_steel_ref / 1.5) - ) - thickness_cap_bot_ref = D_ref * np.sqrt( - 0.10 * p_crossover_ref / (Eweld_ref * strength_yield_steel_ref / 1.5) - ) - - # pressurized geometry values - volume_wall_ref = surfacearea_wall_ref * thickness_wall_ref - volume_cap_top_ref = surfacearea_cap_ref * (thickness_cap_top_ref) - volume_cap_bot_ref = surfacearea_cap_ref * (thickness_cap_bot_ref) - - # pressurized mass/cost values - mass_wall_ref = density_steel_ref * volume_wall_ref - mass_cap_top_ref = density_steel_ref * volume_cap_top_ref - mass_cap_bot_ref = density_steel_ref * volume_cap_bot_ref - cost_tower_ref = costrate_steel_ref * mass_wall_ref + costrate_cap_ref * ( - mass_cap_top_ref + mass_cap_bot_ref - ) - cost_nontower_ref = ( - 2 * h_ref * costrate_ladder_ref - + 2 * cost_door_ref - + cost_mainframe_ref - + cost_nozzlesmanway_ref - + costrate_conduit_ref * h_ref - ) - - # gas - rho_H2_ref = p_crossover_ref / (R_H2_ref * (temp_ref + 273.15)) - m_H2_ref = volume_inner_ref * rho_H2_ref - - # capex - capex_ref = ( - cost_tower_ref + cost_nontower_ref - cost_tower_trad_ref - cost_nontower_trad_ref - ) - - # opex - opex_ref = maintenance_rate_ref * capex_ref + wage_ref * staff_hours_ref - - turbine = { - "tower_length": h_ref, - "section_diameters": [D_ref, D_ref, D_ref], - "section_heights": [0.0, 0.0 + 0.5 * h_ref, 0.0 + h_ref], - # 'section_diameters': [D_ref, D_ref], - # 'section_heights': [0., 0. + h_ref], - } - - ## traditional estimates (non-pressurized) - - pressurized_cylinder = PressurizedTower(1992, turbine) - - assert pressurized_cylinder.get_volume_tower_inner() == approx(volume_inner_ref) - assert pressurized_cylinder.get_volume_tower_material(pressure=0)[0] == approx( - volume_wall_trad_ref - ) - assert pressurized_cylinder.get_volume_tower_material(pressure=0)[1] == approx( - volume_cap_bot_trad_ref - ) - assert pressurized_cylinder.get_volume_tower_material(pressure=0)[2] == approx( - volume_cap_top_trad_ref - ) - assert pressurized_cylinder.get_mass_tower_material(pressure=0)[0] == approx( - mass_wall_trad_ref - ) - assert pressurized_cylinder.get_mass_tower_material(pressure=0)[1] == approx( - mass_cap_bot_trad_ref - ) - assert pressurized_cylinder.get_mass_tower_material(pressure=0)[2] == approx( - mass_cap_top_trad_ref - ) - - assert np.sum(pressurized_cylinder.get_cost_tower_material(pressure=0)) == approx( - cost_tower_trad_ref - ) - assert pressurized_cylinder.get_cost_nontower(traditional=True) == approx( - cost_nontower_trad_ref - ) - - ## pressurized estimates - - assert pressurized_cylinder.operating_pressure == p_crossover_ref - - assert pressurized_cylinder.get_volume_tower_material()[0] == approx(volume_wall_ref) - assert pressurized_cylinder.get_volume_tower_material()[1] == approx(volume_cap_bot_ref) - assert pressurized_cylinder.get_volume_tower_material()[2] == approx(volume_cap_top_ref) - assert pressurized_cylinder.get_mass_tower_material()[0] == approx(mass_wall_ref) - assert pressurized_cylinder.get_mass_tower_material()[1] == approx(mass_cap_bot_ref) - assert pressurized_cylinder.get_mass_tower_material()[2] == approx(mass_cap_top_ref) - - assert np.sum(pressurized_cylinder.get_cost_tower_material()) == approx(cost_tower_ref) - assert pressurized_cylinder.get_cost_nontower() == approx(cost_nontower_ref) - - ## output interface - - # make sure the final values match expectation - assert pressurized_cylinder.get_capex() == approx(capex_ref) - assert pressurized_cylinder.get_opex() == approx(opex_ref) - assert pressurized_cylinder.get_mass_empty() == approx( - mass_wall_ref - + mass_cap_bot_ref - + mass_cap_top_ref - - mass_wall_trad_ref - - mass_cap_bot_trad_ref - - mass_cap_top_trad_ref - ) - assert pressurized_cylinder.get_capacity_H2() == approx(m_H2_ref) - assert pressurized_cylinder.get_pressure_H2() == approx(p_crossover_ref) - - if True: - - def test_cone(self): - """ - a hypothetical (nonsensical) conical tower -> easy to compute - """ - - ### SETUP REFERENCE VALUES - - # input reference values - h_ref = 81.0 - D_base_ref = 10.0 - D_top_ref = 0.0 - - # non-input parameters - d_t_ratio_ref = 320.0 - density_steel_ref = 7817.0 # kg/m^3 - strength_ultimate_steel_ref = 636e6 # Pa - strength_yield_steel_ref = 350e6 # Pa - Eweld_ref = 0.8 - costrate_steel_ref = 1.50 - costrate_cap_ref = 2.66 - costrate_ladder_ref = 32.80 - cost_door_ref = 2000.0 - cost_mainframe_ref = 6300 - cost_nozzlesmanway_ref = 16000 - costrate_conduit_ref = 35 - temp_ref = 25.0 # degC - R_H2_ref = 4126.0 # J/(kg K) - maintenance_rate_ref = 0.03 - staff_hours_ref = 60 - wage_ref = 36 - - # geometric reference values - surfacearea_cap_top_ref = np.pi / 4.0 * D_top_ref**2 - surfacearea_cap_bot_ref = np.pi / 4.0 * D_base_ref**2 - D_top_ref / d_t_ratio_ref - thickness_wall_bot_ref = D_base_ref / d_t_ratio_ref - - def cone_volume(h, d): - return np.pi / 3.0 * (d / 2) ** 2 * h - - # non-pressurized/traditional geometry values - volume_inner_ref = cone_volume(h_ref, D_base_ref) - print(volume_inner_ref) - volume_wall_trad_ref = cone_volume( - h_ref, D_base_ref + thickness_wall_bot_ref - ) - cone_volume(h_ref, D_base_ref - thickness_wall_bot_ref) - volume_cap_top_trad_ref = 0.0 # surfacearea_cap_top_ref*thickness_top_ref - volume_cap_bot_trad_ref = 0.0 # surfacearea_cap_bot_ref*thickness_bot_ref - - # non-pressurized/traditional mass/cost values - mass_wall_trad_ref = density_steel_ref * volume_wall_trad_ref - mass_cap_top_trad_ref = density_steel_ref * volume_cap_top_trad_ref - mass_cap_bot_trad_ref = density_steel_ref * volume_cap_bot_trad_ref - cost_tower_trad_ref = costrate_steel_ref * ( - mass_wall_trad_ref + mass_cap_top_trad_ref + mass_cap_bot_trad_ref - ) - cost_nontower_trad_ref = h_ref * costrate_ladder_ref + cost_door_ref - - # pressurization info - p_crossover_ref = ( - 4 - * Eweld_ref - * strength_ultimate_steel_ref - / (7 * d_t_ratio_ref * (1 - Eweld_ref / 7)) - ) - dt_bot_ref = p_crossover_ref * (D_base_ref / 2) / (2 * strength_ultimate_steel_ref) - thickness_wall_bot_ref = D_base_ref / d_t_ratio_ref + dt_bot_ref - thickness_cap_top_ref = D_top_ref * np.sqrt( - 0.10 * p_crossover_ref / (Eweld_ref * strength_yield_steel_ref / 1.5) - ) - thickness_cap_bot_ref = D_base_ref * np.sqrt( - 0.10 * p_crossover_ref / (Eweld_ref * strength_yield_steel_ref / 1.5) - ) - - # pressurized geometry values - volume_wall_ref = cone_volume(h_ref, D_base_ref + thickness_wall_bot_ref) - cone_volume( - h_ref, D_base_ref - thickness_wall_bot_ref - ) - volume_cap_top_ref = surfacearea_cap_top_ref * (thickness_cap_top_ref) - volume_cap_bot_ref = surfacearea_cap_bot_ref * (thickness_cap_bot_ref) - - # pressurized mass/cost values - mass_wall_ref = density_steel_ref * volume_wall_ref - mass_cap_top_ref = density_steel_ref * volume_cap_top_ref - mass_cap_bot_ref = density_steel_ref * volume_cap_bot_ref - cost_tower_ref = costrate_steel_ref * mass_wall_ref + costrate_cap_ref * ( - mass_cap_top_ref + mass_cap_bot_ref - ) - cost_nontower_ref = ( - 2 * h_ref * costrate_ladder_ref - + 2 * cost_door_ref - + cost_mainframe_ref - + cost_nozzlesmanway_ref - + costrate_conduit_ref * h_ref - ) - - # gas - rho_H2_ref = p_crossover_ref / (R_H2_ref * (temp_ref + 273.15)) - m_H2_ref = volume_inner_ref * rho_H2_ref - - # capex - capex_ref = ( - cost_tower_ref + cost_nontower_ref - cost_tower_trad_ref - cost_nontower_trad_ref - ) - - # opex - opex_ref = maintenance_rate_ref * capex_ref + wage_ref * staff_hours_ref - - turbine = { - "tower_length": h_ref, - # 'section_diameters': [D_base_ref, D_top_ref], - # 'section_heights': [0., 0. + h_ref], - "section_diameters": [ - D_base_ref, - 0.5 * (D_top_ref + D_base_ref), - D_top_ref, - ], - "section_heights": [0.0, 0.0 + h_ref / 2.0, 0.0 + h_ref], - } - - ## traditional estimates (non-pressurized) - - pressurized_cone = PressurizedTower(1992, turbine) - - assert pressurized_cone.get_volume_tower_inner() == approx(volume_inner_ref) - assert pressurized_cone.get_volume_tower_material(pressure=0)[0] == approx( - volume_wall_trad_ref - ) - assert pressurized_cone.get_volume_tower_material(pressure=0)[1] == approx( - volume_cap_bot_trad_ref - ) - assert pressurized_cone.get_volume_tower_material(pressure=0)[2] == approx( - volume_cap_top_trad_ref - ) - assert pressurized_cone.get_mass_tower_material(pressure=0)[0] == approx( - mass_wall_trad_ref - ) - assert pressurized_cone.get_mass_tower_material(pressure=0)[1] == approx( - mass_cap_bot_trad_ref - ) - assert pressurized_cone.get_mass_tower_material(pressure=0)[2] == approx( - mass_cap_top_trad_ref - ) - - assert np.sum(pressurized_cone.get_cost_tower_material(pressure=0)) == approx( - cost_tower_trad_ref - ) - assert pressurized_cone.get_cost_nontower(traditional=True) == approx( - cost_nontower_trad_ref - ) - - ## pressurized estimates - - assert pressurized_cone.operating_pressure == p_crossover_ref - - assert pressurized_cone.get_volume_tower_material()[0] == approx(volume_wall_ref) - assert pressurized_cone.get_volume_tower_material()[1] == approx(volume_cap_bot_ref) - assert pressurized_cone.get_volume_tower_material()[2] == approx(volume_cap_top_ref) - assert pressurized_cone.get_mass_tower_material()[0] == approx(mass_wall_ref) - assert pressurized_cone.get_mass_tower_material()[1] == approx(mass_cap_bot_ref) - assert pressurized_cone.get_mass_tower_material()[2] == approx(mass_cap_top_ref) - - assert np.sum(pressurized_cone.get_cost_tower_material()) == approx(cost_tower_ref) - assert pressurized_cone.get_cost_nontower() == approx(cost_nontower_ref) - - ## output interface - - # make sure the final values match expectation - assert pressurized_cone.get_capex() == approx(capex_ref) - assert pressurized_cone.get_opex() == approx(opex_ref) - assert pressurized_cone.get_mass_empty() == approx( - mass_wall_ref - + mass_cap_bot_ref - + mass_cap_top_ref - - mass_wall_trad_ref - - mass_cap_bot_trad_ref - - mass_cap_top_trad_ref - ) - assert pressurized_cone.get_capacity_H2() == approx(m_H2_ref) - assert pressurized_cone.get_pressure_H2() == approx(p_crossover_ref) - - if True: - - def test_paper(self): - h_ref = 84.0 - D_bot_ref = 5.66 - D_top_ref = 2.83 - # d_t_ratio_ref= 320. - # rho_density_ref= 7817 - # costrate_steel= 1.50 - cost_tower_ref = 183828 - - cost_tower_trad_ref = 183828 - cost_nontower_trad_ref = 188584 - cost_tower_trad_ref - m_H2_stored_ref = 951 # kg - cost_tower_ref = cost_tower_trad_ref + 21182 - cost_cap_bot_ref = 29668 - cost_cap_top_ref = 5464 - cost_nontower_ref = 2756 + 2000 + 2297 + 2450 + 6300 + 15918 - - turbine = { - "tower_length": 84.0, - "section_diameters": [5.66, 4.9525, 4.245, 3.5375, 2.83], - "section_heights": [0.0, 21.0, 42.0, 63.0, 84.0], - } - - pressurized_tower_instance = PressurizedTower(2004, turbine) - pressurized_tower_instance.run() - - PressurizedTower.compute_frustum_volume(h_ref, D_bot_ref, D_top_ref) - - # traditional sizing should get cost within 5% - assert pressurized_tower_instance.get_cost_tower_material(pressure=0)[0] == approx( - cost_tower_trad_ref, rel=0.05 - ) - assert pressurized_tower_instance.get_cost_tower_material(pressure=0)[1] == 0.0 - assert pressurized_tower_instance.get_cost_tower_material(pressure=0)[2] == 0.0 - assert pressurized_tower_instance.get_cost_nontower(traditional=True) == approx( - cost_nontower_trad_ref, rel=0.05 - ) - - # pressurized sizing should get wall cost within 10% - assert pressurized_tower_instance.get_cost_tower_material()[0] == approx( - cost_tower_ref, rel=0.10 - ) - # not sure why but the cap sizing is way off: 200% error allowed for bottom cap - assert pressurized_tower_instance.get_cost_tower_material()[1] == approx( - cost_cap_bot_ref, rel=2.0 - ) - # not sure why but the cap sizing is way off: 100% error allowed for top cap - assert pressurized_tower_instance.get_cost_tower_material()[2] == approx( - cost_cap_top_ref, rel=1.0 - ) - - # non-tower pressurized sizing evidently has some weird assumptions but should - # get within 10% - assert pressurized_tower_instance.get_cost_nontower() == approx( - cost_nontower_ref, rel=0.1 - ) - - # capacity within 10% - assert pressurized_tower_instance.get_capacity_H2() == approx(m_H2_stored_ref, rel=0.1) diff --git a/tests/h2integrate/test_hydrogen/test_salt_cavern_storage.py b/tests/h2integrate/test_hydrogen/test_salt_cavern_storage.py deleted file mode 100644 index 87b93e44a..000000000 --- a/tests/h2integrate/test_hydrogen/test_salt_cavern_storage.py +++ /dev/null @@ -1,62 +0,0 @@ -import pytest -from pytest import fixture - -from h2integrate.converters.hydrogen.pem_model.h2_storage.salt_cavern.salt_cavern import ( - SaltCavernStorage, -) - - -# Test values are based on conclusions of Papadias 2021 and are in 2019 USD -in_dict = {"h2_storage_kg": 1000000, "system_flow_rate": 100000} - - -@fixture -def salt_cavern_storage(): - salt_cavern_storage = SaltCavernStorage(in_dict) - - return salt_cavern_storage - - -def test_init(): - salt_cavern_storage = SaltCavernStorage(in_dict) - - assert salt_cavern_storage.input_dict is not None - assert salt_cavern_storage.output_dict is not None - - -def test_capex_per_kg(salt_cavern_storage): - salt_cavern_storage_capex_per_kg, _installed_capex, _compressor_capex = ( - salt_cavern_storage.salt_cavern_capex() - ) - assert salt_cavern_storage_capex_per_kg == pytest.approx(25.18622259358959) - - -def test_capex(salt_cavern_storage): - _salt_cavern_storage_capex_per_kg, installed_capex, _compressor_capex = ( - salt_cavern_storage.salt_cavern_capex() - ) - assert installed_capex == pytest.approx(24992482.4198) - - -def test_compressor_capex(salt_cavern_storage): - _salt_cavern_storage_capex_per_kg, _installed_capex, compressor_capex = ( - salt_cavern_storage.salt_cavern_capex() - ) - assert compressor_capex == pytest.approx(6516166.67163) - - -def test_capex_output_dict(salt_cavern_storage): - _salt_caven_storage_capex_per_kg, _installed_capex, _compressor_capex = ( - salt_cavern_storage.salt_cavern_capex() - ) - assert salt_cavern_storage.output_dict["salt_cavern_storage_capex"] == pytest.approx( - 24992482.4198 - ) - - -def test_opex(salt_cavern_storage): - _salt_cavern_storage_capex_per_kg, _installed_capex, _compressor_capex = ( - salt_cavern_storage.salt_cavern_capex() - ) - salt_cavern_storage.salt_cavern_opex() - assert salt_cavern_storage.output_dict["salt_cavern_storage_opex"] == pytest.approx(1461664) diff --git a/tests/h2integrate/test_hydrogen/test_underground_pipe_storage.py b/tests/h2integrate/test_hydrogen/test_underground_pipe_storage.py deleted file mode 100644 index a0e516f0d..000000000 --- a/tests/h2integrate/test_hydrogen/test_underground_pipe_storage.py +++ /dev/null @@ -1,65 +0,0 @@ -import pytest -from pytest import fixture - -from h2integrate.converters.hydrogen.pem_model.h2_storage.pipe_storage.underground_pipe_storage import ( # noqa: E501 - UndergroundPipeStorage, -) - - -# Test values are based on conclusions of Papadias 2021 and are in 2019 USD - -in_dict = { - "h2_storage_kg": 1000000, - "system_flow_rate": 100000, - "compressor_output_pressure": 100, -} - - -@fixture -def pipe_storage(): - pipe_storage = UndergroundPipeStorage(in_dict) - - return pipe_storage - - -def test_init(): - pipe_storage = UndergroundPipeStorage(in_dict) - - assert pipe_storage.input_dict is not None - assert pipe_storage.output_dict is not None - - -def test_capex_per_kg(pipe_storage): - pipe_storage_capex_per_kg, _installed_capex, _compressor_capex = ( - pipe_storage.pipe_storage_capex() - ) - assert pipe_storage_capex_per_kg == pytest.approx(512.689247292) - - -def test_capex(pipe_storage): - _pipe_storage_capex_per_kg, installed_capex, _compressor_capex = ( - pipe_storage.pipe_storage_capex() - ) - assert installed_capex == pytest.approx(508745483.851) - - -def test_compressor_capex(pipe_storage): - _pipe_storage_capex_per_kg, _installed_capex, compressor_capex = ( - pipe_storage.pipe_storage_capex() - ) - assert compressor_capex == pytest.approx(5907549.297) - - -def test_capex_output_dict(pipe_storage): - _pipe_storage_capex_per_kg, _installed_capex, _compressor_capex = ( - pipe_storage.pipe_storage_capex() - ) - assert pipe_storage.output_dict["pipe_storage_capex"] == pytest.approx(508745483.851) - - -def test_opex(pipe_storage): - _pipe_storage_capex_per_kg, _installed_capex, _compressor_capex = ( - pipe_storage.pipe_storage_capex() - ) - pipe_storage.pipe_storage_opex() - assert pipe_storage.output_dict["pipe_storage_opex"] == pytest.approx(16439748) From ee7f21fcb1b29fc44c0a27aefffb4afaa7fc8722 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Thu, 27 Nov 2025 19:13:45 -0700 Subject: [PATCH 16/30] Fixed and noted behavior --- h2integrate/storage/hydrogen/h2_storage_cost.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/h2integrate/storage/hydrogen/h2_storage_cost.py b/h2integrate/storage/hydrogen/h2_storage_cost.py index 8cca14a30..1828eb727 100644 --- a/h2integrate/storage/hydrogen/h2_storage_cost.py +++ b/h2integrate/storage/hydrogen/h2_storage_cost.py @@ -133,7 +133,9 @@ def make_storage_input_dict(self, inputs): storage_input["h2_storage_kg"] = max_capacity_kg[0] # system_flow_rate must be in kg/day - storage_input["system_flow_rate"] = storage_max_fill_rate[0] + # NOTE: I believe this conversion is a bug and should not be divided by 24. + # To make the code consistent with previous behavior, I will not change it now. + storage_input["system_flow_rate"] = storage_max_fill_rate[0] / 24 # kg/day to kg/hr return storage_input From 531726dc04f75f3cce4d7bf5615b3ad496fcc879 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Tue, 2 Dec 2025 09:51:01 -0700 Subject: [PATCH 17/30] Fixed bug in steel calc config for o2 heat integration --- examples/01_onshore_steel_mn/tech_config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/01_onshore_steel_mn/tech_config.yaml b/examples/01_onshore_steel_mn/tech_config.yaml index c00a0d205..4be21380b 100644 --- a/examples/01_onshore_steel_mn/tech_config.yaml +++ b/examples/01_onshore_steel_mn/tech_config.yaml @@ -69,7 +69,7 @@ technologies: plant_capacity_mtpy: 1000000. cost_parameters: operational_year: 2035 - o2_heat_integration: false + o2_heat_integration: True lcoh: 7.37 installation_time: 36 # months inflation_rate: 0.0 # 0 for nominal analysis From dc67bb4c4cb32e49f867da171a116cc88a01f253 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Tue, 2 Dec 2025 11:31:08 -0700 Subject: [PATCH 18/30] Moving iron files and adding back singlitico test --- .../test/test_singlitico_cost_model.py | 109 ++++++++++++++++++ .../technologies => converters}/iron/iron.py | 0 h2integrate/converters/iron/iron_mine.py | 14 +-- h2integrate/converters/iron/iron_plant.py | 8 +- h2integrate/converters/iron/iron_transport.py | 2 +- h2integrate/converters/iron/iron_wrapper.py | 6 +- .../iron/load_top_down_coeffs.py | 0 .../iron/martin_ore/__init__.py | 0 .../iron/martin_ore/cost_coeffs.csv | 0 .../iron/martin_ore/cost_inputs.csv | 0 .../iron/martin_ore/cost_model.py | 0 .../iron/martin_ore/finance_model.py | 2 +- .../iron/martin_ore/perf_coeffs.csv | 0 .../iron/martin_ore/perf_inputs.csv | 0 .../iron/martin_ore/perf_model.py | 0 .../iron/martin_ore/variable_om_cost.py | 2 +- .../iron/martin_transport/__init__.py | 0 .../iron/martin_transport/iron_transport.py | 2 +- .../iron/martin_transport/shipping_coords.csv | 0 .../iron/model_locations.yaml | 20 ++-- .../iron/peters/cost_coeffs.csv | 0 .../iron/peters/cost_inputs.csv | 0 .../iron/peters/cost_model.py | 0 .../iron/rosner/__init__.py | 0 .../iron/rosner/cost_coeffs.csv | 0 .../iron/rosner/cost_inputs.csv | 0 .../iron/rosner/cost_model.py | 2 +- .../iron/rosner/finance_model.py | 2 +- .../iron/rosner/perf_coeffs.csv | 0 .../iron/rosner/perf_inputs.csv | 0 .../iron/rosner/perf_model.py | 0 .../iron/rosner_ore/__init__.py | 0 .../iron/rosner_ore/finance_model.py | 2 +- .../iron/rosner_ore/variable_om_cost.py | 2 +- .../iron/rosner_override/__init__.py | 0 .../iron/rosner_override/finance_model.py | 0 .../iron/stinn/cost_coeffs.csv | 0 .../iron/stinn/cost_model.py | 0 .../iron/top_down_coeffs.csv | 0 .../converters/steel/electric_arc_furnance.py | 8 +- tests/h2integrate/test_iron/test_iron_ore.py | 2 +- tests/h2integrate/test_iron/test_iron_post.py | 2 +- tests/h2integrate/test_iron/test_iron_win.py | 2 +- 43 files changed, 145 insertions(+), 42 deletions(-) create mode 100644 h2integrate/converters/hydrogen/test/test_singlitico_cost_model.py rename h2integrate/{simulation/technologies => converters}/iron/iron.py (100%) rename h2integrate/{simulation/technologies => converters}/iron/load_top_down_coeffs.py (100%) rename h2integrate/{simulation/technologies => converters}/iron/martin_ore/__init__.py (100%) rename h2integrate/{simulation/technologies => converters}/iron/martin_ore/cost_coeffs.csv (100%) rename h2integrate/{simulation/technologies => converters}/iron/martin_ore/cost_inputs.csv (100%) rename h2integrate/{simulation/technologies => converters}/iron/martin_ore/cost_model.py (100%) rename h2integrate/{simulation/technologies => converters}/iron/martin_ore/finance_model.py (98%) rename h2integrate/{simulation/technologies => converters}/iron/martin_ore/perf_coeffs.csv (100%) rename h2integrate/{simulation/technologies => converters}/iron/martin_ore/perf_inputs.csv (100%) rename h2integrate/{simulation/technologies => converters}/iron/martin_ore/perf_model.py (100%) rename h2integrate/{simulation/technologies => converters}/iron/martin_ore/variable_om_cost.py (93%) rename h2integrate/{simulation/technologies => converters}/iron/martin_transport/__init__.py (100%) rename h2integrate/{simulation/technologies => converters}/iron/martin_transport/iron_transport.py (98%) rename h2integrate/{simulation/technologies => converters}/iron/martin_transport/shipping_coords.csv (100%) rename h2integrate/{simulation/technologies => converters}/iron/model_locations.yaml (62%) rename h2integrate/{simulation/technologies => converters}/iron/peters/cost_coeffs.csv (100%) rename h2integrate/{simulation/technologies => converters}/iron/peters/cost_inputs.csv (100%) rename h2integrate/{simulation/technologies => converters}/iron/peters/cost_model.py (100%) rename h2integrate/{simulation/technologies => converters}/iron/rosner/__init__.py (100%) rename h2integrate/{simulation/technologies => converters}/iron/rosner/cost_coeffs.csv (100%) rename h2integrate/{simulation/technologies => converters}/iron/rosner/cost_inputs.csv (100%) rename h2integrate/{simulation/technologies => converters}/iron/rosner/cost_model.py (99%) rename h2integrate/{simulation/technologies => converters}/iron/rosner/finance_model.py (99%) rename h2integrate/{simulation/technologies => converters}/iron/rosner/perf_coeffs.csv (100%) rename h2integrate/{simulation/technologies => converters}/iron/rosner/perf_inputs.csv (100%) rename h2integrate/{simulation/technologies => converters}/iron/rosner/perf_model.py (100%) rename h2integrate/{simulation/technologies => converters}/iron/rosner_ore/__init__.py (100%) rename h2integrate/{simulation/technologies => converters}/iron/rosner_ore/finance_model.py (98%) rename h2integrate/{simulation/technologies => converters}/iron/rosner_ore/variable_om_cost.py (93%) rename h2integrate/{simulation/technologies => converters}/iron/rosner_override/__init__.py (100%) rename h2integrate/{simulation/technologies => converters}/iron/rosner_override/finance_model.py (100%) rename h2integrate/{simulation/technologies => converters}/iron/stinn/cost_coeffs.csv (100%) rename h2integrate/{simulation/technologies => converters}/iron/stinn/cost_model.py (100%) rename h2integrate/{simulation/technologies => converters}/iron/top_down_coeffs.csv (100%) diff --git a/h2integrate/converters/hydrogen/test/test_singlitico_cost_model.py b/h2integrate/converters/hydrogen/test/test_singlitico_cost_model.py new file mode 100644 index 000000000..abe06af03 --- /dev/null +++ b/h2integrate/converters/hydrogen/test/test_singlitico_cost_model.py @@ -0,0 +1,109 @@ +import numpy as np +import openmdao.api as om +from pytest import approx + +from h2integrate.converters.hydrogen.singlitico_cost_model import SingliticoCostModel + + +TOL = 1e-3 + +BASELINE = np.array( + [ + # onshore, [capex, opex] + [ + [50.7105172052493, 1.2418205567631722], + ], + # offshore, [capex, opex] + [ + [67.44498788298158, 2.16690312809502], + ], + ] +) + + +class TestSingliticoCostModel: + P_elec_mw = 100.0 # [MW] + RC_elec = 700 # [USD/kW] + + def _create_problem(self, location): + """Helper method to create and set up an OpenMDAO problem.""" + prob = om.Problem() + prob.model.add_subsystem( + "singlitico_cost_model", + SingliticoCostModel( + plant_config={ + "plant": { + "plant_life": 30, + "simulation": { + "n_timesteps": 8760, + }, + }, + }, + tech_config={ + "model_inputs": { + "cost_parameters": { + "location": location, + "electrolyzer_capex": self.RC_elec, + }, + } + }, + ), + promotes=["*"], + ) + prob.setup() + prob.set_val("electrolyzer_size_mw", self.P_elec_mw, units="MW") + prob.set_val("electricity_in", np.ones(8760) * self.P_elec_mw, units="kW") + prob.set_val("total_hydrogen_produced", 1000.0, units="kg/year") + return prob + + def test_calc_capex_onshore(self): + prob = self._create_problem("onshore") + prob.run_model() + + capex_musd = prob["CapEx"] / 1e6 + assert capex_musd == approx(BASELINE[0][0][0], TOL) + + def test_calc_capex_offshore(self): + prob = self._create_problem("offshore") + prob.run_model() + + capex_musd = prob["CapEx"] / 1e6 + assert capex_musd == approx(BASELINE[1][0][0], TOL) + + def test_calc_opex_onshore(self): + prob = self._create_problem("onshore") + prob.run_model() + + opex_musd = prob["OpEx"] / 1e6 + assert opex_musd == approx(BASELINE[0][0][1], TOL) + + def test_calc_opex_offshore(self): + prob = self._create_problem("offshore") + prob.run_model() + + opex_musd = prob["OpEx"] / 1e6 + assert opex_musd == approx(BASELINE[1][0][1], TOL) + + def test_run_onshore(self): + prob = self._create_problem("onshore") + prob.run_model() + + capex_musd = prob["CapEx"] / 1e6 + opex_musd = prob["OpEx"] / 1e6 + + assert capex_musd == approx(BASELINE[0][0][0], TOL) + assert opex_musd == approx(BASELINE[0][0][1], TOL) + + def test_run_offshore(self): + prob = self._create_problem("offshore") + prob.run_model() + + capex_musd = prob["CapEx"] / 1e6 + opex_musd = prob["OpEx"] / 1e6 + + assert capex_musd == approx(BASELINE[1][0][0], TOL) + assert opex_musd == approx(BASELINE[1][0][1], TOL) + + +if __name__ == "__main__": + test_set = TestSingliticoCostModel() diff --git a/h2integrate/simulation/technologies/iron/iron.py b/h2integrate/converters/iron/iron.py similarity index 100% rename from h2integrate/simulation/technologies/iron/iron.py rename to h2integrate/converters/iron/iron.py diff --git a/h2integrate/converters/iron/iron_mine.py b/h2integrate/converters/iron/iron_mine.py index ec21c1b15..4a941af94 100644 --- a/h2integrate/converters/iron/iron_mine.py +++ b/h2integrate/converters/iron/iron_mine.py @@ -5,21 +5,17 @@ from h2integrate.core.utilities import BaseConfig, merge_shared_inputs from h2integrate.core.validators import contains, range_val -from h2integrate.core.model_baseclasses import CostModelBaseClass -from h2integrate.tools.inflation.inflate import inflate_cpi -from h2integrate.simulation.technologies.iron.iron import ( +from h2integrate.converters.iron.iron import ( IronCostModelConfig, IronPerformanceModelConfig, IronPerformanceModelOutputs, run_iron_cost_model, run_size_iron_plant_performance, ) -from h2integrate.simulation.technologies.iron.martin_ore.variable_om_cost import ( - martin_ore_variable_om_cost, -) -from h2integrate.simulation.technologies.iron.rosner_ore.variable_om_cost import ( - rosner_ore_variable_om_cost, -) +from h2integrate.core.model_baseclasses import CostModelBaseClass +from h2integrate.tools.inflation.inflate import inflate_cpi +from h2integrate.converters.iron.martin_ore.variable_om_cost import martin_ore_variable_om_cost +from h2integrate.converters.iron.rosner_ore.variable_om_cost import rosner_ore_variable_om_cost @define diff --git a/h2integrate/converters/iron/iron_plant.py b/h2integrate/converters/iron/iron_plant.py index 52f4ec3ec..1fd52a1c8 100644 --- a/h2integrate/converters/iron/iron_plant.py +++ b/h2integrate/converters/iron/iron_plant.py @@ -5,16 +5,16 @@ from h2integrate.core.utilities import BaseConfig, merge_shared_inputs from h2integrate.core.validators import contains -from h2integrate.core.model_baseclasses import CostModelBaseClass -from h2integrate.tools.inflation.inflate import inflate_cpi, inflate_cepci -from h2integrate.simulation.technologies.iron.iron import ( +from h2integrate.converters.iron.iron import ( IronCostModelConfig, IronPerformanceModelConfig, IronPerformanceModelOutputs, run_iron_cost_model, run_size_iron_plant_performance, ) -from h2integrate.simulation.technologies.iron.load_top_down_coeffs import load_top_down_coeffs +from h2integrate.core.model_baseclasses import CostModelBaseClass +from h2integrate.tools.inflation.inflate import inflate_cpi, inflate_cepci +from h2integrate.converters.iron.load_top_down_coeffs import load_top_down_coeffs @define diff --git a/h2integrate/converters/iron/iron_transport.py b/h2integrate/converters/iron/iron_transport.py index 45d021f04..e5f0bc725 100644 --- a/h2integrate/converters/iron/iron_transport.py +++ b/h2integrate/converters/iron/iron_transport.py @@ -8,7 +8,7 @@ from h2integrate.core.utilities import BaseConfig, merge_shared_inputs from h2integrate.core.validators import contains, range_val from h2integrate.core.model_baseclasses import CostModelBaseClass -from h2integrate.simulation.technologies.iron.load_top_down_coeffs import load_top_down_coeffs +from h2integrate.converters.iron.load_top_down_coeffs import load_top_down_coeffs @define diff --git a/h2integrate/converters/iron/iron_wrapper.py b/h2integrate/converters/iron/iron_wrapper.py index 2fd32ebc0..48546707f 100644 --- a/h2integrate/converters/iron/iron_wrapper.py +++ b/h2integrate/converters/iron/iron_wrapper.py @@ -7,11 +7,9 @@ import h2integrate.tools.profast_reverse_tools as rev_pf_tools from h2integrate.core.utilities import CostModelBaseConfig, merge_shared_inputs from h2integrate.core.validators import contains, range_val +from h2integrate.converters.iron.iron import run_iron_full_model from h2integrate.core.model_baseclasses import CostModelBaseClass -from h2integrate.simulation.technologies.iron.iron import run_iron_full_model -from h2integrate.simulation.technologies.iron.martin_transport.iron_transport import ( - calc_iron_ship_cost, -) +from h2integrate.converters.iron.martin_transport.iron_transport import calc_iron_ship_cost @define diff --git a/h2integrate/simulation/technologies/iron/load_top_down_coeffs.py b/h2integrate/converters/iron/load_top_down_coeffs.py similarity index 100% rename from h2integrate/simulation/technologies/iron/load_top_down_coeffs.py rename to h2integrate/converters/iron/load_top_down_coeffs.py diff --git a/h2integrate/simulation/technologies/iron/martin_ore/__init__.py b/h2integrate/converters/iron/martin_ore/__init__.py similarity index 100% rename from h2integrate/simulation/technologies/iron/martin_ore/__init__.py rename to h2integrate/converters/iron/martin_ore/__init__.py diff --git a/h2integrate/simulation/technologies/iron/martin_ore/cost_coeffs.csv b/h2integrate/converters/iron/martin_ore/cost_coeffs.csv similarity index 100% rename from h2integrate/simulation/technologies/iron/martin_ore/cost_coeffs.csv rename to h2integrate/converters/iron/martin_ore/cost_coeffs.csv diff --git a/h2integrate/simulation/technologies/iron/martin_ore/cost_inputs.csv b/h2integrate/converters/iron/martin_ore/cost_inputs.csv similarity index 100% rename from h2integrate/simulation/technologies/iron/martin_ore/cost_inputs.csv rename to h2integrate/converters/iron/martin_ore/cost_inputs.csv diff --git a/h2integrate/simulation/technologies/iron/martin_ore/cost_model.py b/h2integrate/converters/iron/martin_ore/cost_model.py similarity index 100% rename from h2integrate/simulation/technologies/iron/martin_ore/cost_model.py rename to h2integrate/converters/iron/martin_ore/cost_model.py diff --git a/h2integrate/simulation/technologies/iron/martin_ore/finance_model.py b/h2integrate/converters/iron/martin_ore/finance_model.py similarity index 98% rename from h2integrate/simulation/technologies/iron/martin_ore/finance_model.py rename to h2integrate/converters/iron/martin_ore/finance_model.py index 134d456bb..2ccc88950 100644 --- a/h2integrate/simulation/technologies/iron/martin_ore/finance_model.py +++ b/h2integrate/converters/iron/martin_ore/finance_model.py @@ -2,7 +2,7 @@ import ProFAST from h2integrate.tools.inflation.inflate import inflate_cpi -from h2integrate.simulation.technologies.iron.load_top_down_coeffs import load_top_down_coeffs +from h2integrate.converters.iron.load_top_down_coeffs import load_top_down_coeffs def main(config): diff --git a/h2integrate/simulation/technologies/iron/martin_ore/perf_coeffs.csv b/h2integrate/converters/iron/martin_ore/perf_coeffs.csv similarity index 100% rename from h2integrate/simulation/technologies/iron/martin_ore/perf_coeffs.csv rename to h2integrate/converters/iron/martin_ore/perf_coeffs.csv diff --git a/h2integrate/simulation/technologies/iron/martin_ore/perf_inputs.csv b/h2integrate/converters/iron/martin_ore/perf_inputs.csv similarity index 100% rename from h2integrate/simulation/technologies/iron/martin_ore/perf_inputs.csv rename to h2integrate/converters/iron/martin_ore/perf_inputs.csv diff --git a/h2integrate/simulation/technologies/iron/martin_ore/perf_model.py b/h2integrate/converters/iron/martin_ore/perf_model.py similarity index 100% rename from h2integrate/simulation/technologies/iron/martin_ore/perf_model.py rename to h2integrate/converters/iron/martin_ore/perf_model.py diff --git a/h2integrate/simulation/technologies/iron/martin_ore/variable_om_cost.py b/h2integrate/converters/iron/martin_ore/variable_om_cost.py similarity index 93% rename from h2integrate/simulation/technologies/iron/martin_ore/variable_om_cost.py rename to h2integrate/converters/iron/martin_ore/variable_om_cost.py index b1c0ed102..65b1b7a64 100644 --- a/h2integrate/simulation/technologies/iron/martin_ore/variable_om_cost.py +++ b/h2integrate/converters/iron/martin_ore/variable_om_cost.py @@ -1,6 +1,6 @@ import numpy as np -from h2integrate.simulation.technologies.iron.load_top_down_coeffs import load_top_down_coeffs +from h2integrate.converters.iron.load_top_down_coeffs import load_top_down_coeffs def martin_ore_variable_om_cost(mine_name, cost_df, analysis_start, cost_year, plant_life): diff --git a/h2integrate/simulation/technologies/iron/martin_transport/__init__.py b/h2integrate/converters/iron/martin_transport/__init__.py similarity index 100% rename from h2integrate/simulation/technologies/iron/martin_transport/__init__.py rename to h2integrate/converters/iron/martin_transport/__init__.py diff --git a/h2integrate/simulation/technologies/iron/martin_transport/iron_transport.py b/h2integrate/converters/iron/martin_transport/iron_transport.py similarity index 98% rename from h2integrate/simulation/technologies/iron/martin_transport/iron_transport.py rename to h2integrate/converters/iron/martin_transport/iron_transport.py index 2734a1348..eda5c9f16 100644 --- a/h2integrate/simulation/technologies/iron/martin_transport/iron_transport.py +++ b/h2integrate/converters/iron/martin_transport/iron_transport.py @@ -7,7 +7,7 @@ import pandas as pd import geopy.distance -from h2integrate.simulation.technologies.iron.load_top_down_coeffs import load_top_down_coeffs +from h2integrate.converters.iron.load_top_down_coeffs import load_top_down_coeffs CD = Path(__file__).parent diff --git a/h2integrate/simulation/technologies/iron/martin_transport/shipping_coords.csv b/h2integrate/converters/iron/martin_transport/shipping_coords.csv similarity index 100% rename from h2integrate/simulation/technologies/iron/martin_transport/shipping_coords.csv rename to h2integrate/converters/iron/martin_transport/shipping_coords.csv diff --git a/h2integrate/simulation/technologies/iron/model_locations.yaml b/h2integrate/converters/iron/model_locations.yaml similarity index 62% rename from h2integrate/simulation/technologies/iron/model_locations.yaml rename to h2integrate/converters/iron/model_locations.yaml index c38eb2b09..9a1b2d509 100644 --- a/h2integrate/simulation/technologies/iron/model_locations.yaml +++ b/h2integrate/converters/iron/model_locations.yaml @@ -1,53 +1,53 @@ performance: martin_ore: compatible_tech: ['h2_dri','ng_dri'] - model: 'h2integrate.simulation.technologies.iron.martin_ore.perf_model' + model: 'h2integrate.converters.iron.martin_ore.perf_model' inputs: 'perf_inputs.csv' coeffs: 'perf_coeffs.csv' rosner: compatible_tech: ['h2_dri','ng_dri'] - model: 'h2integrate.simulation.technologies.iron.rosner.perf_model' + model: 'h2integrate.converters.iron.rosner.perf_model' inputs: 'perf_inputs.csv' coeffs: 'perf_coeffs.csv' cost: martin_ore: compatible_tech: ['h2_dri','ng_dri'] - model: 'h2integrate.simulation.technologies.iron.martin_ore.cost_model' + model: 'h2integrate.converters.iron.martin_ore.cost_model' inputs: 'cost_inputs.csv' coeffs: 'cost_coeffs.csv' rosner: compatible_tech: ['h2_dri','ng_dri'] - model: 'h2integrate.simulation.technologies.iron.rosner.cost_model' + model: 'h2integrate.converters.iron.rosner.cost_model' inputs: 'cost_inputs.csv' coeffs: 'cost_coeffs.csv' stinn: compatible_tech: ['blast_furnace', 'ng_dri', 'h2_dri', 'moe', 'ahe', 'mse'] - model: 'h2integrate.simulation.technologies.iron.stinn.cost_model' + model: 'h2integrate.converters.iron.stinn.cost_model' inputs: 'cost_inputs.csv' coeffs: 'cost_coeffs.csv' peters: compatible_tech: ['blast_furnace', 'ng_dri', 'h2_dri', 'moe', 'ahe', 'mse'] - model: 'h2integrate.simulation.technologies.iron.peters.opex_model' + model: 'h2integrate.converters.iron.peters.opex_model' inputs: 'cost_inputs.csv' coeffs: 'cost_coeffs.csv' finance: martin_ore: compatible_tech: ['h2_dri','ng_dri'] - model: 'h2integrate.simulation.technologies.iron.martin_ore.finance_model' + model: 'h2integrate.converters.iron.martin_ore.finance_model' inputs: 'cost_inputs.csv' coeffs: 'cost_coeffs.csv' rosner: compatible_tech: ['h2_dri','ng_dri'] - model: 'h2integrate.simulation.technologies.iron.rosner.finance_model' + model: 'h2integrate.converters.iron.rosner.finance_model' inputs: 'cost_inputs.csv' coeffs: 'cost_coeffs.csv' rosner_ore: compatible_tech: ['h2_dri','ng_dri'] - model: 'h2integrate.simulation.technologies.iron.rosner_ore.finance_model' + model: 'h2integrate.converters.iron.rosner_ore.finance_model' inputs: 'cost_inputs.csv' coeffs: 'cost_coeffs.csv' rosner_override: compatible_tech: ['h2_dri','ng_dri'] - model: 'h2integrate.simulation.technologies.iron.rosner_override.finance_model' + model: 'h2integrate.converters.iron.rosner_override.finance_model' inputs: 'cost_inputs.csv' coeffs: 'cost_coeffs.csv' diff --git a/h2integrate/simulation/technologies/iron/peters/cost_coeffs.csv b/h2integrate/converters/iron/peters/cost_coeffs.csv similarity index 100% rename from h2integrate/simulation/technologies/iron/peters/cost_coeffs.csv rename to h2integrate/converters/iron/peters/cost_coeffs.csv diff --git a/h2integrate/simulation/technologies/iron/peters/cost_inputs.csv b/h2integrate/converters/iron/peters/cost_inputs.csv similarity index 100% rename from h2integrate/simulation/technologies/iron/peters/cost_inputs.csv rename to h2integrate/converters/iron/peters/cost_inputs.csv diff --git a/h2integrate/simulation/technologies/iron/peters/cost_model.py b/h2integrate/converters/iron/peters/cost_model.py similarity index 100% rename from h2integrate/simulation/technologies/iron/peters/cost_model.py rename to h2integrate/converters/iron/peters/cost_model.py diff --git a/h2integrate/simulation/technologies/iron/rosner/__init__.py b/h2integrate/converters/iron/rosner/__init__.py similarity index 100% rename from h2integrate/simulation/technologies/iron/rosner/__init__.py rename to h2integrate/converters/iron/rosner/__init__.py diff --git a/h2integrate/simulation/technologies/iron/rosner/cost_coeffs.csv b/h2integrate/converters/iron/rosner/cost_coeffs.csv similarity index 100% rename from h2integrate/simulation/technologies/iron/rosner/cost_coeffs.csv rename to h2integrate/converters/iron/rosner/cost_coeffs.csv diff --git a/h2integrate/simulation/technologies/iron/rosner/cost_inputs.csv b/h2integrate/converters/iron/rosner/cost_inputs.csv similarity index 100% rename from h2integrate/simulation/technologies/iron/rosner/cost_inputs.csv rename to h2integrate/converters/iron/rosner/cost_inputs.csv diff --git a/h2integrate/simulation/technologies/iron/rosner/cost_model.py b/h2integrate/converters/iron/rosner/cost_model.py similarity index 99% rename from h2integrate/simulation/technologies/iron/rosner/cost_model.py rename to h2integrate/converters/iron/rosner/cost_model.py index cf5262c15..5e0977d84 100644 --- a/h2integrate/simulation/technologies/iron/rosner/cost_model.py +++ b/h2integrate/converters/iron/rosner/cost_model.py @@ -10,7 +10,7 @@ import pandas as pd from h2integrate.core.utilities import load_yaml -from h2integrate.simulation.technologies.iron.load_top_down_coeffs import load_top_down_coeffs +from h2integrate.converters.iron.load_top_down_coeffs import load_top_down_coeffs CD = Path(__file__).parent diff --git a/h2integrate/simulation/technologies/iron/rosner/finance_model.py b/h2integrate/converters/iron/rosner/finance_model.py similarity index 99% rename from h2integrate/simulation/technologies/iron/rosner/finance_model.py rename to h2integrate/converters/iron/rosner/finance_model.py index 5f835011c..46032d3bd 100644 --- a/h2integrate/simulation/technologies/iron/rosner/finance_model.py +++ b/h2integrate/converters/iron/rosner/finance_model.py @@ -3,7 +3,7 @@ import h2integrate.tools.profast_tools as pf_tools from h2integrate.tools.inflation.inflate import inflate_cpi, inflate_cepci -from h2integrate.simulation.technologies.iron.load_top_down_coeffs import load_top_down_coeffs +from h2integrate.converters.iron.load_top_down_coeffs import load_top_down_coeffs def main(config): diff --git a/h2integrate/simulation/technologies/iron/rosner/perf_coeffs.csv b/h2integrate/converters/iron/rosner/perf_coeffs.csv similarity index 100% rename from h2integrate/simulation/technologies/iron/rosner/perf_coeffs.csv rename to h2integrate/converters/iron/rosner/perf_coeffs.csv diff --git a/h2integrate/simulation/technologies/iron/rosner/perf_inputs.csv b/h2integrate/converters/iron/rosner/perf_inputs.csv similarity index 100% rename from h2integrate/simulation/technologies/iron/rosner/perf_inputs.csv rename to h2integrate/converters/iron/rosner/perf_inputs.csv diff --git a/h2integrate/simulation/technologies/iron/rosner/perf_model.py b/h2integrate/converters/iron/rosner/perf_model.py similarity index 100% rename from h2integrate/simulation/technologies/iron/rosner/perf_model.py rename to h2integrate/converters/iron/rosner/perf_model.py diff --git a/h2integrate/simulation/technologies/iron/rosner_ore/__init__.py b/h2integrate/converters/iron/rosner_ore/__init__.py similarity index 100% rename from h2integrate/simulation/technologies/iron/rosner_ore/__init__.py rename to h2integrate/converters/iron/rosner_ore/__init__.py diff --git a/h2integrate/simulation/technologies/iron/rosner_ore/finance_model.py b/h2integrate/converters/iron/rosner_ore/finance_model.py similarity index 98% rename from h2integrate/simulation/technologies/iron/rosner_ore/finance_model.py rename to h2integrate/converters/iron/rosner_ore/finance_model.py index 5427b28bd..5fc3724f1 100644 --- a/h2integrate/simulation/technologies/iron/rosner_ore/finance_model.py +++ b/h2integrate/converters/iron/rosner_ore/finance_model.py @@ -2,7 +2,7 @@ import ProFAST from h2integrate.tools.inflation.inflate import inflate_cpi -from h2integrate.simulation.technologies.iron.load_top_down_coeffs import load_top_down_coeffs +from h2integrate.converters.iron.load_top_down_coeffs import load_top_down_coeffs def main(config): diff --git a/h2integrate/simulation/technologies/iron/rosner_ore/variable_om_cost.py b/h2integrate/converters/iron/rosner_ore/variable_om_cost.py similarity index 93% rename from h2integrate/simulation/technologies/iron/rosner_ore/variable_om_cost.py rename to h2integrate/converters/iron/rosner_ore/variable_om_cost.py index 15d8fd442..bd0c6e535 100644 --- a/h2integrate/simulation/technologies/iron/rosner_ore/variable_om_cost.py +++ b/h2integrate/converters/iron/rosner_ore/variable_om_cost.py @@ -1,6 +1,6 @@ import numpy as np -from h2integrate.simulation.technologies.iron.load_top_down_coeffs import load_top_down_coeffs +from h2integrate.converters.iron.load_top_down_coeffs import load_top_down_coeffs def rosner_ore_variable_om_cost(mine_name, cost_df, analysis_start, cost_year, plant_life): diff --git a/h2integrate/simulation/technologies/iron/rosner_override/__init__.py b/h2integrate/converters/iron/rosner_override/__init__.py similarity index 100% rename from h2integrate/simulation/technologies/iron/rosner_override/__init__.py rename to h2integrate/converters/iron/rosner_override/__init__.py diff --git a/h2integrate/simulation/technologies/iron/rosner_override/finance_model.py b/h2integrate/converters/iron/rosner_override/finance_model.py similarity index 100% rename from h2integrate/simulation/technologies/iron/rosner_override/finance_model.py rename to h2integrate/converters/iron/rosner_override/finance_model.py diff --git a/h2integrate/simulation/technologies/iron/stinn/cost_coeffs.csv b/h2integrate/converters/iron/stinn/cost_coeffs.csv similarity index 100% rename from h2integrate/simulation/technologies/iron/stinn/cost_coeffs.csv rename to h2integrate/converters/iron/stinn/cost_coeffs.csv diff --git a/h2integrate/simulation/technologies/iron/stinn/cost_model.py b/h2integrate/converters/iron/stinn/cost_model.py similarity index 100% rename from h2integrate/simulation/technologies/iron/stinn/cost_model.py rename to h2integrate/converters/iron/stinn/cost_model.py diff --git a/h2integrate/simulation/technologies/iron/top_down_coeffs.csv b/h2integrate/converters/iron/top_down_coeffs.csv similarity index 100% rename from h2integrate/simulation/technologies/iron/top_down_coeffs.csv rename to h2integrate/converters/iron/top_down_coeffs.csv diff --git a/h2integrate/converters/steel/electric_arc_furnance.py b/h2integrate/converters/steel/electric_arc_furnance.py index b45058592..137618379 100644 --- a/h2integrate/converters/steel/electric_arc_furnance.py +++ b/h2integrate/converters/steel/electric_arc_furnance.py @@ -5,16 +5,16 @@ from h2integrate.core.utilities import BaseConfig, merge_shared_inputs from h2integrate.core.validators import contains -from h2integrate.core.model_baseclasses import CostModelBaseClass -from h2integrate.tools.inflation.inflate import inflate_cpi, inflate_cepci -from h2integrate.simulation.technologies.iron.iron import ( +from h2integrate.converters.iron.iron import ( IronCostModelConfig, IronPerformanceModelConfig, IronPerformanceModelOutputs, run_iron_cost_model, run_size_iron_plant_performance, ) -from h2integrate.simulation.technologies.iron.load_top_down_coeffs import load_top_down_coeffs +from h2integrate.core.model_baseclasses import CostModelBaseClass +from h2integrate.tools.inflation.inflate import inflate_cpi, inflate_cepci +from h2integrate.converters.iron.load_top_down_coeffs import load_top_down_coeffs @define diff --git a/tests/h2integrate/test_iron/test_iron_ore.py b/tests/h2integrate/test_iron/test_iron_ore.py index a783125fa..c3e302502 100644 --- a/tests/h2integrate/test_iron/test_iron_ore.py +++ b/tests/h2integrate/test_iron/test_iron_ore.py @@ -4,7 +4,7 @@ from pytest import approx, fixture -from h2integrate.simulation.technologies.iron import iron +from h2integrate.converters.iron import iron @fixture diff --git a/tests/h2integrate/test_iron/test_iron_post.py b/tests/h2integrate/test_iron/test_iron_post.py index cde180cc8..92e80cda3 100644 --- a/tests/h2integrate/test_iron/test_iron_post.py +++ b/tests/h2integrate/test_iron/test_iron_post.py @@ -4,7 +4,7 @@ from pytest import approx, fixture -from h2integrate.simulation.technologies.iron import iron +from h2integrate.converters.iron import iron @fixture diff --git a/tests/h2integrate/test_iron/test_iron_win.py b/tests/h2integrate/test_iron/test_iron_win.py index 5104e75cf..fe3aba886 100644 --- a/tests/h2integrate/test_iron/test_iron_win.py +++ b/tests/h2integrate/test_iron/test_iron_win.py @@ -4,7 +4,7 @@ from pytest import approx, fixture -from h2integrate.simulation.technologies.iron import iron +from h2integrate.converters.iron import iron @fixture From cea8ac8f3d57eb2795bac9df76623d099cfb8245 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Tue, 2 Dec 2025 11:44:42 -0700 Subject: [PATCH 19/30] Fixed iron transport --- h2integrate/converters/iron/iron_transport.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/h2integrate/converters/iron/iron_transport.py b/h2integrate/converters/iron/iron_transport.py index e5f0bc725..9f248e9a7 100644 --- a/h2integrate/converters/iron/iron_transport.py +++ b/h2integrate/converters/iron/iron_transport.py @@ -80,12 +80,7 @@ def compute(self, inputs, outputs): lon = self.options["plant_config"].get("site", {}).get("longitude") site_location = (lat, lon) shipping_coord_fpath = ( - ROOT_DIR - / "simulation" - / "technologies" - / "iron" - / "martin_transport" - / "shipping_coords.csv" + ROOT_DIR / "converters" / "iron" / "martin_transport" / "shipping_coords.csv" ) shipping_locations = pd.read_csv(shipping_coord_fpath, index_col="Unnamed: 0") From 4552e919392192f70caa9dc82a3f3257026b51ad Mon Sep 17 00:00:00 2001 From: John Jasa Date: Tue, 2 Dec 2025 11:46:08 -0700 Subject: [PATCH 20/30] Reverted steel example test values --- tests/h2integrate/test_all_examples.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/h2integrate/test_all_examples.py b/tests/h2integrate/test_all_examples.py index 1fad90eeb..afe024a55 100644 --- a/tests/h2integrate/test_all_examples.py +++ b/tests/h2integrate/test_all_examples.py @@ -31,7 +31,7 @@ def test_steel_example(subtests): ) with subtests.test("Check LCOS"): - assert pytest.approx(model.prob.get_val("steel.LCOS")[0], rel=1e-3) == 1216.29426045 + assert pytest.approx(model.prob.get_val("steel.LCOS")[0], rel=1e-3) == 1213.87728644 with subtests.test("Check total adjusted CapEx"): assert ( @@ -53,7 +53,7 @@ def test_steel_example(subtests): assert pytest.approx(model.prob.get_val("steel.CapEx"), rel=1e-3) == 5.78060014e08 with subtests.test("Check steel OpEx"): - assert pytest.approx(model.prob.get_val("steel.OpEx"), rel=1e-3) == 1.0156052e08 + assert pytest.approx(model.prob.get_val("steel.OpEx"), rel=1e-3) == 1.0129052e08 def test_simple_ammonia_example(subtests): From 6dc6b1c78eeae8c64d6ba74ac2612013d3322eee Mon Sep 17 00:00:00 2001 From: John Jasa Date: Tue, 2 Dec 2025 13:17:28 -0700 Subject: [PATCH 21/30] Reverted hydrogen storage test values --- .../hydrogen/test/test_hydrogen_storage.py | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/h2integrate/storage/hydrogen/test/test_hydrogen_storage.py b/h2integrate/storage/hydrogen/test/test_hydrogen_storage.py index fad61bb5e..8fcfefef9 100644 --- a/h2integrate/storage/hydrogen/test/test_hydrogen_storage.py +++ b/h2integrate/storage/hydrogen/test/test_hydrogen_storage.py @@ -45,7 +45,7 @@ def test_salt_cavern_ex_2(plant_config, subtests): with subtests.test("CapEx"): assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-6) == 65337437.17944019 with subtests.test("OpEx"): - assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 3149096.037312646 + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 2358794.1150327 with subtests.test("VarOpEx"): assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 with subtests.test("Cost year"): @@ -75,7 +75,7 @@ def test_lined_rock_cavern_ex_12_small_case(plant_config, subtests): with subtests.test("CapEx"): assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-6) == 18693728.23242369 with subtests.test("OpEx"): - assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 1099582.4333529277 + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 767466.36227705 with subtests.test("VarOpEx"): assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 with subtests.test("Cost year"): @@ -104,7 +104,7 @@ def test_lined_rock_cavern_ex_1(plant_config, subtests): with subtests.test("CapEx"): assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-6) == 92392496.03198986 with subtests.test("OpEx"): - assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 4292680.718474801 + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 3228373.63748516 with subtests.test("VarOpEx"): assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 with subtests.test("Cost year"): @@ -133,7 +133,7 @@ def test_lined_rock_cavern_ex_14(plant_config, subtests): with subtests.test("CapEx"): assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-6) == 1.28437699 * 1e8 with subtests.test("OpEx"): - assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 5315184.827689768 + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 4330693.49125131 with subtests.test("VarOpEx"): assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 with subtests.test("Cost year"): @@ -162,7 +162,7 @@ def test_buried_pipe_storage(plant_config, subtests): with subtests.test("CapEx"): assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-6) == 1827170156.1390543 with subtests.test("OpEx"): - assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 57720829.60694359 + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-6) == 56972128.44322313 with subtests.test("VarOpEx"): assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 with subtests.test("Cost year"): @@ -224,11 +224,9 @@ def test_lined_rock_cavern_1M_kg(plant_config, subtests): prob.run_model() with subtests.test("CapEx"): - # Expected: 51136144.673 (from original test) assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-5) == 51136144.673 with subtests.test("OpEx"): - # Expected: 2359700 (from original test) - assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-5) == 2359700 + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-5) == 1833238.1260644312 with subtests.test("VarOpEx"): assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 with subtests.test("Cost year"): @@ -290,11 +288,9 @@ def test_salt_cavern_1M_kg(plant_config, subtests): prob.run_model() with subtests.test("CapEx"): - # Expected: 24992482.4198 (from original test) assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-5) == 24992482.4198 with subtests.test("OpEx"): - # Expected: 1461664 (from original test) - assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-5) == 1461664 + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-5) == 1015928.3388478106 with subtests.test("VarOpEx"): assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 with subtests.test("Cost year"): @@ -356,11 +352,9 @@ def test_pipe_storage_1M_kg(plant_config, subtests): prob.run_model() with subtests.test("CapEx"): - # Expected: 508745483.851 (from original test) assert pytest.approx(prob.get_val("sys.CapEx")[0], rel=1e-5) == 508745483.851 with subtests.test("OpEx"): - # Expected: 16439748 (from original test) - assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-5) == 16439748 + assert pytest.approx(prob.get_val("sys.OpEx")[0], rel=1e-5) == 16010851.734082155 with subtests.test("VarOpEx"): assert pytest.approx(np.sum(prob.get_val("sys.VarOpEx")), rel=1e-6) == 0.0 with subtests.test("Cost year"): From d9167a887bd45db717117a3e61a14b3f99131d49 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Tue, 2 Dec 2025 16:38:58 -0700 Subject: [PATCH 22/30] fixing iron paths --- h2integrate/converters/iron/martin_mine_cost_model.py | 4 +--- h2integrate/converters/iron/martin_mine_perf_model.py | 4 +--- tests/h2integrate/test_iron/test_iron_ore.py | 6 +----- tests/h2integrate/test_iron/test_iron_post.py | 6 +----- tests/h2integrate/test_iron/test_iron_win.py | 6 +----- 5 files changed, 5 insertions(+), 21 deletions(-) diff --git a/h2integrate/converters/iron/martin_mine_cost_model.py b/h2integrate/converters/iron/martin_mine_cost_model.py index 80df24baf..ba1d2aa9e 100644 --- a/h2integrate/converters/iron/martin_mine_cost_model.py +++ b/h2integrate/converters/iron/martin_mine_cost_model.py @@ -91,9 +91,7 @@ def setup(self): desc="Iron ore pellets produced", ) - coeff_fpath = ( - ROOT_DIR / "simulation" / "technologies" / "iron" / "martin_ore" / "cost_coeffs.csv" - ) + coeff_fpath = ROOT_DIR / "converters" / "iron" / "martin_ore" / "cost_coeffs.csv" # martin ore performance model coeff_df = pd.read_csv(coeff_fpath, index_col=0) self.coeff_df = self.format_coeff_df(coeff_df, self.config.mine) diff --git a/h2integrate/converters/iron/martin_mine_perf_model.py b/h2integrate/converters/iron/martin_mine_perf_model.py index 8fb48f0af..83e498ff9 100644 --- a/h2integrate/converters/iron/martin_mine_perf_model.py +++ b/h2integrate/converters/iron/martin_mine_perf_model.py @@ -108,9 +108,7 @@ def setup(self): desc="Total iron ore pellets produced anually", ) - coeff_fpath = ( - ROOT_DIR / "simulation" / "technologies" / "iron" / "martin_ore" / "perf_coeffs.csv" - ) + coeff_fpath = ROOT_DIR / "converters" / "iron" / "martin_ore" / "perf_coeffs.csv" # martin ore performance model coeff_df = pd.read_csv(coeff_fpath, index_col=0) self.coeff_df = self.format_coeff_df(coeff_df, self.config.mine) diff --git a/tests/h2integrate/test_iron/test_iron_ore.py b/tests/h2integrate/test_iron/test_iron_ore.py index c3e302502..b6a124290 100644 --- a/tests/h2integrate/test_iron/test_iron_ore.py +++ b/tests/h2integrate/test_iron/test_iron_ore.py @@ -122,11 +122,7 @@ def test_run_martin_iron_ore(iron_ore, subtests): def test_refit_coefficients(iron_ore, subtests): # Determine the model directory based on the model name iron_tech_dir = ( - Path(__file__).parent.parent.parent.parent - / "h2integrate" - / "simulation" - / "technologies" - / "iron" + Path(__file__).parent.parent.parent.parent / "h2integrate" / "converters" / "iron" ) model_name = iron_ore["iron"]["cost_model"]["name"] model_dir = iron_tech_dir / model_name diff --git a/tests/h2integrate/test_iron/test_iron_post.py b/tests/h2integrate/test_iron/test_iron_post.py index 92e80cda3..1ee4f6dc5 100644 --- a/tests/h2integrate/test_iron/test_iron_post.py +++ b/tests/h2integrate/test_iron/test_iron_post.py @@ -165,11 +165,7 @@ def test_rosner_override(iron_post, subtests): def test_refit_coefficients(iron_post, subtests): # Determine the model directory based on the model name iron_tech_dir = ( - Path(__file__).parent.parent.parent.parent - / "h2integrate" - / "simulation" - / "technologies" - / "iron" + Path(__file__).parent.parent.parent.parent / "h2integrate" / "converters" / "iron" ) model_name = iron_post["iron"]["cost_model"]["name"] model_dir = iron_tech_dir / model_name diff --git a/tests/h2integrate/test_iron/test_iron_win.py b/tests/h2integrate/test_iron/test_iron_win.py index fe3aba886..b8dd58c8f 100644 --- a/tests/h2integrate/test_iron/test_iron_win.py +++ b/tests/h2integrate/test_iron/test_iron_win.py @@ -167,11 +167,7 @@ def test_rosner_override(iron_win, subtests): def test_refit_coefficients(iron_win, subtests): # Determine the model directory based on the model name iron_tech_dir = ( - Path(__file__).parent.parent.parent.parent - / "h2integrate" - / "simulation" - / "technologies" - / "iron" + Path(__file__).parent.parent.parent.parent / "h2integrate" / "converters" / "iron" ) model_name = iron_win["iron"]["cost_model"]["name"] model_dir = iron_tech_dir / model_name From e39035f1a8efab8276873c68b877f9989a301a99 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Wed, 3 Dec 2025 13:46:09 -0700 Subject: [PATCH 23/30] Updates for PR --- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5869099c..5b3f88a0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: Testing on: push: - branches: [main, develop, remove_more_of_old_greenheart] + branches: [main, develop] pull_request: branches: [main, develop] schedule: diff --git a/CHANGELOG.md b/CHANGELOG.md index d0ae7ede0..ae66e09f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ - Added solar resource models for Meteosat Prime Meridian and Himawari datasets available through NSRDB - Improved readability of the postprocessing printout by simplifying numerical representation, especially for years - Add open-loop load demand controllers: `DemandOpenLoopConverterController` and `FlexibleDemandOpenLoopConverterController` +- Removed a large portion of the old GreenHEART code that was no longer being used ## 0.4.0 [October 1, 2025] From c0eaf183f1d929f2d0219465843acc3ad7701bbe Mon Sep 17 00:00:00 2001 From: John Jasa Date: Thu, 4 Dec 2025 16:41:46 -0700 Subject: [PATCH 24/30] Updating based on PR comments --- docs/_toc.yml | 1 + docs/technology_models/hydrogen_storage.md | 49 ++++++++++++++++++ .../images/installed_capital_cost_h2.png | Bin 0 -> 107369 bytes .../images/lifetime_storage_cost_h2.png | Bin 0 -> 43216 bytes examples/05_wind_h2_opt/plant_config.yaml | 1 + ...em_electrolyzer.py => pem_electrolyzer.py} | 0 .../{run_PEM_master.py => run_PEM_main.py} | 0 .../hydrogen/pem_model/run_h2_PEM.py | 2 +- .../converters/hydrogen/wombat_model.py | 2 +- h2integrate/core/supported_models.py | 4 +- 10 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 docs/technology_models/hydrogen_storage.md create mode 100644 docs/technology_models/images/installed_capital_cost_h2.png create mode 100644 docs/technology_models/images/lifetime_storage_cost_h2.png rename h2integrate/converters/hydrogen/{eco_tools_pem_electrolyzer.py => pem_electrolyzer.py} (100%) rename h2integrate/converters/hydrogen/pem_model/{run_PEM_master.py => run_PEM_main.py} (100%) diff --git a/docs/_toc.yml b/docs/_toc.yml index dd82b3598..bb3ebd379 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -39,6 +39,7 @@ parts: - file: technology_models/pysam_battery.md - file: technology_models/geologic_hydrogen.md - file: technology_models/grid + - file: technology_models/hydrogen_storage.md - caption: Resource Models chapters: diff --git a/docs/technology_models/hydrogen_storage.md b/docs/technology_models/hydrogen_storage.md new file mode 100644 index 000000000..6cf844182 --- /dev/null +++ b/docs/technology_models/hydrogen_storage.md @@ -0,0 +1,49 @@ +# Bulk Hydrogen Storage Cost Model + +## Storage Types + +H2Integrate models at least three types of bulk hydrogen storage technologies: + +- **Underground Pipe Storage**: Hydrogen stored in underground pipeline networks +- **Lined Rock Caverns (LRC)**: Hydrogen stored in rock caverns with engineered linings +- **Salt Caverns**: Hydrogen stored in solution-mined salt caverns + +These storage options provide different cost-capacity relationships suitable for various scales of hydrogen production and distribution. + +## Cost Correlations + +The bulk hydrogen storage costs are modeled as functions of storage capacity using exponential correlations: + +$$Cost = \exp(a(\ln(m))^2 - b\ln(m) + c)$$ + +where $m$ is the useable amount of H₂ stored in tonnes. + +## Installed Capital Cost and Lifetime Storage Cost + +The figures below show how storage costs scale with capacity for different storage technologies: + +![Installed capital cost scaling](images/installed_capital_cost_h2.png) + +*Figure 1a: Installed capital cost (\$/kg-H₂) as a function of usable hydrogen storage capacity* + +![Lifetime storage cost scaling](images/lifetime_storage_cost_h2.png) + +*Figure 1b: Lifetime storage cost (\$/kg-H₂-stored) as a function of usable hydrogen storage capacity* + +## Cost Correlation Coefficients + +### Capital Cost Coefficients (Figure 1a) + +| Storage | a | b | c | +|--------------------------------|----------|---------|--------| +| Underground pipe storage | 0.004161 | 0.06036 | 6.4581 | +| Underground lined rock caverns | 0.095803 | 1.5868 | 10.332 | +| Underground salt caverns | 0.092548 | 1.6432 | 10.161 | + +### Annual Cost Coefficients (Figure 1b) + +| Storage | a | b | c | +|--------------------------------|----------|---------|--------| +| Underground pipe storage | 0.001559 | 0.03531 | 4.5183 | +| Underground lined rock caverns | 0.092286 | 1.5565 | 8.4658 | +| Underground salt caverns | 0.085863 | 1.5574 | 8.1606 | diff --git a/docs/technology_models/images/installed_capital_cost_h2.png b/docs/technology_models/images/installed_capital_cost_h2.png new file mode 100644 index 0000000000000000000000000000000000000000..fcbfde5602b919b9cfcbc7996c55f03d3a2037fa GIT binary patch literal 107369 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 literal 0 HcmV?d00001 diff --git a/examples/05_wind_h2_opt/plant_config.yaml b/examples/05_wind_h2_opt/plant_config.yaml index 8242aaa3a..fbdd77ebc 100644 --- a/examples/05_wind_h2_opt/plant_config.yaml +++ b/examples/05_wind_h2_opt/plant_config.yaml @@ -20,6 +20,7 @@ plant: # this will naturally grow as we mature the interconnected tech technology_interconnections: [ ["wind", "electrolyzer", "electricity", "cable"], + ["wind", "electrolyzer", ["electricity_out", "electricity_in"]], # etc ] resource_to_tech_connections: [ diff --git a/h2integrate/converters/hydrogen/eco_tools_pem_electrolyzer.py b/h2integrate/converters/hydrogen/pem_electrolyzer.py similarity index 100% rename from h2integrate/converters/hydrogen/eco_tools_pem_electrolyzer.py rename to h2integrate/converters/hydrogen/pem_electrolyzer.py diff --git a/h2integrate/converters/hydrogen/pem_model/run_PEM_master.py b/h2integrate/converters/hydrogen/pem_model/run_PEM_main.py similarity index 100% rename from h2integrate/converters/hydrogen/pem_model/run_PEM_master.py rename to h2integrate/converters/hydrogen/pem_model/run_PEM_main.py diff --git a/h2integrate/converters/hydrogen/pem_model/run_h2_PEM.py b/h2integrate/converters/hydrogen/pem_model/run_h2_PEM.py index 222622a85..e44ef7ff3 100644 --- a/h2integrate/converters/hydrogen/pem_model/run_h2_PEM.py +++ b/h2integrate/converters/hydrogen/pem_model/run_h2_PEM.py @@ -1,7 +1,7 @@ import numpy as np import pandas as pd -from h2integrate.converters.hydrogen.pem_model.run_PEM_master import run_PEM_clusters +from h2integrate.converters.hydrogen.pem_model.run_PEM_main import run_PEM_clusters from h2integrate.converters.hydrogen.pem_model.PEM_H2_LT_electrolyzer_Clusters import eta_h2_hhv diff --git a/h2integrate/converters/hydrogen/wombat_model.py b/h2integrate/converters/hydrogen/wombat_model.py index 2b61ab265..f2abfd248 100644 --- a/h2integrate/converters/hydrogen/wombat_model.py +++ b/h2integrate/converters/hydrogen/wombat_model.py @@ -6,7 +6,7 @@ from wombat.core.library import load_yaml from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.converters.hydrogen.eco_tools_pem_electrolyzer import ( +from h2integrate.converters.hydrogen.pem_electrolyzer import ( ECOElectrolyzerPerformanceModel, ECOElectrolyzerPerformanceModelConfig, ) diff --git a/h2integrate/core/supported_models.py b/h2integrate/core/supported_models.py index b864cd3e2..1735d99e9 100644 --- a/h2integrate/core/supported_models.py +++ b/h2integrate/core/supported_models.py @@ -54,6 +54,7 @@ ReverseOsmosisPerformanceModel, ) from h2integrate.converters.hydrogen.basic_cost_model import BasicElectrolyzerCostModel +from h2integrate.converters.hydrogen.pem_electrolyzer import ECOElectrolyzerPerformanceModel from h2integrate.converters.solar.atb_res_com_pv_cost import ATBResComPVCostModel from h2integrate.converters.solar.atb_utility_pv_cost import ATBUtilityPVCostModel from h2integrate.resource.wind.nrel_developer_wtk_api import WTKNRELDeveloperAPIWindResource @@ -89,9 +90,6 @@ GOESFullDiscSolarAPI, GOESAggregatedSolarAPI, ) -from h2integrate.converters.hydrogen.eco_tools_pem_electrolyzer import ( - ECOElectrolyzerPerformanceModel, -) from h2integrate.converters.water_power.hydro_plant_run_of_river import ( RunOfRiverHydroCostModel, RunOfRiverHydroPerformanceModel, From 1908b0d987ecb23318d99b3256c96d1b72fab867 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Thu, 4 Dec 2025 16:48:46 -0700 Subject: [PATCH 25/30] removed h2 pipe code and tests --- .../hydrogen/h2_transport/h2_compression.py | 4 +- .../hydrogen/h2_transport/h2_export_pipe.py | 436 ------------------ .../hydrogen/h2_transport/h2_pipe_array.py | 124 ----- .../test_h2_export_pipeline.py | 130 ------ .../test_h2_transport/test_h2_pipe_array.py | 63 --- 5 files changed, 2 insertions(+), 755 deletions(-) delete mode 100644 h2integrate/storage/hydrogen/h2_transport/h2_export_pipe.py delete mode 100644 h2integrate/storage/hydrogen/h2_transport/h2_pipe_array.py delete mode 100644 tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_export_pipeline.py delete mode 100644 tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_pipe_array.py diff --git a/h2integrate/storage/hydrogen/h2_transport/h2_compression.py b/h2integrate/storage/hydrogen/h2_transport/h2_compression.py index 749606e62..fd534555e 100644 --- a/h2integrate/storage/hydrogen/h2_transport/h2_compression.py +++ b/h2integrate/storage/hydrogen/h2_transport/h2_compression.py @@ -22,8 +22,8 @@ def __init__( """ Parameters: --------------- - p_outlet: oulet pressure (bar) - flow_Rate_kg_d: mass flow rate in kg/day + p_outlet: outlet pressure (bar) + flow_rate_kg_d: mass flow rate in kg/day """ self.p_inlet = p_inlet # bar self.p_outlet = p_outlet # bar diff --git a/h2integrate/storage/hydrogen/h2_transport/h2_export_pipe.py b/h2integrate/storage/hydrogen/h2_transport/h2_export_pipe.py deleted file mode 100644 index 70d70f6dd..000000000 --- a/h2integrate/storage/hydrogen/h2_transport/h2_export_pipe.py +++ /dev/null @@ -1,436 +0,0 @@ -""" -Author: Jamie Kee -Added to HOPP by: Jared Thomas -Note: ANL costs are in 2018 dollars - -07/15/2024: Jamie removed Z=0.9 assumption with linear approx, -removed f=0.01 assumption with Hofer eqn, added -algebraic solver, and reformatted with black. -08/02/2024: Provide cost overrides -""" - -from __future__ import annotations - -from pathlib import Path - -import numpy as np -import pandas as pd - - -BAR2MPA = 0.1 -BAR2PA = 100_000 -MM2IN = 0.0393701 -M2KM = 1 / 1_000 -M2MM = 1_000 -KM2MI = 0.621371 - - -def run_pipe_analysis( - L: float, - m_dot: float, - p_inlet: float, - p_outlet: float, - depth: float, - risers: int = 1, - data_location: str | Path = Path(__file__).parent / "data_tables", - labor_in_mi: float | None = None, - misc_in_mi: float | None = None, - row_in_mi: float | None = None, - mat_in_mi: float | None = None, - region: str = "SW", -): - """ - This function calculates the cheapest grade, diameter, thickness, subject to ASME B31.12 and .8 - - If $/in/mi values are provided in labor_in_mi, misc_in_mi, row_in_mi, mat_in_mi, those values - will be used in the cost calculations instead of the defaults - """ - if isinstance(data_location, str): - data_location = Path(data_location).resolve() - p_inlet_MPa = p_inlet * BAR2MPA - F = 0.72 # Design option B class 1 - 2011 ASME B31.12 Table PL-3.7.1.2 - E = 1.0 # Long. Weld Factor: Seamless (Table IX-3B) - T_derating = 1 # 2020 ASME B31.8 Table A841.1.8-1 for T<250F, 121C - - # Cost overrides - anl_cost_overrides = {"labor": labor_in_mi, "misc": misc_in_mi, "ROW": row_in_mi} - - # This is a flag for the ASMEB31.8 stress design, if not including risers, then this can be set - # to false - riser = risers > 0 - extra_length = 1 + 0.05 # 5% extra - - # Assuming 5% extra length and 1 riser. Will need two risers for turbine to central platform - total_L = L * extra_length + risers * depth * M2KM # km - - # Import mechanical props and pipe thicknesses (remove A,B ,and A25 since no costing data) - yield_strengths = pd.read_csv( - data_location / "steel_mechanical_props.csv", - index_col=None, - header=0, - ) - yield_strengths = yield_strengths.loc[ - ~yield_strengths["Grade"].isin(["A", "B", "A25"]) - ].reset_index() - schedules_all = pd.read_csv( - data_location / "pipe_dimensions_metric.csv", - index_col=None, - header=0, - ) - steel_costs_kg = pd.read_csv(data_location / "steel_costs_per_kg.csv", index_col=None, header=0) - - # First get the minimum diameter required to achieve the outlet pressure for given length and - # m_dot - min_diam_mm = get_min_diameter_of_pipe(L=L, m_dot=m_dot, p_inlet=p_inlet, p_outlet=p_outlet) - # Filter for diameters larger than min diam required - schedules_spec = schedules_all.loc[schedules_all["DN"] >= (min_diam_mm)] - - # Gather the grades, diameters, and schedules to loop thru - grades = yield_strengths["Grade"].values - diams = schedules_spec["Outer diameter [mm]"].values - schds = schedules_spec.loc[ - :, ~schedules_spec.columns.isin(["DN", "Outer diameter [mm]"]) - ].columns - viable_types = [] - - # Loop thru grades - for grade in grades: - # Get SMYS and SMTS for the specific grade - SMYS = yield_strengths.loc[yield_strengths["Grade"] == grade, "SMYS [Mpa]"].values[0] - SMTS = yield_strengths.loc[yield_strengths["Grade"] == grade, "SMTS [Mpa]"].values[0] - # Loop thru outer diameters - for diam in diams: - diam_row = schedules_spec.loc[schedules_spec["Outer diameter [mm]"] == diam] - dn = diam_row["DN"].values[0] - # Loop thru scheudles (which give the thickness) - for schd in schds: - thickness = diam_row[schd].values[0] - - # Check if thickness satisfies ASME B31.12 - mat_perf_factor = get_mat_factor( - SMYS=SMYS, SMTS=SMTS, design_pressure=p_inlet * BAR2MPA - ) - t_ASME = p_inlet_MPa * dn / (2 * SMYS * F * E * mat_perf_factor) - if thickness < t_ASME: - continue - - # Check if satifies ASME B31.8 - if not checkASMEB318( - SMYS=SMYS, - diam=diam, - thickness=thickness, - riser=riser, - depth=depth, - p_inlet=p_inlet, - T_derating=T_derating, - ): - continue - - # Add qualified pipes to saved answers: - inner_diam = diam - 2 * thickness - viable_types.append([grade, dn, diam, inner_diam, schd, thickness]) - - viable_types_df = pd.DataFrame( - viable_types, - columns=[ - "Grade", - "DN", - "Outer diameter (mm)", - "Inner diameter (mm)", - "Schedule", - "Thickness (mm)", - ], - ).dropna() - - # Calculate material, labor, row, and misc costs - viable_types_df = get_mat_costs( - schedules_spec=viable_types_df, - total_L=total_L, - steel_costs_kg=steel_costs_kg, - mat_cost_override=mat_in_mi, - ) - viable_types_df = get_anl_costs( - costs=viable_types_df, - total_L=total_L, - anl_cost_overrides=anl_cost_overrides, - loc=region, - ) - viable_types_df["total capital cost [$]"] = viable_types_df[ - ["mat cost [$]", "labor cost [$]", "misc cost [$]", "ROW cost [$]"] - ].sum(axis=1) - - # Annual operating cost assumes 1.17% of total capital - # https://doi.org/10.1016/j.esr.2021.100658 - viable_types_df["annual operating cost [$]"] = ( - 0.0117 * viable_types_df["total capital cost [$]"] - ) - - # Take the option with the lowest total capital cost - min_row = viable_types_df.sort_values(by="total capital cost [$]").iloc[:1].reset_index() - return min_row - - -def get_mat_factor(SMYS: float, SMTS: float, design_pressure: float) -> float: - """ - Determine the material performance factor ASMEB31.12. - Dependent on the SMYS and SMTS. - Defaulted to 1 if not within parameters - This may not be a good assumption - """ - dp_array = np.array([6.8948, 13.7895, 15.685, 16.5474, 17.9264, 19.3053, 20.6843]) # MPa - if SMYS <= 358.528 or SMTS <= 455.054: - h_f_array = np.array([1, 1, 0.954, 0.91, 0.88, 0.84, 0.78]) - elif SMYS <= 413.686 and (SMTS > 455.054 and SMTS <= 517.107): - h_f_array = np.array([0.874, 0.874, 0.834, 0.796, 0.77, 0.734, 0.682]) - elif SMYS <= 482.633 and (SMTS > 517.107 and SMTS <= 565.370): - h_f_array = np.array([0.776, 0.776, 0.742, 0.706, 0.684, 0.652, 0.606]) - elif SMYS <= 551.581 and (SMTS > 565.370 and SMTS <= 620.528): - h_f_array = np.array([0.694, 0.694, 0.662, 0.632, 0.61, 0.584, 0.542]) - else: - return 1 - mat_perf_factor = np.interp(design_pressure, dp_array, h_f_array) - return mat_perf_factor - - -def checkASMEB318( - SMYS: float, - diam: float, - thickness: float, - riser: bool, - depth: float, - p_inlet: float, - T_derating: float, -) -> bool: - """ - Determine if pipe parameters satisfy hoop and longitudinal stress requirements - """ - - # Hoop Stress - 2020 ASME B31.8 Table A842.2.2-1 - F1 = 0.50 if riser else 0.72 - - # Hoop stress (MPa) - 2020 ASME B31.8 section A842.2.2.2 eqn (1) - # This is the maximum value for S_h - # Sh <= F1*SMYS*T_derating - S_h_check = F1 * SMYS * T_derating - - # Hoop stress (MPa) - rho_water = 1_000 # kg/m3 - p_hydrostatic = rho_water * 9.81 * depth / BAR2PA # bar - dP = (p_inlet - p_hydrostatic) * BAR2MPA # MPa - S_h = dP * (diam - (thickness if diam / thickness >= 30 else 0)) / (2_000 * thickness) - if S_h >= S_h_check: - return False - - # Longitudinal stress (MPa) - S_L_check = 0.8 * SMYS # 2020 ASME B31.8 Table A842.2.2-1. Same for riser and pipe - S_L = p_inlet * BAR2MPA * (diam - 2 * thickness) / (4 * thickness) - if S_L > S_L_check: - return False - - (0.9 * SMYS) # 2020 ASME B31.8 Table A842.2.2-1. Same for riser and pipe - # Torsional stress?? Under what applied torque? Not sure what to do for this. - - return True - - -def get_anl_costs( - costs: pd.DataFrame, total_L: float, anl_cost_overrides: dict, loc: str = "SW" -) -> pd.DataFrame: - """ - Calculates the labor, right-of-way (ROW), and miscellaneous costs associated with pipe capital - cost - - Users can specify a region (GP,NE,MA,GL,RM,SE,PN,SW,CA) that corresponds to grouping of states - which will apply cost correlations from Brown, D., et al. 2022. “The Development of Natural Gas - and Hydrogen Pipeline Capital Cost Estimating Equations.” International Journal of Hydrogen - Energy https://doi.org/10.1016/j.ijhydene.2022.07.270. - - Alternatively, if a value (not None) is provided in anl_cost_overrides, that value be used as - the $/in/mi cost correlation for the relevant cost type. - """ - - ANL_COEFS = { - "GP": { - "labor": [10406, 0.20953, -0.08419], - "misc": [4944, 0.17351, -0.07621], - "ROW": [2751, -0.28294, 0.00731], - "material": [5813, 0.31599, -0.00376], - }, - "NE": { - "labor": [249131, -0.33162, -0.17892], - "misc": [65990, -0.29673, -0.06856], - "ROW": [83124, -0.66357, -0.07544], - "material": [10409, 0.296847, -0.07257], - }, - "MA": { - "labor": [43692, 0.05683, -0.10108], - "misc": [14616, 0.16354, -0.16186], - "ROW": [1942, 0.17394, -0.01555], - "material": [9113, 0.279875, -0.00840], - }, - "GL": { - "labor": [58154, -0.14821, -0.10596], - "misc": [41238, -0.34751, -0.11104], - "ROW": [14259, -0.65318, 0.06865], - "material": [8971, 0.255012, -0.03138], - }, - "RM": { - "labor": [10406, 0.20953, -0.08419], - "misc": [4944, 0.17351, -0.07621], - "ROW": [2751, -0.28294, 0.00731], - "material": [5813, 0.31599, -0.00376], - }, - "SE": { - "labor": [32094, 0.06110, -0.14828], - "misc": [11270, 0.19077, -0.13669], - "ROW": [9531, -0.37284, 0.02616], - "material": [6207, 0.38224, -0.05211], - }, - "PN": { - "labor": [32094, 0.06110, -0.14828], - "misc": [11270, 0.19077, -0.13669], - "ROW": [9531, -0.37284, 0.02616], - "material": [6207, 0.38224, -0.05211], - }, - "SW": { - "labor": [95295, -0.53848, 0.03070], - "misc": [19211, -0.14178, -0.04697], - "ROW": [72634, -1.07566, 0.05284], - "material": [5605, 0.41642, -0.06441], - }, - "CA": { - "labor": [95295, -0.53848, 0.03070], - "misc": [19211, -0.14178, -0.04697], - "ROW": [72634, -1.07566, 0.05284], - "material": [5605, 0.41642, -0.06441], - }, - } - - if loc not in ANL_COEFS.keys(): - raise ValueError(f"Region {loc} was supplied, but is not a valid region") - - L_mi = total_L * KM2MI - - def cost_per_in_mi(coef: list, DN_in: float, L_mi: float) -> float: - return coef[0] * DN_in ** coef[1] * L_mi ** coef[2] - - diam_col = "DN" - for cost_type in ["labor", "misc", "ROW"]: - cost_per_in_mi_val = anl_cost_overrides[cost_type] - # If no override specified, use defaults - if cost_per_in_mi_val is None: - cost_per_in_mi_val = costs.apply( - lambda x: cost_per_in_mi(ANL_COEFS[loc][cost_type], x[diam_col] * MM2IN, L_mi), - axis=1, - ) - costs[f"{cost_type} cost [$]"] = cost_per_in_mi_val * costs[diam_col] * MM2IN * L_mi - - return costs - - -def get_mat_costs( - schedules_spec: pd.DataFrame, - total_L: float, - steel_costs_kg: pd.DataFrame, - mat_cost_override: float, -): - """ - Calculates the material cost based on $/kg from Savoy for each grade - Inc., S. P. Live Stock List & Current Price. - https://www.savoypipinginc.com/blog/live-stock-and-current-price.html. - Accessed September 22, 2022. - - Users can alternatively provide a $/in/mi override to calculate material cost - """ - rho_steel = 7840 # kg/m3 - L_m = total_L / M2KM - L_mi = total_L * KM2MI - - def get_volume(od_mm: float, id_mm: float, L_m: float) -> float: - return np.pi / 4 * (od_mm**2 - id_mm**2) / M2MM**2 * L_m - - od_col = "Outer diameter (mm)" - id_col = "Inner diameter (mm)" - schedules_spec["volume [m3]"] = schedules_spec.apply( - lambda x: get_volume(x[od_col], x[id_col], L_m), - axis=1, - ) - schedules_spec["weight [kg]"] = schedules_spec["volume [m3]"] * rho_steel - - # If mat cost override is not specified, use $/kg savoy costing - if mat_cost_override is not None: - schedules_spec["mat cost [$]"] = mat_cost_override * L_mi * schedules_spec["DN"] * MM2IN - else: - schedules_spec["mat cost [$]"] = schedules_spec.apply( - lambda x: x["weight [kg]"] - * steel_costs_kg.loc[steel_costs_kg["Grade"] == x["Grade"], "Price [$/kg]"].values[0], - axis=1, - ) - - return schedules_spec - - -def get_min_diameter_of_pipe(L: float, m_dot: float, p_inlet: float, p_outlet: float) -> float: - """ - Overview: - --------- - This function returns the diameter of a pipe for a given length,flow rate, and pressure - boundaries - - Parameters: - ----------- - L : float - Length of pipeline [km] - m_dot : float = Mass flow rate [kg/s] - p_inlet : float = Pressure at inlet of pipe [bar] - p_outlet : float = Pressure at outlet of pipe [bar] - - Returns: - -------- - diameter_mm : float - Diameter of pipe [mm] - - """ - - p_in_Pa = p_inlet * BAR2PA - p_out_Pa = p_outlet * BAR2PA - - p_avg = 2 / 3 * (p_in_Pa + p_out_Pa - p_in_Pa * p_out_Pa / (p_in_Pa + p_out_Pa)) - p_diff = (p_in_Pa**2 - p_out_Pa**2) ** 0.5 - T = 15 + 273.15 # Temperature [K] - R = 8.314 # J/mol-K - z_fit_params = (6.5466916131e-9, 9.9941320278e-1) # Slope fit for 15C - z = z_fit_params[0] * p_avg + z_fit_params[1] - zrt = z * R * T - mw = 2.016 / 1_000 # kg/mol for hydrogen - RO = 0.012 # mm Roughness - mu = 8.764167e-6 # viscosity - L_m = L / M2KM - - f_list = [0.01] - - # Diameter depends on Re and f, but are functions of d. So use initial guess - # of f-0.01, then iteratively solve until f is no longer changing - err = np.inf - max_iter = 50 - while err > 0.001: - d_m = (m_dot / p_diff * 4 / np.pi * (mw / zrt / f_list[-1] / L_m) ** (-0.5)) ** (1 / 2.5) - d_mm = d_m * M2MM - Re = 4 * m_dot / (np.pi * d_m * mu) - f_list.append((-2 * np.log10(4.518 / Re * np.log10(Re / 7) + RO / (3.71 * d_mm))) ** (-2)) - err = abs((f_list[-1] - f_list[-2]) / f_list[-2]) - - # Error out if no solution after max iterations - if len(f_list) > max_iter: - raise ValueError(f"Could not find pipe diameter in {max_iter} iterations") - - return d_mm - - -if __name__ == "__main__": - L = 8 # Length [km] - m_dot = 1.5 # Mass flow rate [kg/s] assuming 300 MW -> 1.5 kg/s - p_inlet = 30 # Inlet pressure [bar] - p_outlet = 10 # Outlet pressure [bar] - depth = 80 # depth of pipe [m] - costs = run_pipe_analysis(L, m_dot, p_inlet, p_outlet, depth) - - for col in costs.columns: - print(col, costs[col][0]) diff --git a/h2integrate/storage/hydrogen/h2_transport/h2_pipe_array.py b/h2integrate/storage/hydrogen/h2_transport/h2_pipe_array.py deleted file mode 100644 index 9e48f2f38..000000000 --- a/h2integrate/storage/hydrogen/h2_transport/h2_pipe_array.py +++ /dev/null @@ -1,124 +0,0 @@ -from numpy import flip, isnan, nansum - -from h2integrate.storage.hydrogen.h2_transport.h2_export_pipe import run_pipe_analysis - - -""" -Args: - sections_distance (array[array]): array of arrays where each element of each sub-array holds the - horizontal distance in m of a pipe section - depth (float): depth of the site in m - p_inlet (float): pipe inlet pressure in bar - p_outlet (float): pipe outlet pressure in bar - mass_flow_rate_inlet (float): flow rate at each inlet to the system -Returns: - capex (float): total capital costs (USD) including labor, materials, and misc - opex (float): annual operating costs (USD) -""" - - -def run_pipe_array(sections_distance, depth, p_inlet, p_outlet, mass_flow_rate): - capex = 0 - opex = 0 - - # loop over each string - for i, pipe_string in enumerate(sections_distance): - # initialize values for each string - m_dot = 0 - p_drop = (p_inlet - p_outlet) / len(pipe_string) - flow_rates = flip(mass_flow_rate[i]) - - # loop over each section - for j, section_length in enumerate(flip(pipe_string)): - # nan represents an empty section (no pipe there, but array cannot be ragged) - if isnan(section_length): - continue - - # get mass flow rate for current section - m_dot += flow_rates[j] - - # get outlet pressure for current section - p_outlet_section = p_inlet - (j + 1) * p_drop - - # get number of risers for current section - if j == len(pipe_string) - 1: - risers = 2 - else: - risers = 1 - - # get specs and costs for each section - section_outputs = run_pipe_analysis( - section_length, m_dot, p_inlet, p_outlet_section, depth, risers=risers - ) - - capex += section_outputs["total capital cost [$]"][0] - opex += section_outputs["annual operating cost [$]"][0] - - return capex, opex - - -# Assuming one pipe diameter for the pipeline -def run_pipe_array_const_diam(sections_distance, depth, p_inlet, p_outlet, mass_flow_rate): - capex = 0 - opex = 0 - - # loop over each string - for i, pipe_string in enumerate(sections_distance): - # Calculate maximum flow rate per pipe segment (pipe is sized to largest segment) - m_dot = max(mass_flow_rate[i]) - - # Add up the length of the segment - tot_length = nansum(pipe_string) - - # Assume each full run has 2 risers - risers = 2 - risers = ( - len(pipe_string) + 1 - ) # Wasnt sure on this - is it 1 per turbine + 1 for the storage? - Jamie - - # get specs and costs for each section - section_outputs = run_pipe_analysis( - tot_length, m_dot, p_inlet, p_outlet, depth, risers=risers - ) - - capex += section_outputs["total capital cost [$]"][0] - opex += section_outputs["annual operating cost [$]"][0] - - return capex, opex - - -if __name__ == "__main__": - sections_distance = [ - [2.85105454, 2.016, 2.016, 2.016, 2.016, 2.016, 2.016, 2.016], - [2.016, 2.016, 2.016, 2.016, 2.016, 2.016, 2.016, 2.016], - [ - 2.85105454, - 2.016, - 2.016, - 2.016, - float("nan"), - float("nan"), - float("nan"), - float("nan"), - ], - ] - - L = 8 # Length [km] - m_dot = 1.5 # Mass flow rate [kg/s] assuming 300 MW -> 1.5 kg/s - p_inlet = 30 # Inlet pressure [bar] - p_outlet = 10 # Outlet pressure [bar] - depth = 80 # depth of pipe [m] - - # capex, opex = run_pipe_array( - # [[L, L], [L, L]], depth, p_inlet, p_outlet, [[m_dot, m_dot], [m_dot, m_dot]] - # ) - - # print("CAPEX (USD): ", capex) - # print("OPEX (USD): ", opex) - - capex, opex = run_pipe_array_const_diam( - [[L, L], [L, L]], depth, p_inlet, p_outlet, [[m_dot, m_dot], [m_dot, m_dot]] - ) - - print("CAPEX (USD): ", capex) - print("OPEX (USD): ", opex) diff --git a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_export_pipeline.py b/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_export_pipeline.py deleted file mode 100644 index ab8b9f5d7..000000000 --- a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_export_pipeline.py +++ /dev/null @@ -1,130 +0,0 @@ -from h2integrate.storage.hydrogen.h2_transport.h2_export_pipe import run_pipe_analysis - - -# test that we the results we got when the code was recieved -class TestExportPipeline: - L = 8 # Length [km] - m_dot = 1.5 # Mass flow rate [kg/s] assuming 300 MW -> 1.5 kg/s - p_inlet = 30 # Inlet pressure [bar] - p_outlet = 10 # Outlet pressure [bar] - depth = 80 # depth of pipe [m] - costs = run_pipe_analysis(L, m_dot, p_inlet, p_outlet, depth) - - def test_grade(self): - assert self.costs["Grade"][0] == "X42" - - def test_od(self): - assert self.costs["Outer diameter (mm)"][0] == 168.28 - - def test_id(self): - assert self.costs["Inner diameter (mm)"][0] == 162.74 - - def test_schedule(self): - assert self.costs["Schedule"][0] == "S 5S" - - def test_thickness(self): - assert self.costs["Thickness (mm)"][0] == 2.77 - - def test_volume(self): - assert self.costs["volume [m3]"][0] == 12.213769866246679 - - def test_weight(self): - assert self.costs["weight [kg]"][0] == 95755.95575137396 - - def test_material_cost(self): - assert self.costs["mat cost [$]"][0] == 210663.1026530227 - - def test_labor_cost(self): - assert self.costs["labor cost [$]"][0] == 1199293.4563603932 - - def test_misc_cost(self): - assert self.costs["misc cost [$]"][0] == 429838.3943856504 - - def test_row_cost(self): # ROW = right of way - assert self.costs["ROW cost [$]"][0] == 365317.5476681454 - - def test_total_cost_output(self): - assert self.costs["total capital cost [$]"][0] == 2205112.501067212 - - def test_total_capital_cost_sum(self): - total_capital_cost = ( - self.costs["mat cost [$]"][0] - + self.costs["labor cost [$]"][0] - + self.costs["misc cost [$]"][0] - + self.costs["ROW cost [$]"][0] - ) - - assert self.costs["total capital cost [$]"][0] == total_capital_cost - - def test_annual_opex(self): - assert ( - self.costs["annual operating cost [$]"][0] - == 0.0117 * self.costs["total capital cost [$]"][0] - ) - - -class TestExportPipelineRegion: - L = 8 # Length [km] - m_dot = 1.5 # Mass flow rate [kg/s] assuming 300 MW -> 1.5 kg/s - p_inlet = 30 # Inlet pressure [bar] - p_outlet = 10 # Outlet pressure [bar] - depth = 80 # depth of pipe [m] - region = "GP" # great plains region - costs = run_pipe_analysis(L, m_dot, p_inlet, p_outlet, depth, region=region) - - def test_material_cost(self): - assert self.costs["mat cost [$]"][0] == 210663.1026530227 - - def test_labor_cost(self): - assert self.costs["labor cost [$]"][0] == 408438.062500015 - - def test_misc_cost(self): - assert self.costs["misc cost [$]"][0] == 184458.92890187018 - - def test_row_cost(self): # ROW = right of way - assert self.costs["ROW cost [$]"][0] == 52426.57591258784 - - def test_total_cost_output(self): - assert self.costs["total capital cost [$]"][0] == 855986.6699674957 - - -class TestExportPipelineOverrides: - L = 8 # Length [km] - m_dot = 1.5 # Mass flow rate [kg/s] assuming 300 MW -> 1.5 kg/s - p_inlet = 30 # Inlet pressure [bar] - p_outlet = 10 # Outlet pressure [bar] - depth = 80 # depth of pipe [m] - labor_in_mi = 1000 - misc_in_mi = 2000 - row_in_mi = 3000 - mat_in_mi = 4000 - costs = run_pipe_analysis( - L, - m_dot, - p_inlet, - p_outlet, - depth, - labor_in_mi=labor_in_mi, - misc_in_mi=misc_in_mi, - row_in_mi=row_in_mi, - mat_in_mi=mat_in_mi, - ) - - def test_material_cost(self): - assert self.costs["mat cost [$]"][0] == 124469.9746153248 - - def test_labor_cost(self): - assert self.costs["labor cost [$]"][0] == 31117.4936538312 - - def test_misc_cost(self): - assert self.costs["misc cost [$]"][0] == 62234.9873076624 - - def test_row_cost(self): # ROW = right of way - assert self.costs["ROW cost [$]"][0] == 93352.4809614936 - - def test_total_cost_output(self): - assert self.costs["total capital cost [$]"][0] == 311174.936538312 - - -if __name__ == "__main__": - test_set = TestExportPipeline() diff --git a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_pipe_array.py b/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_pipe_array.py deleted file mode 100644 index ae8b96619..000000000 --- a/tests/h2integrate/test_hydrogen/test_h2_transport/test_h2_pipe_array.py +++ /dev/null @@ -1,63 +0,0 @@ -from h2integrate.storage.hydrogen.h2_transport.h2_pipe_array import ( - run_pipe_array, - run_pipe_array_const_diam, -) - - -# test that we the results we got when the code was recieved -class TestPipeArraySingleSection: - L = 8 # Length [km] - m_dot = 1.5 # Mass flow rate [kg/s] assuming 300 MW -> 1.5 kg/s - p_inlet = 30 # Inlet pressure [bar] - p_outlet = 10 # Outlet pressure [bar] - depth = 80 # depth of pipe [m] - capex, opex = run_pipe_array([[L]], depth, p_inlet, p_outlet, [[m_dot]]) - - def test_capex(self): - assert self.capex == 2226256.16387454 - - def test_opex(self): - assert self.opex == 26047.197117332118 - - -# TODO check the values in these test, they are gut checked for being slightly above what would be expected for a single distance, but not well determined - - -class TestPipeArrayMultiSection: - L = 8 # Length [km] - m_dot = 1.5 # Mass flow rate [kg/s] assuming 300 MW -> 1.5 kg/s - p_inlet = 30 # Inlet pressure [bar] - p_outlet = 10 # Outlet pressure [bar] - depth = 80 # depth of pipe [m] - - def test_capex(self): - assert self.capex == 5129544.170342567 - - def test_opex(self): - assert self.opex == 60015.666793008044 - - capex, opex = run_pipe_array([[L, L]], depth, p_inlet, p_outlet, [[m_dot, m_dot]]) - - -class TestPipeArrayMultiSectionConstDiameter: - L = 8 # Length [km] - m_dot = 1.5 # Mass flow rate [kg/s] assuming 300 MW -> 1.5 kg/s - p_inlet = 30 # Inlet pressure [bar] - p_outlet = 10 # Outlet pressure [bar] - depth = 80 # depth of pipe [m] - - capex, opex = run_pipe_array_const_diam( - [[L, L], [L, L]], depth, p_inlet, p_outlet, [[m_dot, m_dot], [m_dot, m_dot]] - ) - - def test_capex(self): - assert self.capex == 10360272.637584394 - - def test_opex(self): - assert self.opex == 121215.18985973741 - - -if __name__ == "__main__": - test_set = TestPipeArraySingleSection() - test_set = TestPipeArrayMultiSection() - test_set = TestPipeArrayMultiSectionConstDiameter() From ecc0e555272e80e00488908504f36de1d8f79156 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Thu, 4 Dec 2025 17:09:48 -0700 Subject: [PATCH 26/30] Added back many more docstrings and comments for the hydrogen storage --- .../storage/hydrogen/h2_storage_cost.py | 257 ++++++++++++++---- 1 file changed, 209 insertions(+), 48 deletions(-) diff --git a/h2integrate/storage/hydrogen/h2_storage_cost.py b/h2integrate/storage/hydrogen/h2_storage_cost.py index 1828eb727..e5e17867c 100644 --- a/h2integrate/storage/hydrogen/h2_storage_cost.py +++ b/h2integrate/storage/hydrogen/h2_storage_cost.py @@ -146,29 +146,74 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): class LinedRockCavernStorageCostModel(HydrogenStorageBaseCostModel): - def initialize(self): - super().initialize() - - def setup(self): - super().setup() + """ + Author: Kaitlin Brunik + Created: 7/20/2023 + Institution: National Renewable Energy Lab + Description: This file outputs capital and operational costs of lined rock cavern + hydrogen storage. + It needs to be updated to with operational dynamics. + Costs are in 2018 USD + + Sources: + - [1] Papadias 2021: https://www.sciencedirect.com/science/article/pii/S0360319921030834?via%3Dihub + - [2] Papadias 2021: Bulk Hydrogen as Function of Capacity.docx documentation at + hydrogen_storage.md in the docs + - [3] HDSAM V4.0 Gaseous H2 Geologic Storage sheet + """ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): + """ + Calculates the installed capital cost and operation and maintenance costs for lined rock + cavern hydrogen storage. + + Args: + inputs: OpenMDAO inputs containing: + - max_capacity: total capacity of hydrogen storage [kg] + - max_charge_rate: hydrogen storage charge rate [kg/h] + + Returns via outputs: + - CapEx (float): the installed capital cost in 2018 [USD] (including compressor) + - OpEx (float): the OPEX (annual, fixed) in 2018 excluding electricity costs [USD/yr] + + Additional parameters from storage_input: + - h2_storage_kg (float): total capacity of hydrogen storage [kg] + - system_flow_rate (float): [kg/day] + - labor_rate (float): (default: 37.40) [$2018/hr] + - insurance (float): (default: 1%) [decimal percent] - % of total investment + - property_taxes (float): (default: 1%) [decimal percent] - % of total investment + - licensing_permits (float): (default: 0.1%) [decimal percent] - % of total investment + - compressor_om (float): (default: 4%) [decimal percent] - % of compressor investment + - facility_om (float): (default: 1%) [decimal percent] - % of facility investment + minus compressor investment + """ storage_input = self.make_storage_input_dict(inputs) - h2_storage_kg = storage_input["h2_storage_kg"] - system_flow_rate = storage_input["system_flow_rate"] - labor_rate = storage_input.get("labor_rate", 37.39817) - insurance = storage_input.get("insurance", 1 / 100) - property_taxes = storage_input.get("property_taxes", 1 / 100) - licensing_permits = storage_input.get("licensing_permits", 0.1 / 100) - comp_om = storage_input.get("compressor_om", 4 / 100) - facility_om = storage_input.get("facility_om", 1 / 100) - + # Extract input parameters + h2_storage_kg = storage_input["h2_storage_kg"] # [kg] + system_flow_rate = storage_input["system_flow_rate"] # [kg/day] + labor_rate = storage_input.get("labor_rate", 37.39817) # $(2018)/hr + insurance = storage_input.get("insurance", 1 / 100) # % of total capital investment + property_taxes = storage_input.get( + "property_taxes", 1 / 100 + ) # % of total capital investment + licensing_permits = storage_input.get( + "licensing_permits", 0.1 / 100 + ) # % of total capital investment + comp_om = storage_input.get("compressor_om", 4 / 100) # % of compressor capital investment + facility_om = storage_input.get( + "facility_om", 1 / 100 + ) # % of facility capital investment minus compressor capital investment + + # ============================================================================ # Calculate CAPEX + # ============================================================================ # Installed capital cost per kg from Papadias [2] + # Coefficients for lined rock cavern storage cost equation a = 0.095803 b = 1.5868 c = 10.332 + # Calculate installed capital cost per kg using exponential fit lined_rock_cavern_storage_capex_per_kg = np.exp( a * (np.log(h2_storage_kg / 1000)) ** 2 - b * np.log(h2_storage_kg / 1000) + c ) # 2019 [USD] from Papadias [2] @@ -176,14 +221,17 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): cepci_overall = 1.29 / 1.30 # Convert from $2019 to $2018 installed_capex = cepci_overall * installed_capex + # ============================================================================ # Calculate compressor costs - outlet_pressure = 200 # Max outlet pressure of lined rock cavern in [1] + # ============================================================================ + outlet_pressure = 200 # Max outlet pressure of lined rock cavern in [1] [bar] n_compressors = 2 storage_compressor = Compressor( outlet_pressure, system_flow_rate, n_compressors=n_compressors ) storage_compressor.compressor_power() motor_rating, power = storage_compressor.compressor_system_power() + # Check if motor rating exceeds maximum, add additional compressor if needed if motor_rating > 1600: n_compressors += 1 storage_compressor = Compressor( @@ -195,8 +243,12 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): cepci = 1.36 / 1.29 # convert from $2016 to $2018 comp_capex = comp_capex * cepci + # ============================================================================ # Calculate OPEX - # Labor - Base case is 1 operator, 24 hours a day, 7 days a week for a 100,000 kg/day + # ============================================================================ + # Operations and Maintenance costs [3] + # Labor + # Base case is 1 operator, 24 hours a day, 7 days a week for a 100,000 kg/day # average capacity facility. Scaling factor of 0.25 is used for other sized facilities annual_hours = 8760 * (system_flow_rate / 100000) ** 0.25 overhead = 0.5 @@ -222,29 +274,73 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): class SaltCavernStorageCostModel(HydrogenStorageBaseCostModel): - def initialize(self): - super().initialize() - - def setup(self): - super().setup() + """ + Author: Kaitlin Brunik + Created: 7/20/2023 + Institution: National Renewable Energy Lab + Description: This file outputs capital and operational costs of salt cavern hydrogen storage. + It needs to be updated to with operational dynamics. + Costs are in 2018 USD + + Sources: + - [1] Papadias 2021: https://www.sciencedirect.com/science/article/pii/S0360319921030834?via%3Dihub + - [2] Papadias 2021: Bulk Hydrogen as Function of Capacity.docx documentation at + hydrogen_storage.md in the docs + - [3] HDSAM V4.0 Gaseous H2 Geologic Storage sheet + """ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): + """ + Calculates the installed capital cost and operation and maintenance costs for salt cavern + hydrogen storage. + + Args: + inputs: OpenMDAO inputs containing: + - max_capacity: total capacity of hydrogen storage [kg] + - max_charge_rate: hydrogen storage charge rate [kg/h] + + Returns via outputs: + - CapEx (float): the installed capital cost in 2018 [USD] (including compressor) + - OpEx (float): the OPEX (annual, fixed) in 2018 excluding electricity costs [USD/yr] + + Additional parameters from storage_input: + - h2_storage_kg (float): total capacity of hydrogen storage [kg] + - system_flow_rate (float): [kg/day] + - labor_rate (float): (default: 37.40) [$2018/hr] + - insurance (float): (default: 1%) [decimal percent] - % of total investment + - property_taxes (float): (default: 1%) [decimal percent] - % of total investment + - licensing_permits (float): (default: 0.1%) [decimal percent] - % of total investment + - compressor_om (float): (default: 4%) [decimal percent] - % of compressor investment + - facility_om (float): (default: 1%) [decimal percent] - % of facility investment + minus compressor investment + """ storage_input = self.make_storage_input_dict(inputs) - h2_storage_kg = storage_input["h2_storage_kg"] - system_flow_rate = storage_input["system_flow_rate"] - labor_rate = storage_input.get("labor_rate", 37.39817) - insurance = storage_input.get("insurance", 1 / 100) - property_taxes = storage_input.get("property_taxes", 1 / 100) - licensing_permits = storage_input.get("licensing_permits", 0.1 / 100) - comp_om = storage_input.get("compressor_om", 4 / 100) - facility_om = storage_input.get("facility_om", 1 / 100) - + # Extract input parameters + h2_storage_kg = storage_input["h2_storage_kg"] # [kg] + system_flow_rate = storage_input["system_flow_rate"] # [kg/day] + labor_rate = storage_input.get("labor_rate", 37.39817) # $(2018)/hr + insurance = storage_input.get("insurance", 1 / 100) # % of total capital investment + property_taxes = storage_input.get( + "property_taxes", 1 / 100 + ) # % of total capital investment + licensing_permits = storage_input.get( + "licensing_permits", 0.1 / 100 + ) # % of total capital investment + comp_om = storage_input.get("compressor_om", 4 / 100) # % of compressor capital investment + facility_om = storage_input.get( + "facility_om", 1 / 100 + ) # % of facility capital investment minus compressor capital investment + + # ============================================================================ # Calculate CAPEX + # ============================================================================ # Installed capital cost per kg from Papadias [2] + # Coefficients for salt cavern storage cost equation a = 0.092548 b = 1.6432 c = 10.161 + # Calculate installed capital cost per kg using exponential fit salt_cavern_storage_capex_per_kg = np.exp( a * (np.log(h2_storage_kg / 1000)) ** 2 - b * np.log(h2_storage_kg / 1000) + c ) # 2019 [USD] from Papadias [2] @@ -252,14 +348,17 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): cepci_overall = 1.29 / 1.30 # Convert from $2019 to $2018 installed_capex = cepci_overall * installed_capex + # ============================================================================ # Calculate compressor costs - outlet_pressure = 120 # Max outlet pressure of salt cavern in [1] + # ============================================================================ + outlet_pressure = 120 # Max outlet pressure of salt cavern in [1] [bar] n_compressors = 2 storage_compressor = Compressor( outlet_pressure, system_flow_rate, n_compressors=n_compressors ) storage_compressor.compressor_power() motor_rating, power = storage_compressor.compressor_system_power() + # Check if motor rating exceeds maximum, add additional compressor if needed if motor_rating > 1600: n_compressors += 1 storage_compressor = Compressor( @@ -271,8 +370,12 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): cepci = 1.36 / 1.29 # convert from $2016 to $2018 comp_capex = comp_capex * cepci + # ============================================================================ # Calculate OPEX - # Labor - Base case is 1 operator, 24 hours a day, 7 days a week for a 100,000 kg/day + # ============================================================================ + # Operations and Maintenance costs [3] + # Labor + # Base case is 1 operator, 24 hours a day, 7 days a week for a 100,000 kg/day # average capacity facility. Scaling factor of 0.25 is used for other sized facilities annual_hours = 8760 * (system_flow_rate / 100000) ** 0.25 overhead = 0.5 @@ -298,32 +401,83 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): class PipeStorageCostModel(HydrogenStorageBaseCostModel): - def initialize(self): - super().initialize() - - def setup(self): - super().setup() + """ + Author: Kaitlin Brunik + Updated: 7/20/2023 + Institution: National Renewable Energy Lab + Description: This file outputs capital and operational costs of underground pipeline hydrogen + storage. It needs to be updated to with operational dynamics and physical size + (footprint and mass). + Oversize pipe: pipe OD = 24'' schedule 60 [1] + Max pressure: 100 bar + Costs are in 2018 USD + + Sources: + - [1] Papadias 2021: https://www.sciencedirect.com/science/article/pii/S0360319921030834?via%3Dihub + - [2] Papadias 2021: Bulk Hydrogen as Function of Capacity.docx documentation at + hydrogen_storage.md in the docs + - [3] HDSAM V4.0 Gaseous H2 Geologic Storage sheet + """ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): + """ + Calculates the installed capital cost and operation and maintenance costs for underground + pipe hydrogen storage. + + Args: + inputs: OpenMDAO inputs containing: + - max_capacity: total capacity of hydrogen storage [kg] + - max_charge_rate: hydrogen storage charge rate [kg/h] + + Returns via outputs: + - CapEx (float): the installed capital cost in 2018 [USD] (including compressor) + - OpEx (float): the OPEX (annual, fixed) in 2018 excluding electricity costs [USD/yr] + + Additional parameters from storage_input: + - h2_storage_kg (float): total capacity of hydrogen storage [kg] + - system_flow_rate (float): [kg/day] + - labor_rate (float): (default: 37.40) [$2018/hr] + - insurance (float): (default: 1%) [decimal percent] - % of total investment + - property_taxes (float): (default: 1%) [decimal percent] - % of total investment + - licensing_permits (float): (default: 0.1%) [decimal percent] - % of total investment + - compressor_om (float): (default: 4%) [decimal percent] - % of compressor investment + - facility_om (float): (default: 1%) [decimal percent] - % of facility investment + minus compressor investment + Notes: + - Oversize pipe: pipe OD = 24'' schedule 60 + - Max pressure: 100 bar + - compressor_output_pressure must be 100 bar for underground pipe storage + """ storage_input = self.make_storage_input_dict(inputs) - h2_storage_kg = storage_input["h2_storage_kg"] - system_flow_rate = storage_input["system_flow_rate"] - labor_rate = storage_input.get("labor_rate", 37.39817) - insurance = storage_input.get("insurance", 1 / 100) - property_taxes = storage_input.get("property_taxes", 1 / 100) - licensing_permits = storage_input.get("licensing_permits", 0.1 / 100) - comp_om = storage_input.get("compressor_om", 4 / 100) - facility_om = storage_input.get("facility_om", 1 / 100) + # Extract input parameters + h2_storage_kg = storage_input["h2_storage_kg"] # [kg] + system_flow_rate = storage_input["system_flow_rate"] # [kg/day] + labor_rate = storage_input.get("labor_rate", 37.39817) # $(2018)/hr + insurance = storage_input.get("insurance", 1 / 100) # % of total capital investment + property_taxes = storage_input.get( + "property_taxes", 1 / 100 + ) # % of total capital investment + licensing_permits = storage_input.get( + "licensing_permits", 0.1 / 100 + ) # % of total capital investment + comp_om = storage_input.get("compressor_om", 4 / 100) # % of compressor capital investment + facility_om = storage_input.get( + "facility_om", 1 / 100 + ) # % of facility capital investment minus compressor capital investment # compressor_output_pressure must be 100 bar for underground pipe storage - compressor_output_pressure = 100 + compressor_output_pressure = 100 # [bar] + # ============================================================================ # Calculate CAPEX + # ============================================================================ # Installed capital cost per kg from Papadias [2] + # Coefficients for underground pipe storage cost equation a = 0.0041617 b = 0.060369 c = 6.4581 + # Calculate installed capital cost per kg using exponential fit pipe_storage_capex_per_kg = np.exp( a * (np.log(h2_storage_kg / 1000)) ** 2 - b * np.log(h2_storage_kg / 1000) + c ) # 2019 [USD] from Papadias [2] @@ -331,9 +485,11 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): cepci_overall = 1.29 / 1.30 # Convert from $2019 to $2018 installed_capex = cepci_overall * installed_capex + # ============================================================================ # Calculate compressor costs + # ============================================================================ outlet_pressure = ( - compressor_output_pressure # Max outlet pressure of underground pipe storage [1] + compressor_output_pressure # Max outlet pressure of underground pipe storage [1] [bar] ) n_compressors = 2 storage_compressor = Compressor( @@ -341,6 +497,7 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): ) storage_compressor.compressor_power() motor_rating, power = storage_compressor.compressor_system_power() + # Check if motor rating exceeds maximum, add additional compressor if needed if motor_rating > 1600: n_compressors += 1 storage_compressor = Compressor( @@ -352,8 +509,12 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): cepci = 1.36 / 1.29 # convert from $2016 to $2018 comp_capex = comp_capex * cepci + # ============================================================================ # Calculate OPEX - # Labor - Base case is 1 operator, 24 hours a day, 7 days a week for a 100,000 kg/day + # ============================================================================ + # Operations and Maintenance costs [3] + # Labor + # Base case is 1 operator, 24 hours a day, 7 days a week for a 100,000 kg/day # average capacity facility. Scaling factor of 0.25 is used for other sized facilities annual_hours = 8760 * (system_flow_rate / 100000) ** 0.25 overhead = 0.5 From 04cef5a83e5c1497de3a69bec098f8c0aa748bf6 Mon Sep 17 00:00:00 2001 From: kbrunik Date: Fri, 5 Dec 2025 15:36:17 -0600 Subject: [PATCH 27/30] fix tests --- examples/05_wind_h2_opt/plant_config.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/05_wind_h2_opt/plant_config.yaml b/examples/05_wind_h2_opt/plant_config.yaml index fbdd77ebc..8242aaa3a 100644 --- a/examples/05_wind_h2_opt/plant_config.yaml +++ b/examples/05_wind_h2_opt/plant_config.yaml @@ -20,7 +20,6 @@ plant: # this will naturally grow as we mature the interconnected tech technology_interconnections: [ ["wind", "electrolyzer", "electricity", "cable"], - ["wind", "electrolyzer", ["electricity_out", "electricity_in"]], # etc ] resource_to_tech_connections: [ From febbf5bac16014ae2a69305496b085821b82f679 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Tue, 9 Dec 2025 14:53:25 -0700 Subject: [PATCH 28/30] Removing defunct code per PR review --- .../converters/hydrogen/basic_cost_model.py | 47 +++---------------- .../converters/hydrogen/pem_electrolyzer.py | 18 ------- 2 files changed, 7 insertions(+), 58 deletions(-) diff --git a/h2integrate/converters/hydrogen/basic_cost_model.py b/h2integrate/converters/hydrogen/basic_cost_model.py index 3c728d902..d91d793d2 100644 --- a/h2integrate/converters/hydrogen/basic_cost_model.py +++ b/h2integrate/converters/hydrogen/basic_cost_model.py @@ -49,13 +49,11 @@ def setup(self): def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # unpack inputs - plant_config = self.options["plant_config"] + self.options["plant_config"] electrolyzer_size_mw = float(inputs["electrolyzer_size_mw"][0]) - useful_life = plant_config["plant"]["plant_life"] electrical_generation_timeseries_kw = inputs["electricity_in"] electrolyzer_capex_kw = self.config.electrolyzer_capex - time_between_replacement = self.config.time_between_replacement # run hydrogen production cost model - from hopp examples if self.config.location == "onshore": @@ -86,7 +84,6 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): # Capital costs provide by Hydrogen Production Cost From PEM Electrolysis - 2019 (HFTO # Program Record) - stack_capital_cost = 342 # [$/kW] mechanical_bop_cost = 36 # [$/kW] for a compressor electrical_bop_cost = 82 # [$/kW] for a rectifier @@ -107,24 +104,11 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): permitting = 15 / 100 # [%] land = 250000 # [$] - stack_replacment_cost = 15 / 100 # [% of installed capital cost] - fixed_OM = 0.24 # [$/kg H2] - - program_record = False - - # Chose to use numbers provided by GPRA pathways - if program_record: - total_direct_electrolyzer_cost_kw = ( - (stack_capital_cost * (1 + stack_installation_factor)) - + mechanical_bop_cost - + (electrical_bop_cost * (1 + elec_installation_factor)) - ) - else: - total_direct_electrolyzer_cost_kw = ( - (electrolyzer_capex_kw * (1 + stack_installation_factor)) - + mechanical_bop_cost - + (electrical_bop_cost * (1 + elec_installation_factor)) - ) + total_direct_electrolyzer_cost_kw = ( + (electrolyzer_capex_kw * (1 + stack_installation_factor)) + + mechanical_bop_cost + + (electrical_bop_cost * (1 + elec_installation_factor)) + ) # Assign CapEx for electrolyzer from capacity based installed CapEx electrolyzer_total_installed_capex = ( @@ -155,28 +139,11 @@ def compute(self, inputs, outputs, discrete_inputs, discrete_outputs): property_tax_insurance = 1.5 / 100 # [% of Cap/y] variable_OM = 1.30 # [$/MWh] - # Amortized refurbishment expense [$/MWh] - include_refurb_in_opex = False - if not include_refurb_in_opex: - amortized_refurbish_cost = 0.0 - else: - amortized_refurbish_cost = ( - (total_direct_electrolyzer_cost_kw * stack_replacment_cost) - * max(((useful_life * 8760 * cap_factor) / time_between_replacement - 1), 0) - / useful_life - / 8760 - / cap_factor - * 1000 - ) - # Total O&M costs [% of installed cap/year] total_OM_costs = ( fixed_OM + (property_tax_insurance * total_direct_electrolyzer_cost_kw) ) / total_direct_electrolyzer_cost_kw + ( - (variable_OM + amortized_refurbish_cost) - / 1000 - * 8760 - * (cap_factor / total_direct_electrolyzer_cost_kw) + variable_OM / 1000 * 8760 * (cap_factor / total_direct_electrolyzer_cost_kw) ) electrolyzer_OM_cost = electrolyzer_total_installed_capex * total_OM_costs # Capacity based diff --git a/h2integrate/converters/hydrogen/pem_electrolyzer.py b/h2integrate/converters/hydrogen/pem_electrolyzer.py index e7533db7d..50b724282 100644 --- a/h2integrate/converters/hydrogen/pem_electrolyzer.py +++ b/h2integrate/converters/hydrogen/pem_electrolyzer.py @@ -87,24 +87,6 @@ def compute(self, inputs, outputs): electrolyzer_size_mw = inputs["n_clusters"][0] * self.config.cluster_rating_MW electrolyzer_capex_kw = self.config.electrolyzer_capex - # # IF GRID CONNECTED - # if plant_config["plant"]["grid_connection"]: - # # NOTE: if grid-connected, it assumes that hydrogen demand is input and there is not - # # multi-cluster control strategies. - # This capability exists at the cluster level, not at the - # # system level. - # if config["sizing"]["hydrogen_dmd"] is not None: - # grid_connection_scenario = "grid-only" - # hydrogen_production_capacity_required_kgphr = config[ - # "sizing" - # ]["hydrogen_dmd"] - # energy_to_electrolyzer_kw = [] - # else: - # grid_connection_scenario = "off-grid" - # hydrogen_production_capacity_required_kgphr = [] - # energy_to_electrolyzer_kw = np.ones(8760) * electrolyzer_size_mw * 1e3 - # # IF NOT GRID CONNECTED - # else: hydrogen_production_capacity_required_kgphr = [] grid_connection_scenario = "off-grid" energy_to_electrolyzer_kw = inputs["electricity_in"] From 2cbb07c959b8af673865426568467a72a129c730 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Wed, 10 Dec 2025 16:28:17 -0700 Subject: [PATCH 29/30] Minor refactoring based on PR feedback --- .../converters/hydrogen/pem_electrolyzer.py | 9 ++-- h2integrate/converters/steel/steel.py | 48 +++++++------------ 2 files changed, 20 insertions(+), 37 deletions(-) diff --git a/h2integrate/converters/hydrogen/pem_electrolyzer.py b/h2integrate/converters/hydrogen/pem_electrolyzer.py index 50b724282..f69d9475c 100644 --- a/h2integrate/converters/hydrogen/pem_electrolyzer.py +++ b/h2integrate/converters/hydrogen/pem_electrolyzer.py @@ -1,3 +1,5 @@ +import math + from attrs import field, define from h2integrate.core.utilities import BaseConfig, merge_shared_inputs @@ -6,10 +8,6 @@ from h2integrate.converters.hydrogen.electrolyzer_baseclass import ElectrolyzerPerformanceBaseClass -def ceildiv(a, b): - return -(a // -b) - - @define class ECOElectrolyzerPerformanceModelConfig(BaseConfig): """ @@ -91,10 +89,9 @@ def compute(self, inputs, outputs): grid_connection_scenario = "off-grid" energy_to_electrolyzer_kw = inputs["electricity_in"] - n_pem_clusters = int(ceildiv(electrolyzer_size_mw, self.config.cluster_rating_MW)) + n_pem_clusters = int(math.ceil(electrolyzer_size_mw / self.config.cluster_rating_MW)) electrolyzer_actual_capacity_MW = n_pem_clusters * self.config.cluster_rating_MW - ## run using greensteel model pem_param_dict = { "eol_eff_percent_loss": self.config.eol_eff_percent_loss, "uptime_hours_until_eol": self.config.uptime_hours_until_eol, diff --git a/h2integrate/converters/steel/steel.py b/h2integrate/converters/steel/steel.py index 36e27cb8e..782e9fda8 100644 --- a/h2integrate/converters/steel/steel.py +++ b/h2integrate/converters/steel/steel.py @@ -15,6 +15,23 @@ class SteelPerformanceModelConfig(BaseConfig): capacity_factor: float = field() +class SteelPerformanceModel(SteelPerformanceBaseClass): + """ + An OpenMDAO component for modeling the performance of an steel plant. + Computes annual steel production based on plant capacity and capacity factor. + """ + + def setup(self): + super().setup() + self.config = SteelPerformanceModelConfig.from_dict( + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + ) + + def compute(self, inputs, outputs): + steel_production_mtpy = self.config.plant_capacity_mtpy * self.config.capacity_factor + outputs["steel"] = steel_production_mtpy / len(inputs["electricity_in"]) + + @define class SteelCostAndFinancialModelConfig(BaseConfig): installation_time: int = field() @@ -54,43 +71,12 @@ class SteelCostAndFinancialModelConfig(BaseConfig): maintenance_materials_unitcost: float = field(default=7.72) -def run_steel_model(plant_capacity_mtpy: float, plant_capacity_factor: float) -> float: - """Calculate annual steel production.""" - return plant_capacity_mtpy * plant_capacity_factor - - -class SteelPerformanceModel(SteelPerformanceBaseClass): - """ - An OpenMDAO component for modeling the performance of an steel plant. - Computes annual steel production based on plant capacity and capacity factor. - """ - - def initialize(self): - super().initialize() - - def setup(self): - super().setup() - self.config = SteelPerformanceModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") - ) - - def compute(self, inputs, outputs): - steel_production_mtpy = run_steel_model( - self.config.plant_capacity_mtpy, - self.config.capacity_factor, - ) - outputs["steel"] = steel_production_mtpy / len(inputs["electricity_in"]) - - class SteelCostAndFinancialModel(SteelCostBaseClass): """ An OpenMDAO component for calculating the costs associated with steel production. Includes CapEx, OpEx, and byproduct credits. """ - def initialize(self): - super().initialize() - def setup(self): self.config = SteelCostAndFinancialModelConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") From 1e49cbfba6e1c0133aa8d0a3123628523d22f36f Mon Sep 17 00:00:00 2001 From: John Jasa Date: Wed, 10 Dec 2025 16:32:52 -0700 Subject: [PATCH 30/30] Added note on primary commodity electricity tech --- docs/user_guide/model_overview.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/user_guide/model_overview.md b/docs/user_guide/model_overview.md index 2f7b22e72..da1768ac0 100644 --- a/docs/user_guide/model_overview.md +++ b/docs/user_guide/model_overview.md @@ -53,6 +53,10 @@ The inputs, outputs, and corresponding technology that are currently available i | `desal` | water | electricity | | `natural_gas` | electricity | natural gas | +```{note} +When the Primary Commodity is electricity, those converters are considered electricity producing technologies and their electricity production is summed for financial calculations. +``` + (transport)= ## Transport `Transport` models are used to either: