From 3301db6a0cb55a10c605c9ae48cf0c30edfb2b08 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 27 Jan 2026 08:45:57 -0800 Subject: [PATCH 1/4] add additional class name input to for enhanced logging --- h2integrate/core/utilities.py | 37 ++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/h2integrate/core/utilities.py b/h2integrate/core/utilities.py index 155e73a37..81688a1c3 100644 --- a/h2integrate/core/utilities.py +++ b/h2integrate/core/utilities.py @@ -122,17 +122,18 @@ class BaseConfig: """ @classmethod - def from_dict(cls, data: dict, strict=True): + def from_dict(cls, data: dict, strict=True, additional_cls_name: str | None = None): """Maps a data dictionary to an `attr`-defined class. - TODO: Add an error to ensure that either none or all the parameters are passed in - Args: data : dict The data dictionary to be mapped. strict: bool A flag enabling strict parameter processing, meaning that no extra parameters may be passed in or an AttributeError will be raised. + additional_cls_name (str | None): The name of the model class creating the configuration + data class. Provides an easier to diagnose error message for end users when + the class name is provided. Returns: cls The `attr`-defined class. @@ -142,10 +143,17 @@ def from_dict(cls, data: dict, strict=True): class_attr_names = [a.name for a in cls.__attrs_attrs__] extra_args = [d for d in data if d not in class_attr_names] if len(extra_args): - raise AttributeError( - f"The initialization for {cls.__name__} was given extraneous " - f"inputs: {extra_args}" - ) + if additional_cls_name is not None: + msg = ( + f"{additional_cls_name} setup failed as a result of {cls.__name__}" + f" receiving extraneous inputs: {extra_args}" + ) + else: + msg = ( + f"The initialization for {cls.__name__} was given extraneous " + f"inputs: {extra_args}" + ) + raise AttributeError(msg) kwargs = {a.name: data[a.name] for a in cls.__attrs_attrs__ if a.name in data and a.init} @@ -156,10 +164,17 @@ def from_dict(cls, data: dict, strict=True): undefined = sorted(set(required_inputs) - set(kwargs)) if undefined: - raise AttributeError( - f"The class definition for {cls.__name__} is missing the following inputs: " - f"{undefined}" - ) + if additional_cls_name is not None: + msg = ( + f"{additional_cls_name} setup failed as a result of {cls.__name__}" + f" missing the following inputs: {undefined}" + ) + else: + msg = ( + f"The class definition for {cls.__name__} is missing the following inputs: " + f"{undefined}" + ) + raise AttributeError(msg) return cls(**kwargs) def as_dict(self) -> dict: From a459c7e646386bd17f8705c3fea681c9c46e5f12 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 27 Jan 2026 09:03:54 -0800 Subject: [PATCH 2/4] update calling of all config classes to include calling clas name --- .../control_rules/pyomo_rule_baseclass.py | 3 ++- .../converters/demand_openloop_controller.py | 3 ++- .../flexible_demand_openloop_controller.py | 3 ++- .../passthrough_openloop_controller.py | 3 ++- .../control_strategies/pyomo_controllers.py | 3 ++- .../storage/demand_openloop_controller.py | 1 + .../converters/ammonia/ammonia_synloop.py | 6 ++++-- .../ammonia/simple_ammonia_model.py | 6 ++++-- .../co2/marine/direct_ocean_capture.py | 6 ++++-- .../marine/ocean_alkalinity_enhancement.py | 19 +++++++++++++----- h2integrate/converters/grid/grid.py | 6 ++++-- h2integrate/converters/hopp/hopp_wrapper.py | 1 + .../converters/hydrogen/basic_cost_model.py | 3 ++- .../custom_electrolyzer_cost_model.py | 3 ++- .../geologic/aspen_surface_processing.py | 6 ++++-- .../hydrogen/geologic/mathur_modified.py | 6 +++++- .../hydrogen/geologic/simple_natural_geoh2.py | 3 ++- .../geologic/templeton_serpentinization.py | 3 ++- .../converters/hydrogen/pem_electrolyzer.py | 1 + .../hydrogen/singlitico_cost_model.py | 3 ++- .../converters/hydrogen/wombat_model.py | 3 ++- h2integrate/converters/iron/iron_dri_base.py | 7 ++++++- h2integrate/converters/iron/iron_mine.py | 7 ++++++- h2integrate/converters/iron/iron_plant.py | 6 +++++- h2integrate/converters/iron/iron_transport.py | 2 ++ h2integrate/converters/iron/iron_wrapper.py | 1 + .../converters/iron/martin_mine_cost_model.py | 6 +++++- .../converters/iron/martin_mine_perf_model.py | 1 + .../methanol/co2h_methanol_plant.py | 9 ++++++--- .../converters/methanol/smr_methanol_plant.py | 9 ++++++--- .../natural_gas/natural_gas_cc_ct.py | 6 ++++-- h2integrate/converters/nitrogen/simple_ASU.py | 6 ++++-- .../converters/solar/atb_res_com_pv_cost.py | 3 ++- .../converters/solar/atb_utility_pv_cost.py | 3 ++- h2integrate/converters/solar/solar_pysam.py | 1 + .../converters/steel/electric_arc_furnance.py | 6 +++++- h2integrate/converters/steel/steel.py | 6 ++++-- .../converters/steel/steel_eaf_base.py | 7 ++++++- .../converters/water/desal/desalination.py | 6 ++++-- .../water_power/hydro_plant_run_of_river.py | 6 ++++-- h2integrate/converters/wind/atb_wind_cost.py | 3 ++- h2integrate/converters/wind/floris.py | 3 ++- h2integrate/converters/wind/wind_pysam.py | 8 ++++++-- h2integrate/core/feedstocks.py | 6 ++++-- h2integrate/core/sites.py | 5 ++++- h2integrate/finances/numpy_financial_npv.py | 5 ++++- h2integrate/finances/profast_base.py | 5 ++++- h2integrate/resource/river.py | 5 ++++- .../solar/nrel_developer_goes_api_models.py | 20 +++++++++++++++---- .../nrel_developer_himawari_api_models.py | 15 +++++++++++--- ...eveloper_meteosat_prime_meridian_models.py | 10 ++++++++-- h2integrate/resource/solar/openmeteo_solar.py | 5 ++++- .../resource/wind/nrel_developer_wtk_api.py | 5 ++++- h2integrate/resource/wind/openmeteo_wind.py | 5 ++++- .../storage/battery/atb_battery_cost.py | 4 +++- h2integrate/storage/battery/pysam_battery.py | 1 + h2integrate/storage/generic_storage_cost.py | 4 +++- .../storage/hydrogen/h2_storage_cost.py | 1 + h2integrate/storage/hydrogen/mch_storage.py | 1 + h2integrate/storage/simple_generic_storage.py | 1 + .../storage/simple_storage_auto_sizing.py | 1 + h2integrate/transporters/generic_combiner.py | 3 ++- h2integrate/transporters/generic_splitter.py | 3 ++- h2integrate/transporters/generic_summer.py | 3 ++- 64 files changed, 233 insertions(+), 78 deletions(-) diff --git a/h2integrate/control/control_rules/pyomo_rule_baseclass.py b/h2integrate/control/control_rules/pyomo_rule_baseclass.py index e34d6028d..4eed272e6 100644 --- a/h2integrate/control/control_rules/pyomo_rule_baseclass.py +++ b/h2integrate/control/control_rules/pyomo_rule_baseclass.py @@ -29,7 +29,8 @@ def initialize(self): def setup(self): self.config = PyomoRuleBaseConfig.from_dict( - self.options["tech_config"]["model_inputs"]["dispatch_rule_parameters"] + self.options["tech_config"]["model_inputs"]["dispatch_rule_parameters"], + additional_cls_name=self.__class__.__name__, ) self.add_discrete_output( diff --git a/h2integrate/control/control_strategies/converters/demand_openloop_controller.py b/h2integrate/control/control_strategies/converters/demand_openloop_controller.py index fc17ece85..b9125e7ec 100644 --- a/h2integrate/control/control_strategies/converters/demand_openloop_controller.py +++ b/h2integrate/control/control_strategies/converters/demand_openloop_controller.py @@ -33,7 +33,8 @@ def setup(self): ``tech_config``. """ self.config = DemandOpenLoopControlBaseConfig.from_dict( - self.options["tech_config"]["model_inputs"]["control_parameters"] + self.options["tech_config"]["model_inputs"]["control_parameters"], + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/control/control_strategies/converters/flexible_demand_openloop_controller.py b/h2integrate/control/control_strategies/converters/flexible_demand_openloop_controller.py index 9374ba4d6..398c89f85 100644 --- a/h2integrate/control/control_strategies/converters/flexible_demand_openloop_controller.py +++ b/h2integrate/control/control_strategies/converters/flexible_demand_openloop_controller.py @@ -59,7 +59,8 @@ def setup(self): flexible demand output profile, which will be populated in ``compute``. """ self.config = FlexibleDemandOpenLoopConverterControllerConfig.from_dict( - self.options["tech_config"]["model_inputs"]["control_parameters"] + self.options["tech_config"]["model_inputs"]["control_parameters"], + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/control/control_strategies/passthrough_openloop_controller.py b/h2integrate/control/control_strategies/passthrough_openloop_controller.py index b6be718d5..9f3261449 100644 --- a/h2integrate/control/control_strategies/passthrough_openloop_controller.py +++ b/h2integrate/control/control_strategies/passthrough_openloop_controller.py @@ -23,7 +23,8 @@ class PassThroughOpenLoopController(ControllerBaseClass): def setup(self): self.config = PassThroughOpenLoopControllerConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "control") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "control"), + additional_cls_name=self.__class__.__name__, ) self.add_input( diff --git a/h2integrate/control/control_strategies/pyomo_controllers.py b/h2integrate/control/control_strategies/pyomo_controllers.py index 70f4063b5..17c85e88c 100644 --- a/h2integrate/control/control_strategies/pyomo_controllers.py +++ b/h2integrate/control/control_strategies/pyomo_controllers.py @@ -697,7 +697,8 @@ class HeuristicLoadFollowingController(SimpleBatteryControllerHeuristic): def setup(self): """Initialize HeuristicLoadFollowingController.""" self.config = HeuristicLoadFollowingControllerConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "control") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "control"), + additional_cls_name=self.__class__.__name__, ) self.n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] diff --git a/h2integrate/control/control_strategies/storage/demand_openloop_controller.py b/h2integrate/control/control_strategies/storage/demand_openloop_controller.py index a1d0d59a0..64cac74f0 100644 --- a/h2integrate/control/control_strategies/storage/demand_openloop_controller.py +++ b/h2integrate/control/control_strategies/storage/demand_openloop_controller.py @@ -167,6 +167,7 @@ def setup(self): self.config = DemandOpenLoopStorageControllerConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "control"), strict=False, + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/ammonia/ammonia_synloop.py b/h2integrate/converters/ammonia/ammonia_synloop.py index b567f895d..a4d715156 100644 --- a/h2integrate/converters/ammonia/ammonia_synloop.py +++ b/h2integrate/converters/ammonia/ammonia_synloop.py @@ -142,7 +142,8 @@ class AmmoniaSynLoopPerformanceModel(ResizeablePerformanceModelBaseClass): def setup(self): n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] self.config = AmmoniaSynLoopPerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) super().setup() @@ -416,7 +417,8 @@ def setup(self): ) self.config = AmmoniaSynLoopCostConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/ammonia/simple_ammonia_model.py b/h2integrate/converters/ammonia/simple_ammonia_model.py index 934c04c05..713467202 100644 --- a/h2integrate/converters/ammonia/simple_ammonia_model.py +++ b/h2integrate/converters/ammonia/simple_ammonia_model.py @@ -35,7 +35,8 @@ def initialize(self): def setup(self): n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] self.config = AmmoniaPerformanceModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) self.add_input("hydrogen_in", val=0.0, shape=n_timesteps, units="kg/h") self.add_output("ammonia_out", val=0.0, shape=n_timesteps, units="kg/h") @@ -100,7 +101,8 @@ class SimpleAmmoniaCostModel(CostModelBaseClass): def setup(self): self.config = AmmoniaCostModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() # Inputs for cost model configuration diff --git a/h2integrate/converters/co2/marine/direct_ocean_capture.py b/h2integrate/converters/co2/marine/direct_ocean_capture.py index 4d4365157..5d3cf26ec 100644 --- a/h2integrate/converters/co2/marine/direct_ocean_capture.py +++ b/h2integrate/converters/co2/marine/direct_ocean_capture.py @@ -93,7 +93,8 @@ def initialize(self): def setup(self): self.config = DOCPerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) super().setup() self.add_output( @@ -167,7 +168,8 @@ def initialize(self): def setup(self): self.config = DOCCostModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py b/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py index e9b343dfb..729d18917 100644 --- a/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py +++ b/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py @@ -81,7 +81,8 @@ def initialize(self): def setup(self): self.config = OAEPerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) super().setup() n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] @@ -265,10 +266,14 @@ def initialize(self): def setup(self): if "cost" in self.options["tech_config"]["model_inputs"]: self.config = OAECostModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) else: - self.config = OAECostModelConfig.from_dict(data={}) + self.config = OAECostModelConfig.from_dict( + data={}, + additional_cls_name=self.__class__.__name__, + ) super().setup() self.add_input( "mass_sellable_product", @@ -351,10 +356,14 @@ def initialize(self): def setup(self): if "cost" in self.options["tech_config"]["model_inputs"]: self.config = OAECostModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) else: - self.config = OAECostModelConfig.from_dict(data={}) + self.config = OAECostModelConfig.from_dict( + data={}, + additional_cls_name=self.__class__.__name__, + ) super().setup() n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] self.add_input( diff --git a/h2integrate/converters/grid/grid.py b/h2integrate/converters/grid/grid.py index 959abd8f2..261fe1a28 100644 --- a/h2integrate/converters/grid/grid.py +++ b/h2integrate/converters/grid/grid.py @@ -49,7 +49,8 @@ def initialize(self): def setup(self): self.config = GridPerformanceModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] @@ -169,7 +170,8 @@ class GridCostModel(CostModelBaseClass): def setup(self): self.config = GridCostModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/hopp/hopp_wrapper.py b/h2integrate/converters/hopp/hopp_wrapper.py index 2732ca594..f27b493ba 100644 --- a/h2integrate/converters/hopp/hopp_wrapper.py +++ b/h2integrate/converters/hopp/hopp_wrapper.py @@ -30,6 +30,7 @@ def setup(self): self.config = HOPPComponentModelConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=False, + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/hydrogen/basic_cost_model.py b/h2integrate/converters/hydrogen/basic_cost_model.py index f2b61f624..574993abb 100644 --- a/h2integrate/converters/hydrogen/basic_cost_model.py +++ b/h2integrate/converters/hydrogen/basic_cost_model.py @@ -37,7 +37,8 @@ class BasicElectrolyzerCostModel(ElectrolyzerCostBaseClass): def setup(self): self.config = BasicElectrolyzerCostModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/hydrogen/custom_electrolyzer_cost_model.py b/h2integrate/converters/hydrogen/custom_electrolyzer_cost_model.py index d88365393..c78cf8b96 100644 --- a/h2integrate/converters/hydrogen/custom_electrolyzer_cost_model.py +++ b/h2integrate/converters/hydrogen/custom_electrolyzer_cost_model.py @@ -29,7 +29,8 @@ class CustomElectrolyzerCostModel(ElectrolyzerCostBaseClass): def setup(self): self.config = CustomElectrolyzerCostModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/hydrogen/geologic/aspen_surface_processing.py b/h2integrate/converters/hydrogen/geologic/aspen_surface_processing.py index 49fc3a083..7fa55c487 100644 --- a/h2integrate/converters/hydrogen/geologic/aspen_surface_processing.py +++ b/h2integrate/converters/hydrogen/geologic/aspen_surface_processing.py @@ -78,7 +78,8 @@ class AspenGeoH2SurfacePerformanceModel(GeoH2SurfacePerformanceBaseClass): def setup(self): self.config = AspenGeoH2SurfacePerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) super().setup() n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] @@ -238,7 +239,8 @@ class AspenGeoH2SurfaceCostModel(GeoH2SurfaceCostBaseClass): def setup(self): self.config = AspenGeoH2SurfaceCostConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] diff --git a/h2integrate/converters/hydrogen/geologic/mathur_modified.py b/h2integrate/converters/hydrogen/geologic/mathur_modified.py index 03b0016c0..5f7717035 100644 --- a/h2integrate/converters/hydrogen/geologic/mathur_modified.py +++ b/h2integrate/converters/hydrogen/geologic/mathur_modified.py @@ -171,7 +171,11 @@ def setup(self): self.target_dollar_year = 2024 config_dict.update({"cost_year": self.target_dollar_year}) - self.config = GeoH2SubsurfaceCostConfig.from_dict(config_dict, strict=True) + self.config = GeoH2SubsurfaceCostConfig.from_dict( + config_dict, + strict=True, + additional_cls_name=self.__class__.__name__, + ) super().setup() diff --git a/h2integrate/converters/hydrogen/geologic/simple_natural_geoh2.py b/h2integrate/converters/hydrogen/geologic/simple_natural_geoh2.py index 0710244d2..223988acd 100644 --- a/h2integrate/converters/hydrogen/geologic/simple_natural_geoh2.py +++ b/h2integrate/converters/hydrogen/geologic/simple_natural_geoh2.py @@ -99,7 +99,8 @@ class NaturalGeoH2PerformanceModel(GeoH2SubsurfacePerformanceBaseClass): def setup(self): self.config = NaturalGeoH2PerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) super().setup() n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] diff --git a/h2integrate/converters/hydrogen/geologic/templeton_serpentinization.py b/h2integrate/converters/hydrogen/geologic/templeton_serpentinization.py index fcf5b94f9..a68ecdd7d 100644 --- a/h2integrate/converters/hydrogen/geologic/templeton_serpentinization.py +++ b/h2integrate/converters/hydrogen/geologic/templeton_serpentinization.py @@ -76,7 +76,8 @@ class StimulatedGeoH2PerformanceModel(GeoH2SubsurfacePerformanceBaseClass): def setup(self): n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] self.config = StimulatedGeoH2PerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/hydrogen/pem_electrolyzer.py b/h2integrate/converters/hydrogen/pem_electrolyzer.py index faa7fe86d..0d4253243 100644 --- a/h2integrate/converters/hydrogen/pem_electrolyzer.py +++ b/h2integrate/converters/hydrogen/pem_electrolyzer.py @@ -63,6 +63,7 @@ def setup(self): self.config = ECOElectrolyzerPerformanceModelConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=False, + additional_cls_name=self.__class__.__name__, ) super().setup() self.add_output("efficiency", val=0.0, desc="Average efficiency of the electrolyzer") diff --git a/h2integrate/converters/hydrogen/singlitico_cost_model.py b/h2integrate/converters/hydrogen/singlitico_cost_model.py index 87e7459bc..8c36a7d9d 100644 --- a/h2integrate/converters/hydrogen/singlitico_cost_model.py +++ b/h2integrate/converters/hydrogen/singlitico_cost_model.py @@ -31,7 +31,8 @@ class SingliticoCostModel(ElectrolyzerCostBaseClass): def setup(self): self.config = SingliticoCostModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/hydrogen/wombat_model.py b/h2integrate/converters/hydrogen/wombat_model.py index 5ab991c62..e287a9745 100644 --- a/h2integrate/converters/hydrogen/wombat_model.py +++ b/h2integrate/converters/hydrogen/wombat_model.py @@ -38,7 +38,8 @@ class WOMBATElectrolyzerModel(ECOElectrolyzerPerformanceModel): def setup(self): super().setup() self.config = WOMBATModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) plant_life = int(self.options["plant_config"]["plant"]["plant_life"]) self.add_output("capacity_factor", val=0.0, units=None) diff --git a/h2integrate/converters/iron/iron_dri_base.py b/h2integrate/converters/iron/iron_dri_base.py index f70e505af..5db6a35a6 100644 --- a/h2integrate/converters/iron/iron_dri_base.py +++ b/h2integrate/converters/iron/iron_dri_base.py @@ -38,6 +38,7 @@ def setup(self): self.config = IronReductionPerformanceBaseConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=True, + additional_cls_name=self.__class__.__name__, ) self.add_input( @@ -319,7 +320,11 @@ def setup(self): config_dict.update({"cost_year": target_dollar_year}) - self.config = IronReductionCostBaseConfig.from_dict(config_dict, strict=False) + self.config = IronReductionCostBaseConfig.from_dict( + config_dict, + strict=False, + additional_cls_name=self.__class__.__name__, + ) super().setup() diff --git a/h2integrate/converters/iron/iron_mine.py b/h2integrate/converters/iron/iron_mine.py index b6cca3e90..22508aa9d 100644 --- a/h2integrate/converters/iron/iron_mine.py +++ b/h2integrate/converters/iron/iron_mine.py @@ -67,6 +67,7 @@ def setup(self): self.config = IronMinePerformanceConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=False, + additional_cls_name=self.__class__.__name__, ) self.add_discrete_output( "iron_mine_performance", val=pd.DataFrame, desc="iron mine performance results" @@ -134,7 +135,11 @@ def setup(self): config_dict.update({"cost_year": self.target_dollar_year}) config_dict.update({"plant_life": self.plant_life}) - self.config = IronMineCostConfig.from_dict(config_dict, strict=False) + self.config = IronMineCostConfig.from_dict( + config_dict, + strict=False, + additional_cls_name=self.__class__.__name__, + ) super().setup() self.add_input("LCOE", val=self.config.LCOE, units="USD/MW/h") diff --git a/h2integrate/converters/iron/iron_plant.py b/h2integrate/converters/iron/iron_plant.py index 1cd784e02..57de4a09d 100644 --- a/h2integrate/converters/iron/iron_plant.py +++ b/h2integrate/converters/iron/iron_plant.py @@ -66,6 +66,7 @@ def setup(self): self.config = IronPlantPerformanceConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=False, + additional_cls_name=self.__class__.__name__, ) self.add_input("iron_ore_in", val=0.0, shape=n_timesteps, units="t/h") self.add_discrete_output( @@ -141,7 +142,10 @@ def setup(self): config_dict.update({"cost_year": self.target_dollar_year}) config_dict.update({"plant_life": self.plant_life}) - self.config = IronPlantCostConfig.from_dict(config_dict) + self.config = IronPlantCostConfig.from_dict( + config_dict, + additional_cls_name=self.__class__.__name__, + ) super().setup() self.add_input("LCOE", val=self.config.LCOE, units="USD/MW/h") diff --git a/h2integrate/converters/iron/iron_transport.py b/h2integrate/converters/iron/iron_transport.py index d9fafee0d..38b43699e 100644 --- a/h2integrate/converters/iron/iron_transport.py +++ b/h2integrate/converters/iron/iron_transport.py @@ -38,6 +38,7 @@ def setup(self): self.config = IronTransportPerformanceConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=False, + additional_cls_name=self.__class__.__name__, ) self.add_output("land_transport_distance", val=0.0, units="km") self.add_output("water_transport_distance", val=0.0, units="km") @@ -182,6 +183,7 @@ def setup(self): self.config = IronTransportCostConfig.from_dict( config_dict, strict=True, + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/iron/iron_wrapper.py b/h2integrate/converters/iron/iron_wrapper.py index 2fdf50c38..1ed057f6e 100644 --- a/h2integrate/converters/iron/iron_wrapper.py +++ b/h2integrate/converters/iron/iron_wrapper.py @@ -83,6 +83,7 @@ def setup(self): self.config = IronConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), strict=False, + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/iron/martin_mine_cost_model.py b/h2integrate/converters/iron/martin_mine_cost_model.py index da6594bca..2f5554b57 100644 --- a/h2integrate/converters/iron/martin_mine_cost_model.py +++ b/h2integrate/converters/iron/martin_mine_cost_model.py @@ -70,7 +70,11 @@ def setup(self): self.target_dollar_year = 2024 config_dict.update({"cost_year": self.target_dollar_year}) - self.config = MartinIronMineCostConfig.from_dict(config_dict, strict=True) + self.config = MartinIronMineCostConfig.from_dict( + config_dict, + strict=True, + additional_cls_name=self.__class__.__name__, + ) super().setup() diff --git a/h2integrate/converters/iron/martin_mine_perf_model.py b/h2integrate/converters/iron/martin_mine_perf_model.py index d39bf17ae..af659dc8c 100644 --- a/h2integrate/converters/iron/martin_mine_perf_model.py +++ b/h2integrate/converters/iron/martin_mine_perf_model.py @@ -41,6 +41,7 @@ def setup(self): self.config = MartinIronMinePerformanceConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=True, + additional_cls_name=self.__class__.__name__, ) self.add_input( diff --git a/h2integrate/converters/methanol/co2h_methanol_plant.py b/h2integrate/converters/methanol/co2h_methanol_plant.py index eb7d817bf..70955c046 100644 --- a/h2integrate/converters/methanol/co2h_methanol_plant.py +++ b/h2integrate/converters/methanol/co2h_methanol_plant.py @@ -52,7 +52,8 @@ class CO2HMethanolPlantPerformanceModel(MethanolPerformanceBaseClass): def setup(self): n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] self.config = CO2HPerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) super().setup() @@ -155,7 +156,8 @@ class CO2HMethanolPlantCostModel(MethanolCostBaseClass): def setup(self): n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] self.config = CO2HCostConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() @@ -214,7 +216,8 @@ class CO2HMethanolPlantFinanceModel(MethanolFinanceBaseClass): def setup(self): n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] self.config = MethanolFinanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "finance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "finance"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/methanol/smr_methanol_plant.py b/h2integrate/converters/methanol/smr_methanol_plant.py index e30aec6eb..fbd5b17be 100644 --- a/h2integrate/converters/methanol/smr_methanol_plant.py +++ b/h2integrate/converters/methanol/smr_methanol_plant.py @@ -45,7 +45,8 @@ class SMRMethanolPlantPerformanceModel(MethanolPerformanceBaseClass): def setup(self): n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] self.config = SMRPerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) super().setup() @@ -138,7 +139,8 @@ class SMRMethanolPlantCostModel(MethanolCostBaseClass): def setup(self): self.config = SMRCostConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] @@ -203,7 +205,8 @@ class SMRMethanolPlantFinanceModel(MethanolFinanceBaseClass): def setup(self): self.config = MethanolFinanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "finance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "finance"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/natural_gas/natural_gas_cc_ct.py b/h2integrate/converters/natural_gas/natural_gas_cc_ct.py index 183aefac0..bb9c7b9c9 100644 --- a/h2integrate/converters/natural_gas/natural_gas_cc_ct.py +++ b/h2integrate/converters/natural_gas/natural_gas_cc_ct.py @@ -60,7 +60,8 @@ def initialize(self): def setup(self): self.config = NaturalGasPerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] @@ -230,7 +231,8 @@ class NaturalGasCostModel(CostModelBaseClass): def setup(self): self.config = NaturalGasCostModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] diff --git a/h2integrate/converters/nitrogen/simple_ASU.py b/h2integrate/converters/nitrogen/simple_ASU.py index 71d8e3592..cd3d0c063 100644 --- a/h2integrate/converters/nitrogen/simple_ASU.py +++ b/h2integrate/converters/nitrogen/simple_ASU.py @@ -69,7 +69,8 @@ def initialize(self): def setup(self): n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] self.config = SimpleASUPerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) if self.config.size_from_N2_demand: self.add_input("nitrogen_in", val=0.0, shape=n_timesteps, units="kg/h") @@ -297,7 +298,8 @@ def initialize(self): def setup(self): self.config = SimpleASUCostConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/solar/atb_res_com_pv_cost.py b/h2integrate/converters/solar/atb_res_com_pv_cost.py index 3f034d32a..b5e8540b7 100644 --- a/h2integrate/converters/solar/atb_res_com_pv_cost.py +++ b/h2integrate/converters/solar/atb_res_com_pv_cost.py @@ -32,7 +32,8 @@ class ATBResComPVCostModelConfig(CostModelBaseConfig): class ATBResComPVCostModel(CostModelBaseClass): def setup(self): self.config = ATBResComPVCostModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/solar/atb_utility_pv_cost.py b/h2integrate/converters/solar/atb_utility_pv_cost.py index f6e581c19..25a223dd1 100644 --- a/h2integrate/converters/solar/atb_utility_pv_cost.py +++ b/h2integrate/converters/solar/atb_utility_pv_cost.py @@ -28,7 +28,8 @@ class ATBUtilityPVCostModelConfig(CostModelBaseConfig): class ATBUtilityPVCostModel(CostModelBaseClass): def setup(self): self.config = ATBUtilityPVCostModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/solar/solar_pysam.py b/h2integrate/converters/solar/solar_pysam.py index c026cf644..59d7cc22a 100644 --- a/h2integrate/converters/solar/solar_pysam.py +++ b/h2integrate/converters/solar/solar_pysam.py @@ -147,6 +147,7 @@ def setup(self): self.design_config = PYSAMSolarPlantPerformanceModelDesignConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=False, + additional_cls_name=self.__class__.__name__, ) self.add_input( "system_capacity_DC", diff --git a/h2integrate/converters/steel/electric_arc_furnance.py b/h2integrate/converters/steel/electric_arc_furnance.py index e9055d3d8..c710df19a 100644 --- a/h2integrate/converters/steel/electric_arc_furnance.py +++ b/h2integrate/converters/steel/electric_arc_furnance.py @@ -58,6 +58,7 @@ def setup(self): self.config = EAFPlantPerformanceConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=False, + additional_cls_name=self.__class__.__name__, ) self.add_discrete_output( @@ -130,7 +131,10 @@ def setup(self): config_dict.update({"cost_year": self.target_dollar_year}) config_dict.update({"plant_life": self.plant_life}) - self.config = EAFPlantCostConfig.from_dict(config_dict) + self.config = EAFPlantCostConfig.from_dict( + config_dict, + additional_cls_name=self.__class__.__name__, + ) super().setup() self.add_input("LCOE", val=self.config.LCOE, units="USD/MW/h") diff --git a/h2integrate/converters/steel/steel.py b/h2integrate/converters/steel/steel.py index c87f13759..c1b845b20 100644 --- a/h2integrate/converters/steel/steel.py +++ b/h2integrate/converters/steel/steel.py @@ -24,7 +24,8 @@ class SteelPerformanceModel(SteelPerformanceBaseClass): def setup(self): super().setup() self.config = SteelPerformanceModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) def compute(self, inputs, outputs): @@ -79,7 +80,8 @@ class SteelCostAndFinancialModel(SteelCostBaseClass): def setup(self): self.config = SteelCostAndFinancialModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/steel/steel_eaf_base.py b/h2integrate/converters/steel/steel_eaf_base.py index 0f8ecf014..ab42f94fc 100644 --- a/h2integrate/converters/steel/steel_eaf_base.py +++ b/h2integrate/converters/steel/steel_eaf_base.py @@ -38,6 +38,7 @@ def setup(self): self.config = ElectricArcFurnacePerformanceBaseConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=True, + additional_cls_name=self.__class__.__name__, ) self.add_input( @@ -323,7 +324,11 @@ def setup(self): config_dict.update({"cost_year": target_dollar_year}) - self.config = ElectricArcFurnaceCostBaseConfig.from_dict(config_dict, strict=False) + self.config = ElectricArcFurnaceCostBaseConfig.from_dict( + config_dict, + strict=False, + additional_cls_name=self.__class__.__name__, + ) super().setup() diff --git a/h2integrate/converters/water/desal/desalination.py b/h2integrate/converters/water/desal/desalination.py index 2e31a63a7..de9b8641c 100644 --- a/h2integrate/converters/water/desal/desalination.py +++ b/h2integrate/converters/water/desal/desalination.py @@ -35,7 +35,8 @@ class ReverseOsmosisPerformanceModel(DesalinationPerformanceBaseClass): def setup(self): super().setup() self.config = ReverseOsmosisPerformanceModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) self.add_output( "electricity_in", @@ -124,7 +125,8 @@ class ReverseOsmosisCostModel(DesalinationCostBaseClass): def setup(self): self.config = ReverseOsmosisCostModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/water_power/hydro_plant_run_of_river.py b/h2integrate/converters/water_power/hydro_plant_run_of_river.py index c4ddd2575..aadfdca13 100644 --- a/h2integrate/converters/water_power/hydro_plant_run_of_river.py +++ b/h2integrate/converters/water_power/hydro_plant_run_of_river.py @@ -40,7 +40,8 @@ def setup(self): super().setup() n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] self.config = RunOfRiverHydroPerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) self.add_input("discharge", val=0.0, shape=n_timesteps, units="m**3/s") @@ -91,7 +92,8 @@ class RunOfRiverHydroCostModel(CostModelBaseClass): def setup(self): self.config = RunOfRiverHydroCostConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/wind/atb_wind_cost.py b/h2integrate/converters/wind/atb_wind_cost.py index 63ae5d35a..6cf0f185c 100644 --- a/h2integrate/converters/wind/atb_wind_cost.py +++ b/h2integrate/converters/wind/atb_wind_cost.py @@ -50,7 +50,8 @@ class ATBWindPlantCostModel(CostModelBaseClass): def setup(self): self.config = ATBWindPlantCostModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/converters/wind/floris.py b/h2integrate/converters/wind/floris.py index 623b68322..a77ea6cdc 100644 --- a/h2integrate/converters/wind/floris.py +++ b/h2integrate/converters/wind/floris.py @@ -122,7 +122,8 @@ def setup(self): # initialize wind turbine config self.config = FlorisWindPlantPerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) if self.config.hybrid_turbine_design: diff --git a/h2integrate/converters/wind/wind_pysam.py b/h2integrate/converters/wind/wind_pysam.py index db312f333..d0dfec507 100644 --- a/h2integrate/converters/wind/wind_pysam.py +++ b/h2integrate/converters/wind/wind_pysam.py @@ -196,7 +196,10 @@ def setup(self): layout_mode = layout_params.get("layout_mode", "basicgrid") layout_options = layout_params.get("layout_options", {}) if layout_mode == "basicgrid": - self.layout_config = BasicGridLayoutConfig.from_dict(layout_options) + self.layout_config = BasicGridLayoutConfig.from_dict( + layout_options, + additional_cls_name=self.__class__.__name__, + ) self.layout_mode = layout_mode # initialize power-curve recalc config @@ -209,7 +212,8 @@ def setup(self): # initialize wind turbine config self.config = PYSAMWindPlantPerformanceModelConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) self.add_input( diff --git a/h2integrate/core/feedstocks.py b/h2integrate/core/feedstocks.py index a0d133940..c23e44d6e 100644 --- a/h2integrate/core/feedstocks.py +++ b/h2integrate/core/feedstocks.py @@ -30,7 +30,8 @@ def initialize(self): def setup(self): self.config = FeedstockPerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] feedstock_type = self.config.feedstock_type @@ -70,7 +71,8 @@ class FeedstockCostConfig(CostModelBaseConfig): class FeedstockCostModel(CostModelBaseClass): def setup(self): self.config = FeedstockCostConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + additional_cls_name=self.__class__.__name__, ) n_timesteps = self.options["plant_config"]["plant"]["simulation"]["n_timesteps"] diff --git a/h2integrate/core/sites.py b/h2integrate/core/sites.py index f1d3135b7..eee6504f5 100644 --- a/h2integrate/core/sites.py +++ b/h2integrate/core/sites.py @@ -43,7 +43,10 @@ class SiteLocationComponentConfig(SiteBaseConfig): class SiteLocationComponent(SiteBaseComponent): def __init__(self, site_config: dict, name=None, val=1.0, **kwargs): - self.config = SiteLocationComponentConfig.from_dict(site_config) + self.config = SiteLocationComponentConfig.from_dict( + site_config, + additional_cls_name=self.__class__.__name__, + ) super().__init__(name, val, **kwargs) def set_outputs(self): diff --git a/h2integrate/finances/numpy_financial_npv.py b/h2integrate/finances/numpy_financial_npv.py index edaef5837..5365be589 100644 --- a/h2integrate/finances/numpy_financial_npv.py +++ b/h2integrate/finances/numpy_financial_npv.py @@ -111,7 +111,10 @@ def setup(self): ) finance_params.update({"plant_life": plant_config["plant"]["plant_life"]}) - self.config = NumpyFinancialNPVFinanceConfig.from_dict(finance_params) + self.config = NumpyFinancialNPVFinanceConfig.from_dict( + finance_params, + additional_cls_name=self.__class__.__name__, + ) tech_config = self.tech_config = self.options["tech_config"] for tech in tech_config: diff --git a/h2integrate/finances/profast_base.py b/h2integrate/finances/profast_base.py index 31643ea76..e30f60e9c 100644 --- a/h2integrate/finances/profast_base.py +++ b/h2integrate/finances/profast_base.py @@ -550,7 +550,10 @@ def setup(self): fin_params = check_parameter_inputs(finance_params, plant_config) # initialize financial parameters - self.params = BasicProFASTParameterConfig.from_dict(fin_params) + self.params = BasicProFASTParameterConfig.from_dict( + fin_params, + additional_cls_name=self.__class__.__name__, + ) # initialize default capital item parameters capital_item_params = plant_config["finance_parameters"]["model_inputs"].get( diff --git a/h2integrate/resource/river.py b/h2integrate/resource/river.py index a666a27fe..ab7eeba36 100644 --- a/h2integrate/resource/river.py +++ b/h2integrate/resource/river.py @@ -47,7 +47,10 @@ def initialize(self): def setup(self): # Define inputs and outputs - self.config = RiverResourceConfig.from_dict(self.options["resource_config"]) + self.config = RiverResourceConfig.from_dict( + self.options["resource_config"], + additional_cls_name=self.__class__.__name__, + ) site_config = self.options["plant_config"]["site"] self.add_input("latitude", site_config.get("latitude", 0.0), units="deg") diff --git a/h2integrate/resource/solar/nrel_developer_goes_api_models.py b/h2integrate/resource/solar/nrel_developer_goes_api_models.py index 1beece719..3f8b19849 100644 --- a/h2integrate/resource/solar/nrel_developer_goes_api_models.py +++ b/h2integrate/resource/solar/nrel_developer_goes_api_models.py @@ -48,7 +48,10 @@ def setup(self): self.base_url = "https://developer.nrel.gov/api/nsrdb/v2/solar/nsrdb-GOES-aggregated-v4-0-0-download.csv?" # create the resource config - self.config = GOESAggregatedAPIConfig.from_dict(resource_specs) + self.config = GOESAggregatedAPIConfig.from_dict( + resource_specs, + additional_cls_name=self.__class__.__name__, + ) super().setup() @@ -96,7 +99,10 @@ def setup(self): "https://developer.nrel.gov/api/nsrdb/v2/solar/nsrdb-GOES-conus-v4-0-0-download.csv?" ) # create the resource config - self.config = GOESConusAPIConfig.from_dict(resource_specs) + self.config = GOESConusAPIConfig.from_dict( + resource_specs, + additional_cls_name=self.__class__.__name__, + ) super().setup() @@ -141,7 +147,10 @@ def setup(self): self.base_url = "https://developer.nrel.gov/api/nsrdb/v2/solar/nsrdb-GOES-full-disc-v4-0-0-download.csv?" # create the resource config - self.config = GOESFullDiscAPIConfig.from_dict(resource_specs) + self.config = GOESFullDiscAPIConfig.from_dict( + resource_specs, + additional_cls_name=self.__class__.__name__, + ) super().setup() @@ -212,5 +221,8 @@ def setup(self): "https://developer.nrel.gov/api/nsrdb/v2/solar/nsrdb-GOES-tmy-v4-0-0-download.csv?" ) # create the resource config - self.config = GOESTMYAPIConfig.from_dict(resource_specs) + self.config = GOESTMYAPIConfig.from_dict( + resource_specs, + additional_cls_name=self.__class__.__name__, + ) super().setup() diff --git a/h2integrate/resource/solar/nrel_developer_himawari_api_models.py b/h2integrate/resource/solar/nrel_developer_himawari_api_models.py index 6ffc3cd74..cb3225d9f 100644 --- a/h2integrate/resource/solar/nrel_developer_himawari_api_models.py +++ b/h2integrate/resource/solar/nrel_developer_himawari_api_models.py @@ -49,7 +49,10 @@ def setup(self): self.base_url = "https://developer.nrel.gov/api/nsrdb/v2/solar/himawari7-download.csv?" # create the resource config - self.config = Himawari7SolarAPIConfig.from_dict(resource_specs) + self.config = Himawari7SolarAPIConfig.from_dict( + resource_specs, + additional_cls_name=self.__class__.__name__, + ) super().setup() @@ -95,7 +98,10 @@ def setup(self): self.base_url = "https://developer.nrel.gov/api/nsrdb/v2/solar/himawari-download.csv?" # create the resource config - self.config = Himawari8SolarAPIConfig.from_dict(resource_specs) + self.config = Himawari8SolarAPIConfig.from_dict( + resource_specs, + additional_cls_name=self.__class__.__name__, + ) super().setup() @@ -157,5 +163,8 @@ def setup(self): self.base_url = "https://developer.nrel.gov/api/nsrdb/v2/solar/himawari-tmy-download.csv?" # create the resource config - self.config = HimawariTMYAPIConfig.from_dict(resource_specs) + self.config = HimawariTMYAPIConfig.from_dict( + resource_specs, + additional_cls_name=self.__class__.__name__, + ) super().setup() diff --git a/h2integrate/resource/solar/nrel_developer_meteosat_prime_meridian_models.py b/h2integrate/resource/solar/nrel_developer_meteosat_prime_meridian_models.py index a553d71d3..a3ee3ffd3 100644 --- a/h2integrate/resource/solar/nrel_developer_meteosat_prime_meridian_models.py +++ b/h2integrate/resource/solar/nrel_developer_meteosat_prime_meridian_models.py @@ -51,7 +51,10 @@ def setup(self): "https://developer.nrel.gov/api/nsrdb/v2/solar/nsrdb-msg-v1-0-0-download.csv?" ) # create the resource config - self.config = MeteosatPrimeMeridianAPIConfig.from_dict(resource_specs) + self.config = MeteosatPrimeMeridianAPIConfig.from_dict( + resource_specs, + additional_cls_name=self.__class__.__name__, + ) super().setup() @@ -117,7 +120,10 @@ def setup(self): resource_specs = self.helper_setup_method() # create the resource config - self.config = MeteosatPrimeMeridianTMYAPIConfig.from_dict(resource_specs) + self.config = MeteosatPrimeMeridianTMYAPIConfig.from_dict( + resource_specs, + additional_cls_name=self.__class__.__name__, + ) self.base_url = f"https://developer.nrel.gov/api/nsrdb/v2/solar/nsrdb-msg-v1-0-0-{self.config.resource_year.split('-')[0]}-download.csv?" diff --git a/h2integrate/resource/solar/openmeteo_solar.py b/h2integrate/resource/solar/openmeteo_solar.py index 731ab38d4..d4fdfe8b4 100644 --- a/h2integrate/resource/solar/openmeteo_solar.py +++ b/h2integrate/resource/solar/openmeteo_solar.py @@ -53,7 +53,10 @@ def setup(self): resource_specs = self.helper_setup_method() # create the resource config - self.config = OpenMeteoHistoricalSolarAPIConfig.from_dict(resource_specs) + self.config = OpenMeteoHistoricalSolarAPIConfig.from_dict( + resource_specs, + additional_cls_name=self.__class__.__name__, + ) # set UTC variable depending on timezone, used for filenaming self.utc = False diff --git a/h2integrate/resource/wind/nrel_developer_wtk_api.py b/h2integrate/resource/wind/nrel_developer_wtk_api.py index 0b81328d7..01878fd40 100644 --- a/h2integrate/resource/wind/nrel_developer_wtk_api.py +++ b/h2integrate/resource/wind/nrel_developer_wtk_api.py @@ -53,7 +53,10 @@ def setup(self): resource_specs = self.helper_setup_method() # create the resource config - self.config = WTKNRELDeveloperAPIConfig.from_dict(resource_specs) + self.config = WTKNRELDeveloperAPIConfig.from_dict( + resource_specs, + additional_cls_name=self.__class__.__name__, + ) # set UTC variable depending on timezone, used for filenaming self.utc = False diff --git a/h2integrate/resource/wind/openmeteo_wind.py b/h2integrate/resource/wind/openmeteo_wind.py index d575d973d..e01bf11de 100644 --- a/h2integrate/resource/wind/openmeteo_wind.py +++ b/h2integrate/resource/wind/openmeteo_wind.py @@ -52,7 +52,10 @@ def setup(self): resource_specs = self.helper_setup_method() # create the resource config - self.config = OpenMeteoHistoricalWindAPIConfig.from_dict(resource_specs) + self.config = OpenMeteoHistoricalWindAPIConfig.from_dict( + resource_specs, + additional_cls_name=self.__class__.__name__, + ) # set UTC variable depending on timezone, used for filenaming self.utc = False diff --git a/h2integrate/storage/battery/atb_battery_cost.py b/h2integrate/storage/battery/atb_battery_cost.py index 9f2749cdc..994bcdd76 100644 --- a/h2integrate/storage/battery/atb_battery_cost.py +++ b/h2integrate/storage/battery/atb_battery_cost.py @@ -56,7 +56,9 @@ class ATBBatteryCostModel(CostModelBaseClass): def setup(self): self.config = ATBBatteryCostConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), strict=False + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + strict=False, + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/storage/battery/pysam_battery.py b/h2integrate/storage/battery/pysam_battery.py index 6b1b551fd..d043cfd89 100644 --- a/h2integrate/storage/battery/pysam_battery.py +++ b/h2integrate/storage/battery/pysam_battery.py @@ -223,6 +223,7 @@ def setup(self): self.config = PySAMBatteryPerformanceModelConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=False, + additional_cls_name=self.__class__.__name__, ) self.add_input( diff --git a/h2integrate/storage/generic_storage_cost.py b/h2integrate/storage/generic_storage_cost.py index 4ec34e200..93aa8e38b 100644 --- a/h2integrate/storage/generic_storage_cost.py +++ b/h2integrate/storage/generic_storage_cost.py @@ -54,7 +54,9 @@ class GenericStorageCostModel(CostModelBaseClass): def setup(self): self.config = GenericStorageCostConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), strict=False + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), + strict=False, + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/storage/hydrogen/h2_storage_cost.py b/h2integrate/storage/hydrogen/h2_storage_cost.py index b8edd8d02..ffa61ddb2 100644 --- a/h2integrate/storage/hydrogen/h2_storage_cost.py +++ b/h2integrate/storage/hydrogen/h2_storage_cost.py @@ -97,6 +97,7 @@ def setup(self): self.config = HydrogenStorageBaseCostModelConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), strict=False, + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/storage/hydrogen/mch_storage.py b/h2integrate/storage/hydrogen/mch_storage.py index a5007f8cd..5a1e4ad39 100644 --- a/h2integrate/storage/hydrogen/mch_storage.py +++ b/h2integrate/storage/hydrogen/mch_storage.py @@ -74,6 +74,7 @@ def setup(self): self.config = MCHTOLStorageCostModelConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "cost"), strict=False, + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/storage/simple_generic_storage.py b/h2integrate/storage/simple_generic_storage.py index 2f8c7b73e..890a27009 100644 --- a/h2integrate/storage/simple_generic_storage.py +++ b/h2integrate/storage/simple_generic_storage.py @@ -25,6 +25,7 @@ def setup(self): self.config = SimpleGenericStorageConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=False, + additional_cls_name=self.__class__.__name__, ) commodity_name = self.config.commodity_name commodity_units = self.config.commodity_units diff --git a/h2integrate/storage/simple_storage_auto_sizing.py b/h2integrate/storage/simple_storage_auto_sizing.py index a16cb070a..89706cefb 100644 --- a/h2integrate/storage/simple_storage_auto_sizing.py +++ b/h2integrate/storage/simple_storage_auto_sizing.py @@ -51,6 +51,7 @@ def setup(self): self.config = StorageSizingModelConfig.from_dict( merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), strict=False, + additional_cls_name=self.__class__.__name__, ) super().setup() diff --git a/h2integrate/transporters/generic_combiner.py b/h2integrate/transporters/generic_combiner.py index 221368350..3cdfb759e 100644 --- a/h2integrate/transporters/generic_combiner.py +++ b/h2integrate/transporters/generic_combiner.py @@ -34,7 +34,8 @@ def initialize(self): def setup(self): self.config = GenericCombinerPerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) diff --git a/h2integrate/transporters/generic_splitter.py b/h2integrate/transporters/generic_splitter.py index a1259fa38..d3e7a1b87 100644 --- a/h2integrate/transporters/generic_splitter.py +++ b/h2integrate/transporters/generic_splitter.py @@ -78,7 +78,8 @@ def initialize(self): def setup(self): # Initialize config from tech config self.config = GenericSplitterPerformanceConfig.from_dict( - self.options["tech_config"]["model_inputs"]["performance_parameters"] + self.options["tech_config"]["model_inputs"]["performance_parameters"], + additional_cls_name=self.__class__.__name__, ) self.add_input( diff --git a/h2integrate/transporters/generic_summer.py b/h2integrate/transporters/generic_summer.py index 6926b1670..b5ad809bc 100644 --- a/h2integrate/transporters/generic_summer.py +++ b/h2integrate/transporters/generic_summer.py @@ -36,7 +36,8 @@ def initialize(self): def setup(self): self.config = GenericSummerPerformanceConfig.from_dict( - merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance") + merge_shared_inputs(self.options["tech_config"]["model_inputs"], "performance"), + additional_cls_name=self.__class__.__name__, ) n_timesteps = int(self.options["plant_config"]["plant"]["simulation"]["n_timesteps"]) From 5b9e6a9dc0c24da6805016f91e4adfaf701aa6dd Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 27 Jan 2026 10:46:57 -0800 Subject: [PATCH 3/4] add tests for BaseConfig.from_dict --- h2integrate/core/test/test_utilities.py | 84 +++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/h2integrate/core/test/test_utilities.py b/h2integrate/core/test/test_utilities.py index fe79b44f7..9a3da0c15 100644 --- a/h2integrate/core/test/test_utilities.py +++ b/h2integrate/core/test/test_utilities.py @@ -5,9 +5,12 @@ import yaml import numpy as np +import pytest +from attrs import field, define from h2integrate import ROOT_DIR, EXAMPLE_DIR, RESOURCE_DEFAULT_DIR from h2integrate.core.utilities import ( + BaseConfig, get_path, find_file, make_unique_case_name, @@ -427,5 +430,86 @@ def test_comprehensive_realistic_example(self): self.assertEqual(plant_config["financial"]["discount_rate"], 0.08) +@define +class DemoConfig(BaseConfig): + """Test class for the basic functionality of `BaseConfig`.""" + + x: int = field() + y: str = field(default="y") + + +class BaseDemoModel: + """Demo base model for testing.""" + + def __init__(self, config: dict): + self.config = DemoConfig.from_dict( + config, strict=False, additional_cls_name=self.__class__.__name__ + ) + + +class BaseDemoModelStrict: + """Demo base model for testing.""" + + def __init__(self, config: dict): + self.config = DemoConfig.from_dict(config, strict=True) + + +class BaseDemoModelStrictAdditional: + """Demo base model for testing.""" + + def __init__(self, config: dict): + self.config = DemoConfig.from_dict( + config, strict=True, additional_cls_name=self.__class__.__name__ + ) + + +class BaseDemoModelAdditional: + """Demo base model for testing.""" + + def __init__(self, config: dict): + super().__init__(config) + + +def test_BaseConfig(subtests): + """Tests the BaseConfig class.""" + + with subtests.test("Check basic passing inputs"): + demo = BaseDemoModel({"x": 1}) + assert demo.config.x == 1 + assert demo.config.y == "y" + + with subtests.test("Check allowed inputs overload"): + demo = BaseDemoModel({"x": 1, "z": 2}) + assert demo.config.x == 1 + assert demo.config.y == "y" + + with subtests.test("Check prohibited inputs overload with additional"): + msg = ( + "BaseDemoModelStrictAdditional setup failed as a result of DemoConfig" + " receiving extraneous inputs" + ) + with pytest.raises(AttributeError, match=msg): + demo = BaseDemoModelStrictAdditional({"x": 1, "z": 2}) + + with subtests.test("Check prohibited inputs overload w/o additional"): + msg = "The initialization for DemoConfig" " was given extraneous inputs" + with pytest.raises(AttributeError, match=msg): + demo = BaseDemoModelStrict({"x": 1, "z": 2}) + assert demo.config.y == "y" + + with subtests.test("Check undefined inputs overload with additional"): + msg = ( + "BaseDemoModelStrictAdditional setup failed as a result of DemoConfig" + " missing the following inputs" + ) + with pytest.raises(AttributeError, match=msg): + demo = BaseDemoModelStrictAdditional({}) + + with subtests.test("Check prohibited inputs overload w/o additional"): + msg = "The class definition for DemoConfig is missing the following inputs" + with pytest.raises(AttributeError, match=msg): + demo = BaseDemoModelStrict({}) + + if __name__ == "__main__": unittest.main() From dff111d23378f1c74a606c140b554e9ef8518d99 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 27 Jan 2026 11:03:24 -0800 Subject: [PATCH 4/4] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 486553cc9..387b9e7d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - Remove unused dependencies. - Fixes typos for skipped folders. - Fixes missing dependencies for `gis` modifier used in new iron mapping tests. +- Adds `additional_cls_name` kwarg to `BaseConfif.from_dict()` to allow for configuration errors buried in parent or child classes to provide which model had the offending misconfiguration for simpler user debugging. ## 0.5.1 [December 18, 2025]