From 08d387e54fe9dd060cf2bfb35953c7b363726fbf Mon Sep 17 00:00:00 2001 From: John Jasa Date: Tue, 19 May 2026 12:37:41 -0600 Subject: [PATCH 1/4] Working on just-in-time importing --- h2integrate/core/h2integrate_model.py | 70 ++-- h2integrate/core/supported_models.py | 512 +++++++++----------------- pyproject.toml | 1 + 3 files changed, 232 insertions(+), 351 deletions(-) diff --git a/h2integrate/core/h2integrate_model.py b/h2integrate/core/h2integrate_model.py index f5a0c6769..5b16fefa8 100644 --- a/h2integrate/core/h2integrate_model.py +++ b/h2integrate/core/h2integrate_model.py @@ -3,30 +3,18 @@ from enum import IntEnum import numpy as np -import networkx as nx -import openmdao.api as om -import matplotlib.pyplot as plt -from h2integrate.core.sites import SiteLocationComponent -from h2integrate.core.utilities import create_xdsm_from_config from h2integrate.core.dict_utils import check_inputs from h2integrate.core.file_utils import get_path, find_file, load_yaml -from h2integrate.finances.finances import AdjustedCapexOpexComp, AdjustedCapacityFactorComp from h2integrate.core.supported_models import ( no_cost_models, supported_models, no_replacement_schedule_models, ) -from h2integrate.core.inputs.validation import load_tech_yaml, load_plant_yaml, load_driver_yaml -from h2integrate.core.pose_optimization import PoseOptimization -from h2integrate.postprocess.sql_to_csv import convert_sql_to_csv_summary from h2integrate.core.commodity_stream_definitions import ( multivariable_streams, is_electricity_producer, ) -from h2integrate.control.control_strategies.pyomo_storage_controller_baseclass import ( - PyomoStorageControllerBaseClass, -) try: @@ -44,6 +32,8 @@ class State(IntEnum): class H2IntegrateModel: def __init__(self, config_input): + import openmdao.api as om + # read in config file; it's a yaml dict that looks like this: self.load_config(config_input) @@ -192,6 +182,12 @@ def load_config(self, config_input): self.system_summary = config.get("system_summary") # Load and validate each component configuration using the helper method + from h2integrate.core.inputs.validation import ( + load_tech_yaml, + load_plant_yaml, + load_driver_yaml, + ) + self.driver_config, self.driver_config_path, _ = self._load_component_config( "driver_config", config.get("driver_config"), config_path, load_driver_yaml ) @@ -212,17 +208,20 @@ def load_config(self, config_input): if "control_strategy" in vals: controller_model_name = vals["control_strategy"]["model"] controller_cls = supported_models.get(controller_model_name) - if controller_cls is not None and issubclass( - controller_cls, PyomoStorageControllerBaseClass - ): - model_inputs = self.technology_config["technologies"][name]["model_inputs"] - if ( - "control_parameters" not in model_inputs - or model_inputs["control_parameters"] is None - ): - model_inputs["control_parameters"] = {"tech_name": name} - else: - model_inputs["control_parameters"]["tech_name"] = name + if controller_cls is not None: + from h2integrate.control.control_strategies.pyomo_storage_controller_baseclass import ( # noqa: E501 + PyomoStorageControllerBaseClass, + ) + + if issubclass(controller_cls, PyomoStorageControllerBaseClass): + model_inputs = self.technology_config["technologies"][name]["model_inputs"] + if ( + "control_parameters" not in model_inputs + or model_inputs["control_parameters"] is None + ): + model_inputs["control_parameters"] = {"tech_name": name} + else: + model_inputs["control_parameters"]["tech_name"] = name def create_custom_models(self, model_config, config_parent_path, model_types, prefix=""): """This method loads custom models from the specified directory and adds them to the @@ -416,6 +415,10 @@ def create_site_group(self, plant_config_dict: dict, site_config: dict): Returns: om.Group: OpenMDAO group for a site """ + import openmdao.api as om + + from h2integrate.core.sites import SiteLocationComponent + # Initialize the site group site_group = om.Group() @@ -453,12 +456,16 @@ def create_plant_model(self): the same for each technology. This includes site information, project parameters, control strategy, and finance parameters. """ + import openmdao.api as om + plant_group = om.Group() # Create the plant model group and add components self.plant = self.model.add_subsystem("plant", plant_group, promotes=["*"]) def create_technology_models(self): + import openmdao.api as om + # Loop through each technology and instantiate an OpenMDAO object (assume it exists) # for each technology @@ -732,6 +739,10 @@ def create_finance_model(self): # attaches a ProFAST finance model component to the plant. """ + import openmdao.api as om + + from h2integrate.finances.finances import AdjustedCapexOpexComp, AdjustedCapacityFactorComp + # if there aren't any finance parameters don't setup a finance model if "finance_parameters" not in self.plant_config: return @@ -1386,6 +1397,9 @@ def connect_technologies(self): # Check if there are any loops in the technology interconnections # If loops are present, add solvers to resolve the coupling # Check if there are any cycles (loops) in the technology graph + import networkx as nx + import openmdao.api as om + if list(nx.simple_cycles(self.technology_graph)): # If cycles are found, set solvers for the plant to resolve the coupling self.plant.nonlinear_solver = om.NonlinearBlockGS() @@ -1420,6 +1434,8 @@ def create_driver_model(self): Add the driver to the OpenMDAO model and add recorder. """ + from h2integrate.core.pose_optimization import PoseOptimization + myopt = PoseOptimization(self.driver_config) if "driver" in self.driver_config: myopt.set_driver(self.prob) @@ -1479,12 +1495,16 @@ def post_process(self, print_results=True, summarize_sql=False, show_plots=False self.print_results(self.prob.model, excludes=["*resource_data"]) if summarize_sql and self.recorder_path is not None: + from h2integrate.postprocess.sql_to_csv import convert_sql_to_csv_summary + convert_sql_to_csv_summary(self.recorder_path, save_to_file=True) for model in self.performance_models: if hasattr(model, "post_process") and callable(model.post_process): model.post_process(show_plots=show_plots) if show_plots: + import matplotlib.pyplot as plt + plt.show() self.state = State.POST_PROCESS @@ -1672,6 +1692,8 @@ def create_xdsm(self, outfile="connections_xdsm"): technology_interconnections = self.plant_config.get("technology_interconnections", []) if len(technology_interconnections) > 0: + from h2integrate.core.utilities import create_xdsm_from_config + create_xdsm_from_config(self.plant_config, output_file=outfile) else: raise ValueError( @@ -1690,6 +1712,8 @@ def create_technology_graph(self): self.technology_graph (nx.DiGraph): A directed graph with technologies as nodes and interconnections as edges. """ + import networkx as nx + self.technology_graph = nx.DiGraph() for connection in self.plant_config.get("technology_interconnections", {}): diff --git a/h2integrate/core/supported_models.py b/h2integrate/core/supported_models.py index b2abecd78..98b48c1ae 100644 --- a/h2integrate/core/supported_models.py +++ b/h2integrate/core/supported_models.py @@ -1,333 +1,189 @@ -from h2integrate.resource.river import RiverResource -from h2integrate.resource.tidal import TidalResource -from h2integrate.core.feedstocks import FeedstockCostModel, FeedstockPerformanceModel -from h2integrate.transporters.pipe import PipePerformanceModel -from h2integrate.transporters.cable import CablePerformanceModel -from h2integrate.converters.grid.grid import GridCostModel, GridPerformanceModel -from h2integrate.finances.profast_lco import ProFastLCO -from h2integrate.finances.profast_npv import ProFastNPV -from h2integrate.demand.generic_demand import GenericDemandComponent -from h2integrate.converters.steel.steel import SteelPerformanceModel, SteelCostAndFinancialModel -from h2integrate.converters.wind.floris import FlorisWindPlantPerformanceModel -from h2integrate.demand.flexible_demand import FlexibleDemandComponent -from h2integrate.converters.wind.wind_pysam import PYSAMWindPlantPerformanceModel -from h2integrate.transporters.generic_summer import GenericSummerPerformanceModel -from h2integrate.converters.hopp.hopp_wrapper import HOPPComponent -from h2integrate.converters.solar.solar_pysam import PYSAMSolarPlantPerformanceModel -from h2integrate.finances.numpy_financial_npv import NumpyFinancialNPV -from h2integrate.resource.wind.openmeteo_wind import OpenMeteoHistoricalWindResource -from h2integrate.storage.generic_storage_cost import GenericStorageCostModel -from h2integrate.storage.hydrogen.mch_storage import MCHTOLStorageCostModel -from h2integrate.converters.steel.cmu_eaf_cost import CMUElectricArcFurnaceCostModel -from h2integrate.converters.wind.atb_wind_cost import ATBWindPlantCostModel -from h2integrate.storage.battery.pysam_battery import PySAMBatteryPerformanceModel -from h2integrate.transporters.generic_combiner import GenericCombinerPerformanceModel -from h2integrate.transporters.generic_splitter import GenericSplitterPerformanceModel -from h2integrate.converters.iron.iron_dri_plant import ( - HydrogenIronReductionPlantCostComponent, - NaturalGasIronReductionPlantCostComponent, - HydrogenIronReductionPlantPerformanceComponent, - NaturalGasIronReductionPlantPerformanceComponent, -) -from h2integrate.converters.iron.iron_transport import ( - IronTransportCostComponent, - IronTransportPerformanceComponent, -) -from h2integrate.converters.nitrogen.simple_ASU import SimpleASUCostModel, SimpleASUPerformanceModel -from h2integrate.converters.wind.wind_plant_ard import ArdWindPlantModel -from h2integrate.resource.solar.openmeteo_solar import OpenMeteoHistoricalSolarResource -from h2integrate.converters.hydrogen.h2_fuel_cell import ( - H2FuelCellCostModel, - LinearH2FuelCellPerformanceModel, -) -from h2integrate.converters.hydrogen.wombat_model import WOMBATElectrolyzerModel -from h2integrate.converters.nuclear.nuclear_plant import ( - QuinnNuclearCostModel, - QuinnNuclearPerformanceModel, -) -from h2integrate.converters.steel.steel_eaf_plant import ( - HydrogenEAFPlantCostComponent, - NaturalGasEAFPlantCostComponent, - HydrogenEAFPlantPerformanceComponent, - NaturalGasEAFPlantPerformanceComponent, -) -from h2integrate.storage.battery.atb_battery_cost import ATBBatteryCostModel -from h2integrate.storage.hydrogen.h2_storage_cost import ( - PipeStorageCostModel, - SaltCavernStorageCostModel, - CompressedGasStorageCostModel, - LinedRockCavernStorageCostModel, -) -from h2integrate.transporters.gas_stream_combiner import GasStreamCombinerPerformanceModel -from h2integrate.transporters.generic_transporter import GenericTransporterPerformanceModel -from h2integrate.converters.generic_converter_cost import GenericConverterCostModel -from h2integrate.converters.iron.humbert_ewin_perf import HumbertEwinPerformanceComponent -from h2integrate.storage.storage_performance_model import StoragePerformanceModel -from h2integrate.converters.ammonia.ammonia_synloop import ( - AmmoniaSynLoopCostModel, - AmmoniaSynLoopPerformanceModel, -) -from h2integrate.converters.water_power.tidal_pysam import PySAMTidalPerformanceModel -from h2integrate.storage.simple_storage_auto_sizing import StorageAutoSizingModel -from h2integrate.converters.water.desal.desalination import ( - ReverseOsmosisCostModel, - ReverseOsmosisPerformanceModel, -) -from h2integrate.resource.wind.nlr_developer_wtk_api import WTKNLRDeveloperAPIWindResource -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.converters.iron.martin_mine_cost_model import MartinIronMineCostComponent -from h2integrate.converters.iron.martin_mine_perf_model import MartinIronMinePerformanceComponent -from h2integrate.converters.methanol.smr_methanol_plant import ( - SMRMethanolPlantCostModel, - SMRMethanolPlantFinanceModel, - SMRMethanolPlantPerformanceModel, -) -from h2integrate.converters.ammonia.simple_ammonia_model import ( - SimpleAmmoniaCostModel, - SimpleAmmoniaPerformanceModel, -) -from h2integrate.converters.iron.humbert_stinn_ewin_cost import HumbertStinnEwinCostComponent -from h2integrate.converters.methanol.co2h_methanol_plant import ( - CO2HMethanolPlantCostModel, - CO2HMethanolPlantFinanceModel, - CO2HMethanolPlantPerformanceModel, -) -from h2integrate.converters.natural_gas.natural_gas_cc_ct import ( - NaturalGasCostModel, - NaturalGasPerformanceModel, -) -from h2integrate.converters.water_power.pysam_marine_cost import PySAMMarineCostModel -from h2integrate.converters.hydrogen.singlitico_cost_model import SingliticoCostModel -from h2integrate.converters.co2.marine.direct_ocean_capture import DOCCostModel, DOCPerformanceModel -from h2integrate.converters.hydrogen.steam_methane_reformer import ( - SteamMethaneReformerCostModel, - SteamMethaneReformerPerformanceModel, -) -from h2integrate.converters.natural_gas.dummy_gas_components import ( - SimpleGasConsumerCost, - SimpleGasProducerCost, - SimpleGasConsumerPerformance, - SimpleGasProducerPerformance, -) -from h2integrate.converters.hydrogen.geologic.mathur_modified import GeoH2SubsurfaceCostModel -from h2integrate.resource.solar.nlr_developer_goes_api_models import ( - GOESTMYSolarAPI, - GOESConusSolarAPI, - GOESFullDiscSolarAPI, - GOESAggregatedSolarAPI, -) -from h2integrate.converters.steel.cmu_electric_arc_furnace_dri import ( - CMUElectricArcFurnaceDRIPerformanceComponent, -) -from h2integrate.converters.steel.cmu_electric_arc_furnace_scrap import ( - CMUElectricArcFurnaceScrapOnlyPerformanceComponent, -) -from h2integrate.converters.water_power.hydro_plant_run_of_river import ( - RunOfRiverHydroCostModel, - RunOfRiverHydroPerformanceModel, -) -from h2integrate.resource.solar.nlr_developer_himawari_api_models import ( - Himawari7SolarAPI, - Himawari8SolarAPI, - HimawariTMYSolarAPI, -) -from h2integrate.converters.hydrogen.geologic.simple_natural_geoh2 import ( - NaturalGeoH2PerformanceModel, -) -from h2integrate.control.control_rules.converters.generic_converter import ( - PyomoDispatchGenericConverter, -) -from h2integrate.converters.co2.marine.ocean_alkalinity_enhancement import ( - OAECostModel, - OAEPerformanceModel, - OAECostAndFinancialModel, -) -from h2integrate.converters.hydrogen.custom_electrolyzer_cost_model import ( - CustomElectrolyzerCostModel, -) -from h2integrate.converters.hydrogen.geologic.aspen_surface_processing import ( - AspenGeoH2SurfaceCostModel, - AspenGeoH2SurfacePerformanceModel, -) -from h2integrate.converters.hydrogen.geologic.templeton_serpentinization import ( - StimulatedGeoH2PerformanceModel, -) -from h2integrate.control.control_rules.storage.pyomo_storage_rule_baseclass import ( - PyomoRuleStorageBaseclass, -) -from h2integrate.resource.solar.nlr_developer_meteosat_prime_meridian_models import ( - MeteosatPrimeMeridianSolarAPI, - MeteosatPrimeMeridianTMYSolarAPI, -) -from h2integrate.control.control_strategies.storage.heuristic_pyomo_controller import ( - HeuristicLoadFollowingStorageController, -) -from h2integrate.control.control_strategies.storage.optimized_pyomo_controller import ( - OptimizedDispatchStorageController, -) -from h2integrate.control.control_strategies.storage.simple_openloop_controller import ( - SimpleStorageOpenLoopController, -) -from h2integrate.control.control_strategies.storage.plm_openloop_storage_controller import ( - PeakLoadManagementHeuristicOpenLoopStorageController, -) -from h2integrate.control.control_rules.storage.pyomo_storage_rule_min_operating_cost import ( - PyomoRuleStorageMinOperatingCosts, -) -from h2integrate.control.control_strategies.storage.plm_optimized_storage_controller import ( - PeakLoadManagementOptimizedStorageController, -) -from h2integrate.control.control_rules.converters.generic_converter_min_operating_cost import ( - PyomoDispatchGenericConverterMinOperatingCosts, -) -from h2integrate.control.control_strategies.storage.demand_openloop_storage_controller import ( - DemandOpenLoopStorageController, -) +import importlib -supported_models = { - # Resources - "TidalResource": TidalResource, - "RiverResource": RiverResource, - "WTKNLRDeveloperAPIWindResource": WTKNLRDeveloperAPIWindResource, - "OpenMeteoHistoricalWindResource": OpenMeteoHistoricalWindResource, - "OpenMeteoHistoricalSolarResource": OpenMeteoHistoricalSolarResource, - "GOESAggregatedSolarAPI": GOESAggregatedSolarAPI, - "GOESConusSolarAPI": GOESConusSolarAPI, - "GOESFullDiscSolarAPI": GOESFullDiscSolarAPI, - "GOESTMYSolarAPI": GOESTMYSolarAPI, - "MeteosatPrimeMeridianSolarAPI": MeteosatPrimeMeridianSolarAPI, - "MeteosatPrimeMeridianTMYSolarAPI": MeteosatPrimeMeridianTMYSolarAPI, - "Himawari7SolarAPI": Himawari7SolarAPI, - "Himawari8SolarAPI": Himawari8SolarAPI, - "HimawariTMYSolarAPI": HimawariTMYSolarAPI, - # Converters - "GenericConverterCostModel": GenericConverterCostModel, - "ATBWindPlantCostModel": ATBWindPlantCostModel, - "PYSAMWindPlantPerformanceModel": PYSAMWindPlantPerformanceModel, - "FlorisWindPlantPerformanceModel": FlorisWindPlantPerformanceModel, - "ArdWindPlantModel": ArdWindPlantModel, - "PYSAMSolarPlantPerformanceModel": PYSAMSolarPlantPerformanceModel, - "ATBUtilityPVCostModel": ATBUtilityPVCostModel, - "ATBResComPVCostModel": ATBResComPVCostModel, - "PySAMTidalPerformanceModel": PySAMTidalPerformanceModel, - "PySAMMarineCostModel": PySAMMarineCostModel, - "RunOfRiverHydroPerformanceModel": RunOfRiverHydroPerformanceModel, - "RunOfRiverHydroCostModel": RunOfRiverHydroCostModel, - "ECOElectrolyzerPerformanceModel": ECOElectrolyzerPerformanceModel, - "SingliticoCostModel": SingliticoCostModel, - "BasicElectrolyzerCostModel": BasicElectrolyzerCostModel, - "CustomElectrolyzerCostModel": CustomElectrolyzerCostModel, - "WOMBATElectrolyzerModel": WOMBATElectrolyzerModel, - "LinearH2FuelCellPerformanceModel": LinearH2FuelCellPerformanceModel, - "H2FuelCellCostModel": H2FuelCellCostModel, - "SteamMethaneReformerPerformanceModel": SteamMethaneReformerPerformanceModel, - "SteamMethaneReformerCostModel": SteamMethaneReformerCostModel, - "SimpleASUCostModel": SimpleASUCostModel, - "SimpleASUPerformanceModel": SimpleASUPerformanceModel, - "HOPPComponent": HOPPComponent, - "MartinIronMinePerformanceComponent": MartinIronMinePerformanceComponent, # standalone model - "MartinIronMineCostComponent": MartinIronMineCostComponent, # standalone model - "NaturalGasIronReductionPlantPerformanceComponent": NaturalGasIronReductionPlantPerformanceComponent, # noqa: E501 - "NaturalGasIronReductionPlantCostComponent": NaturalGasIronReductionPlantCostComponent, # standalone model # noqa: E501 - "HydrogenIronReductionPlantPerformanceComponent": HydrogenIronReductionPlantPerformanceComponent, # noqa: E501 - "HydrogenIronReductionPlantCostComponent": HydrogenIronReductionPlantCostComponent, # standalone model # noqa: E501 - "HumbertEwinPerformanceComponent": HumbertEwinPerformanceComponent, - "HumbertStinnEwinCostComponent": HumbertStinnEwinCostComponent, - "NaturalGasEAFPlantPerformanceComponent": NaturalGasEAFPlantPerformanceComponent, - "NaturalGasEAFPlantCostComponent": NaturalGasEAFPlantCostComponent, # standalone model - "HydrogenEAFPlantPerformanceComponent": HydrogenEAFPlantPerformanceComponent, - "HydrogenEAFPlantCostComponent": HydrogenEAFPlantCostComponent, # standalone model - "CMUElectricArcFurnaceScrapOnlyPerformanceComponent": ( - CMUElectricArcFurnaceScrapOnlyPerformanceComponent - ), - "CMUElectricArcFurnaceDRIPerformanceComponent": CMUElectricArcFurnaceDRIPerformanceComponent, - "CMUElectricArcFurnaceCostModel": CMUElectricArcFurnaceCostModel, - "ReverseOsmosisPerformanceModel": ReverseOsmosisPerformanceModel, - "ReverseOsmosisCostModel": ReverseOsmosisCostModel, - "SimpleAmmoniaPerformanceModel": SimpleAmmoniaPerformanceModel, - "SimpleAmmoniaCostModel": SimpleAmmoniaCostModel, - "AmmoniaSynLoopPerformanceModel": AmmoniaSynLoopPerformanceModel, - "AmmoniaSynLoopCostModel": AmmoniaSynLoopCostModel, - "SteelPerformanceModel": SteelPerformanceModel, - "SteelCostAndFinancialModel": SteelCostAndFinancialModel, - "SMRMethanolPlantPerformanceModel": SMRMethanolPlantPerformanceModel, - "SMRMethanolPlantCostModel": SMRMethanolPlantCostModel, - "SMRMethanolPlantFinanceModel": SMRMethanolPlantFinanceModel, - "CO2HMethanolPlantPerformanceModel": CO2HMethanolPlantPerformanceModel, - "CO2HMethanolPlantCostModel": CO2HMethanolPlantCostModel, - "CO2HMethanolPlantFinanceModel": CO2HMethanolPlantFinanceModel, - "DOCPerformanceModel": DOCPerformanceModel, - "DOCCostModel": DOCCostModel, - "OAEPerformanceModel": OAEPerformanceModel, - "OAECostModel": OAECostModel, - "OAECostAndFinancialModel": OAECostAndFinancialModel, - "NaturalGeoH2PerformanceModel": NaturalGeoH2PerformanceModel, - "StimulatedGeoH2PerformanceModel": StimulatedGeoH2PerformanceModel, - "GeoH2SubsurfaceCostModel": GeoH2SubsurfaceCostModel, - "AspenGeoH2SurfacePerformanceModel": AspenGeoH2SurfacePerformanceModel, - "AspenGeoH2SurfaceCostModel": AspenGeoH2SurfaceCostModel, - "NaturalGasPerformanceModel": NaturalGasPerformanceModel, - "QuinnNuclearPerformanceModel": QuinnNuclearPerformanceModel, - "QuinnNuclearCostModel": QuinnNuclearCostModel, - "NaturalGasCostModel": NaturalGasCostModel, - # Transport - "cable": CablePerformanceModel, - "pipe": PipePerformanceModel, - "GenericCombinerPerformanceModel": GenericCombinerPerformanceModel, - "GenericSplitterPerformanceModel": GenericSplitterPerformanceModel, - "GenericTransporterPerformanceModel": GenericTransporterPerformanceModel, - "IronTransportPerformanceComponent": IronTransportPerformanceComponent, - "IronTransportCostComponent": IronTransportCostComponent, - # Simple Summers - "GenericSummerPerformanceModel": GenericSummerPerformanceModel, - # Storage - "PySAMBatteryPerformanceModel": PySAMBatteryPerformanceModel, - "StoragePerformanceModel": StoragePerformanceModel, - "StorageAutoSizingModel": StorageAutoSizingModel, - "LinedRockCavernStorageCostModel": LinedRockCavernStorageCostModel, - "CompressedGasStorageCostModel": CompressedGasStorageCostModel, - "SaltCavernStorageCostModel": SaltCavernStorageCostModel, - "MCHTOLStorageCostModel": MCHTOLStorageCostModel, - "PipeStorageCostModel": PipeStorageCostModel, - "ATBBatteryCostModel": ATBBatteryCostModel, - "GenericStorageCostModel": GenericStorageCostModel, - # Control - "SimpleStorageOpenLoopController": SimpleStorageOpenLoopController, - "DemandOpenLoopStorageController": DemandOpenLoopStorageController, - "PeakLoadManagementHeuristicOpenLoopStorageController": ( - PeakLoadManagementHeuristicOpenLoopStorageController - ), - "PeakLoadManagementOptimizedStorageController": (PeakLoadManagementOptimizedStorageController), - "HeuristicLoadFollowingStorageController": HeuristicLoadFollowingStorageController, - "OptimizedDispatchStorageController": OptimizedDispatchStorageController, - "GenericDemandComponent": GenericDemandComponent, - "FlexibleDemandComponent": FlexibleDemandComponent, - # Dispatch - "PyomoDispatchGenericConverter": PyomoDispatchGenericConverter, - "PyomoRuleStorageBaseclass": PyomoRuleStorageBaseclass, - "PyomoRuleStorageMinOperatingCosts": PyomoRuleStorageMinOperatingCosts, - "PyomoDispatchGenericConverterMinOperatingCosts": PyomoDispatchGenericConverterMinOperatingCosts, # noqa: E501 - # Feedstock - "FeedstockPerformanceModel": FeedstockPerformanceModel, - "FeedstockCostModel": FeedstockCostModel, - # Grid - "GridPerformanceModel": GridPerformanceModel, - "GridCostModel": GridCostModel, - # Finance - "ProFastLCO": ProFastLCO, - "ProFastNPV": ProFastNPV, - "NumpyFinancialNPV": NumpyFinancialNPV, - # Dummy components for multivariable stream demonstrations - "SimpleGasProducerPerformance": SimpleGasProducerPerformance, - "SimpleGasProducerCost": SimpleGasProducerCost, - "SimpleGasConsumerPerformance": SimpleGasConsumerPerformance, - "SimpleGasConsumerCost": SimpleGasConsumerCost, - "GasStreamCombinerPerformanceModel": GasStreamCombinerPerformanceModel, -} +class _ModelRegistry(dict): + """A dict subclass that imports model classes on first access. + + Stores entries as ``"ClassName": "relative.module.path:ClassName"`` + strings, where the module path is relative to the ``h2integrate`` + package (the ``h2integrate.`` prefix is prepended automatically). + On first ``__getitem__``, ``get``, or iteration over values, the class + is imported, cached, and returned. This avoids importing every model + module (and their heavy transitive dependencies) at package-load time. + """ + + _PKG_PREFIX = "h2integrate." + + def _resolve(self, key): + value = super().__getitem__(key) + if isinstance(value, str): + mod_path, attr_name = value.rsplit(":", 1) + module = importlib.import_module(self._PKG_PREFIX + mod_path) + cls = getattr(module, attr_name) + super().__setitem__(key, cls) + return cls + return value + + def __getitem__(self, key): + return self._resolve(key) + + def get(self, key, default=None): + if key in self: + return self._resolve(key) + return default + + def values(self): + return [self[k] for k in super().keys()] + + def items(self): + return [(k, self[k]) for k in super().keys()] + + def copy(self): + """Return a new _ModelRegistry with the same (still-deferred) entries.""" + new = _ModelRegistry() + for k in super().keys(): + new[k] = super().__getitem__(k) + return new + + +supported_models = _ModelRegistry( + { + # Resources + "TidalResource": "resource.tidal:TidalResource", + "RiverResource": "resource.river:RiverResource", + "WTKNLRDeveloperAPIWindResource": "resource.wind.nlr_developer_wtk_api:WTKNLRDeveloperAPIWindResource", + "OpenMeteoHistoricalWindResource": "resource.wind.openmeteo_wind:OpenMeteoHistoricalWindResource", + "OpenMeteoHistoricalSolarResource": "resource.solar.openmeteo_solar:OpenMeteoHistoricalSolarResource", + "GOESAggregatedSolarAPI": "resource.solar.nlr_developer_goes_api_models:GOESAggregatedSolarAPI", + "GOESConusSolarAPI": "resource.solar.nlr_developer_goes_api_models:GOESConusSolarAPI", + "GOESFullDiscSolarAPI": "resource.solar.nlr_developer_goes_api_models:GOESFullDiscSolarAPI", + "GOESTMYSolarAPI": "resource.solar.nlr_developer_goes_api_models:GOESTMYSolarAPI", + "MeteosatPrimeMeridianSolarAPI": "resource.solar.nlr_developer_meteosat_prime_meridian_models:MeteosatPrimeMeridianSolarAPI", + "MeteosatPrimeMeridianTMYSolarAPI": "resource.solar.nlr_developer_meteosat_prime_meridian_models:MeteosatPrimeMeridianTMYSolarAPI", + "Himawari7SolarAPI": "resource.solar.nlr_developer_himawari_api_models:Himawari7SolarAPI", + "Himawari8SolarAPI": "resource.solar.nlr_developer_himawari_api_models:Himawari8SolarAPI", + "HimawariTMYSolarAPI": "resource.solar.nlr_developer_himawari_api_models:HimawariTMYSolarAPI", + # Converters + "GenericConverterCostModel": "converters.generic_converter_cost:GenericConverterCostModel", + "ATBWindPlantCostModel": "converters.wind.atb_wind_cost:ATBWindPlantCostModel", + "PYSAMWindPlantPerformanceModel": "converters.wind.wind_pysam:PYSAMWindPlantPerformanceModel", + "FlorisWindPlantPerformanceModel": "converters.wind.floris:FlorisWindPlantPerformanceModel", + "ArdWindPlantModel": "converters.wind.wind_plant_ard:ArdWindPlantModel", + "PYSAMSolarPlantPerformanceModel": "converters.solar.solar_pysam:PYSAMSolarPlantPerformanceModel", + "ATBUtilityPVCostModel": "converters.solar.atb_utility_pv_cost:ATBUtilityPVCostModel", + "ATBResComPVCostModel": "converters.solar.atb_res_com_pv_cost:ATBResComPVCostModel", + "PySAMTidalPerformanceModel": "converters.water_power.tidal_pysam:PySAMTidalPerformanceModel", + "PySAMMarineCostModel": "converters.water_power.pysam_marine_cost:PySAMMarineCostModel", + "RunOfRiverHydroPerformanceModel": "converters.water_power.hydro_plant_run_of_river:RunOfRiverHydroPerformanceModel", + "RunOfRiverHydroCostModel": "converters.water_power.hydro_plant_run_of_river:RunOfRiverHydroCostModel", + "ECOElectrolyzerPerformanceModel": "converters.hydrogen.pem_electrolyzer:ECOElectrolyzerPerformanceModel", + "SingliticoCostModel": "converters.hydrogen.singlitico_cost_model:SingliticoCostModel", + "BasicElectrolyzerCostModel": "converters.hydrogen.basic_cost_model:BasicElectrolyzerCostModel", + "CustomElectrolyzerCostModel": "converters.hydrogen.custom_electrolyzer_cost_model:CustomElectrolyzerCostModel", + "WOMBATElectrolyzerModel": "converters.hydrogen.wombat_model:WOMBATElectrolyzerModel", + "LinearH2FuelCellPerformanceModel": "converters.hydrogen.h2_fuel_cell:LinearH2FuelCellPerformanceModel", + "H2FuelCellCostModel": "converters.hydrogen.h2_fuel_cell:H2FuelCellCostModel", + "SteamMethaneReformerPerformanceModel": "converters.hydrogen.steam_methane_reformer:SteamMethaneReformerPerformanceModel", + "SteamMethaneReformerCostModel": "converters.hydrogen.steam_methane_reformer:SteamMethaneReformerCostModel", + "SimpleASUCostModel": "converters.nitrogen.simple_ASU:SimpleASUCostModel", + "SimpleASUPerformanceModel": "converters.nitrogen.simple_ASU:SimpleASUPerformanceModel", + "HOPPComponent": "converters.hopp.hopp_wrapper:HOPPComponent", + "MartinIronMinePerformanceComponent": "converters.iron.martin_mine_perf_model:MartinIronMinePerformanceComponent", + "MartinIronMineCostComponent": "converters.iron.martin_mine_cost_model:MartinIronMineCostComponent", + "NaturalGasIronReductionPlantPerformanceComponent": "converters.iron.iron_dri_plant:NaturalGasIronReductionPlantPerformanceComponent", + "NaturalGasIronReductionPlantCostComponent": "converters.iron.iron_dri_plant:NaturalGasIronReductionPlantCostComponent", + "HydrogenIronReductionPlantPerformanceComponent": "converters.iron.iron_dri_plant:HydrogenIronReductionPlantPerformanceComponent", + "HydrogenIronReductionPlantCostComponent": "converters.iron.iron_dri_plant:HydrogenIronReductionPlantCostComponent", + "HumbertEwinPerformanceComponent": "converters.iron.humbert_ewin_perf:HumbertEwinPerformanceComponent", + "HumbertStinnEwinCostComponent": "converters.iron.humbert_stinn_ewin_cost:HumbertStinnEwinCostComponent", + "NaturalGasEAFPlantPerformanceComponent": "converters.steel.steel_eaf_plant:NaturalGasEAFPlantPerformanceComponent", + "NaturalGasEAFPlantCostComponent": "converters.steel.steel_eaf_plant:NaturalGasEAFPlantCostComponent", + "HydrogenEAFPlantPerformanceComponent": "converters.steel.steel_eaf_plant:HydrogenEAFPlantPerformanceComponent", + "HydrogenEAFPlantCostComponent": "converters.steel.steel_eaf_plant:HydrogenEAFPlantCostComponent", + "CMUElectricArcFurnaceScrapOnlyPerformanceComponent": "converters.steel.cmu_electric_arc_furnace_scrap:CMUElectricArcFurnaceScrapOnlyPerformanceComponent", + "CMUElectricArcFurnaceDRIPerformanceComponent": "converters.steel.cmu_electric_arc_furnace_dri:CMUElectricArcFurnaceDRIPerformanceComponent", + "CMUElectricArcFurnaceCostModel": "converters.steel.cmu_eaf_cost:CMUElectricArcFurnaceCostModel", + "ReverseOsmosisPerformanceModel": "converters.water.desal.desalination:ReverseOsmosisPerformanceModel", + "ReverseOsmosisCostModel": "converters.water.desal.desalination:ReverseOsmosisCostModel", + "SimpleAmmoniaPerformanceModel": "converters.ammonia.simple_ammonia_model:SimpleAmmoniaPerformanceModel", + "SimpleAmmoniaCostModel": "converters.ammonia.simple_ammonia_model:SimpleAmmoniaCostModel", + "AmmoniaSynLoopPerformanceModel": "converters.ammonia.ammonia_synloop:AmmoniaSynLoopPerformanceModel", + "AmmoniaSynLoopCostModel": "converters.ammonia.ammonia_synloop:AmmoniaSynLoopCostModel", + "SteelPerformanceModel": "converters.steel.steel:SteelPerformanceModel", + "SteelCostAndFinancialModel": "converters.steel.steel:SteelCostAndFinancialModel", + "SMRMethanolPlantPerformanceModel": "converters.methanol.smr_methanol_plant:SMRMethanolPlantPerformanceModel", + "SMRMethanolPlantCostModel": "converters.methanol.smr_methanol_plant:SMRMethanolPlantCostModel", + "SMRMethanolPlantFinanceModel": "converters.methanol.smr_methanol_plant:SMRMethanolPlantFinanceModel", + "CO2HMethanolPlantPerformanceModel": "converters.methanol.co2h_methanol_plant:CO2HMethanolPlantPerformanceModel", + "CO2HMethanolPlantCostModel": "converters.methanol.co2h_methanol_plant:CO2HMethanolPlantCostModel", + "CO2HMethanolPlantFinanceModel": "converters.methanol.co2h_methanol_plant:CO2HMethanolPlantFinanceModel", + "DOCPerformanceModel": "converters.co2.marine.direct_ocean_capture:DOCPerformanceModel", + "DOCCostModel": "converters.co2.marine.direct_ocean_capture:DOCCostModel", + "OAEPerformanceModel": "converters.co2.marine.ocean_alkalinity_enhancement:OAEPerformanceModel", + "OAECostModel": "converters.co2.marine.ocean_alkalinity_enhancement:OAECostModel", + "OAECostAndFinancialModel": "converters.co2.marine.ocean_alkalinity_enhancement:OAECostAndFinancialModel", + "NaturalGeoH2PerformanceModel": "converters.hydrogen.geologic.simple_natural_geoh2:NaturalGeoH2PerformanceModel", + "StimulatedGeoH2PerformanceModel": "converters.hydrogen.geologic.templeton_serpentinization:StimulatedGeoH2PerformanceModel", + "GeoH2SubsurfaceCostModel": "converters.hydrogen.geologic.mathur_modified:GeoH2SubsurfaceCostModel", + "AspenGeoH2SurfacePerformanceModel": "converters.hydrogen.geologic.aspen_surface_processing:AspenGeoH2SurfacePerformanceModel", + "AspenGeoH2SurfaceCostModel": "converters.hydrogen.geologic.aspen_surface_processing:AspenGeoH2SurfaceCostModel", + "NaturalGasPerformanceModel": "converters.natural_gas.natural_gas_cc_ct:NaturalGasPerformanceModel", + "QuinnNuclearPerformanceModel": "converters.nuclear.nuclear_plant:QuinnNuclearPerformanceModel", + "QuinnNuclearCostModel": "converters.nuclear.nuclear_plant:QuinnNuclearCostModel", + "NaturalGasCostModel": "converters.natural_gas.natural_gas_cc_ct:NaturalGasCostModel", + # Transport + "cable": "transporters.cable:CablePerformanceModel", + "pipe": "transporters.pipe:PipePerformanceModel", + "GenericCombinerPerformanceModel": "transporters.generic_combiner:GenericCombinerPerformanceModel", + "GenericSplitterPerformanceModel": "transporters.generic_splitter:GenericSplitterPerformanceModel", + "GenericTransporterPerformanceModel": "transporters.generic_transporter:GenericTransporterPerformanceModel", + "IronTransportPerformanceComponent": "converters.iron.iron_transport:IronTransportPerformanceComponent", + "IronTransportCostComponent": "converters.iron.iron_transport:IronTransportCostComponent", + # Simple Summers + "GenericSummerPerformanceModel": "transporters.generic_summer:GenericSummerPerformanceModel", + # Storage + "PySAMBatteryPerformanceModel": "storage.battery.pysam_battery:PySAMBatteryPerformanceModel", + "StoragePerformanceModel": "storage.storage_performance_model:StoragePerformanceModel", + "StorageAutoSizingModel": "storage.simple_storage_auto_sizing:StorageAutoSizingModel", + "LinedRockCavernStorageCostModel": "storage.hydrogen.h2_storage_cost:LinedRockCavernStorageCostModel", + "CompressedGasStorageCostModel": "storage.hydrogen.h2_storage_cost:CompressedGasStorageCostModel", + "SaltCavernStorageCostModel": "storage.hydrogen.h2_storage_cost:SaltCavernStorageCostModel", + "MCHTOLStorageCostModel": "storage.hydrogen.mch_storage:MCHTOLStorageCostModel", + "PipeStorageCostModel": "storage.hydrogen.h2_storage_cost:PipeStorageCostModel", + "ATBBatteryCostModel": "storage.battery.atb_battery_cost:ATBBatteryCostModel", + "GenericStorageCostModel": "storage.generic_storage_cost:GenericStorageCostModel", + # Control + "SimpleStorageOpenLoopController": "control.control_strategies.storage.simple_openloop_controller:SimpleStorageOpenLoopController", + "DemandOpenLoopStorageController": "control.control_strategies.storage.demand_openloop_storage_controller:DemandOpenLoopStorageController", + "PeakLoadManagementHeuristicOpenLoopStorageController": "control.control_strategies.storage.plm_openloop_storage_controller:PeakLoadManagementHeuristicOpenLoopStorageController", + "PeakLoadManagementOptimizedStorageController": "control.control_strategies.storage.plm_optimized_storage_controller:PeakLoadManagementOptimizedStorageController", + "HeuristicLoadFollowingStorageController": "control.control_strategies.storage.heuristic_pyomo_controller:HeuristicLoadFollowingStorageController", + "OptimizedDispatchStorageController": "control.control_strategies.storage.optimized_pyomo_controller:OptimizedDispatchStorageController", + "GenericDemandComponent": "demand.generic_demand:GenericDemandComponent", + "FlexibleDemandComponent": "demand.flexible_demand:FlexibleDemandComponent", + # Dispatch + "PyomoDispatchGenericConverter": "control.control_rules.converters.generic_converter:PyomoDispatchGenericConverter", + "PyomoRuleStorageBaseclass": "control.control_rules.storage.pyomo_storage_rule_baseclass:PyomoRuleStorageBaseclass", + "PyomoRuleStorageMinOperatingCosts": "control.control_rules.storage.pyomo_storage_rule_min_operating_cost:PyomoRuleStorageMinOperatingCosts", + "PyomoDispatchGenericConverterMinOperatingCosts": "control.control_rules.converters.generic_converter_min_operating_cost:PyomoDispatchGenericConverterMinOperatingCosts", + # Feedstock + "FeedstockPerformanceModel": "core.feedstocks:FeedstockPerformanceModel", + "FeedstockCostModel": "core.feedstocks:FeedstockCostModel", + # Grid + "GridPerformanceModel": "converters.grid.grid:GridPerformanceModel", + "GridCostModel": "converters.grid.grid:GridCostModel", + # Finance + "ProFastLCO": "finances.profast_lco:ProFastLCO", + "ProFastNPV": "finances.profast_npv:ProFastNPV", + "NumpyFinancialNPV": "finances.numpy_financial_npv:NumpyFinancialNPV", + # Dummy components for multivariable stream demonstrations + "SimpleGasProducerPerformance": "converters.natural_gas.dummy_gas_components:SimpleGasProducerPerformance", + "SimpleGasProducerCost": "converters.natural_gas.dummy_gas_components:SimpleGasProducerCost", + "SimpleGasConsumerPerformance": "converters.natural_gas.dummy_gas_components:SimpleGasConsumerPerformance", + "SimpleGasConsumerCost": "converters.natural_gas.dummy_gas_components:SimpleGasConsumerCost", + "GasStreamCombinerPerformanceModel": "transporters.gas_stream_combiner:GasStreamCombinerPerformanceModel", + } +) # This next section is to demarcate specific models that belong to certain categories that are diff --git a/pyproject.toml b/pyproject.toml index 0a67eb712..b4484e055 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -243,6 +243,7 @@ ignore = [ [tool.ruff.lint.per-file-ignores] "*/__init__.py" = ["E501", "D104", "F401"] +"*/supported_models.py" = ["E501"] "test/*" = ["D100", "D101", "D102", "D103"] [tool.ruff.lint.pycodestyle] From 0e755b8c803ec0b120f31759b5444020c4e1c555 Mon Sep 17 00:00:00 2001 From: John Jasa Date: Tue, 19 May 2026 13:14:25 -0600 Subject: [PATCH 2/4] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30d4744c7..4234d60c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Adds `H2IntegrateModel`, `load_yaml`, `write_yaml`, and `write_readable_yaml` as package-level imports [PR 728](https://github.com/NatLabRockies/H2Integrate/pull/728). - Update N2 diagram for Pyomo heuristic control from static image to dynamic and interactive embedded diagram [PR 726](https://github.com/NatLabRockies/H2Integrate/pull/726) - Added ability to use timeseries for finance calculations [PR 725](https://github.com/NatLabRockies/H2Integrate/pull/725) +- Reduced import time for `h2integrate_model.py` by deferring imports of heavy dependencies until they are needed [PR 762](https://github.com/NatLabRockies/H2Integrate/pull/762) ## 0.8 [April 15, 2026] - Updated README and docs intro page with expanded H2I description, reorganized sections, and streamlined installation instructions [PR 677](https://github.com/NatLabRockies/H2Integrate/pull/677) From 0125e344f9b3f01da54c487d55d0d8e2d49899fd Mon Sep 17 00:00:00 2001 From: John Jasa Date: Thu, 21 May 2026 13:15:27 -0600 Subject: [PATCH 3/4] Updates based on PR feedback --- h2integrate/core/h2integrate_model.py | 8 ++------ h2integrate/core/test/test_supported_models.py | 14 -------------- 2 files changed, 2 insertions(+), 20 deletions(-) delete mode 100644 h2integrate/core/test/test_supported_models.py diff --git a/h2integrate/core/h2integrate_model.py b/h2integrate/core/h2integrate_model.py index 4485f2359..742a22ac3 100644 --- a/h2integrate/core/h2integrate_model.py +++ b/h2integrate/core/h2integrate_model.py @@ -3,8 +3,10 @@ from enum import IntEnum import numpy as np +import networkx as nx import openmdao.api as om +from h2integrate.core.utilities import create_xdsm_from_config from h2integrate.core.dict_utils import check_inputs from h2integrate.core.file_utils import get_path, find_file, load_yaml from h2integrate.core.supported_models import ( @@ -1391,8 +1393,6 @@ def connect_technologies(self): # Check if there are any loops in the technology interconnections # If loops are present, add solvers to resolve the coupling # Check if there are any cycles (loops) in the technology graph - import networkx as nx - if list(nx.simple_cycles(self.technology_graph)): # If cycles are found, set solvers for the plant to resolve the coupling self.plant.nonlinear_solver = om.NonlinearBlockGS() @@ -1685,8 +1685,6 @@ def create_xdsm(self, outfile="connections_xdsm"): technology_interconnections = self.plant_config.get("technology_interconnections", []) if len(technology_interconnections) > 0: - from h2integrate.core.utilities import create_xdsm_from_config - create_xdsm_from_config(self.plant_config, output_file=outfile) else: raise ValueError( @@ -1705,8 +1703,6 @@ def create_technology_graph(self): self.technology_graph (nx.DiGraph): A directed graph with technologies as nodes and interconnections as edges. """ - import networkx as nx - self.technology_graph = nx.DiGraph() for connection in self.plant_config.get("technology_interconnections", {}): diff --git a/h2integrate/core/test/test_supported_models.py b/h2integrate/core/test/test_supported_models.py deleted file mode 100644 index da94d49dd..000000000 --- a/h2integrate/core/test/test_supported_models.py +++ /dev/null @@ -1,14 +0,0 @@ -import pytest - -from h2integrate.core.supported_models import supported_models - - -@pytest.mark.unit -def test_dictionary_mapping(): - """Tests that the supported_models dictionary keys exactly match the model class name, - except for allowed transport models that simplify configuration readability. - """ - allowed_mismatch = ("cable", "pipe") - mismatches = {k for k, v in supported_models.items() if k != v.__name__} - mismatches = mismatches.difference(allowed_mismatch) - assert len(mismatches) == 0, f"Model dictionary keys don't match their class name: {mismatches}" From 7f7de109972ad460c354f8394cd7071f1eedf5ce Mon Sep 17 00:00:00 2001 From: John Jasa Date: Thu, 21 May 2026 13:46:28 -0600 Subject: [PATCH 4/4] Update h2integrate/core/supported_models.py Co-authored-by: Rob Hammond <13874373+RHammond2@users.noreply.github.com> --- h2integrate/core/supported_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/h2integrate/core/supported_models.py b/h2integrate/core/supported_models.py index aa9483775..7f8c9ea02 100644 --- a/h2integrate/core/supported_models.py +++ b/h2integrate/core/supported_models.py @@ -16,7 +16,7 @@ class _ModelRegistry(dict): _PKG_PREFIX = "h2integrate." def _resolve(self, key): - """Import and cache the class for *key*, returning the resolved class.""" + """Import and cache the class for :py:attr:`key`, returning the resolved class.""" value = super().__getitem__(key) if isinstance(value, str): mod_path, attr_name = value.rsplit(":", 1)