From b3eae0a0a2c3b631401561607d7fdb6790cc473c Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 11 Aug 2026 14:59:23 -0700 Subject: [PATCH 01/11] replace custom validators with direct replacement built-in validators --- .../user_finance_model/simple_lco.py | 7 +-- .../storage/pyomo_storage_rule_baseclass.py | 15 ++--- .../openloop_control_base.py | 29 ++++++--- .../pyomo_storage_controller_baseclass.py | 9 ++- .../storage/heuristic_pyomo_controller.py | 15 +++-- .../storage/optimized_pyomo_controller.py | 13 ++-- .../plm_optimized_storage_controller.py | 11 ++-- .../system_level/solver_options.py | 8 +-- .../ammonia/ammonia_synloop_performance.py | 63 +++++++++++-------- .../co2/marine/direct_ocean_capture.py | 36 +++++------ .../marine/ocean_alkalinity_enhancement.py | 26 ++++---- .../converters/generic_converter_cost.py | 9 +-- .../converters/hydrogen/basic_cost_model.py | 6 +- .../custom_electrolyzer_cost_model.py | 7 +-- .../hydrogen/geologic/mathur_modified.py | 5 +- .../hydrogen/geologic/simple_natural_geoh2.py | 5 +- .../converters/hydrogen/h2_fuel_cell.py | 13 ++-- .../converters/hydrogen/htse_electrolyzer.py | 17 +++-- .../converters/hydrogen/pem_electrolyzer.py | 14 ++--- .../hydrogen/steam_methane_reformer.py | 15 +++-- h2integrate/converters/iron/iron_dri_base.py | 7 +-- h2integrate/converters/iron/iron_transport.py | 8 +-- .../converters/iron/martin_mine_cost_model.py | 6 +- .../natural_gas/dummy_gas_components.py | 23 ++++--- .../natural_gas/natural_gas_cc_ct.py | 17 +++-- h2integrate/converters/nitrogen/simple_ASU.py | 20 ++++-- .../converters/nuclear/nuclear_plant.py | 15 +++-- .../nuclear/nuclear_plant_thermal.py | 24 ++++--- .../converters/solar/atb_res_com_pv_cost.py | 7 +-- .../converters/solar/atb_utility_pv_cost.py | 7 +-- h2integrate/converters/solar/solar_pysam.py | 10 +-- .../converters/steel/steel_eaf_base.py | 7 +-- .../converters/water/desal/desalination.py | 12 ++-- .../water_power/pysam_marine_cost.py | 20 +++--- .../converters/water_power/tidal_pysam.py | 8 +-- h2integrate/converters/wind/atb_wind_cost.py | 7 +-- h2integrate/converters/wind/floris.py | 6 +- .../wind/layout/simple_grid_layout.py | 8 +-- h2integrate/converters/wind/wind_pysam.py | 12 ++-- h2integrate/core/sites.py | 7 +-- h2integrate/core/validators.py | 34 ---------- h2integrate/demand/flexible_demand.py | 13 ++-- h2integrate/feedstocks/eia_ng_price.py | 25 +++----- h2integrate/finances/numpy_financial_npv.py | 13 ++-- h2integrate/finances/profast_base.py | 44 +++++++------ .../solar/nlr_developer_goes_api_models.py | 10 +-- .../nlr_developer_himawari_api_models.py | 8 +-- ...eveloper_meteosat_prime_meridian_models.py | 6 +- h2integrate/resource/solar/openmeteo_solar.py | 7 ++- .../resource/wind/nlr_developer_wtk_api.py | 5 +- h2integrate/resource/wind/openmeteo_wind.py | 7 ++- .../storage/battery/atb_battery_cost.py | 10 +-- h2integrate/storage/battery/pysam_battery.py | 10 +-- h2integrate/storage/generic_storage_cost.py | 10 +-- .../storage/hydrogen/h2_storage_cost.py | 26 ++++---- .../storage/simple_storage_auto_sizing.py | 15 +++-- h2integrate/storage/storage_baseclass.py | 7 +-- .../storage/storage_performance_model.py | 21 ++++--- h2integrate/transporters/generic_splitter.py | 8 ++- 59 files changed, 421 insertions(+), 412 deletions(-) diff --git a/examples/08_wind_electrolyzer/user_finance_model/simple_lco.py b/examples/08_wind_electrolyzer/user_finance_model/simple_lco.py index 993cfd9cf..bd754fdff 100644 --- a/examples/08_wind_electrolyzer/user_finance_model/simple_lco.py +++ b/examples/08_wind_electrolyzer/user_finance_model/simple_lco.py @@ -1,15 +1,14 @@ import numpy as np import openmdao.api as om -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig -from h2integrate.core.validators import gt_zero, range_val @define(kw_only=True) class SimpleLCOFinanceConfig(BaseConfig): - discount_rate: float = field(validator=range_val(0, 1)) - plant_life: int = field(converter=int, validator=gt_zero) + discount_rate: float = field(validator=(validators.ge(0), validators.le(1))) + plant_life: int = field(converter=int, validator=validators.gt(0)) class SimpleLCOFinance(om.ExplicitComponent): diff --git a/h2integrate/control/control_rules/storage/pyomo_storage_rule_baseclass.py b/h2integrate/control/control_rules/storage/pyomo_storage_rule_baseclass.py index e55a9837c..8b804da41 100644 --- a/h2integrate/control/control_rules/storage/pyomo_storage_rule_baseclass.py +++ b/h2integrate/control/control_rules/storage/pyomo_storage_rule_baseclass.py @@ -1,9 +1,8 @@ import pyomo.environ as pyo -from attrs import field, define +from attrs import field, define, validators from pyomo.network import Port from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gt_zero, range_val from h2integrate.control.control_rules.pyomo_rule_baseclass import ( PyomoRuleBaseClass, PyomoRuleBaseConfig, @@ -12,13 +11,15 @@ @define(kw_only=True) class PyomoStorageRuleBaseConfig(PyomoRuleBaseConfig): - max_capacity: float = field(validator=gt_zero) + max_capacity: float = field(validator=validators.gt(0)) - min_soc_fraction: float = field(default=0.1, validator=range_val(0, 1)) - max_soc_fraction: float = field(default=0.9, validator=range_val(0, 1)) + min_soc_fraction: float = field(default=0.1, validator=(validators.ge(0), validators.le(1))) + max_soc_fraction: float = field(default=0.9, validator=(validators.ge(0), validators.le(1))) - charge_efficiency: float = field(default=0.938, validator=range_val(0, 1)) - discharge_efficiency: float = field(default=0.938, validator=range_val(0, 1)) + charge_efficiency: float = field(default=0.938, validator=(validators.ge(0), validators.le(1))) + discharge_efficiency: float = field( + default=0.938, validator=(validators.ge(0), validators.le(1)) + ) class PyomoRuleStorageBaseclass(PyomoRuleBaseClass): diff --git a/h2integrate/control/control_strategies/openloop_control_base.py b/h2integrate/control/control_strategies/openloop_control_base.py index 27eed25cd..4e7f4640b 100644 --- a/h2integrate/control/control_strategies/openloop_control_base.py +++ b/h2integrate/control/control_strategies/openloop_control_base.py @@ -5,7 +5,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig -from h2integrate.core.validators import gte_zero, range_val_or_none @define(kw_only=True) @@ -64,15 +63,29 @@ class OpenLoopControlBaseConfig(BaseConfig): require_storage_parameters: ClassVar[bool] = False max_capacity: float | None = field(default=None) - max_soc_fraction: float | None = field(default=None, validator=range_val_or_none(0, 1)) - min_soc_fraction: float | None = field(default=None, validator=range_val_or_none(0, 1)) - init_soc_fraction: float | None = field(default=None, validator=range_val_or_none(0, 1)) - max_charge_rate: float | None = field(default=None, validator=validators.optional(gte_zero)) + max_soc_fraction: float | None = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) + min_soc_fraction: float | None = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) + init_soc_fraction: float | None = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) + max_charge_rate: float | None = field( + default=None, validator=validators.optional(validators.ge(0)) + ) charge_equals_discharge: bool = field(default=True) max_discharge_rate: float | None = field(default=None) - charge_efficiency: float | None = field(default=None, validator=range_val_or_none(0, 1)) - discharge_efficiency: float | None = field(default=None, validator=range_val_or_none(0, 1)) - round_trip_efficiency: float | None = field(default=None, validator=range_val_or_none(0, 1)) + charge_efficiency: float | None = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) + discharge_efficiency: float | None = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) + round_trip_efficiency: float | None = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) def __attrs_post_init__(self): if self.commodity_amount_units is None: diff --git a/h2integrate/control/control_strategies/pyomo_storage_controller_baseclass.py b/h2integrate/control/control_strategies/pyomo_storage_controller_baseclass.py index f7d723020..526efbe1d 100644 --- a/h2integrate/control/control_strategies/pyomo_storage_controller_baseclass.py +++ b/h2integrate/control/control_strategies/pyomo_storage_controller_baseclass.py @@ -3,10 +3,9 @@ import numpy as np import openmdao.api as om import pyomo.environ as pyomo -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig -from h2integrate.core.validators import range_val if TYPE_CHECKING: # to avoid circular imports @@ -55,9 +54,9 @@ class PyomoStorageControllerBaseConfig(BaseConfig): """ max_capacity: float = field() - max_soc_fraction: float = field(validator=range_val(0, 1)) - min_soc_fraction: float = field(validator=range_val(0, 1)) - init_soc_fraction: float = field(validator=range_val(0, 1)) + max_soc_fraction: float = field(validator=(validators.ge(0), validators.le(1))) + min_soc_fraction: float = field(validator=(validators.ge(0), validators.le(1))) + init_soc_fraction: float = field(validator=(validators.ge(0), validators.le(1))) n_control_window_hours: int = field() commodity: str = field() commodity_rate_units: str = field() diff --git a/h2integrate/control/control_strategies/storage/heuristic_pyomo_controller.py b/h2integrate/control/control_strategies/storage/heuristic_pyomo_controller.py index aeba9e66d..4eaf29db2 100644 --- a/h2integrate/control/control_strategies/storage/heuristic_pyomo_controller.py +++ b/h2integrate/control/control_strategies/storage/heuristic_pyomo_controller.py @@ -2,10 +2,9 @@ import numpy as np import pyomo.environ as pyomo -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import range_val_or_none from h2integrate.control.control_strategies.pyomo_storage_controller_baseclass import ( PyomoStorageControllerBaseClass, PyomoStorageControllerBaseConfig, @@ -33,9 +32,15 @@ class HeuristicLoadFollowingStorageControllerConfig(PyomoStorageControllerBaseCo provided. """ - charge_efficiency: float = field(validator=range_val_or_none(0, 1), default=None) - discharge_efficiency: float = field(validator=range_val_or_none(0, 1), default=None) - round_trip_efficiency: float | None = field(default=None, validator=range_val_or_none(0, 1)) + charge_efficiency: float = field( + validator=validators.optional((validators.ge(0), validators.le(1))), default=None + ) + discharge_efficiency: float = field( + validator=validators.optional((validators.ge(0), validators.le(1))), default=None + ) + round_trip_efficiency: float | None = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) def __attrs_post_init__(self): """ diff --git a/h2integrate/control/control_strategies/storage/optimized_pyomo_controller.py b/h2integrate/control/control_strategies/storage/optimized_pyomo_controller.py index 58c6108f0..2c79354ce 100644 --- a/h2integrate/control/control_strategies/storage/optimized_pyomo_controller.py +++ b/h2integrate/control/control_strategies/storage/optimized_pyomo_controller.py @@ -2,11 +2,10 @@ import numpy as np import pyomo.environ as pyomo -from attrs import field, define +from attrs import field, define, validators from pyomo.util.check_units import assert_units_consistent from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import range_val from h2integrate.control.control_rules.plant_dispatch_model import PyomoDispatchPlantModel from h2integrate.control.control_strategies.controller_opt_problem_state import DispatchProblemState from h2integrate.control.control_strategies.pyomo_storage_controller_baseclass import ( @@ -64,13 +63,17 @@ class OptimizedDispatchStorageControllerConfig(PyomoStorageControllerBaseConfig) """ max_charge_rate: int | float = field() - charge_efficiency: float = field(validator=range_val(0, 1), default=None) - discharge_efficiency: float = field(validator=range_val(0, 1), default=None) + charge_efficiency: float = field(validator=(validators.ge(0), validators.le(1)), default=None) + discharge_efficiency: float = field( + validator=(validators.ge(0), validators.le(1)), default=None + ) cost_per_production: float = field(default=None) cost_per_charge: float = field(default=None) cost_per_discharge: float = field(default=None) commodity_met_value: float = field(default=None) - time_weighting_factor: float = field(validator=range_val(0, 1), default=0.995) + time_weighting_factor: float = field( + validator=(validators.ge(0), validators.le(1)), default=0.995 + ) time_duration: float = field(default=1.0) # hours def make_dispatch_inputs(self): diff --git a/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py b/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py index 30cbbb80e..0b51fd703 100644 --- a/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py +++ b/h2integrate/control/control_strategies/storage/plm_optimized_storage_controller.py @@ -5,11 +5,10 @@ import numpy as np import pandas as pd import pyomo.environ as pyomo -from attrs import field, define +from attrs import field, define, validators from pyomo.opt import SolverStatus, TerminationCondition from h2integrate.core.utilities import merge_shared_inputs, build_time_series_from_plant_config -from h2integrate.core.validators import range_val from h2integrate.control.control_strategies.controller_opt_problem_state import DispatchProblemState from h2integrate.control.control_strategies.pyomo_storage_controller_baseclass import ( SolverOptions, @@ -79,11 +78,13 @@ class PeakLoadManagementOptimizedControllerConfig(PyomoStorageControllerBaseConf peak_window: dict = field() performance_incentive: float = field(default=None) performance_incentive_per_event: float = field(default=None) - charge_efficiency: float = field(validator=range_val(0, 1), default=1.0) - discharge_efficiency: float = field(validator=range_val(0, 1), default=1.0) + charge_efficiency: float = field(validator=(validators.ge(0), validators.le(1)), default=1.0) + discharge_efficiency: float = field(validator=(validators.ge(0), validators.le(1)), default=1.0) n_max_events: int = field(default=10) n_control_window_hours: float = field(default=24.0) - signal_threshold_percentile: float = field(default=0.0, validator=range_val(0, 100)) + signal_threshold_percentile: float = field( + default=0.0, validator=(validators.ge(0), validators.le(100)) + ) event_duration: dict = field(default=None) min_peak_separation: dict = field(default=None) diff --git a/h2integrate/control/control_strategies/system_level/solver_options.py b/h2integrate/control/control_strategies/system_level/solver_options.py index e3b5186c8..4013b0c4c 100644 --- a/h2integrate/control/control_strategies/system_level/solver_options.py +++ b/h2integrate/control/control_strategies/system_level/solver_options.py @@ -1,10 +1,10 @@ from typing import ClassVar import openmdao.api as om -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig -from h2integrate.core.validators import gt_zero, contains, gte_zero +from h2integrate.core.validators import contains @define(kw_only=True) @@ -31,10 +31,10 @@ class SLCSolverOptionsConfig(BaseConfig): solver_name: str = field( default="gauss_seidel", validator=contains(["gauss_seidel", "newton", "block_jacobi"]) ) - max_iter: int = field(default=20, converter=int, validator=gte_zero) + max_iter: int = field(default=20, converter=int, validator=validators.ge(0)) atol: float | None = field(default=None) rtol: float | None = field(default=None) - convergence_tolerance: float = field(default=1e-6, validator=gt_zero) + convergence_tolerance: float = field(default=1e-6, validator=validators.gt(0)) iprint: int = field(default=2) solver_option_kwargs: dict = field(default={}) diff --git a/h2integrate/converters/ammonia/ammonia_synloop_performance.py b/h2integrate/converters/ammonia/ammonia_synloop_performance.py index 98ff5154f..9972a9585 100644 --- a/h2integrate/converters/ammonia/ammonia_synloop_performance.py +++ b/h2integrate/converters/ammonia/ammonia_synloop_performance.py @@ -3,7 +3,6 @@ from h2integrate.core.dynamics import apply_ramping_limits, startup_loss_multiplier from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gt_zero, range_val from h2integrate.tools.constants import H_MW, N_MW, AR_MW from h2integrate.core.model_baseclasses import ( ResizeablePerformanceModelBaseClass, @@ -79,36 +78,48 @@ class AmmoniaSynLoopPerformanceConfig(ResizeablePerformanceModelBaseConfig): """ - production_capacity: float = field(validator=gt_zero) - catalyst_consumption_rate: float = field(validator=gt_zero) - catalyst_replacement_interval: float = field(validator=gt_zero) - capacity_factor: float = field(validator=range_val(0, 1)) - energy_demand: float = field(validator=gt_zero) - heat_output: float = field(validator=gt_zero) - feed_gas_t: float = field(validator=gt_zero) - feed_gas_p: float = field(validator=gt_zero) - feed_gas_x_n2: float = field(validator=range_val(0, 1)) - feed_gas_x_h2: float = field(validator=range_val(0, 1)) - feed_gas_mass_ratio: float = field(validator=gt_zero) - purge_gas_t: float = field(validator=gt_zero) - purge_gas_p: float = field(validator=gt_zero) - purge_gas_x_n2: float = field(validator=range_val(0, 1)) - purge_gas_x_h2: float = field(validator=range_val(0, 1)) - purge_gas_x_ar: float = field(validator=range_val(0, 1)) - purge_gas_x_nh3: float = field(validator=range_val(0, 1)) - purge_gas_mass_ratio: float = field(validator=gt_zero) + production_capacity: float = field(validator=validators.gt(0)) + catalyst_consumption_rate: float = field(validator=validators.gt(0)) + catalyst_replacement_interval: float = field(validator=validators.gt(0)) + capacity_factor: float = field(validator=(validators.ge(0), validators.le(1))) + energy_demand: float = field(validator=validators.gt(0)) + heat_output: float = field(validator=validators.gt(0)) + feed_gas_t: float = field(validator=validators.gt(0)) + feed_gas_p: float = field(validator=validators.gt(0)) + feed_gas_x_n2: float = field(validator=(validators.ge(0), validators.le(1))) + feed_gas_x_h2: float = field(validator=(validators.ge(0), validators.le(1))) + feed_gas_mass_ratio: float = field(validator=validators.gt(0)) + purge_gas_t: float = field(validator=validators.gt(0)) + purge_gas_p: float = field(validator=validators.gt(0)) + purge_gas_x_n2: float = field(validator=(validators.ge(0), validators.le(1))) + purge_gas_x_h2: float = field(validator=(validators.ge(0), validators.le(1))) + purge_gas_x_ar: float = field(validator=(validators.ge(0), validators.le(1))) + purge_gas_x_nh3: float = field(validator=(validators.ge(0), validators.le(1))) + purge_gas_mass_ratio: float = field(validator=validators.gt(0)) # dynamics inputs - turndown_ratio: float = field(default=0.0, validator=range_val(0.0, 1.0)) - ramp_up_rate_fraction: float = field(default=1.0, validator=range_val(0.0, 1.0)) - ramp_down_rate_fraction: float = field(default=1.0, validator=range_val(0.0, 1.0)) + turndown_ratio: float = field(default=0.0, validator=(validators.ge(0), validators.le(1))) + ramp_up_rate_fraction: float = field( + default=1.0, validator=(validators.ge(0), validators.le(1)) + ) + ramp_down_rate_fraction: float = field( + default=1.0, validator=(validators.ge(0), validators.le(1)) + ) include_cold_start: bool = field(default=False) - off_hours_cold_start: float = field(default=None, validator=validators.optional(gt_zero)) - cold_start_delay_hours: float = field(default=None, validator=validators.optional(gt_zero)) + off_hours_cold_start: float = field( + default=None, validator=validators.optional(validators.gt(0)) + ) + cold_start_delay_hours: float = field( + default=None, validator=validators.optional(validators.gt(0)) + ) include_warm_start: bool = field(default=False) - off_hours_warm_start: float = field(default=None, validator=validators.optional(gt_zero)) - warm_start_delay_hours: float = field(default=None, validator=validators.optional(gt_zero)) + off_hours_warm_start: float = field( + default=None, validator=validators.optional(validators.gt(0)) + ) + warm_start_delay_hours: float = field( + default=None, validator=validators.optional(validators.gt(0)) + ) def __attrs_post_init__(self): super().__attrs_post_init__() diff --git a/h2integrate/converters/co2/marine/direct_ocean_capture.py b/h2integrate/converters/co2/marine/direct_ocean_capture.py index 8c12b822a..85f255ee7 100644 --- a/h2integrate/converters/co2/marine/direct_ocean_capture.py +++ b/h2integrate/converters/co2/marine/direct_ocean_capture.py @@ -1,8 +1,8 @@ -from attrs import field, define +from attrs import field, define, validators from mcm.capture import echem_mcc from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gt_zero, contains, gte_zero, range_val, must_equal +from h2integrate.core.validators import contains, must_equal from h2integrate.core.model_baseclasses import CostModelBaseClass, PerformanceModelBaseClass @@ -50,23 +50,23 @@ class DOCPerformanceConfig(BaseConfig): save_plots (bool, optional): If true, save plots of results. Defaults to False. """ - number_ed_min: int = field(validator=gt_zero) - number_ed_max: int = field(validator=gt_zero) + number_ed_min: int = field(validator=validators.gt(0)) + number_ed_max: int = field(validator=validators.gt(0)) use_storage_tanks: bool = field() - store_hours: float = field(validator=gte_zero) - power_single_ed_w: float = field(validator=gte_zero) - flow_rate_single_ed_m3s: float = field(validator=gt_zero) - E_HCl: float = field(validator=gte_zero) - E_NaOH: float = field(validator=gte_zero) - y_ext: float = field(validator=range_val(0, 1)) - y_pur: float = field(validator=range_val(0, 1)) - y_vac: float = field(validator=range_val(0, 1)) - frac_ed_flow: float = field(validator=range_val(0, 1)) - temp_C: float = field(validator=gte_zero) - sal: float = field(validator=gte_zero) - dic_i: float = field(validator=gte_zero) - pH_i: float = field(validator=gte_zero) - initial_tank_volume_m3: float = field(validator=gte_zero) + store_hours: float = field(validator=validators.ge(0)) + power_single_ed_w: float = field(validator=validators.ge(0)) + flow_rate_single_ed_m3s: float = field(validator=validators.gt(0)) + E_HCl: float = field(validator=validators.ge(0)) + E_NaOH: float = field(validator=validators.ge(0)) + y_ext: float = field(validator=(validators.ge(0), validators.le(1))) + y_pur: float = field(validator=(validators.ge(0), validators.le(1))) + y_vac: float = field(validator=(validators.ge(0), validators.le(1))) + frac_ed_flow: float = field(validator=(validators.ge(0), validators.le(1))) + temp_C: float = field(validator=validators.ge(0)) + sal: float = field(validator=validators.ge(0)) + dic_i: float = field(validator=validators.ge(0)) + pH_i: float = field(validator=validators.ge(0)) + initial_tank_volume_m3: float = field(validator=validators.ge(0)) save_outputs: bool = field(default=False) save_plots: bool = field(default=False) diff --git a/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py b/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py index 6132068c0..de74b68ab 100644 --- a/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py +++ b/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py @@ -1,8 +1,8 @@ -from attrs import field, define +from attrs import field, define, validators from mcm.capture import echem_oae from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gt_zero, contains, gte_zero, range_val, must_equal +from h2integrate.core.validators import contains, must_equal from h2integrate.core.model_baseclasses import ( CostModelBaseClass, CostModelBaseConfig, @@ -47,18 +47,18 @@ class OAEPerformanceConfig(BaseConfig): save_plots (bool, optional): If true, save plots of results. Defaults to False. """ - number_ed_min: int = field(validator=gt_zero) - number_ed_max: int = field(validator=gt_zero) + number_ed_min: int = field(validator=validators.gt(0)) + number_ed_max: int = field(validator=validators.gt(0)) use_storage_tanks: bool = field() - store_hours: float = field(validator=gte_zero) - assumed_CDR_rate: float = field(validator=range_val(0, 1)) - frac_base_flow: float = field(validator=range_val(0, 1)) - max_ed_system_flow_rate_m3s: float = field(validator=gt_zero) - initial_temp_C: float = field(validator=gte_zero) - initial_salinity_ppt: float = field(validator=gte_zero) - initial_dic_mol_per_L: float = field(validator=gte_zero) - initial_pH: float = field(validator=gte_zero) - initial_tank_volume_m3: float = field(validator=gte_zero) + store_hours: float = field(validator=validators.ge(0)) + assumed_CDR_rate: float = field(validator=(validators.ge(0), validators.le(1))) + frac_base_flow: float = field(validator=(validators.ge(0), validators.le(1))) + max_ed_system_flow_rate_m3s: float = field(validator=validators.gt(0)) + initial_temp_C: float = field(validator=validators.ge(0)) + initial_salinity_ppt: float = field(validator=validators.ge(0)) + initial_dic_mol_per_L: float = field(validator=validators.ge(0)) + initial_pH: float = field(validator=validators.ge(0)) + initial_tank_volume_m3: float = field(validator=validators.ge(0)) acid_disposal_method: str = field( validator=contains(["sell acid", "sell rca", "acid disposal"]) ) diff --git a/h2integrate/converters/generic_converter_cost.py b/h2integrate/converters/generic_converter_cost.py index ee237b93a..1677e95bd 100644 --- a/h2integrate/converters/generic_converter_cost.py +++ b/h2integrate/converters/generic_converter_cost.py @@ -1,7 +1,6 @@ -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gte_zero, range_val_or_none from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -27,11 +26,13 @@ class GenericConverterCostConfig(CostModelBaseConfig): commodity: str = field(converter=str.strip) commodity_rate_units: str = field(converter=str.strip) - unit_capex: float | int = field(validator=gte_zero) + unit_capex: float | int = field(validator=validators.ge(0)) unit_varopex: float = field() unit_opex: float | int | None = field(default=None) - opex_fraction: float | None = field(default=None, validator=range_val_or_none(0, 1)) + opex_fraction: float | None = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) commodity_amount_units: str = field(default=None) def __attrs_post_init__(self): diff --git a/h2integrate/converters/hydrogen/basic_cost_model.py b/h2integrate/converters/hydrogen/basic_cost_model.py index 9f6cbdb50..fb754ba0c 100644 --- a/h2integrate/converters/hydrogen/basic_cost_model.py +++ b/h2integrate/converters/hydrogen/basic_cost_model.py @@ -1,10 +1,10 @@ import warnings import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gt_zero, contains, must_equal +from h2integrate.core.validators import contains, must_equal from h2integrate.core.model_baseclasses import CostModelBaseConfig from h2integrate.converters.hydrogen.electrolyzer_baseclass import ElectrolyzerCostBaseClass @@ -26,7 +26,7 @@ class BasicElectrolyzerCostModelConfig(CostModelBaseConfig): location: str = field(validator=contains(["onshore", "offshore"])) electrolyzer_capex: int = field() - time_between_replacement: int = field(validator=gt_zero) + time_between_replacement: int = field(validator=validators.gt(0)) cost_year: int = field(default=2016, converter=int, validator=must_equal(2016)) diff --git a/h2integrate/converters/hydrogen/custom_electrolyzer_cost_model.py b/h2integrate/converters/hydrogen/custom_electrolyzer_cost_model.py index 89085c6ff..b96c1a239 100644 --- a/h2integrate/converters/hydrogen/custom_electrolyzer_cost_model.py +++ b/h2integrate/converters/hydrogen/custom_electrolyzer_cost_model.py @@ -1,7 +1,6 @@ -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gte_zero from h2integrate.core.model_baseclasses import CostModelBaseConfig from h2integrate.converters.hydrogen.electrolyzer_baseclass import ElectrolyzerCostBaseClass @@ -18,8 +17,8 @@ class CustomElectrolyzerCostModelConfig(CostModelBaseConfig): fixed_om_USD_per_kW_per_year """ - capex_USD_per_kW: float = field(validator=gte_zero) - fixed_om_USD_per_kW_per_year: float = field(validator=gte_zero) + capex_USD_per_kW: float = field(validator=validators.ge(0)) + fixed_om_USD_per_kW_per_year: float = field(validator=validators.ge(0)) class CustomElectrolyzerCostModel(ElectrolyzerCostBaseClass): diff --git a/h2integrate/converters/hydrogen/geologic/mathur_modified.py b/h2integrate/converters/hydrogen/geologic/mathur_modified.py index 5f7717035..ee3922c57 100644 --- a/h2integrate/converters/hydrogen/geologic/mathur_modified.py +++ b/h2integrate/converters/hydrogen/geologic/mathur_modified.py @@ -1,9 +1,8 @@ import copy -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import range_val from h2integrate.tools.inflation.inflate import inflate_cpi, inflate_cepci from h2integrate.converters.hydrogen.geologic.h2_well_subsurface_baseclass import ( GeoH2SubsurfaceCostConfig, @@ -84,7 +83,7 @@ class GeoH2SubsurfaceCostConfig(GeoH2SubsurfaceCostConfig): contingency_pct: float = field() preprod_time: float = field() as_spent_ratio: float = field() - cost_year: int = field(converter=int, validator=range_val(2010, 2024)) + cost_year: int = field(converter=int, validator=(validators.ge(2010), validators.le(2024))) use_cost_curve: bool = field() constant_drill_cost: float | None = field(default=None) diff --git a/h2integrate/converters/hydrogen/geologic/simple_natural_geoh2.py b/h2integrate/converters/hydrogen/geologic/simple_natural_geoh2.py index 770e23ac5..15dd6d22a 100644 --- a/h2integrate/converters/hydrogen/geologic/simple_natural_geoh2.py +++ b/h2integrate/converters/hydrogen/geologic/simple_natural_geoh2.py @@ -1,8 +1,7 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import range_val from h2integrate.converters.hydrogen.geologic.h2_well_subsurface_baseclass import ( GeoH2SubsurfacePerformanceConfig, GeoH2SubsurfacePerformanceBaseClass, @@ -62,7 +61,7 @@ class NaturalGeoH2PerformanceConfig(GeoH2SubsurfacePerformanceConfig): initial_wellhead_flow: float = field() gas_flow_density: float = field() ramp_up_time_months: float = field() - percent_increase_during_rampup: float = field(validator=range_val(0, 100)) + percent_increase_during_rampup: float = field(validator=(validators.ge(0), validators.le(100))) gas_reservoir_size: float = field() use_arps_decline_curve: bool = field() decline_fit_params: dict = field(default=None) diff --git a/h2integrate/converters/hydrogen/h2_fuel_cell.py b/h2integrate/converters/hydrogen/h2_fuel_cell.py index f6288386d..27c2eb351 100644 --- a/h2integrate/converters/hydrogen/h2_fuel_cell.py +++ b/h2integrate/converters/hydrogen/h2_fuel_cell.py @@ -1,8 +1,7 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gte_zero, range_val from h2integrate.tools.constants import HHV_H2_MJ_PER_KG from h2integrate.core.model_baseclasses import ( CostModelBaseClass, @@ -21,8 +20,8 @@ class LinearH2FuelCellPerformanceConfig(BaseConfig): fuel cell (0 <= efficiency <= 1). """ - system_capacity_kw: float = field(validator=gte_zero) - fuel_cell_efficiency_hhv: float = field(validator=range_val(0, 1)) + system_capacity_kw: float = field(validator=validators.ge(0)) + fuel_cell_efficiency_hhv: float = field(validator=(validators.ge(0), validators.le(1))) class LinearH2FuelCellPerformanceModel(PerformanceModelBaseClass): @@ -169,9 +168,9 @@ class H2FuelCellCostConfig(CostModelBaseConfig): The `cost_year` field is inherited from `CostModelBaseConfig`. """ - system_capacity_kw: float = field(validator=gte_zero) - capex_per_kw: float = field(validator=gte_zero) - fixed_opex_per_kw_per_year: float = field(validator=gte_zero) + system_capacity_kw: float = field(validator=validators.ge(0)) + capex_per_kw: float = field(validator=validators.ge(0)) + fixed_opex_per_kw_per_year: float = field(validator=validators.ge(0)) class H2FuelCellCostModel(CostModelBaseClass): diff --git a/h2integrate/converters/hydrogen/htse_electrolyzer.py b/h2integrate/converters/hydrogen/htse_electrolyzer.py index 6f15326c0..87715e0b3 100644 --- a/h2integrate/converters/hydrogen/htse_electrolyzer.py +++ b/h2integrate/converters/hydrogen/htse_electrolyzer.py @@ -1,8 +1,7 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gt_zero from h2integrate.core.model_baseclasses import ( CostModelBaseConfig, ResizeablePerformanceModelBaseConfig, @@ -31,12 +30,12 @@ class HTSEElectrolyzerPerformanceModelConfig(ResizeablePerformanceModelBaseConfi stay on (unitless). Defaults to ``0.1``. """ - n_clusters: int = field(validator=gt_zero) - nominal_heat_required: float = field(validator=gt_zero) - nominal_electricity_required: float = field(validator=gt_zero) - cluster_rating_MW: float = field(validator=gt_zero) - uptime_hours_until_eol: int = field(default=80000, validator=gt_zero) - turndown_ratio: float = field(default=0.1, validator=gt_zero) + n_clusters: int = field(validator=validators.gt(0)) + nominal_heat_required: float = field(validator=validators.gt(0)) + nominal_electricity_required: float = field(validator=validators.gt(0)) + cluster_rating_MW: float = field(validator=validators.gt(0)) + uptime_hours_until_eol: int = field(default=80000, validator=validators.gt(0)) + turndown_ratio: float = field(default=0.1, validator=validators.gt(0)) class HTSEPerformanceModel(ElectrolyzerPerformanceBaseClass): @@ -274,7 +273,7 @@ class HTSECostModelConfig(CostModelBaseConfig): cost_year (int): Dollar year corresponding to the input costs. Defaults to ``2025``. """ - unit_capex: float = field(validator=gt_zero) + unit_capex: float = field(validator=validators.gt(0)) fixed_opex: float | None = field(default=None) fixed_capex: float | None = field(default=None) cost_year: int = field(default=2025, converter=int) diff --git a/h2integrate/converters/hydrogen/pem_electrolyzer.py b/h2integrate/converters/hydrogen/pem_electrolyzer.py index 5459f494e..e9b76042e 100644 --- a/h2integrate/converters/hydrogen/pem_electrolyzer.py +++ b/h2integrate/converters/hydrogen/pem_electrolyzer.py @@ -1,10 +1,10 @@ import math import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gt_zero, contains +from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import ResizeablePerformanceModelBaseConfig from h2integrate.converters.hydrogen.utilities import size_electrolyzer_for_hydrogen_demand from h2integrate.converters.hydrogen.pem_model.run_h2_PEM import run_h2_PEM @@ -43,13 +43,13 @@ class ECOElectrolyzerPerformanceModelConfig(ResizeablePerformanceModelBaseConfig (https://www.hydrogen.energy.gov/docs/hydrogenprogramlibraries/pdfs/24005-clean-hydrogen-production-cost-pem-electrolyzer.pdf?sfvrsn=8cb10889_1) """ - n_clusters: int = field(validator=gt_zero) + n_clusters: int = field(validator=validators.gt(0)) location: str = field(validator=contains(["onshore", "offshore"])) - cluster_rating_MW: float = field(validator=gt_zero) - eol_eff_percent_loss: float = field(validator=gt_zero) - uptime_hours_until_eol: int = field(validator=gt_zero) + cluster_rating_MW: float = field(validator=validators.gt(0)) + eol_eff_percent_loss: float = field(validator=validators.gt(0)) + uptime_hours_until_eol: int = field(validator=validators.gt(0)) include_degradation_penalty: bool = field() - turndown_ratio: float = field(validator=gt_zero) + turndown_ratio: float = field(validator=validators.gt(0)) electrolyzer_capex: int = field() diff --git a/h2integrate/converters/hydrogen/steam_methane_reformer.py b/h2integrate/converters/hydrogen/steam_methane_reformer.py index 9c22ba877..b3f829540 100644 --- a/h2integrate/converters/hydrogen/steam_methane_reformer.py +++ b/h2integrate/converters/hydrogen/steam_methane_reformer.py @@ -1,9 +1,8 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from openmdao.utils import units from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gt_zero, gte_zero from h2integrate.core.model_baseclasses import ( CostModelBaseClass, CostModelBaseConfig, @@ -28,9 +27,9 @@ class SteamMethaneReformerPerformanceConfig(BaseConfig): reforming process in kWh/kg. """ - system_capacity_tonnes_per_day: float = field(validator=gte_zero) - natural_gas_usage_mmbtu_per_kg: float = field(validator=gt_zero) - electricity_usage_kwh_per_kg: float = field(validator=gte_zero) + system_capacity_tonnes_per_day: float = field(validator=validators.ge(0)) + natural_gas_usage_mmbtu_per_kg: float = field(validator=validators.gt(0)) + electricity_usage_kwh_per_kg: float = field(validator=validators.ge(0)) class SteamMethaneReformerPerformanceModel(PerformanceModelBaseClass): @@ -260,9 +259,9 @@ class SteamMethaneReformerCostModelConfig(CostModelBaseConfig): cost_year (int): Dollar year corresponding to input costs. """ - capex_per_kw: float | int = field(validator=gte_zero) - fixed_opex_per_kw_per_year: float | int = field(validator=gte_zero) - variable_opex_per_kwh: float | int = field(validator=gte_zero) + capex_per_kw: float | int = field(validator=validators.ge(0)) + fixed_opex_per_kw_per_year: float | int = field(validator=validators.ge(0)) + variable_opex_per_kwh: float | int = field(validator=validators.ge(0)) class SteamMethaneReformerCostModel(CostModelBaseClass): diff --git a/h2integrate/converters/iron/iron_dri_base.py b/h2integrate/converters/iron/iron_dri_base.py index dd0c91460..f8b65d07c 100644 --- a/h2integrate/converters/iron/iron_dri_base.py +++ b/h2integrate/converters/iron/iron_dri_base.py @@ -1,11 +1,10 @@ import numpy as np import pandas as pd -from attrs import field, define +from attrs import field, define, validators from openmdao.utils import units from h2integrate import ROOT_DIR from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gte_zero from h2integrate.core.model_baseclasses import ( CostModelBaseClass, CostModelBaseConfig, @@ -286,8 +285,8 @@ class IronReductionCostBaseConfig(CostModelBaseConfig): sponge_iron_production_rate_tonnes_per_hr: float = field() cost_year: int = field(converter=int) - skilled_labor_cost: float = field(validator=gte_zero) - unskilled_labor_cost: float = field(validator=gte_zero) + skilled_labor_cost: float = field(validator=validators.ge(0)) + unskilled_labor_cost: float = field(validator=validators.ge(0)) class IronReductionPlantBaseCostComponent(CostModelBaseClass): diff --git a/h2integrate/converters/iron/iron_transport.py b/h2integrate/converters/iron/iron_transport.py index fcff1019b..6acc11dd8 100644 --- a/h2integrate/converters/iron/iron_transport.py +++ b/h2integrate/converters/iron/iron_transport.py @@ -3,12 +3,12 @@ import numpy as np import pandas as pd import openmdao.api as om -from attrs import field, define +from attrs import field, define, validators from geopy import distance from h2integrate import ROOT_DIR from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains, range_val +from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import CostModelBaseClass from h2integrate.converters.iron.load_top_down_coeffs import load_top_down_coeffs @@ -163,8 +163,8 @@ def compute(self, inputs, outputs): @define(kw_only=True) class IronTransportCostConfig(BaseConfig): - transport_year: int = field(converter=int, validator=range_val(2022, 2065)) - cost_year: int = field(converter=int, validator=range_val(2010, 2024)) + transport_year: int = field(converter=int, validator=(validators.ge(2022), validators.le(2065))) + cost_year: int = field(converter=int, validator=(validators.ge(2010), validators.le(2024))) marginal_cost: float = field(default=0.0) diff --git a/h2integrate/converters/iron/martin_mine_cost_model.py b/h2integrate/converters/iron/martin_mine_cost_model.py index d9ba84f21..c2d43b470 100644 --- a/h2integrate/converters/iron/martin_mine_cost_model.py +++ b/h2integrate/converters/iron/martin_mine_cost_model.py @@ -1,12 +1,12 @@ import copy import pandas as pd -from attrs import field, define +from attrs import field, define, validators from openmdao.utils import units from h2integrate import ROOT_DIR from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains, range_val +from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import CostModelBaseClass from h2integrate.tools.inflation.inflate import inflate_cpi @@ -35,7 +35,7 @@ class MartinIronMineCostConfig(BaseConfig): # the cost model is based on costs from 2021 and can be adjusted to another cost year # using CPI adjustment. - cost_year: int = field(converter=int, validator=range_val(2010, 2024)) + cost_year: int = field(converter=int, validator=(validators.ge(2010), validators.le(2024))) class MartinIronMineCostComponent(CostModelBaseClass): diff --git a/h2integrate/converters/natural_gas/dummy_gas_components.py b/h2integrate/converters/natural_gas/dummy_gas_components.py index f35537b74..a827e2d3e 100644 --- a/h2integrate/converters/natural_gas/dummy_gas_components.py +++ b/h2integrate/converters/natural_gas/dummy_gas_components.py @@ -11,10 +11,9 @@ """ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gt_zero, gte_zero from h2integrate.core.model_baseclasses import ( CostModelBaseClass, CostModelBaseConfig, @@ -41,12 +40,12 @@ class SimpleGasProducerPerformanceConfig(BaseConfig): random_seed: Seed for random number generator (for reproducibility) """ - base_flow_rate: float = field(default=100.0, validator=gt_zero) - base_temperature: float = field(default=300.0, validator=gt_zero) - base_pressure: float = field(default=10.0, validator=gt_zero) - flow_variation: float = field(default=20.0, validator=gte_zero) - temp_variation: float = field(default=10.0, validator=gte_zero) - pressure_variation: float = field(default=1.0, validator=gte_zero) + base_flow_rate: float = field(default=100.0, validator=validators.gt(0)) + base_temperature: float = field(default=300.0, validator=validators.gt(0)) + base_pressure: float = field(default=10.0, validator=validators.gt(0)) + flow_variation: float = field(default=20.0, validator=validators.ge(0)) + temp_variation: float = field(default=10.0, validator=validators.ge(0)) + pressure_variation: float = field(default=1.0, validator=validators.ge(0)) random_seed: int | None = field(default=None) @@ -210,8 +209,8 @@ class SimpleGasProducerCostConfig(CostModelBaseConfig): opex: Fixed operational expenditure in USD/year """ - capex: float = field(default=1_000_000.0, validator=gte_zero) - opex: float = field(default=50_000.0, validator=gte_zero) + capex: float = field(default=1_000_000.0, validator=validators.ge(0)) + opex: float = field(default=50_000.0, validator=validators.ge(0)) class SimpleGasProducerCost(CostModelBaseClass): @@ -246,8 +245,8 @@ class SimpleGasConsumerCostConfig(CostModelBaseConfig): opex: Fixed operational expenditure in USD/year """ - capex: float = field(default=2_000_000.0, validator=gte_zero) - opex: float = field(default=100_000.0, validator=gte_zero) + capex: float = field(default=2_000_000.0, validator=validators.ge(0)) + opex: float = field(default=100_000.0, validator=validators.ge(0)) class SimpleGasConsumerCost(CostModelBaseClass): diff --git a/h2integrate/converters/natural_gas/natural_gas_cc_ct.py b/h2integrate/converters/natural_gas/natural_gas_cc_ct.py index fde5b8590..c86ccccfd 100644 --- a/h2integrate/converters/natural_gas/natural_gas_cc_ct.py +++ b/h2integrate/converters/natural_gas/natural_gas_cc_ct.py @@ -1,8 +1,7 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gt_zero, gte_zero from h2integrate.core.model_baseclasses import ( CostModelBaseClass, CostModelBaseConfig, @@ -28,8 +27,8 @@ class NaturalGasPerformanceConfig(BaseConfig): - NGCC: 6-8 MMBtu/MWh """ - system_capacity_mw: float = field(validator=gte_zero) - heat_rate_mmbtu_per_mwh: float = field(validator=gt_zero) + system_capacity_mw: float = field(validator=validators.ge(0)) + heat_rate_mmbtu_per_mwh: float = field(validator=validators.gt(0)) class NaturalGasPerformanceModel(PerformanceModelBaseClass): @@ -214,11 +213,11 @@ class NaturalGasCostModelConfig(CostModelBaseConfig): cost_year (int): Dollar year corresponding to input costs. """ - system_capacity_mw: float | int = field(validator=gt_zero) - capex_per_kw: float | int = field(validator=gte_zero) - fixed_opex_per_kw_per_year: float | int = field(validator=gte_zero) - variable_opex_per_mwh: float | int = field(validator=gte_zero) - heat_rate_mmbtu_per_mwh: float = field(validator=gt_zero) + system_capacity_mw: float | int = field(validator=validators.gt(0)) + capex_per_kw: float | int = field(validator=validators.ge(0)) + fixed_opex_per_kw_per_year: float | int = field(validator=validators.ge(0)) + variable_opex_per_mwh: float | int = field(validator=validators.ge(0)) + heat_rate_mmbtu_per_mwh: float = field(validator=validators.gt(0)) class NaturalGasCostModel(CostModelBaseClass): diff --git a/h2integrate/converters/nitrogen/simple_ASU.py b/h2integrate/converters/nitrogen/simple_ASU.py index 0c5fa59aa..660426151 100644 --- a/h2integrate/converters/nitrogen/simple_ASU.py +++ b/h2integrate/converters/nitrogen/simple_ASU.py @@ -1,8 +1,8 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains, range_val +from h2integrate.core.validators import contains from h2integrate.tools.constants import N_MW, AR_MW, O2_MW from h2integrate.core.model_baseclasses import ( CostModelBaseClass, @@ -42,10 +42,18 @@ class SimpleASUPerformanceConfig(BaseConfig): rated_N2_kg_pr_hr: float | None = field(default=None) ASU_rated_power_kW: float | None = field(default=None) - N2_fraction_in_air: float = field(default=0.7811, validator=range_val(0, 1)) - O2_fraction_in_air: float = field(default=0.2096, validator=range_val(0, 1)) - Ar_fraction_in_air: float = field(default=0.0093, validator=range_val(0, 1)) - efficiency_kWh_pr_kg_N2: float = field(default=0.29, validator=range_val(0.10, 0.50)) + N2_fraction_in_air: float = field( + default=0.7811, validator=(validators.ge(0), validators.le(1)) + ) + O2_fraction_in_air: float = field( + default=0.2096, validator=(validators.ge(0), validators.le(1)) + ) + Ar_fraction_in_air: float = field( + default=0.0093, validator=(validators.ge(0), validators.le(1)) + ) + efficiency_kWh_pr_kg_N2: float = field( + default=0.29, validator=(validators.ge(0.1), validators.le(0.5)) + ) # 0.29 is efficiency of pressure swing absorption # 0.119 is efficiency of cryogenic diff --git a/h2integrate/converters/nuclear/nuclear_plant.py b/h2integrate/converters/nuclear/nuclear_plant.py index 231fe5ff2..1abfbd665 100644 --- a/h2integrate/converters/nuclear/nuclear_plant.py +++ b/h2integrate/converters/nuclear/nuclear_plant.py @@ -1,8 +1,7 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gt_zero, gte_zero from h2integrate.core.model_baseclasses import ( CostModelBaseClass, CostModelBaseConfig, @@ -18,7 +17,7 @@ class NuclearPerformanceConfig(BaseConfig): system_capacity_kw (float): Rated electric capacity in kW. """ - system_capacity_kw: float = field(validator=gt_zero) + system_capacity_kw: float = field(validator=validators.gt(0)) class QuinnNuclearPerformanceModel(PerformanceModelBaseClass): @@ -103,12 +102,12 @@ class QuinnNuclearCostModelConfig(CostModelBaseConfig): cost_year (int): Dollar year corresponding to input costs. """ - system_capacity_kw: float = field(validator=gt_zero) - capex_per_kw: float = field(validator=gte_zero) - fixed_opex_per_kw_year: float = field(validator=gte_zero) - variable_opex_per_mwh: float = field(validator=gte_zero) + system_capacity_kw: float = field(validator=validators.gt(0)) + capex_per_kw: float = field(validator=validators.ge(0)) + fixed_opex_per_kw_year: float = field(validator=validators.ge(0)) + variable_opex_per_mwh: float = field(validator=validators.ge(0)) reference_capacity_kw: float | None = field(default=None) - capex_scaling_exponent: float = field(default=1.0, validator=gt_zero) + capex_scaling_exponent: float = field(default=1.0, validator=validators.gt(0)) def __attrs_post_init__(self): if self.reference_capacity_kw is None: diff --git a/h2integrate/converters/nuclear/nuclear_plant_thermal.py b/h2integrate/converters/nuclear/nuclear_plant_thermal.py index 500a087b1..b2848cb97 100644 --- a/h2integrate/converters/nuclear/nuclear_plant_thermal.py +++ b/h2integrate/converters/nuclear/nuclear_plant_thermal.py @@ -1,8 +1,8 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gt_zero, contains, range_val +from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import ( CostModelBaseClass, CostModelBaseConfig, @@ -32,10 +32,14 @@ class SimpleThermalNuclearReactorConfig(BaseConfig): """ operating_mode: str = field(validator=contains(["heat", "electricity"])) - electricity_command_value: float = field(validator=gt_zero) - high_pressure_electrical_efficiency: float = field(validator=range_val(0.0, 1.0)) - low_pressure_electrical_efficiency: float = field(validator=range_val(0.0, 1.0)) - rated_capacity: float = field(validator=gt_zero) + electricity_command_value: float = field(validator=validators.gt(0)) + high_pressure_electrical_efficiency: float = field( + validator=(validators.ge(0), validators.le(1)) + ) + low_pressure_electrical_efficiency: float = field( + validator=(validators.ge(0), validators.le(1)) + ) + rated_capacity: float = field(validator=validators.gt(0)) minimum_heat_extract: float = field(default=0.0) @@ -206,10 +210,10 @@ class SimpleThermalNuclearReactorCostConfig(CostModelBaseConfig): cost_year (int): Dollar year corresponding to the input costs. Defaults to ``2025``. """ - rated_capacity: float = field(validator=gt_zero) - upfront_cost: float = field(validator=gt_zero) - fixed_om_cost: float = field(validator=gt_zero) - variable_om_cost: float = field(validator=gt_zero) + rated_capacity: float = field(validator=validators.gt(0)) + upfront_cost: float = field(validator=validators.gt(0)) + fixed_om_cost: float = field(validator=validators.gt(0)) + variable_om_cost: float = field(validator=validators.gt(0)) cost_year: int = field(default=2025, converter=int) diff --git a/h2integrate/converters/solar/atb_res_com_pv_cost.py b/h2integrate/converters/solar/atb_res_com_pv_cost.py index 974a66da8..ad47b75ed 100644 --- a/h2integrate/converters/solar/atb_res_com_pv_cost.py +++ b/h2integrate/converters/solar/atb_res_com_pv_cost.py @@ -1,7 +1,6 @@ -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gt_zero from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -24,8 +23,8 @@ class ATBResComPVCostModelConfig(CostModelBaseConfig): cost_year (int): dollar year corresponding to input costs """ - capex_per_kWdc: float | int = field(validator=gt_zero) - opex_per_kWdc_per_year: float | int = field(validator=gt_zero) + capex_per_kWdc: float | int = field(validator=validators.gt(0)) + opex_per_kWdc_per_year: float | int = field(validator=validators.gt(0)) pv_capacity_kWdc: float = field() diff --git a/h2integrate/converters/solar/atb_utility_pv_cost.py b/h2integrate/converters/solar/atb_utility_pv_cost.py index 2fca31e22..e0eb1c7f4 100644 --- a/h2integrate/converters/solar/atb_utility_pv_cost.py +++ b/h2integrate/converters/solar/atb_utility_pv_cost.py @@ -1,7 +1,6 @@ -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gt_zero from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -21,8 +20,8 @@ class ATBUtilityPVCostModelConfig(CostModelBaseConfig): cost_year (int): dollar year corresponding to input costs """ - capex_per_kWac: float | int = field(validator=gt_zero) - opex_per_kWac_per_year: float | int = field(validator=gt_zero) + capex_per_kWac: float | int = field(validator=validators.gt(0)) + opex_per_kWac_per_year: float | int = field(validator=validators.gt(0)) class ATBUtilityPVCostModel(CostModelBaseClass): diff --git a/h2integrate/converters/solar/solar_pysam.py b/h2integrate/converters/solar/solar_pysam.py index 60a15e666..e8803b998 100644 --- a/h2integrate/converters/solar/solar_pysam.py +++ b/h2integrate/converters/solar/solar_pysam.py @@ -2,10 +2,10 @@ import numpy as np import PySAM.Pvwattsv8 as Pvwatts -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains, range_val_or_none +from h2integrate.core.validators import contains from h2integrate.converters.tools import check_pysam_input_params from h2integrate.converters.solar.solar_baseclass import SolarPerformanceBaseClass @@ -46,14 +46,16 @@ class PYSAMSolarPlantPerformanceModelDesignConfig(BaseConfig): pv_capacity_kWdc: float = field() dc_ac_ratio: float = field( - default=None, validator=range_val_or_none(0.0, 2.0) + default=None, validator=validators.optional((validators.ge(0), validators.le(2))) ) # default value depends on config create_model_from: str = field( default="new", validator=contains(["default", "new"]), converter=(str.strip, str.lower) ) - tilt: float = field(default=None, validator=range_val_or_none(0.0, 90.0)) + tilt: float = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(90))) + ) tilt_angle_func: str = field( default="none", diff --git a/h2integrate/converters/steel/steel_eaf_base.py b/h2integrate/converters/steel/steel_eaf_base.py index 62f6dfa58..a7c578225 100644 --- a/h2integrate/converters/steel/steel_eaf_base.py +++ b/h2integrate/converters/steel/steel_eaf_base.py @@ -1,11 +1,10 @@ import numpy as np import pandas as pd -from attrs import field, define +from attrs import field, define, validators from openmdao.utils import units from h2integrate import ROOT_DIR from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gte_zero from h2integrate.core.model_baseclasses import ( CostModelBaseClass, CostModelBaseConfig, @@ -291,8 +290,8 @@ class ElectricArcFurnaceCostBaseConfig(CostModelBaseConfig): steel_production_rate_tonnes_per_hr: float = field() cost_year: int = field(converter=int) - skilled_labor_cost: float = field(validator=gte_zero) - unskilled_labor_cost: float = field(validator=gte_zero) + skilled_labor_cost: float = field(validator=validators.ge(0)) + unskilled_labor_cost: float = field(validator=validators.ge(0)) class ElectricArcFurnacePlantBaseCostComponent(CostModelBaseClass): diff --git a/h2integrate/converters/water/desal/desalination.py b/h2integrate/converters/water/desal/desalination.py index 1444b59c3..f34ee84d2 100644 --- a/h2integrate/converters/water/desal/desalination.py +++ b/h2integrate/converters/water/desal/desalination.py @@ -1,7 +1,7 @@ -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gt_zero, contains, must_equal +from h2integrate.core.validators import contains, must_equal from h2integrate.core.model_baseclasses import CostModelBaseConfig from h2integrate.converters.water.desal.desalination_baseclass import ( DesalinationCostBaseClass, @@ -21,9 +21,9 @@ class ReverseOsmosisPerformanceModelConfig(BaseConfig): Default = 997. """ - freshwater_kg_per_hour: float = field(validator=gt_zero) + freshwater_kg_per_hour: float = field(validator=validators.gt(0)) salinity: str = field(validator=contains(["seawater", "brackish"])) - freshwater_density: float = field(validator=gt_zero, default=997) + freshwater_density: float = field(validator=validators.gt(0), default=997) class ReverseOsmosisPerformanceModel(DesalinationPerformanceBaseClass): @@ -127,8 +127,8 @@ class ReverseOsmosisCostModelConfig(CostModelBaseConfig): Default = 997. """ - freshwater_kg_per_hour: float = field(validator=gt_zero) - freshwater_density: float = field(validator=gt_zero) + freshwater_kg_per_hour: float = field(validator=validators.gt(0)) + freshwater_density: float = field(validator=validators.gt(0)) cost_year: int = field(default=2013, converter=int, validator=must_equal(2013)) diff --git a/h2integrate/converters/water_power/pysam_marine_cost.py b/h2integrate/converters/water_power/pysam_marine_cost.py index 32791347b..35ac461dc 100644 --- a/h2integrate/converters/water_power/pysam_marine_cost.py +++ b/h2integrate/converters/water_power/pysam_marine_cost.py @@ -1,8 +1,8 @@ import PySAM.MhkCosts as MhkCost -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gt_zero, contains, range_val, must_equal +from h2integrate.core.validators import contains, must_equal from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -44,15 +44,15 @@ class PySAMMarineCostConfig(CostModelBaseConfig): through the [System Advisor Model](https://sam.nlr.gov/) """ - device_rating_kw: float = field(validator=gt_zero) - num_devices: int = field(validator=gt_zero) + device_rating_kw: float = field(validator=validators.gt(0)) + num_devices: int = field(validator=validators.gt(0)) reference_model_number: int = field(validator=contains([1, 2, 3, 5, 6])) - water_depth: float = field(validator=gt_zero) - distance_to_shore: float = field(validator=gt_zero) - number_rows: int = field(validator=gt_zero) - device_spacing: float = field(validator=gt_zero) - row_spacing: float = field(validator=gt_zero) - cable_system_overbuild: float = field(validator=range_val(0, 100)) + water_depth: float = field(validator=validators.gt(0)) + distance_to_shore: float = field(validator=validators.gt(0)) + number_rows: int = field(validator=validators.gt(0)) + device_spacing: float = field(validator=validators.gt(0)) + row_spacing: float = field(validator=validators.gt(0)) + cable_system_overbuild: float = field(validator=(validators.ge(0), validators.le(100))) pysam_cost_options: dict = field(default={}) cost_year: int = field( default=2022, converter=int, validator=must_equal(2022) diff --git a/h2integrate/converters/water_power/tidal_pysam.py b/h2integrate/converters/water_power/tidal_pysam.py index 41ef504d4..0df34044f 100644 --- a/h2integrate/converters/water_power/tidal_pysam.py +++ b/h2integrate/converters/water_power/tidal_pysam.py @@ -1,8 +1,8 @@ import PySAM.MhkTidal as MhkTidal -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gt_zero, contains +from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import PerformanceModelBaseClass @@ -31,8 +31,8 @@ class PySAMTidalPerformanceConfig(BaseConfig): """ - device_rating_kw: float = field(validator=gt_zero) - num_devices: int = field(validator=gt_zero) + device_rating_kw: float = field(validator=validators.gt(0)) + num_devices: int = field(validator=validators.gt(0)) tidal_power_curve: list[list[float]] | None = field(default=None) create_model_from: str = field( diff --git a/h2integrate/converters/wind/atb_wind_cost.py b/h2integrate/converters/wind/atb_wind_cost.py index 1f9a75809..efb1e0421 100644 --- a/h2integrate/converters/wind/atb_wind_cost.py +++ b/h2integrate/converters/wind/atb_wind_cost.py @@ -1,7 +1,6 @@ -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gte_zero from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -21,8 +20,8 @@ class ATBWindPlantCostModelConfig(CostModelBaseConfig): system in $/kW/year """ - capex_per_kW: float | int = field(validator=gte_zero) - opex_per_kW_per_year: float | int = field(validator=gte_zero) + capex_per_kW: float | int = field(validator=validators.ge(0)) + opex_per_kW_per_year: float | int = field(validator=validators.ge(0)) class ATBWindPlantCostModel(CostModelBaseClass): diff --git a/h2integrate/converters/wind/floris.py b/h2integrate/converters/wind/floris.py index 49cda71a1..f79306e86 100644 --- a/h2integrate/converters/wind/floris.py +++ b/h2integrate/converters/wind/floris.py @@ -5,7 +5,7 @@ from floris import TimeSeries, FlorisModel from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gt_zero, contains, range_val +from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import CacheBaseClass, CacheBaseConfig from h2integrate.converters.wind.tools.resource_tools import ( calculate_air_density, @@ -57,11 +57,11 @@ class FlorisWindPlantPerformanceConfig(CacheBaseConfig): is not yet implemented. Will result in NotImplementedError if True. """ - num_turbines: int = field(converter=int, validator=gt_zero) + num_turbines: int = field(converter=int, validator=validators.gt(0)) floris_wake_config: dict = field() floris_turbine_config: dict = field() default_turbulence_intensity: float = field() - operational_losses: float = field(validator=range_val(0.0, 100.0)) + operational_losses: float = field(validator=(validators.ge(0), validators.le(100))) hub_height: float = field(default=-1, validator=validators.ge(-1)) adjust_air_density_for_elevation: bool = field(default=False) operation_model: str = field(default="cosine-loss") diff --git a/h2integrate/converters/wind/layout/simple_grid_layout.py b/h2integrate/converters/wind/layout/simple_grid_layout.py index 26d64af60..650e260eb 100644 --- a/h2integrate/converters/wind/layout/simple_grid_layout.py +++ b/h2integrate/converters/wind/layout/simple_grid_layout.py @@ -1,8 +1,8 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig -from h2integrate.core.validators import contains, gte_zero, range_val +from h2integrate.core.validators import contains @define(kw_only=True) @@ -28,14 +28,14 @@ class BasicGridLayoutConfig(BaseConfig): row_D_spacing: float = field() turbine_D_spacing: float = field() rotation_angle_deg: float = field(default=0.0) - row_phase_offset: float = field(default=0.0, validator=range_val(0.0, 1.0)) + row_phase_offset: float = field(default=0.0, validator=(validators.ge(0), validators.le(1))) layout_shape: str = field( default="square", converter=(str.lower, str.strip), validator=contains(["square", "rectangle"]), ) - turbine_aspect_ratio: float = field(default=1.0, validator=gte_zero) + turbine_aspect_ratio: float = field(default=1.0, validator=validators.ge(0)) def __attrs_post_init__(self): if self.layout_shape == "square" and self.turbine_aspect_ratio != 1.0: diff --git a/h2integrate/converters/wind/wind_pysam.py b/h2integrate/converters/wind/wind_pysam.py index 782e26321..06f351735 100644 --- a/h2integrate/converters/wind/wind_pysam.py +++ b/h2integrate/converters/wind/wind_pysam.py @@ -5,10 +5,10 @@ import numpy as np import PySAM.Windpower as Windpower import matplotlib.pyplot as plt -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gt_zero, contains +from h2integrate.core.validators import contains from h2integrate.converters.wind.wind_plant_baseclass import WindPerformanceBaseClass from h2integrate.converters.wind.layout.simple_grid_layout import ( BasicGridLayoutConfig, @@ -73,10 +73,10 @@ class PYSAMWindPlantPerformanceModelConfig(BaseConfig): power curve. defaults to True. """ - num_turbines: int = field(converter=int, validator=gt_zero) - hub_height: float = field(validator=gt_zero) - rotor_diameter: float = field(validator=gt_zero) - turbine_rating_kw: float = field(validator=gt_zero) + num_turbines: int = field(converter=int, validator=validators.gt(0)) + hub_height: float = field(validator=validators.gt(0)) + rotor_diameter: float = field(validator=validators.gt(0)) + turbine_rating_kw: float = field(validator=validators.gt(0)) create_model_from: str = field( default="new", validator=contains(["default", "new"]), converter=(str.strip, str.lower) diff --git a/h2integrate/core/sites.py b/h2integrate/core/sites.py index eee6504f5..4d59bb738 100644 --- a/h2integrate/core/sites.py +++ b/h2integrate/core/sites.py @@ -1,14 +1,13 @@ import openmdao.api as om -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig -from h2integrate.core.validators import range_val @define class SiteBaseConfig(BaseConfig): - latitude: float = field(default=0.0, validator=range_val(-90.0, 90.0)) - longitude: float = field(default=0.0, validator=range_val(-180.0, 180.0)) + latitude: float = field(default=0.0, validator=(validators.ge(-90), validators.le(90))) + longitude: float = field(default=0.0, validator=(validators.ge(-180), validators.le(180))) class SiteBaseComponent(om.IndepVarComp): diff --git a/h2integrate/core/validators.py b/h2integrate/core/validators.py index 27bb59187..d922a1ef8 100644 --- a/h2integrate/core/validators.py +++ b/h2integrate/core/validators.py @@ -3,40 +3,6 @@ """ -def gt_zero(instance, attribute, value): - """Validates that an attribute's value is greater than zero.""" - if value <= 0: - raise ValueError(f"{attribute} must be greater than zero") - - -def gte_zero(instance, attribute, value): - """Validates that an attribute's value is greater than or equal to zero.""" - if value < 0: - raise ValueError(f"{attribute} must be greater than or equal to zero") - - -def range_val(min_val, max_val): - """Validates that an attribute's value is between two values, inclusive ([min_val, max_val]).""" - - def validator(instance, attribute, value): - if value < min_val or value > max_val: - raise ValueError(f"{attribute} must be in range [{min_val}, {max_val}]") - - return validator - - -def range_val_or_none(min_val, max_val): - """Validates that an attribute's value is between two values, inclusive ([min_val, max_val]). - Ignores None type values.""" - - def validator(instance, attribute, value): - if value is not None: - if value < min_val or value > max_val: - raise ValueError(f"{attribute} must be in range [{min_val}, {max_val}]") - - return validator - - def contains(items): """Validates that an item is part of a given list.""" diff --git a/h2integrate/demand/flexible_demand.py b/h2integrate/demand/flexible_demand.py index f4bcf23cc..3e248a3d3 100644 --- a/h2integrate/demand/flexible_demand.py +++ b/h2integrate/demand/flexible_demand.py @@ -1,8 +1,7 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gte_zero, range_val from h2integrate.demand.demand_base import DemandComponentBase, DemandComponentBaseConfig @@ -33,11 +32,11 @@ class FlexibleDemandComponentConfig(DemandComponentBaseConfig): ``sum({commodity}_flexible_demand_profile)/sum({commodity}_demand)`` """ - rated_demand: float = field(validator=gte_zero) - turndown_ratio: float = field(validator=range_val(0, 1.0)) - ramp_down_rate_fraction: float = field(validator=range_val(0, 1.0)) - ramp_up_rate_fraction: float = field(validator=range_val(0, 1.0)) - min_utilization: float = field(validator=range_val(0, 1.0)) + rated_demand: float = field(validator=validators.ge(0)) + turndown_ratio: float = field(validator=(validators.ge(0), validators.le(1))) + ramp_down_rate_fraction: float = field(validator=(validators.ge(0), validators.le(1))) + ramp_up_rate_fraction: float = field(validator=(validators.ge(0), validators.le(1))) + min_utilization: float = field(validator=(validators.ge(0), validators.le(1))) class FlexibleDemandComponent(DemandComponentBase): diff --git a/h2integrate/feedstocks/eia_ng_price.py b/h2integrate/feedstocks/eia_ng_price.py index 96cca5815..0edf06a50 100644 --- a/h2integrate/feedstocks/eia_ng_price.py +++ b/h2integrate/feedstocks/eia_ng_price.py @@ -4,12 +4,11 @@ import attrs import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.preprocess import eia, geospatial from h2integrate.core.utilities import merge_shared_inputs from h2integrate.core.file_utils import get_path, check_feedstock_dir -from h2integrate.core.validators import range_val from h2integrate.feedstocks.feedstocks import FeedstockCostModel from h2integrate.core.model_baseclasses import BaseConfig @@ -58,30 +57,26 @@ class EIANaturalGasFeedstockConfig(BaseConfig): Defaults to 0.0. """ - resource_year: int = field(validator=attrs.validators.in_(range(2001, CURRENT_YEAR + 1))) - monthly: bool = field(validator=attrs.validators.instance_of(bool)) - price_category: str = field( - converter=str.lower, validator=attrs.validators.in_(eia.EIA_NG_FACET) - ) + resource_year: int = field(validator=validators.in_(range(2001, CURRENT_YEAR + 1))) + monthly: bool = field(validator=validators.instance_of(bool)) + price_category: str = field(converter=str.lower, validator=validators.in_(eia.EIA_NG_FACET)) api_key_file: str | None = field(default=None, converter=attrs.converters.optional(get_path)) state: str = field( default=None, converter=attrs.converters.optional( attrs.converters.pipe(geospatial.convert_state_value, geospatial.convert_state_to_code) ), - validator=attrs.validators.optional( - attrs.validators.in_([*geospatial.US_STATE_MAP, *geospatial.US_STATE_MAP.values()]) + validator=validators.optional( + validators.in_([*geospatial.US_STATE_MAP, *geospatial.US_STATE_MAP.values()]) ), ) latitude: float | None = field( - default=None, validator=attrs.validators.optional(range_val(-90.0, 90.0)) + default=None, validator=validators.optional((validators.ge(-90), validators.le(90))) ) longitude: float | None = field( - default=None, validator=attrs.validators.optional(range_val(-180.0, 180.0)) - ) - site_name: str = field( - default=None, validator=attrs.validators.optional(attrs.validators.instance_of(str)) + default=None, validator=validators.optional((validators.ge(-180), validators.le(180))) ) + site_name: str = field(default=None, validator=validators.optional(validators.instance_of(str))) cost_year: int = field(default=CURRENT_YEAR) annual_cost: float = field(default=0.0, converter=float) start_up_cost: float = field(default=0.0, converter=float) @@ -94,7 +89,7 @@ class EIANaturalGasFeedstockConfig(BaseConfig): price: np.ndarray = field( default=np.zeros(8760, dtype=float), init=False, - validator=attrs.validators.instance_of(np.ndarray), + validator=validators.instance_of(np.ndarray), ) def __attrs_post_init__(self): diff --git a/h2integrate/finances/numpy_financial_npv.py b/h2integrate/finances/numpy_financial_npv.py index 2fbfa6bde..9c2d729e7 100644 --- a/h2integrate/finances/numpy_financial_npv.py +++ b/h2integrate/finances/numpy_financial_npv.py @@ -4,12 +4,11 @@ import pandas as pd import openmdao.api as om import numpy_financial as npf -from attrs import field, define +from attrs import field, define, validators from openmdao.utils.units import convert_units from h2integrate.core.utilities import BaseConfig from h2integrate.finances.tools import _compute_rate_units, check_plant_config_and_profast_params -from h2integrate.core.validators import gte_zero, range_val @define(kw_only=True) @@ -72,11 +71,11 @@ class NumpyFinancialNPVFinanceConfig(BaseConfig): ``save_npv_breakdown`` is True. Defaults to 'default'. """ - plant_life: int = field(converter=int, validator=gte_zero) - real_discount_rate: float = field(validator=range_val(0, 1)) - debt_rate: float = field(default=0.0, validator=range_val(0, 1)) - debt_equity_ratio: float = field(default=0.0, validator=gte_zero) - inflation_rate: float = field(default=0.0, validator=range_val(0, 1)) + plant_life: int = field(converter=int, validator=validators.ge(0)) + real_discount_rate: float = field(validator=(validators.ge(0), validators.le(1))) + debt_rate: float = field(default=0.0, validator=(validators.ge(0), validators.le(1))) + debt_equity_ratio: float = field(default=0.0, validator=validators.ge(0)) + inflation_rate: float = field(default=0.0, validator=(validators.ge(0), validators.le(1))) commodity_sell_price: int | float = field(default=0.0) commodity_sell_price_units: str = field() save_cost_breakdown: bool = field(default=False) diff --git a/h2integrate/finances/profast_base.py b/h2integrate/finances/profast_base.py index 955de0799..244040c90 100644 --- a/h2integrate/finances/profast_base.py +++ b/h2integrate/finances/profast_base.py @@ -1,12 +1,12 @@ import attrs import numpy as np import openmdao.api as om -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, attr_filter, attr_serializer from h2integrate.finances.tools import check_plant_config_and_profast_params from h2integrate.core.dict_utils import update_defaults -from h2integrate.core.validators import gt_zero, contains, gte_zero, range_val +from h2integrate.core.validators import contains from h2integrate.tools.profast_tools import create_years_of_operation, create_and_populate_profast @@ -172,41 +172,43 @@ class BasicProFASTParameterConfig(BaseConfig): """ # --- Primary finance parameters --- - plant_life: int = field(converter=int, validator=gte_zero) - analysis_start_year: int = field(converter=int, validator=range_val(1000, 4000)) - installation_time: int = field(converter=int, validator=gte_zero) + plant_life: int = field(converter=int, validator=validators.ge(0)) + analysis_start_year: int = field( + converter=int, validator=(validators.ge(1000), validators.le(4000)) + ) + installation_time: int = field(converter=int, validator=validators.ge(0)) - discount_rate: float = field(validator=range_val(0, 1)) - debt_equity_ratio: float = field(validator=gt_zero) - property_tax_and_insurance: float = field(validator=range_val(0, 1)) + discount_rate: float = field(validator=(validators.ge(0), validators.le(1))) + debt_equity_ratio: float = field(validator=validators.gt(0)) + property_tax_and_insurance: float = field(validator=(validators.ge(0), validators.le(1))) - total_income_tax_rate: float = field(validator=range_val(0, 1)) - capital_gains_tax_rate: float = field(validator=range_val(0, 1)) - sales_tax_rate: float = field(validator=range_val(0, 1)) - debt_interest_rate: float = field(validator=range_val(0, 1)) + total_income_tax_rate: float = field(validator=(validators.ge(0), validators.le(1))) + capital_gains_tax_rate: float = field(validator=(validators.ge(0), validators.le(1))) + sales_tax_rate: float = field(validator=(validators.ge(0), validators.le(1))) + debt_interest_rate: float = field(validator=(validators.ge(0), validators.le(1))) - inflation_rate: float = field(validator=range_val(0, 1)) + inflation_rate: float = field(validator=(validators.ge(0), validators.le(1))) cash_onhand_months: int = field(converter=int) # int? - admin_expense: float = field(validator=range_val(0, 1)) + admin_expense: float = field(validator=(validators.ge(0), validators.le(1))) # --- Optional parameters --- - non_depr_assets: float = field(default=0.0, validator=gte_zero) - end_of_proj_sale_non_depr_assets: float = field(default=0.0, validator=gte_zero) + non_depr_assets: float = field(default=0.0, validator=validators.ge(0)) + end_of_proj_sale_non_depr_assets: float = field(default=0.0, validator=validators.ge(0)) - tax_loss_carry_forward_years: int = field(default=0, validator=gte_zero) + tax_loss_carry_forward_years: int = field(default=0, validator=validators.ge(0)) tax_losses_monetized: bool = field(default=True) sell_undepreciated_cap: bool = field(default=True) credit_card_fees: float = field(default=0.0) - demand_rampup: float = field(default=0.0, validator=gte_zero) + demand_rampup: float = field(default=0.0, validator=validators.ge(0)) # --- Debt configuration --- debt_type: str = field( default="Revolving debt", validator=contains(["Revolving debt", "One time loan"]) ) - loan_period_if_used: int = field(default=0, validator=gte_zero) + loan_period_if_used: int = field(default=0, validator=validators.ge(0)) # --- Nested dictionaries (financial categories) --- commodity: dict = field( @@ -314,7 +316,9 @@ class ProFASTDefaultCapitalItem(BaseConfig): depr_period: int = field(converter=int, validator=contains([3, 5, 7, 10, 15, 20])) depr_type: str = field(converter=str.strip, validator=contains(["MACRS", "Straight line"])) refurb: int | float | list[float] = field(default=[0.0]) - replacement_cost_percent: float = field(default=0.0, validator=range_val(0, 1)) + replacement_cost_percent: float = field( + default=0.0, validator=(validators.ge(0), validators.le(1)) + ) def create_dict(self): """Create a ProFAST-compatible dictionary of attributes. diff --git a/h2integrate/resource/solar/nlr_developer_goes_api_models.py b/h2integrate/resource/solar/nlr_developer_goes_api_models.py index 00af223d3..6e89a2891 100644 --- a/h2integrate/resource/solar/nlr_developer_goes_api_models.py +++ b/h2integrate/resource/solar/nlr_developer_goes_api_models.py @@ -1,8 +1,8 @@ from pathlib import Path -from attrs import field, define +from attrs import field, define, validators -from h2integrate.core.validators import contains, range_val +from h2integrate.core.validators import contains from h2integrate.resource.resource_base import ResourceBaseAPIConfig from h2integrate.resource.solar.nlr_developer_api_base import NLRDeveloperAPISolarResourceBase @@ -33,7 +33,7 @@ class GOESAggregatedAPIConfig(ResourceBaseAPIConfig): """ - resource_year: int = field(converter=int, validator=range_val(1998, 2024)) + resource_year: int = field(converter=int, validator=(validators.ge(1998), validators.le(2024))) dataset_desc: str = "goes_aggregated_v4" resource_type: str = "solar" valid_intervals: list[int] = field(factory=lambda: [30, 60]) @@ -82,7 +82,7 @@ class GOESConusAPIConfig(ResourceBaseAPIConfig): """ - resource_year: int = field(converter=int, validator=range_val(2018, 2024)) + resource_year: int = field(converter=int, validator=(validators.ge(2018), validators.le(2024))) dataset_desc: str = "goes_conus_v4" resource_type: str = "solar" valid_intervals: list[int] = field(factory=lambda: [5, 15, 30, 60]) @@ -132,7 +132,7 @@ class GOESFullDiscAPIConfig(ResourceBaseAPIConfig): """ - resource_year: int = field(converter=int, validator=range_val(2018, 2024)) + resource_year: int = field(converter=int, validator=(validators.ge(2018), validators.le(2024))) dataset_desc: str = "goes_fulldisc_v4" resource_type: str = "solar" valid_intervals: list[int] = field(factory=lambda: [10, 30, 60]) diff --git a/h2integrate/resource/solar/nlr_developer_himawari_api_models.py b/h2integrate/resource/solar/nlr_developer_himawari_api_models.py index a6b74350a..5416ca4e3 100644 --- a/h2integrate/resource/solar/nlr_developer_himawari_api_models.py +++ b/h2integrate/resource/solar/nlr_developer_himawari_api_models.py @@ -1,8 +1,8 @@ from pathlib import Path -from attrs import field, define +from attrs import field, define, validators -from h2integrate.core.validators import contains, range_val +from h2integrate.core.validators import contains from h2integrate.resource.resource_base import ResourceBaseAPIConfig from h2integrate.resource.solar.nlr_developer_api_base import NLRDeveloperAPISolarResourceBase @@ -34,7 +34,7 @@ class Himawari7SolarAPIConfig(ResourceBaseAPIConfig): """ - resource_year: int = field(converter=int, validator=range_val(2011, 2015)) + resource_year: int = field(converter=int, validator=(validators.ge(2011), validators.le(2015))) dataset_desc: str = "himawari7_v3" resource_type: str = "solar" valid_intervals: list[int] = field(factory=lambda: [30, 60]) @@ -83,7 +83,7 @@ class Himawari8SolarAPIConfig(ResourceBaseAPIConfig): """ - resource_year: int = field(converter=int, validator=range_val(2016, 2020)) + resource_year: int = field(converter=int, validator=(validators.ge(2016), validators.le(2020))) dataset_desc: str = "himawari8_v3" resource_type: str = "solar" valid_intervals: list[int] = field(factory=lambda: [10, 30, 60]) diff --git a/h2integrate/resource/solar/nlr_developer_meteosat_prime_meridian_models.py b/h2integrate/resource/solar/nlr_developer_meteosat_prime_meridian_models.py index a0c572415..8a22ee33b 100644 --- a/h2integrate/resource/solar/nlr_developer_meteosat_prime_meridian_models.py +++ b/h2integrate/resource/solar/nlr_developer_meteosat_prime_meridian_models.py @@ -1,8 +1,8 @@ from pathlib import Path -from attrs import field, define +from attrs import field, define, validators -from h2integrate.core.validators import contains, range_val +from h2integrate.core.validators import contains from h2integrate.resource.resource_base import ResourceBaseAPIConfig from h2integrate.resource.solar.nlr_developer_api_base import NLRDeveloperAPISolarResourceBase @@ -34,7 +34,7 @@ class MeteosatPrimeMeridianAPIConfig(ResourceBaseAPIConfig): """ - resource_year: int = field(converter=int, validator=range_val(2005, 2022)) + resource_year: int = field(converter=int, validator=(validators.ge(2005), validators.le(2022))) dataset_desc: str = "nsrdb_msg_v4" resource_type: str = "solar" valid_intervals: list[int] = field(factory=lambda: [15, 30, 60]) diff --git a/h2integrate/resource/solar/openmeteo_solar.py b/h2integrate/resource/solar/openmeteo_solar.py index b0cb81f6e..44839e59a 100644 --- a/h2integrate/resource/solar/openmeteo_solar.py +++ b/h2integrate/resource/solar/openmeteo_solar.py @@ -5,10 +5,9 @@ import pandas as pd import requests_cache import openmeteo_requests -from attrs import field, define +from attrs import field, define, validators from retry_requests import retry -from h2integrate.core.validators import range_val from h2integrate.resource.resource_base import ResourceBaseAPIConfig from h2integrate.resource.utilities.download_tools import make_time_index_openmeteo from h2integrate.resource.solar.solar_resource_base import SolarResourceBaseAPIModel @@ -44,7 +43,9 @@ class OpenMeteoHistoricalSolarAPIConfig(ResourceBaseAPIConfig): """ - resource_year: int = field(converter=int, validator=range_val(1940, datetime.now().year - 1)) + resource_year: int = field( + converter=int, validator=(validators.ge(1940), validators.le(datetime.now().year - 1)) + ) include_leap_day: bool = field(default=False) dataset_desc: str = "openmeteo_archive_solar" resource_type: str = "solar" diff --git a/h2integrate/resource/wind/nlr_developer_wtk_api.py b/h2integrate/resource/wind/nlr_developer_wtk_api.py index 34dc09449..ee668ab24 100644 --- a/h2integrate/resource/wind/nlr_developer_wtk_api.py +++ b/h2integrate/resource/wind/nlr_developer_wtk_api.py @@ -2,9 +2,8 @@ from pathlib import Path import pandas as pd -from attrs import field, define +from attrs import field, define, validators -from h2integrate.core.validators import range_val from h2integrate.resource.resource_base import ResourceBaseAPIConfig from h2integrate.resource.wind.wind_resource_base import WindResourceBaseAPIModel from h2integrate.resource.utilities.nlr_developer_api_keys import ( @@ -38,7 +37,7 @@ class WTKNLRDeveloperAPIConfig(ResourceBaseAPIConfig): """ - resource_year: int = field(converter=int, validator=range_val(2007, 2014)) + resource_year: int = field(converter=int, validator=(validators.ge(2007), validators.le(2014))) dataset_desc: str = "wtk_v2" resource_type: str = "wind" valid_intervals: list[int] = field(factory=lambda: [5, 15, 30, 60]) diff --git a/h2integrate/resource/wind/openmeteo_wind.py b/h2integrate/resource/wind/openmeteo_wind.py index 8b89cae7b..3f1e4ec2a 100644 --- a/h2integrate/resource/wind/openmeteo_wind.py +++ b/h2integrate/resource/wind/openmeteo_wind.py @@ -4,10 +4,9 @@ import pandas as pd import requests_cache import openmeteo_requests -from attrs import field, define +from attrs import field, define, validators from retry_requests import retry -from h2integrate.core.validators import range_val from h2integrate.resource.resource_base import ResourceBaseAPIConfig from h2integrate.resource.wind.wind_resource_base import WindResourceBaseAPIModel from h2integrate.resource.utilities.download_tools import make_time_index_openmeteo @@ -39,7 +38,9 @@ class OpenMeteoHistoricalWindAPIConfig(ResourceBaseAPIConfig): """ - resource_year: int = field(converter=int, validator=range_val(1940, datetime.now().year - 1)) + resource_year: int = field( + converter=int, validator=(validators.ge(1940), validators.le(datetime.now().year - 1)) + ) include_leap_day: bool = field(default=False) dataset_desc: str = "openmeteo_archive" resource_type: str = "wind" diff --git a/h2integrate/storage/battery/atb_battery_cost.py b/h2integrate/storage/battery/atb_battery_cost.py index f63e943fe..434729186 100644 --- a/h2integrate/storage/battery/atb_battery_cost.py +++ b/h2integrate/storage/battery/atb_battery_cost.py @@ -1,8 +1,8 @@ -from attrs import field, define +from attrs import field, define, validators from openmdao.utils import units from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import contains, gte_zero, range_val +from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -31,9 +31,9 @@ class ATBBatteryCostConfig(CostModelBaseConfig): (i.e., kW*h or kg). If not provided, defaults to commodity_rate_units*h. """ - energy_capex: float | int = field(validator=gte_zero) - power_capex: float | int = field(validator=gte_zero) - opex_fraction: float = field(validator=range_val(0, 1)) + energy_capex: float | int = field(validator=validators.ge(0)) + power_capex: float | int = field(validator=validators.ge(0)) + opex_fraction: float = field(validator=(validators.ge(0), validators.le(1))) max_capacity: float = field() max_charge_rate: float = field() commodity_rate_units: str = field(validator=contains(["W", "kW", "MW", "GW", "TW"])) diff --git a/h2integrate/storage/battery/pysam_battery.py b/h2integrate/storage/battery/pysam_battery.py index 22804de6d..5b69cf2eb 100644 --- a/h2integrate/storage/battery/pysam_battery.py +++ b/h2integrate/storage/battery/pysam_battery.py @@ -1,10 +1,10 @@ import numpy as np import PySAM.BatteryTools as BatteryTools import PySAM.BatteryStateful as BatteryStateful -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gt_zero, contains, range_val +from h2integrate.core.validators import contains from h2integrate.storage.storage_baseclass import ( StoragePerformanceBase, StoragePerformanceBaseConfig, @@ -51,14 +51,14 @@ class PySAMBatteryPerformanceModelConfig(StoragePerformanceBaseConfig): Defaults to 0.001. """ - max_capacity: float = field(validator=gt_zero) - max_charge_rate: float = field(validator=gt_zero) + max_capacity: float = field(validator=validators.gt(0)) + max_charge_rate: float = field(validator=validators.gt(0)) chemistry: str = field( validator=contains(["LFPGraphite", "LMOLTO", "LeadAcid", "NMCGraphite"]), ) - init_soc_fraction: float = field(validator=range_val(0, 1)) + init_soc_fraction: float = field(validator=(validators.ge(0), validators.le(1))) control_variable: str = field( default="input_power", validator=contains(["input_power", "input_current"]) ) diff --git a/h2integrate/storage/generic_storage_cost.py b/h2integrate/storage/generic_storage_cost.py index 4e4cd7bbe..8a2887df3 100644 --- a/h2integrate/storage/generic_storage_cost.py +++ b/h2integrate/storage/generic_storage_cost.py @@ -1,8 +1,8 @@ -from attrs import field, define +from attrs import field, define, validators from openmdao.utils import units from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import contains, gte_zero, range_val +from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -20,9 +20,9 @@ class GenericStorageCostConfig(CostModelBaseConfig): from `CostModelBaseConfig`. """ - capacity_capex: float | int = field(validator=gte_zero) - charge_capex: float | int = field(validator=gte_zero) - opex_fraction: float = field(validator=range_val(0, 1)) + capacity_capex: float | int = field(validator=validators.ge(0)) + charge_capex: float | int = field(validator=validators.ge(0)) + opex_fraction: float = field(validator=(validators.ge(0), validators.le(1))) max_capacity: float = field() max_charge_rate: float = field() commodity_rate_units: str = field( diff --git a/h2integrate/storage/hydrogen/h2_storage_cost.py b/h2integrate/storage/hydrogen/h2_storage_cost.py index dd5ce37a2..ee2b5a30f 100644 --- a/h2integrate/storage/hydrogen/h2_storage_cost.py +++ b/h2integrate/storage/hydrogen/h2_storage_cost.py @@ -1,9 +1,9 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from openmdao.utils import units from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains, gte_zero, range_val +from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import CostModelBaseClass from h2integrate.storage.hydrogen.h2_transport.h2_compression import Compressor @@ -41,16 +41,18 @@ class HydrogenStorageBaseCostModelConfig(BaseConfig): commodity_rate_units: str = field(default="kg/h", validator=contains(["kg/h", "g/h", "t/h"])) cost_year: int = field(default=2018, converter=int, validator=contains([2018])) - labor_rate: float = field(default=37.39817, validator=gte_zero) - insurance: float = field(default=0.01, validator=range_val(0, 1)) - property_taxes: float = field(default=0.01, validator=range_val(0, 1)) - licensing_permits: float = field(default=0.001, validator=range_val(0, 1)) - compressor_om: float = field(default=0.04, validator=range_val(0, 1)) - facility_om: float = field(default=0.01, validator=range_val(0, 1)) - inlet_pressure_bar: float = field(default=20, validator=gte_zero) - storage_pressure_bar: float = field(default=200, validator=range_val(0, 700)) - cg_capex_per_kg_350_bar: float = field(default=1333.11625, validator=gte_zero) - cg_capex_per_kg_700_bar: float = field(default=1999.67437, validator=gte_zero) + labor_rate: float = field(default=37.39817, validator=validators.ge(0)) + insurance: float = field(default=0.01, validator=(validators.ge(0), validators.le(1))) + property_taxes: float = field(default=0.01, validator=(validators.ge(0), validators.le(1))) + licensing_permits: float = field(default=0.001, validator=(validators.ge(0), validators.le(1))) + compressor_om: float = field(default=0.04, validator=(validators.ge(0), validators.le(1))) + facility_om: float = field(default=0.01, validator=(validators.ge(0), validators.le(1))) + inlet_pressure_bar: float = field(default=20, validator=validators.ge(0)) + storage_pressure_bar: float = field( + default=200, validator=(validators.ge(0), validators.le(700)) + ) + cg_capex_per_kg_350_bar: float = field(default=1333.11625, validator=validators.ge(0)) + cg_capex_per_kg_700_bar: float = field(default=1999.67437, validator=validators.ge(0)) marginal_cost: float = field(default=0.0) def __attrs_post_init__(self): diff --git a/h2integrate/storage/simple_storage_auto_sizing.py b/h2integrate/storage/simple_storage_auto_sizing.py index 785bf3b59..c3ead9012 100644 --- a/h2integrate/storage/simple_storage_auto_sizing.py +++ b/h2integrate/storage/simple_storage_auto_sizing.py @@ -1,8 +1,7 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import range_val_or_none from h2integrate.storage.storage_baseclass import ( StoragePerformanceBase, StoragePerformanceBaseConfig, @@ -46,9 +45,15 @@ class StorageSizingModelConfig(StoragePerformanceBaseConfig): set_demand_as_avg_commodity_in: bool = field() demand_profile: int | float | list = field(default=0.0) - charge_efficiency: float | None = field(default=None, validator=range_val_or_none(0, 1)) - discharge_efficiency: float | None = field(default=None, validator=range_val_or_none(0, 1)) - round_trip_efficiency: float | None = field(default=None, validator=range_val_or_none(0, 1)) + charge_efficiency: float | None = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) + discharge_efficiency: float | None = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) + round_trip_efficiency: float | None = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) commodity_amount_units: str = field(default=None) diff --git a/h2integrate/storage/storage_baseclass.py b/h2integrate/storage/storage_baseclass.py index 829e9696a..4fe0c8f5d 100644 --- a/h2integrate/storage/storage_baseclass.py +++ b/h2integrate/storage/storage_baseclass.py @@ -1,9 +1,8 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from openmdao.utils import units as om_units from h2integrate.core.utilities import BaseConfig -from h2integrate.core.validators import range_val from h2integrate.core.model_baseclasses import PerformanceModelBaseClass @@ -21,8 +20,8 @@ class StoragePerformanceBaseConfig(BaseConfig): """ # Below are used in all storage models - min_soc_fraction: float = field(validator=range_val(0, 1)) - max_soc_fraction: float = field(validator=range_val(0, 1)) + min_soc_fraction: float = field(validator=(validators.ge(0), validators.le(1))) + max_soc_fraction: float = field(validator=(validators.ge(0), validators.le(1))) demand_profile: int | float | list = field() diff --git a/h2integrate/storage/storage_performance_model.py b/h2integrate/storage/storage_performance_model.py index d597446b7..a020291c5 100644 --- a/h2integrate/storage/storage_performance_model.py +++ b/h2integrate/storage/storage_performance_model.py @@ -1,8 +1,7 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gt_zero, range_val, range_val_or_none from h2integrate.storage.storage_baseclass import ( StoragePerformanceBase, StoragePerformanceBaseConfig, @@ -55,18 +54,24 @@ class StoragePerformanceModelConfig(StoragePerformanceBaseConfig): commodity: str = field() commodity_rate_units: str = field() - max_capacity: float = field(validator=gt_zero) - max_charge_rate: float = field(validator=gt_zero) + max_capacity: float = field(validator=validators.gt(0)) + max_charge_rate: float = field(validator=validators.gt(0)) - init_soc_fraction: float = field(validator=range_val(0, 1)) + init_soc_fraction: float = field(validator=(validators.ge(0), validators.le(1))) commodity_amount_units: str = field(default=None) max_discharge_rate: float | None = field(default=None) charge_equals_discharge: bool = field(default=True) - charge_efficiency: float | None = field(default=None, validator=range_val_or_none(0, 1)) - discharge_efficiency: float | None = field(default=None, validator=range_val_or_none(0, 1)) - round_trip_efficiency: float | None = field(default=None, validator=range_val_or_none(0, 1)) + charge_efficiency: float | None = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) + discharge_efficiency: float | None = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) + round_trip_efficiency: float | None = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) def __attrs_post_init__(self): """ diff --git a/h2integrate/transporters/generic_splitter.py b/h2integrate/transporters/generic_splitter.py index 1d33affaf..b85007e05 100644 --- a/h2integrate/transporters/generic_splitter.py +++ b/h2integrate/transporters/generic_splitter.py @@ -1,9 +1,9 @@ import numpy as np import openmdao.api as om -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig -from h2integrate.core.validators import contains, range_val_or_none +from h2integrate.core.validators import contains @define(kw_only=True) @@ -19,7 +19,9 @@ class GenericSplitterPerformanceConfig(BaseConfig): ) commodity: str = field(converter=(str.lower, str.strip)) commodity_rate_units: str = field() - fraction_to_priority_tech: float = field(default=None, validator=range_val_or_none(0, 1)) + fraction_to_priority_tech: float = field( + default=None, validator=validators.optional((validators.ge(0), validators.le(1))) + ) prescribed_commodity_to_priority_tech: float = field(default=None) def __attrs_post_init__(self): From b0a98be542351dc3bc18204921cfba03e2ad4e96 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:32:10 -0700 Subject: [PATCH 02/11] add handling for single key input for validation --- h2integrate/core/validators.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/h2integrate/core/validators.py b/h2integrate/core/validators.py index d922a1ef8..a85a6fb81 100644 --- a/h2integrate/core/validators.py +++ b/h2integrate/core/validators.py @@ -20,7 +20,8 @@ def has_required_keys(required_keys): required_keys (list[str] | tuple[str, ...]): Keys that must be present in the input dictionary. """ - + if isinstance(required_keys, str): + required_keys = [required_keys] required_keys = tuple(required_keys) def validator(instance, attribute, value): From f8cd08762419bdb6cf1dc0527403569b93b6eb43 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:59:08 -0700 Subject: [PATCH 03/11] replace contains with attrs.validators.in_ --- .../storage/plm_openloop_storage_controller.py | 6 +++--- .../system_level/solver_options.py | 3 +-- .../converters/co2/marine/direct_ocean_capture.py | 4 ++-- .../co2/marine/ocean_alkalinity_enhancement.py | 4 ++-- h2integrate/converters/grid/grid.py | 7 +++---- h2integrate/converters/hydrogen/basic_cost_model.py | 4 ++-- .../geologic/h2_well_subsurface_baseclass.py | 13 ++++++------- h2integrate/converters/hydrogen/pem_electrolyzer.py | 3 +-- .../converters/hydrogen/singlitico_cost_model.py | 6 +++--- h2integrate/converters/iron/humbert_ewin_perf.py | 7 ++++--- .../converters/iron/humbert_stinn_ewin_cost.py | 8 +++++--- h2integrate/converters/iron/iron_transport.py | 3 +-- .../converters/iron/martin_mine_cost_model.py | 7 ++++--- .../converters/iron/martin_mine_perf_model.py | 9 +++++---- .../converters/methanol/methanol_baseclass.py | 9 ++++----- h2integrate/converters/nitrogen/simple_ASU.py | 7 ++++--- .../converters/nuclear/nuclear_plant_thermal.py | 3 +-- h2integrate/converters/solar/solar_pysam.py | 9 +++++---- .../steel/cmu_electric_arc_furnace_dri.py | 7 +++---- h2integrate/converters/water/desal/desalination.py | 4 ++-- .../converters/water_power/pysam_marine_cost.py | 4 ++-- h2integrate/converters/water_power/tidal_pysam.py | 7 ++++--- h2integrate/converters/wind/floris.py | 4 ++-- .../converters/wind/layout/simple_grid_layout.py | 3 +-- h2integrate/converters/wind/wind_pysam.py | 9 +++++---- h2integrate/finances/profast_base.py | 9 +++++---- .../resource/solar/nlr_developer_goes_api_models.py | 3 +-- .../solar/nlr_developer_himawari_api_models.py | 3 +-- .../nlr_developer_meteosat_prime_meridian_models.py | 3 +-- h2integrate/storage/battery/atb_battery_cost.py | 3 +-- h2integrate/storage/battery/pysam_battery.py | 5 ++--- h2integrate/storage/generic_storage_cost.py | 3 +-- h2integrate/storage/hydrogen/h2_storage_cost.py | 9 +++++---- h2integrate/storage/hydrogen/mch_storage.py | 7 +++---- h2integrate/transporters/generic_splitter.py | 4 ++-- h2integrate/transporters/generic_summer.py | 5 ++--- .../transporters/test/test_generic_splitter.py | 2 +- 37 files changed, 100 insertions(+), 106 deletions(-) diff --git a/h2integrate/control/control_strategies/storage/plm_openloop_storage_controller.py b/h2integrate/control/control_strategies/storage/plm_openloop_storage_controller.py index 50621bace..073cb9bac 100644 --- a/h2integrate/control/control_strategies/storage/plm_openloop_storage_controller.py +++ b/h2integrate/control/control_strategies/storage/plm_openloop_storage_controller.py @@ -4,10 +4,10 @@ import numpy as np import pandas as pd -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs, build_time_series_from_plant_config -from h2integrate.core.validators import contains, has_required_keys +from h2integrate.core.validators import has_required_keys from h2integrate.control.control_strategies.openloop_control_base import ( OpenLoopControlBase, OpenLoopControlBaseConfig, @@ -61,7 +61,7 @@ class PeakLoadManagementHeuristicOpenLoopStorageControllerConfig(OpenLoopControl demand_profile_upstream: int | float | list | None = field() dispatch_priority_demand_profile: str = field( - validator=contains(["demand_profile", "demand_profile_upstream"]), + validator=validators.in_(["demand_profile", "demand_profile_upstream"]), ) n_override_events: int | None = field(default=None) override_events_period: int | str | None = field(default=None) diff --git a/h2integrate/control/control_strategies/system_level/solver_options.py b/h2integrate/control/control_strategies/system_level/solver_options.py index 4013b0c4c..97d6b8cfa 100644 --- a/h2integrate/control/control_strategies/system_level/solver_options.py +++ b/h2integrate/control/control_strategies/system_level/solver_options.py @@ -4,7 +4,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig -from h2integrate.core.validators import contains @define(kw_only=True) @@ -29,7 +28,7 @@ class SLCSolverOptionsConfig(BaseConfig): """ solver_name: str = field( - default="gauss_seidel", validator=contains(["gauss_seidel", "newton", "block_jacobi"]) + default="gauss_seidel", validator=validators.in_(["gauss_seidel", "newton", "block_jacobi"]) ) max_iter: int = field(default=20, converter=int, validator=validators.ge(0)) atol: float | None = field(default=None) diff --git a/h2integrate/converters/co2/marine/direct_ocean_capture.py b/h2integrate/converters/co2/marine/direct_ocean_capture.py index 85f255ee7..de384f3b7 100644 --- a/h2integrate/converters/co2/marine/direct_ocean_capture.py +++ b/h2integrate/converters/co2/marine/direct_ocean_capture.py @@ -2,7 +2,7 @@ from mcm.capture import echem_mcc from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains, must_equal +from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import CostModelBaseClass, PerformanceModelBaseClass @@ -150,7 +150,7 @@ class DOCCostModelConfig(DOCPerformanceConfig): cost_year (int): dollar year corresponding to cost values """ - infrastructure_type: str = field(validator=contains(["desal", "swCool", "new"])) + infrastructure_type: str = field(validator=validators.in_(["desal", "swCool", "new"])) cost_year: int = field(default=2023, converter=int, validator=must_equal(2023)) diff --git a/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py b/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py index de74b68ab..ef8a59d03 100644 --- a/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py +++ b/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py @@ -2,7 +2,7 @@ from mcm.capture import echem_oae from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains, must_equal +from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import ( CostModelBaseClass, CostModelBaseConfig, @@ -60,7 +60,7 @@ class OAEPerformanceConfig(BaseConfig): initial_pH: float = field(validator=validators.ge(0)) initial_tank_volume_m3: float = field(validator=validators.ge(0)) acid_disposal_method: str = field( - validator=contains(["sell acid", "sell rca", "acid disposal"]) + validator=validators.in_(["sell acid", "sell rca", "acid disposal"]) ) save_outputs: bool = field(default=False) save_plots: bool = field(default=False) diff --git a/h2integrate/converters/grid/grid.py b/h2integrate/converters/grid/grid.py index ede74548b..836f7ec1d 100644 --- a/h2integrate/converters/grid/grid.py +++ b/h2integrate/converters/grid/grid.py @@ -1,8 +1,7 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import ( CostModelBaseClass, CostModelBaseConfig, @@ -184,10 +183,10 @@ class GridCostModelConfig(CostModelBaseConfig): electricity_buy_price: float | list[float] | np.ndarray | None = field(default=None) # $/kWh electricity_sell_price: float | list[float] | np.ndarray | None = field(default=None) # $/kWh buy_price_mode: str | None = field( - default="per_timestep", validator=contains(["per_year", "per_timestep", "constant"]) + default="per_timestep", validator=validators.in_(["per_year", "per_timestep", "constant"]) ) sell_price_mode: str | None = field( - default="per_timestep", validator=contains(["per_year", "per_timestep", "constant"]) + default="per_timestep", validator=validators.in_(["per_year", "per_timestep", "constant"]) ) diff --git a/h2integrate/converters/hydrogen/basic_cost_model.py b/h2integrate/converters/hydrogen/basic_cost_model.py index fb754ba0c..97acce5ac 100644 --- a/h2integrate/converters/hydrogen/basic_cost_model.py +++ b/h2integrate/converters/hydrogen/basic_cost_model.py @@ -4,7 +4,7 @@ from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import contains, must_equal +from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import CostModelBaseConfig from h2integrate.converters.hydrogen.electrolyzer_baseclass import ElectrolyzerCostBaseClass @@ -24,7 +24,7 @@ class BasicElectrolyzerCostModelConfig(CostModelBaseConfig): (https://www.hydrogen.energy.gov/docs/hydrogenprogramlibraries/pdfs/24005-clean-hydrogen-production-cost-pem-electrolyzer.pdf?sfvrsn=8cb10889_1) """ - location: str = field(validator=contains(["onshore", "offshore"])) + location: str = field(validator=validators.in_(["onshore", "offshore"])) electrolyzer_capex: int = field() time_between_replacement: int = field(validator=validators.gt(0)) cost_year: int = field(default=2016, converter=int, validator=must_equal(2016)) diff --git a/h2integrate/converters/hydrogen/geologic/h2_well_subsurface_baseclass.py b/h2integrate/converters/hydrogen/geologic/h2_well_subsurface_baseclass.py index 3ab8eb7f5..5d20f9e01 100644 --- a/h2integrate/converters/hydrogen/geologic/h2_well_subsurface_baseclass.py +++ b/h2integrate/converters/hydrogen/geologic/h2_well_subsurface_baseclass.py @@ -1,7 +1,6 @@ -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import ( CostModelBaseClass, CostModelBaseConfig, @@ -39,9 +38,9 @@ class GeoH2SubsurfacePerformanceConfig(BaseConfig): """ borehole_depth: float = field() - well_diameter: str = field(validator=contains(["small", "large"])) - well_geometry: str = field(validator=contains(["vertical", "horizontal"])) - rock_type: str = field(validator=contains(["peridotite", "bei_troctolite"])) + well_diameter: str = field(validator=validators.in_(["small", "large"])) + well_geometry: str = field(validator=validators.in_(["vertical", "horizontal"])) + rock_type: str = field(validator=validators.in_(["peridotite", "bei_troctolite"])) grain_size: float = field() @@ -134,8 +133,8 @@ class GeoH2SubsurfaceCostConfig(CostModelBaseConfig): """ borehole_depth: float = field() - well_diameter: str = field(validator=contains(["small", "large"])) - well_geometry: str = field(validator=contains(["vertical", "horizontal"])) + well_diameter: str = field(validator=validators.in_(["small", "large"])) + well_geometry: str = field(validator=validators.in_(["vertical", "horizontal"])) class GeoH2SubsurfaceCostBaseClass(CostModelBaseClass): diff --git a/h2integrate/converters/hydrogen/pem_electrolyzer.py b/h2integrate/converters/hydrogen/pem_electrolyzer.py index e9b76042e..7e31df626 100644 --- a/h2integrate/converters/hydrogen/pem_electrolyzer.py +++ b/h2integrate/converters/hydrogen/pem_electrolyzer.py @@ -4,7 +4,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import ResizeablePerformanceModelBaseConfig from h2integrate.converters.hydrogen.utilities import size_electrolyzer_for_hydrogen_demand from h2integrate.converters.hydrogen.pem_model.run_h2_PEM import run_h2_PEM @@ -44,7 +43,7 @@ class ECOElectrolyzerPerformanceModelConfig(ResizeablePerformanceModelBaseConfig """ n_clusters: int = field(validator=validators.gt(0)) - location: str = field(validator=contains(["onshore", "offshore"])) + location: str = field(validator=validators.in_(["onshore", "offshore"])) cluster_rating_MW: float = field(validator=validators.gt(0)) eol_eff_percent_loss: float = field(validator=validators.gt(0)) uptime_hours_until_eol: int = field(validator=validators.gt(0)) diff --git a/h2integrate/converters/hydrogen/singlitico_cost_model.py b/h2integrate/converters/hydrogen/singlitico_cost_model.py index 19b5642e7..5e1317447 100644 --- a/h2integrate/converters/hydrogen/singlitico_cost_model.py +++ b/h2integrate/converters/hydrogen/singlitico_cost_model.py @@ -1,7 +1,7 @@ -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import contains, must_equal +from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import CostModelBaseConfig from h2integrate.converters.hydrogen.electrolyzer_baseclass import ElectrolyzerCostBaseClass @@ -19,7 +19,7 @@ class SingliticoCostModelConfig(CostModelBaseConfig): (https://www.hydrogen.energy.gov/docs/hydrogenprogramlibraries/pdfs/24005-clean-hydrogen-production-cost-pem-electrolyzer.pdf?sfvrsn=8cb10889_1) """ - location: str = field(validator=contains(["onshore", "offshore"])) + location: str = field(validator=validators.in_(["onshore", "offshore"])) electrolyzer_capex: int = field() cost_year: int = field(default=2021, converter=int, validator=must_equal(2021)) diff --git a/h2integrate/converters/iron/humbert_ewin_perf.py b/h2integrate/converters/iron/humbert_ewin_perf.py index 0d87433ee..01e55ae15 100644 --- a/h2integrate/converters/iron/humbert_ewin_perf.py +++ b/h2integrate/converters/iron/humbert_ewin_perf.py @@ -15,10 +15,9 @@ """ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import PerformanceModelBaseClass @@ -37,7 +36,9 @@ class HumbertEwinConfig(BaseConfig): """ electrolysis_type: str = field( - kw_only=True, converter=(str.lower, str.strip), validator=contains(["ahe", "mse", "moe"]) + kw_only=True, + converter=(str.lower, str.strip), + validator=validators.in_(["ahe", "mse", "moe"]), ) # product selection ore_fe_wt_pct: float = field(kw_only=True) capacity_mw: float = field(kw_only=True) diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index 06257c969..99a6cf5e9 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -17,10 +17,10 @@ """ import numpy as np -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import contains, must_equal +from h2integrate.core.validators import must_equal from h2integrate.tools.constants import FE_MW, faraday from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -51,7 +51,9 @@ class HumbertStinnEwinCostConfig(CostModelBaseConfig): """ electrolysis_type: str = field( - kw_only=True, converter=(str.lower, str.strip), validator=contains(["ahe", "mse", "moe"]) + kw_only=True, + converter=(str.lower, str.strip), + validator=validators.in_(["ahe", "mse", "moe"]), ) # product selection # Set cost year to 2018 - fixed for Stinn modeling cost_year: int = field(default=2018, converter=int, validator=must_equal(2018)) diff --git a/h2integrate/converters/iron/iron_transport.py b/h2integrate/converters/iron/iron_transport.py index 6acc11dd8..82bb4205c 100644 --- a/h2integrate/converters/iron/iron_transport.py +++ b/h2integrate/converters/iron/iron_transport.py @@ -8,7 +8,6 @@ from h2integrate import ROOT_DIR from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import CostModelBaseClass from h2integrate.converters.iron.load_top_down_coeffs import load_top_down_coeffs @@ -18,7 +17,7 @@ class IronTransportPerformanceConfig(BaseConfig): find_closest_ship_site: bool = field() shipment_site: str = field( converter=(str.lower, str.capitalize), - validator=contains(["None", "Duluth", "Chicago", "Cleveland", "Buffalo"]), + validator=validators.in_(["None", "Duluth", "Chicago", "Cleveland", "Buffalo"]), ) # diff --git a/h2integrate/converters/iron/martin_mine_cost_model.py b/h2integrate/converters/iron/martin_mine_cost_model.py index c2d43b470..d7e80f26d 100644 --- a/h2integrate/converters/iron/martin_mine_cost_model.py +++ b/h2integrate/converters/iron/martin_mine_cost_model.py @@ -6,7 +6,6 @@ from h2integrate import ROOT_DIR from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import CostModelBaseClass from h2integrate.tools.inflation.inflate import inflate_cpi @@ -28,10 +27,12 @@ class MartinIronMineCostConfig(BaseConfig): max_ore_production_rate_tonnes_per_hr: float = field() taconite_pellet_type: str = field( - converter=(str.lower, str.strip), validator=contains(["std", "drg"]) + converter=(str.lower, str.strip), validator=validators.in_(["std", "drg"]) ) - mine: str = field(validator=contains(["Hibbing", "Northshore", "United", "Minorca", "Tilden"])) + mine: str = field( + validator=validators.in_(["Hibbing", "Northshore", "United", "Minorca", "Tilden"]) + ) # the cost model is based on costs from 2021 and can be adjusted to another cost year # using CPI adjustment. diff --git a/h2integrate/converters/iron/martin_mine_perf_model.py b/h2integrate/converters/iron/martin_mine_perf_model.py index 2c33fcd87..2363fd597 100644 --- a/h2integrate/converters/iron/martin_mine_perf_model.py +++ b/h2integrate/converters/iron/martin_mine_perf_model.py @@ -1,11 +1,10 @@ import numpy as np import pandas as pd -from attrs import field, define +from attrs import field, define, validators from openmdao.utils import units from h2integrate import ROOT_DIR from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import PerformanceModelBaseClass @@ -24,10 +23,12 @@ class MartinIronMinePerformanceConfig(BaseConfig): max_ore_production_rate_tonnes_per_hr: float = field() taconite_pellet_type: str = field( - converter=(str.lower, str.strip), validator=contains(["std", "drg"]) + converter=(str.lower, str.strip), validator=validators.in_(["std", "drg"]) ) - mine: str = field(validator=contains(["Hibbing", "Northshore", "United", "Minorca", "Tilden"])) + mine: str = field( + validator=validators.in_(["Hibbing", "Northshore", "United", "Minorca", "Tilden"]) + ) class MartinIronMinePerformanceComponent(PerformanceModelBaseClass): diff --git a/h2integrate/converters/methanol/methanol_baseclass.py b/h2integrate/converters/methanol/methanol_baseclass.py index e2c2737e5..b3ea5b8a7 100644 --- a/h2integrate/converters/methanol/methanol_baseclass.py +++ b/h2integrate/converters/methanol/methanol_baseclass.py @@ -1,15 +1,14 @@ import openmdao.api as om -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import CostModelBaseClass, PerformanceModelBaseClass @define(kw_only=True) class MethanolPerformanceConfig(BaseConfig): plant_capacity_kgpy: float = field() - plant_capacity_flow: str = field(validator=contains(["hydrogen", "methanol"])) + plant_capacity_flow: str = field(validator=validators.in_(["hydrogen", "methanol"])) capacity_factor: float = field() co2e_emit_ratio: float = field() h2o_consume_ratio: float = field() @@ -61,7 +60,7 @@ def setup(self): @define(kw_only=True) class MethanolCostConfig(BaseConfig): plant_capacity_kgpy: float = field() - plant_capacity_flow: str = field(validator=contains(["hydrogen", "methanol"])) + plant_capacity_flow: str = field(validator=validators.in_(["hydrogen", "methanol"])) toc_kg_y: float = field() foc_kg_y2: float = field() voc_kg: float = field() @@ -115,7 +114,7 @@ class MethanolFinanceConfig(BaseConfig): tasc_toc_multiplier: float = field() fixed_charge_rate: float = field() plant_capacity_kgpy: float = field() - plant_capacity_flow: str = field(validator=contains(["hydrogen", "methanol"])) + plant_capacity_flow: str = field(validator=validators.in_(["hydrogen", "methanol"])) class MethanolFinanceBaseClass(om.ExplicitComponent): diff --git a/h2integrate/converters/nitrogen/simple_ASU.py b/h2integrate/converters/nitrogen/simple_ASU.py index 660426151..f6d05f69b 100644 --- a/h2integrate/converters/nitrogen/simple_ASU.py +++ b/h2integrate/converters/nitrogen/simple_ASU.py @@ -2,7 +2,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.tools.constants import N_MW, AR_MW, O2_MW from h2integrate.core.model_baseclasses import ( CostModelBaseClass, @@ -269,14 +268,16 @@ class SimpleASUCostConfig(CostModelBaseConfig): capex_usd_per_unit: float = field() capex_unit: str = field( - validator=contains(["kg/hour", "kw", "mw", "tonne/hour", "kg/day", "tonne/day"]), + validator=validators.in_(["kg/hour", "kw", "mw", "tonne/hour", "kg/day", "tonne/day"]), converter=(str.strip, str.lower), ) opex_usd_per_unit_per_year: float = field(default=0.0) opex_unit: str = field( default="none", - validator=contains(["kg/hour", "kw", "mw", "tonne/hour", "kg/day", "tonne/day", "none"]), + validator=validators.in_( + ["kg/hour", "kw", "mw", "tonne/hour", "kg/day", "tonne/day", "none"] + ), converter=(str.strip, str.lower), ) diff --git a/h2integrate/converters/nuclear/nuclear_plant_thermal.py b/h2integrate/converters/nuclear/nuclear_plant_thermal.py index b2848cb97..9f82f825d 100644 --- a/h2integrate/converters/nuclear/nuclear_plant_thermal.py +++ b/h2integrate/converters/nuclear/nuclear_plant_thermal.py @@ -2,7 +2,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import ( CostModelBaseClass, CostModelBaseConfig, @@ -31,7 +30,7 @@ class SimpleThermalNuclearReactorConfig(BaseConfig): Defaults to ``0.0``. """ - operating_mode: str = field(validator=contains(["heat", "electricity"])) + operating_mode: str = field(validator=validators.in_(["heat", "electricity"])) electricity_command_value: float = field(validator=validators.gt(0)) high_pressure_electrical_efficiency: float = field( validator=(validators.ge(0), validators.le(1)) diff --git a/h2integrate/converters/solar/solar_pysam.py b/h2integrate/converters/solar/solar_pysam.py index e8803b998..0454d7c80 100644 --- a/h2integrate/converters/solar/solar_pysam.py +++ b/h2integrate/converters/solar/solar_pysam.py @@ -5,7 +5,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.converters.tools import check_pysam_input_params from h2integrate.converters.solar.solar_baseclass import SolarPerformanceBaseClass @@ -50,7 +49,9 @@ class PYSAMSolarPlantPerformanceModelDesignConfig(BaseConfig): ) # default value depends on config create_model_from: str = field( - default="new", validator=contains(["default", "new"]), converter=(str.strip, str.lower) + default="new", + validator=validators.in_(["default", "new"]), + converter=(str.strip, str.lower), ) tilt: float = field( @@ -59,13 +60,13 @@ class PYSAMSolarPlantPerformanceModelDesignConfig(BaseConfig): tilt_angle_func: str = field( default="none", - validator=contains(["none", "lat-func", "lat"]), + validator=validators.in_(["none", "lat-func", "lat"]), converter=(str.strip, str.lower), ) config_name: str = field( default="PVWattsSingleOwner", - validator=contains( + validator=validators.in_( [ "PVWattsCommercial", "PVWattsCommunitySolar", diff --git a/h2integrate/converters/steel/cmu_electric_arc_furnace_dri.py b/h2integrate/converters/steel/cmu_electric_arc_furnace_dri.py index 725e8f33d..2625f7a28 100644 --- a/h2integrate/converters/steel/cmu_electric_arc_furnace_dri.py +++ b/h2integrate/converters/steel/cmu_electric_arc_furnace_dri.py @@ -1,11 +1,10 @@ """Electric Arc Furnace performance model based on CMU decarbSTEEL EAF Model""" import numpy as np -from attrs import field, define +from attrs import field, define, validators from openmdao.utils import units from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.tools.constants import ( C_MW, CO_MW, @@ -54,10 +53,10 @@ class CMUElectricArcFurnaceDRIPerformanceConfig(BaseConfig): "SiO2": 1.0 / 100, # mass fraction SiO2, 'Model Inputs & Outputs!B28' } ) - pellet_grade: str = field(default="DR", validator=contains(["DR", "BF", "custom"])) + pellet_grade: str = field(default="DR", validator=validators.in_(["DR", "BF", "custom"])) pct_DRI: float = field(default=60.0 / 100) # mass fraction, 'Model Inputs & Outputs!B61' DRI_feed_temp: str = field( - default="hot", validator=contains(["hot", "cold"]) + default="hot", validator=validators.in_(["hot", "cold"]) ) # hot = 873 K or cold = 298 K, 'Model Inputs & Outputs!B63' DRI_composition: dict[str, float] | None = None SiO2_ratio: float | None = None diff --git a/h2integrate/converters/water/desal/desalination.py b/h2integrate/converters/water/desal/desalination.py index f34ee84d2..483ff2c68 100644 --- a/h2integrate/converters/water/desal/desalination.py +++ b/h2integrate/converters/water/desal/desalination.py @@ -1,7 +1,7 @@ from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains, must_equal +from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import CostModelBaseConfig from h2integrate.converters.water.desal.desalination_baseclass import ( DesalinationCostBaseClass, @@ -22,7 +22,7 @@ class ReverseOsmosisPerformanceModelConfig(BaseConfig): """ freshwater_kg_per_hour: float = field(validator=validators.gt(0)) - salinity: str = field(validator=contains(["seawater", "brackish"])) + salinity: str = field(validator=validators.in_(["seawater", "brackish"])) freshwater_density: float = field(validator=validators.gt(0), default=997) diff --git a/h2integrate/converters/water_power/pysam_marine_cost.py b/h2integrate/converters/water_power/pysam_marine_cost.py index 35ac461dc..41c919dd8 100644 --- a/h2integrate/converters/water_power/pysam_marine_cost.py +++ b/h2integrate/converters/water_power/pysam_marine_cost.py @@ -2,7 +2,7 @@ from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import contains, must_equal +from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -46,7 +46,7 @@ class PySAMMarineCostConfig(CostModelBaseConfig): device_rating_kw: float = field(validator=validators.gt(0)) num_devices: int = field(validator=validators.gt(0)) - reference_model_number: int = field(validator=contains([1, 2, 3, 5, 6])) + reference_model_number: int = field(validator=validators.in_([1, 2, 3, 5, 6])) water_depth: float = field(validator=validators.gt(0)) distance_to_shore: float = field(validator=validators.gt(0)) number_rows: int = field(validator=validators.gt(0)) diff --git a/h2integrate/converters/water_power/tidal_pysam.py b/h2integrate/converters/water_power/tidal_pysam.py index 0df34044f..d21c6e096 100644 --- a/h2integrate/converters/water_power/tidal_pysam.py +++ b/h2integrate/converters/water_power/tidal_pysam.py @@ -2,7 +2,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import PerformanceModelBaseClass @@ -36,12 +35,14 @@ class PySAMTidalPerformanceConfig(BaseConfig): tidal_power_curve: list[list[float]] | None = field(default=None) create_model_from: str = field( - default="new", validator=contains(["default", "new"]), converter=(str.strip, str.lower) + default="new", + validator=validators.in_(["default", "new"]), + converter=(str.strip, str.lower), ) config_name: str = field( default="MEtidalNone", - validator=contains( + validator=validators.in_( [ "MEtidalLCOECalculator", "MEtidalNone", diff --git a/h2integrate/converters/wind/floris.py b/h2integrate/converters/wind/floris.py index f79306e86..057563cd3 100644 --- a/h2integrate/converters/wind/floris.py +++ b/h2integrate/converters/wind/floris.py @@ -5,7 +5,6 @@ from floris import TimeSeries, FlorisModel from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import CacheBaseClass, CacheBaseConfig from h2integrate.converters.wind.tools.resource_tools import ( calculate_air_density, @@ -67,7 +66,8 @@ class FlorisWindPlantPerformanceConfig(CacheBaseConfig): operation_model: str = field(default="cosine-loss") layout: dict = field(default={}) resource_data_averaging_method: str = field( - default="weighted_average", validator=contains(["weighted_average", "average", "nearest"]) + default="weighted_average", + validator=validators.in_(["weighted_average", "average", "nearest"]), ) hybrid_turbine_design: bool = field(default=False) diff --git a/h2integrate/converters/wind/layout/simple_grid_layout.py b/h2integrate/converters/wind/layout/simple_grid_layout.py index 650e260eb..bbe3db412 100644 --- a/h2integrate/converters/wind/layout/simple_grid_layout.py +++ b/h2integrate/converters/wind/layout/simple_grid_layout.py @@ -2,7 +2,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig -from h2integrate.core.validators import contains @define(kw_only=True) @@ -33,7 +32,7 @@ class BasicGridLayoutConfig(BaseConfig): layout_shape: str = field( default="square", converter=(str.lower, str.strip), - validator=contains(["square", "rectangle"]), + validator=validators.in_(["square", "rectangle"]), ) turbine_aspect_ratio: float = field(default=1.0, validator=validators.ge(0)) diff --git a/h2integrate/converters/wind/wind_pysam.py b/h2integrate/converters/wind/wind_pysam.py index 06f351735..b0f197df9 100644 --- a/h2integrate/converters/wind/wind_pysam.py +++ b/h2integrate/converters/wind/wind_pysam.py @@ -8,7 +8,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.converters.wind.wind_plant_baseclass import WindPerformanceBaseClass from h2integrate.converters.wind.layout.simple_grid_layout import ( BasicGridLayoutConfig, @@ -46,7 +45,7 @@ class PySAMPowerCurveCalculationInputs(BaseConfig): wind_default_cut_in_speed: int | float = field(default=4) wind_default_cut_out_speed: int | float = field(default=25) wind_default_drive_train: int = field( - default=0, converter=int, validator=contains([0, 1, 2, 3]) + default=0, converter=int, validator=validators.in_([0, 1, 2, 3]) ) @@ -79,12 +78,14 @@ class PYSAMWindPlantPerformanceModelConfig(BaseConfig): turbine_rating_kw: float = field(validator=validators.gt(0)) create_model_from: str = field( - default="new", validator=contains(["default", "new"]), converter=(str.strip, str.lower) + default="new", + validator=validators.in_(["default", "new"]), + converter=(str.strip, str.lower), ) config_name: str = field( default="WindPowerSingleOwner", - validator=contains( + validator=validators.in_( [ "WindPowerAllEquityPartnershipFlip", "WindPowerCommercial", diff --git a/h2integrate/finances/profast_base.py b/h2integrate/finances/profast_base.py index 244040c90..9e144f3db 100644 --- a/h2integrate/finances/profast_base.py +++ b/h2integrate/finances/profast_base.py @@ -6,7 +6,6 @@ from h2integrate.core.utilities import BaseConfig, attr_filter, attr_serializer from h2integrate.finances.tools import check_plant_config_and_profast_params from h2integrate.core.dict_utils import update_defaults -from h2integrate.core.validators import contains from h2integrate.tools.profast_tools import create_years_of_operation, create_and_populate_profast @@ -206,7 +205,7 @@ class BasicProFASTParameterConfig(BaseConfig): # --- Debt configuration --- debt_type: str = field( - default="Revolving debt", validator=contains(["Revolving debt", "One time loan"]) + default="Revolving debt", validator=validators.in_(["Revolving debt", "One time loan"]) ) loan_period_if_used: int = field(default=0, validator=validators.ge(0)) @@ -313,8 +312,10 @@ class ProFASTDefaultCapitalItem(BaseConfig): """ - depr_period: int = field(converter=int, validator=contains([3, 5, 7, 10, 15, 20])) - depr_type: str = field(converter=str.strip, validator=contains(["MACRS", "Straight line"])) + depr_period: int = field(converter=int, validator=validators.in_([3, 5, 7, 10, 15, 20])) + depr_type: str = field( + converter=str.strip, validator=validators.in_(["MACRS", "Straight line"]) + ) refurb: int | float | list[float] = field(default=[0.0]) replacement_cost_percent: float = field( default=0.0, validator=(validators.ge(0), validators.le(1)) diff --git a/h2integrate/resource/solar/nlr_developer_goes_api_models.py b/h2integrate/resource/solar/nlr_developer_goes_api_models.py index 6e89a2891..fe1c2120c 100644 --- a/h2integrate/resource/solar/nlr_developer_goes_api_models.py +++ b/h2integrate/resource/solar/nlr_developer_goes_api_models.py @@ -2,7 +2,6 @@ from attrs import field, define, validators -from h2integrate.core.validators import contains from h2integrate.resource.resource_base import ResourceBaseAPIConfig from h2integrate.resource.solar.nlr_developer_api_base import NLRDeveloperAPISolarResourceBase @@ -185,7 +184,7 @@ class GOESTMYAPIConfig(ResourceBaseAPIConfig): resource_year: str = field( converter=str.lower, - validator=contains( + validator=validators.in_( [ "tmy-2022", "tdy-2022", diff --git a/h2integrate/resource/solar/nlr_developer_himawari_api_models.py b/h2integrate/resource/solar/nlr_developer_himawari_api_models.py index 5416ca4e3..6c6c2883f 100644 --- a/h2integrate/resource/solar/nlr_developer_himawari_api_models.py +++ b/h2integrate/resource/solar/nlr_developer_himawari_api_models.py @@ -2,7 +2,6 @@ from attrs import field, define, validators -from h2integrate.core.validators import contains from h2integrate.resource.resource_base import ResourceBaseAPIConfig from h2integrate.resource.solar.nlr_developer_api_base import NLRDeveloperAPISolarResourceBase @@ -133,7 +132,7 @@ class HimawariTMYAPIConfig(ResourceBaseAPIConfig): resource_year: str = field( converter=str.lower, - validator=contains( + validator=validators.in_( [ "tmy-2020", "tdy-2020", diff --git a/h2integrate/resource/solar/nlr_developer_meteosat_prime_meridian_models.py b/h2integrate/resource/solar/nlr_developer_meteosat_prime_meridian_models.py index 8a22ee33b..2399f04d7 100644 --- a/h2integrate/resource/solar/nlr_developer_meteosat_prime_meridian_models.py +++ b/h2integrate/resource/solar/nlr_developer_meteosat_prime_meridian_models.py @@ -2,7 +2,6 @@ from attrs import field, define, validators -from h2integrate.core.validators import contains from h2integrate.resource.resource_base import ResourceBaseAPIConfig from h2integrate.resource.solar.nlr_developer_api_base import NLRDeveloperAPISolarResourceBase @@ -88,7 +87,7 @@ class MeteosatPrimeMeridianTMYAPIConfig(ResourceBaseAPIConfig): resource_year: str = field( converter=str.lower, - validator=contains( + validator=validators.in_( [ "tmy-2014", "tdy-2014", diff --git a/h2integrate/storage/battery/atb_battery_cost.py b/h2integrate/storage/battery/atb_battery_cost.py index 434729186..de40474d4 100644 --- a/h2integrate/storage/battery/atb_battery_cost.py +++ b/h2integrate/storage/battery/atb_battery_cost.py @@ -2,7 +2,6 @@ from openmdao.utils import units from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -36,7 +35,7 @@ class ATBBatteryCostConfig(CostModelBaseConfig): opex_fraction: float = field(validator=(validators.ge(0), validators.le(1))) max_capacity: float = field() max_charge_rate: float = field() - commodity_rate_units: str = field(validator=contains(["W", "kW", "MW", "GW", "TW"])) + commodity_rate_units: str = field(validator=validators.in_(["W", "kW", "MW", "GW", "TW"])) commodity_amount_units: str = field(default=None) def __attrs_post_init__(self): diff --git a/h2integrate/storage/battery/pysam_battery.py b/h2integrate/storage/battery/pysam_battery.py index 5b69cf2eb..be9cb5d6a 100644 --- a/h2integrate/storage/battery/pysam_battery.py +++ b/h2integrate/storage/battery/pysam_battery.py @@ -4,7 +4,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.storage.storage_baseclass import ( StoragePerformanceBase, StoragePerformanceBaseConfig, @@ -55,12 +54,12 @@ class PySAMBatteryPerformanceModelConfig(StoragePerformanceBaseConfig): max_charge_rate: float = field(validator=validators.gt(0)) chemistry: str = field( - validator=contains(["LFPGraphite", "LMOLTO", "LeadAcid", "NMCGraphite"]), + validator=validators.in_(["LFPGraphite", "LMOLTO", "LeadAcid", "NMCGraphite"]), ) init_soc_fraction: float = field(validator=(validators.ge(0), validators.le(1))) control_variable: str = field( - default="input_power", validator=contains(["input_power", "input_current"]) + default="input_power", validator=validators.in_(["input_power", "input_current"]) ) ref_module_capacity: int | float = field(default=400) ref_module_surface_area: int | float = field(default=30) diff --git a/h2integrate/storage/generic_storage_cost.py b/h2integrate/storage/generic_storage_cost.py index 8a2887df3..569d3d10a 100644 --- a/h2integrate/storage/generic_storage_cost.py +++ b/h2integrate/storage/generic_storage_cost.py @@ -2,7 +2,6 @@ from openmdao.utils import units from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -26,7 +25,7 @@ class GenericStorageCostConfig(CostModelBaseConfig): max_capacity: float = field() max_charge_rate: float = field() commodity_rate_units: str = field( - validator=contains(["W", "kW", "MW", "GW", "TW", "g/h", "kg/h", "t/h", "MMBtu/h"]) + validator=validators.in_(["W", "kW", "MW", "GW", "TW", "g/h", "kg/h", "t/h", "MMBtu/h"]) ) commodity_amount_units: str = field(default=None) diff --git a/h2integrate/storage/hydrogen/h2_storage_cost.py b/h2integrate/storage/hydrogen/h2_storage_cost.py index ee2b5a30f..c639373a1 100644 --- a/h2integrate/storage/hydrogen/h2_storage_cost.py +++ b/h2integrate/storage/hydrogen/h2_storage_cost.py @@ -3,7 +3,6 @@ from openmdao.utils import units from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import CostModelBaseClass from h2integrate.storage.hydrogen.h2_transport.h2_compression import Compressor @@ -35,12 +34,14 @@ class HydrogenStorageBaseCostModelConfig(BaseConfig): max_capacity: float | None = field(default=None) max_charge_rate: float | None = field(default=None) sizing_mode: str = field( - default="set", converter=(str.strip, str.lower), validator=contains(["auto", "set"]) + default="set", converter=(str.strip, str.lower), validator=validators.in_(["auto", "set"]) ) - commodity_rate_units: str = field(default="kg/h", validator=contains(["kg/h", "g/h", "t/h"])) + commodity_rate_units: str = field( + default="kg/h", validator=validators.in_(["kg/h", "g/h", "t/h"]) + ) - cost_year: int = field(default=2018, converter=int, validator=contains([2018])) + cost_year: int = field(default=2018, converter=int, validator=validators.in_([2018])) labor_rate: float = field(default=37.39817, validator=validators.ge(0)) insurance: float = field(default=0.01, validator=(validators.ge(0), validators.le(1))) property_taxes: float = field(default=0.01, validator=(validators.ge(0), validators.le(1))) diff --git a/h2integrate/storage/hydrogen/mch_storage.py b/h2integrate/storage/hydrogen/mch_storage.py index 412e5c15a..cb9cae73a 100644 --- a/h2integrate/storage/hydrogen/mch_storage.py +++ b/h2integrate/storage/hydrogen/mch_storage.py @@ -1,9 +1,8 @@ import numpy as np -from attrs import field, define +from attrs import field, define, validators from openmdao.utils import units from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains from h2integrate.core.model_baseclasses import CostModelBaseClass @@ -21,9 +20,9 @@ class MCHTOLStorageCostModelConfig(BaseConfig): charge_equals_discharge: bool = field(default=True) commodity_name: str = field(default="hydrogen") - commodity_units: str = field(default="kg/h", validator=contains(["kg/h", "g/h", "t/h"])) + commodity_units: str = field(default="kg/h", validator=validators.in_(["kg/h", "g/h", "t/h"])) - cost_year: int = field(default=2024, converter=int, validator=contains([2024])) + cost_year: int = field(default=2024, converter=int, validator=validators.in_([2024])) marginal_cost: float = field(default=0.0) def __attrs_post_init__(self): diff --git a/h2integrate/transporters/generic_splitter.py b/h2integrate/transporters/generic_splitter.py index b85007e05..b5da4e60a 100644 --- a/h2integrate/transporters/generic_splitter.py +++ b/h2integrate/transporters/generic_splitter.py @@ -3,7 +3,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig -from h2integrate.core.validators import contains @define(kw_only=True) @@ -15,7 +14,8 @@ class GenericSplitterPerformanceConfig(BaseConfig): """ split_mode: str = field( - converter=(str.lower, str.strip), validator=contains(["prescribed_commodity", "fraction"]) + converter=(str.lower, str.strip), + validator=validators.in_(["prescribed_commodity", "fraction"]), ) commodity: str = field(converter=(str.lower, str.strip)) commodity_rate_units: str = field() diff --git a/h2integrate/transporters/generic_summer.py b/h2integrate/transporters/generic_summer.py index c0ef79a15..26ffbf3cf 100644 --- a/h2integrate/transporters/generic_summer.py +++ b/h2integrate/transporters/generic_summer.py @@ -1,8 +1,7 @@ import openmdao.api as om -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains @define(kw_only=True) @@ -17,7 +16,7 @@ class GenericSummerPerformanceConfig(BaseConfig): operation_mode: str = field( default="production", converter=(str.lower, str.strip), - validator=contains(["production", "consumption"]), + validator=validators.in_(["production", "consumption"]), ) diff --git a/h2integrate/transporters/test/test_generic_splitter.py b/h2integrate/transporters/test/test_generic_splitter.py index 7f9f2cfb3..6e07368a8 100644 --- a/h2integrate/transporters/test/test_generic_splitter.py +++ b/h2integrate/transporters/test/test_generic_splitter.py @@ -179,7 +179,7 @@ def test_splitter_invalid_mode(splitter_tech_config_electricity, plant_config): with pytest.raises( ValueError, - match="Item invalid_mode not found in list", + match="'split_mode' must be in ", ): prob = om.Problem() comp = GenericSplitterPerformanceModel(tech_config=tech_config, plant_config=plant_config) From ff13add3e936cbeaa768cb125a386b998c97d261 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:59:44 -0700 Subject: [PATCH 04/11] remove contains --- h2integrate/core/validators.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/h2integrate/core/validators.py b/h2integrate/core/validators.py index a85a6fb81..f9b10f887 100644 --- a/h2integrate/core/validators.py +++ b/h2integrate/core/validators.py @@ -3,16 +3,6 @@ """ -def contains(items): - """Validates that an item is part of a given list.""" - - def validator(instance, attribute, value): - if value not in items: - raise ValueError(f"Item {value} not found in list for {attribute}: {items}") - - return validator - - def has_required_keys(required_keys): """Validates that a value is a dict containing all required keys. From 446fcb754ece1b0389969bab068661d920856901 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 11 Aug 2026 16:30:38 -0700 Subject: [PATCH 05/11] remove has_required_keys with deep_mapping --- .../plm_openloop_storage_controller.py | 45 ++++++++++++++++--- h2integrate/core/validators.py | 28 ------------ 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/h2integrate/control/control_strategies/storage/plm_openloop_storage_controller.py b/h2integrate/control/control_strategies/storage/plm_openloop_storage_controller.py index 073cb9bac..b9809b0d3 100644 --- a/h2integrate/control/control_strategies/storage/plm_openloop_storage_controller.py +++ b/h2integrate/control/control_strategies/storage/plm_openloop_storage_controller.py @@ -7,7 +7,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs, build_time_series_from_plant_config -from h2integrate.core.validators import has_required_keys from h2integrate.control.control_strategies.openloop_control_base import ( OpenLoopControlBase, OpenLoopControlBaseConfig, @@ -65,11 +64,47 @@ class PeakLoadManagementHeuristicOpenLoopStorageControllerConfig(OpenLoopControl ) n_override_events: int | None = field(default=None) override_events_period: int | str | None = field(default=None) - peak_range: dict = field(validator=has_required_keys(["start", "end"])) - advance_discharge_period: dict = field(validator=has_required_keys(["units", "val"])) - delay_charge_period: dict = field(validator=has_required_keys(["units", "val"])) + peak_range: dict = field( + validator=validators.deep_mapping( + key_validator=validators.in_(["start", "end"]), + mapping_validator=( + validators.instance_of(dict), + validators.min_len(2), + validators.max_len(2), + ), + ) + ) + advance_discharge_period: dict = field( + validator=validators.deep_mapping( + key_validator=validators.in_(["units", "val"]), + mapping_validator=( + validators.instance_of(dict), + validators.min_len(2), + validators.max_len(2), + ), + ) + ) + delay_charge_period: dict = field( + validator=validators.deep_mapping( + key_validator=validators.in_(["units", "val"]), + mapping_validator=( + validators.instance_of(dict), + validators.min_len(2), + validators.max_len(2), + ), + ) + ) allow_charge_in_peak_range: bool = field(default=True) - min_peak_proximity: dict = field(validator=has_required_keys(["units", "val"])) + min_peak_proximity: dict = field( + validator=validators.deep_mapping( + key_validator=validators.in_(["units", "val"]), + mapping_validator=( + validators.instance_of(dict), + validators.min_len(2), + validators.max_len(2), + ), + ) + ) def __attrs_post_init__(self): super().__attrs_post_init__() diff --git a/h2integrate/core/validators.py b/h2integrate/core/validators.py index f9b10f887..661327c7c 100644 --- a/h2integrate/core/validators.py +++ b/h2integrate/core/validators.py @@ -3,34 +3,6 @@ """ -def has_required_keys(required_keys): - """Validates that a value is a dict containing all required keys. - - Args: - required_keys (list[str] | tuple[str, ...]): Keys that must be present - in the input dictionary. - """ - if isinstance(required_keys, str): - required_keys = [required_keys] - required_keys = tuple(required_keys) - - def validator(instance, attribute, value): - if not isinstance(value, dict): - raise ValueError( - f"{attribute.name} must be a dict containing keys {required_keys}, " - f"got {type(value).__name__}." - ) - - missing_keys = [key for key in required_keys if key not in value] - if missing_keys: - raise ValueError( - f"{attribute.name} is missing required key(s): {missing_keys}. " - f"Expected keys include: {required_keys}." - ) - - return validator - - def must_equal(required_value): """Validates that an item equals a specific value""" From 23b4b5fbb389e1dac4eb29c8337c63dd850d54ac Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Tue, 11 Aug 2026 16:34:20 -0700 Subject: [PATCH 06/11] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ed5fe20c..b22fe003f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Replaces all but `must_equal` from the `h2integrate.core.validators` with built in attrs validators. + ## 0.9 [August 10, 2026] ### New Features From bd786380a0315e99acf30d2a29f7b2bd9ea40715 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Wed, 12 Aug 2026 10:55:59 -0700 Subject: [PATCH 07/11] update docs references to validators --- .../developer_guide/technology_model_types.md | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/docs/developer_guide/technology_model_types.md b/docs/developer_guide/technology_model_types.md index 0bfcdcbe4..fd8be17c2 100644 --- a/docs/developer_guide/technology_model_types.md +++ b/docs/developer_guide/technology_model_types.md @@ -12,10 +12,9 @@ Here is an example of a solar PV performance model. ```python import PySAM.Pvwattsv8 as Pvwatts -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import contains, range_val_or_none from h2integrate.converters.solar.solar_baseclass import SolarPerformanceBaseClass @@ -29,11 +28,17 @@ class PYSAMSolarPlantPerformanceModelDesignConfig(BaseConfig): """ pv_capacity_kWdc: float = field() - dc_ac_ratio: float = field(default=None, validator=range_val_or_none(0.0, 2.0)) - tilt: float = field(default=None, validator=range_val_or_none(0.0, 90.0)) + dc_ac_ratio: float = field( + default=None, + validator=validators.optional((validators.ge(0), validators.le(2))) + ) + tilt: float = field( + default=None, + validator=validators.optional((validators.ge(0), validators.le(90))) + ) config_name: str = field( default="PVWattsSingleOwner", - validator=contains(["PVWattsSingleOwner", "PVWattsCommercial"]), # truncated + validator=validators.in_(["PVWattsSingleOwner", "PVWattsCommercial"]), # truncated ) @@ -107,10 +112,10 @@ model always reports costs in a fixed dollar-year), inherit the config from `BaseConfig` and pin `cost_year` to a constant: ```python -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gt_zero, must_equal +from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import CostModelBaseClass @@ -118,8 +123,8 @@ from h2integrate.core.model_baseclasses import CostModelBaseClass class ReverseOsmosisCostModelConfig(BaseConfig): # Config values come from tech_config['model_inputs']['cost_parameters'] # or tech_config['model_inputs']['shared_parameters']. - freshwater_kg_per_hour: float = field(validator=gt_zero) - freshwater_density: float = field(validator=gt_zero) + freshwater_kg_per_hour: float = field(validator=validators.gt(0)) + freshwater_density: float = field(validator=validators.gt(0)) # cost_year is fixed because this model always reports 2013 USD. cost_year: int = field(default=2013, converter=int, validator=must_equal(2013)) @@ -148,17 +153,16 @@ inherit the config from `CostModelBaseConfig` instead. `CostModelBaseConfig` adds a required `cost_year` field, forcing the user to supply it: ```python -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import gt_zero from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @define(kw_only=True) class ATBUtilityPVCostModelConfig(CostModelBaseConfig): - capex_per_kWac: float | int = field(validator=gt_zero) - opex_per_kWac_per_year: float | int = field(validator=gt_zero) + capex_per_kWac: float | int = field(validator=validators.gt(0)) + opex_per_kWac_per_year: float | int = field(validator=validators.gt(0)) # ``cost_year`` is inherited from CostModelBaseConfig and is user-provided. From 9e78883dc82ecef7f6d86fb8a6f7f4a3798dfd82 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Wed, 12 Aug 2026 12:09:29 -0700 Subject: [PATCH 08/11] add pr # --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96c978ef8..5d7d3717d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - Enable `PySAMWindPlantPerformanceModel` to accept more than 300 turbines by overriding the default maximum in the PySAM model. [PR 831](https://github.com/NatLabRockies/H2Integrate/pull/831) - Add `PySAMWavePerformanceModel` and `WaveResource` to wrap PySAM MhkWave as an H2I performance model, replacing the HOPP wave module in example 09. [PR 825](https://github.com/NatLabRockies/H2Integrate/pull/825) - Replace HOPP with native H2I wind, solar, and battery models in example 11. Adds `percent_load_missed` and `curtailment_percent` outputs to `DemandComponentBase`, allows zero capacity in wind/solar/battery performance models. [PR 826](https://github.com/NatLabRockies/H2Integrate/pull/826) -- Replaces all but `must_equal` from the `h2integrate.core.validators` with built in attrs validators. +- Replaces all but `must_equal` from the `h2integrate.core.validators` with built in attrs validators. [PR 835](https://github.com/NatLabRockies/H2Integrate/pull/835) ## 0.9 [August 10, 2026] From 2dae98be72c72a666385713e2d5625e64f36079d Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Wed, 12 Aug 2026 12:27:44 -0700 Subject: [PATCH 09/11] fix find and replace error --- h2integrate/converters/wind/wind_pysam.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/h2integrate/converters/wind/wind_pysam.py b/h2integrate/converters/wind/wind_pysam.py index 120d2bafc..ff81e1646 100644 --- a/h2integrate/converters/wind/wind_pysam.py +++ b/h2integrate/converters/wind/wind_pysam.py @@ -72,10 +72,10 @@ class PYSAMWindPlantPerformanceModelConfig(BaseConfig): power curve. defaults to True. """ - num_turbines: int = field(converter=int, validator=validators.gt(0)) - hub_height: float = field(validator=validators.gt(0)) - rotor_diameter: float = field(validator=validators.gt(0)) - turbine_rating_kw: float = field(validator=validators.gt(0)) + num_turbines: int = field(converter=int, validator=validators.ge(0)) + hub_height: float = field(validator=validators.ge(0)) + rotor_diameter: float = field(validator=validators.ge(0)) + turbine_rating_kw: float = field(validator=validators.ge(0)) create_model_from: str = field( default="new", From ee5c3f6add509174609f600c272cb91efb504064 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:25:58 -0700 Subject: [PATCH 10/11] replace missed validators --- h2integrate/converters/water_power/wave_pysam.py | 13 +++++++------ h2integrate/resource/resource_base.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/h2integrate/converters/water_power/wave_pysam.py b/h2integrate/converters/water_power/wave_pysam.py index 13b818e8f..6c05f12fe 100644 --- a/h2integrate/converters/water_power/wave_pysam.py +++ b/h2integrate/converters/water_power/wave_pysam.py @@ -1,9 +1,8 @@ import pandas as pd import PySAM.MhkWave as MhkWave -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import gt_zero, contains from h2integrate.core.model_baseclasses import PerformanceModelBaseClass @@ -36,18 +35,20 @@ class PySAMWavePerformanceConfig(BaseConfig): `here `__. """ - device_rating_kw: float = field(validator=gt_zero) - num_devices: int = field(validator=gt_zero) + device_rating_kw: float = field(validator=validators.gt(0)) + num_devices: int = field(validator=validators.gt(0)) wave_power_matrix: list[list[float]] | None = field(default=None) resource_year: int = field(default=2010, converter=int) create_model_from: str = field( - default="new", validator=contains(["default", "new"]), converter=(str.strip, str.lower) + default="new", + validator=validators.in_(["default", "new"]), + converter=(str.strip, str.lower), ) config_name: str = field( default="MEwaveNone", - validator=contains( + validator=validators.in_( [ "MEwaveBatterySingleOwner", "MEwaveLCOECalculator", diff --git a/h2integrate/resource/resource_base.py b/h2integrate/resource/resource_base.py index db447a893..ab1dcd9c1 100644 --- a/h2integrate/resource/resource_base.py +++ b/h2integrate/resource/resource_base.py @@ -19,7 +19,7 @@ class ResourceBaseAPIConfig(BaseConfig): Subclasses should include the following attributes that are not set in this BaseConfig: - **resource_year** (*int*): Year to download resource data for. - Recommended to have a range_val validator. + Recommended to have a validator for upper and lower limits. - **resource_data** (*dict*, optional): Dictionary of user-provided resource data. Defaults to {}. - **resource_dir** (*str | Path*, optional): Folder to save resource files to or From 77f3301ff39e95b021f47c222f6b6d9511e14352 Mon Sep 17 00:00:00 2001 From: "Hammond, Rob" <13874373+RHammond2@users.noreply.github.com> Date: Thu, 13 Aug 2026 10:46:30 -0700 Subject: [PATCH 11/11] replace must_equal with in_ --- CHANGELOG.md | 2 +- docs/developer_guide/technology_model_types.md | 3 +-- .../converters/ammonia/simple_ammonia_model.py | 5 ++--- .../co2/marine/direct_ocean_capture.py | 3 +-- .../co2/marine/ocean_alkalinity_enhancement.py | 3 +-- .../converters/hydrogen/basic_cost_model.py | 3 +-- .../hydrogen/singlitico_cost_model.py | 3 +-- .../converters/iron/humbert_stinn_ewin_cost.py | 3 +-- h2integrate/converters/steel/cmu_eaf_cost.py | 5 ++--- h2integrate/converters/steel/steel.py | 5 ++--- .../converters/water/desal/desalination.py | 3 +-- .../converters/water_power/pysam_marine_cost.py | 3 +-- h2integrate/core/validators.py | 17 ----------------- 13 files changed, 15 insertions(+), 43 deletions(-) delete mode 100644 h2integrate/core/validators.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d7d3717d..7c76a296e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - Enable `PySAMWindPlantPerformanceModel` to accept more than 300 turbines by overriding the default maximum in the PySAM model. [PR 831](https://github.com/NatLabRockies/H2Integrate/pull/831) - Add `PySAMWavePerformanceModel` and `WaveResource` to wrap PySAM MhkWave as an H2I performance model, replacing the HOPP wave module in example 09. [PR 825](https://github.com/NatLabRockies/H2Integrate/pull/825) - Replace HOPP with native H2I wind, solar, and battery models in example 11. Adds `percent_load_missed` and `curtailment_percent` outputs to `DemandComponentBase`, allows zero capacity in wind/solar/battery performance models. [PR 826](https://github.com/NatLabRockies/H2Integrate/pull/826) -- Replaces all but `must_equal` from the `h2integrate.core.validators` with built in attrs validators. [PR 835](https://github.com/NatLabRockies/H2Integrate/pull/835) +- Replaces all custom attrs validators in `h2integrate.core.validators` with built in attrs validators. [PR 835](https://github.com/NatLabRockies/H2Integrate/pull/835) ## 0.9 [August 10, 2026] diff --git a/docs/developer_guide/technology_model_types.md b/docs/developer_guide/technology_model_types.md index 83b468acf..a1975787d 100644 --- a/docs/developer_guide/technology_model_types.md +++ b/docs/developer_guide/technology_model_types.md @@ -115,7 +115,6 @@ model always reports costs in a fixed dollar-year), inherit the config from from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import CostModelBaseClass @@ -126,7 +125,7 @@ class ReverseOsmosisCostModelConfig(BaseConfig): freshwater_kg_per_hour: float = field(validator=validators.gt(0)) freshwater_density: float = field(validator=validators.gt(0)) # cost_year is fixed because this model always reports 2013 USD. - cost_year: int = field(default=2013, converter=int, validator=must_equal(2013)) + cost_year: int = field(default=2013, converter=int, validator=validators.in_([2013])) class ReverseOsmosisCostModel(CostModelBaseClass): diff --git a/h2integrate/converters/ammonia/simple_ammonia_model.py b/h2integrate/converters/ammonia/simple_ammonia_model.py index b8d6ddd37..21a5b3200 100644 --- a/h2integrate/converters/ammonia/simple_ammonia_model.py +++ b/h2integrate/converters/ammonia/simple_ammonia_model.py @@ -1,7 +1,6 @@ -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import ( CostModelBaseClass, CostModelBaseConfig, @@ -104,7 +103,7 @@ class AmmoniaCostModelConfig(CostModelBaseConfig): iron_based_catalyst_consumption: float = field() oxygen_byproduct: float = field() capex_scaling_exponent: float = field() - cost_year: int = field(default=2022, converter=int, validator=must_equal(2022)) + cost_year: int = field(default=2022, converter=int, validator=validators.in_([2022])) class SimpleAmmoniaCostModel(CostModelBaseClass): diff --git a/h2integrate/converters/co2/marine/direct_ocean_capture.py b/h2integrate/converters/co2/marine/direct_ocean_capture.py index de384f3b7..826e68330 100644 --- a/h2integrate/converters/co2/marine/direct_ocean_capture.py +++ b/h2integrate/converters/co2/marine/direct_ocean_capture.py @@ -2,7 +2,6 @@ from mcm.capture import echem_mcc from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import CostModelBaseClass, PerformanceModelBaseClass @@ -151,7 +150,7 @@ class DOCCostModelConfig(DOCPerformanceConfig): """ infrastructure_type: str = field(validator=validators.in_(["desal", "swCool", "new"])) - cost_year: int = field(default=2023, converter=int, validator=must_equal(2023)) + cost_year: int = field(default=2023, converter=int, validator=validators.in_([2023])) class DOCCostModel(CostModelBaseClass): diff --git a/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py b/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py index ef8a59d03..e6e8813df 100644 --- a/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py +++ b/h2integrate/converters/co2/marine/ocean_alkalinity_enhancement.py @@ -2,7 +2,6 @@ from mcm.capture import echem_oae from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import ( CostModelBaseClass, CostModelBaseConfig, @@ -253,7 +252,7 @@ class OAECostModelConfig(CostModelBaseConfig): cost_year (int): dollar year corresponding to cost values """ - cost_year: int = field(default=2024, converter=int, validator=must_equal(2024)) + cost_year: int = field(default=2024, converter=int, validator=validators.in_([2024])) class OAECostModel(CostModelBaseClass): diff --git a/h2integrate/converters/hydrogen/basic_cost_model.py b/h2integrate/converters/hydrogen/basic_cost_model.py index 97acce5ac..552390f96 100644 --- a/h2integrate/converters/hydrogen/basic_cost_model.py +++ b/h2integrate/converters/hydrogen/basic_cost_model.py @@ -4,7 +4,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import CostModelBaseConfig from h2integrate.converters.hydrogen.electrolyzer_baseclass import ElectrolyzerCostBaseClass @@ -27,7 +26,7 @@ class BasicElectrolyzerCostModelConfig(CostModelBaseConfig): location: str = field(validator=validators.in_(["onshore", "offshore"])) electrolyzer_capex: int = field() time_between_replacement: int = field(validator=validators.gt(0)) - cost_year: int = field(default=2016, converter=int, validator=must_equal(2016)) + cost_year: int = field(default=2016, converter=int, validator=validators.in_([2016])) class BasicElectrolyzerCostModel(ElectrolyzerCostBaseClass): diff --git a/h2integrate/converters/hydrogen/singlitico_cost_model.py b/h2integrate/converters/hydrogen/singlitico_cost_model.py index 5e1317447..66dada608 100644 --- a/h2integrate/converters/hydrogen/singlitico_cost_model.py +++ b/h2integrate/converters/hydrogen/singlitico_cost_model.py @@ -1,7 +1,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import CostModelBaseConfig from h2integrate.converters.hydrogen.electrolyzer_baseclass import ElectrolyzerCostBaseClass @@ -21,7 +20,7 @@ class SingliticoCostModelConfig(CostModelBaseConfig): location: str = field(validator=validators.in_(["onshore", "offshore"])) electrolyzer_capex: int = field() - cost_year: int = field(default=2021, converter=int, validator=must_equal(2021)) + cost_year: int = field(default=2021, converter=int, validator=validators.in_([2021])) class SingliticoCostModel(ElectrolyzerCostBaseClass): diff --git a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py index 99a6cf5e9..6e0fd203f 100644 --- a/h2integrate/converters/iron/humbert_stinn_ewin_cost.py +++ b/h2integrate/converters/iron/humbert_stinn_ewin_cost.py @@ -20,7 +20,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import must_equal from h2integrate.tools.constants import FE_MW, faraday from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -56,7 +55,7 @@ class HumbertStinnEwinCostConfig(CostModelBaseConfig): validator=validators.in_(["ahe", "mse", "moe"]), ) # product selection # Set cost year to 2018 - fixed for Stinn modeling - cost_year: int = field(default=2018, converter=int, validator=must_equal(2018)) + cost_year: int = field(default=2018, converter=int, validator=validators.in_([2018])) labor_rate_cost: float = field(default=55.90) anode_cost_per_tonne: float = field(default=1660.716) annual_labor_hours_per_position: int | float = field(default=2000) diff --git a/h2integrate/converters/steel/cmu_eaf_cost.py b/h2integrate/converters/steel/cmu_eaf_cost.py index e09355799..89c5be53f 100644 --- a/h2integrate/converters/steel/cmu_eaf_cost.py +++ b/h2integrate/converters/steel/cmu_eaf_cost.py @@ -1,7 +1,6 @@ -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -44,7 +43,7 @@ class CMUElectricArcFurnaceCostConfig(CostModelBaseConfig): mean_hourly_wage: float = field(default=31.82) # person hours per ton steel, '6. Production Cost!B43' > '6. Production Cost!J73' eaf_labor_required_per_tLS: float = field(default=4 / 20) - cost_year: int = field(default=2022, converter=int, validator=must_equal(2022)) + cost_year: int = field(default=2022, converter=int, validator=validators.in_([2022])) class CMUElectricArcFurnaceCostModel(CostModelBaseClass): diff --git a/h2integrate/converters/steel/steel.py b/h2integrate/converters/steel/steel.py index ccbd5f671..68e3e4cfa 100644 --- a/h2integrate/converters/steel/steel.py +++ b/h2integrate/converters/steel/steel.py @@ -1,8 +1,7 @@ import ProFAST -from attrs import field, define +from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import must_equal from h2integrate.converters.steel.steel_baseclass import ( SteelCostBaseClass, SteelPerformanceBaseClass, @@ -58,7 +57,7 @@ class SteelCostAndFinancialModelConfig(BaseConfig): # Financial parameters - flattened from the nested structure grid_prices: dict = field() financial_assumptions: dict = field() - cost_year: int = field(default=2022, converter=int, validator=must_equal(2022)) + cost_year: int = field(default=2022, converter=int, validator=validators.in_([2022])) # Feedstock parameters - flattened from the nested structure excess_oxygen: float = field(default=395) diff --git a/h2integrate/converters/water/desal/desalination.py b/h2integrate/converters/water/desal/desalination.py index 483ff2c68..04f7b41e6 100644 --- a/h2integrate/converters/water/desal/desalination.py +++ b/h2integrate/converters/water/desal/desalination.py @@ -1,7 +1,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import BaseConfig, merge_shared_inputs -from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import CostModelBaseConfig from h2integrate.converters.water.desal.desalination_baseclass import ( DesalinationCostBaseClass, @@ -129,7 +128,7 @@ class ReverseOsmosisCostModelConfig(CostModelBaseConfig): freshwater_kg_per_hour: float = field(validator=validators.gt(0)) freshwater_density: float = field(validator=validators.gt(0)) - cost_year: int = field(default=2013, converter=int, validator=must_equal(2013)) + cost_year: int = field(default=2013, converter=int, validator=validators.in_([2013])) class ReverseOsmosisCostModel(DesalinationCostBaseClass): diff --git a/h2integrate/converters/water_power/pysam_marine_cost.py b/h2integrate/converters/water_power/pysam_marine_cost.py index 41c919dd8..ff8d89f48 100644 --- a/h2integrate/converters/water_power/pysam_marine_cost.py +++ b/h2integrate/converters/water_power/pysam_marine_cost.py @@ -2,7 +2,6 @@ from attrs import field, define, validators from h2integrate.core.utilities import merge_shared_inputs -from h2integrate.core.validators import must_equal from h2integrate.core.model_baseclasses import CostModelBaseClass, CostModelBaseConfig @@ -55,7 +54,7 @@ class PySAMMarineCostConfig(CostModelBaseConfig): cable_system_overbuild: float = field(validator=(validators.ge(0), validators.le(100))) pysam_cost_options: dict = field(default={}) cost_year: int = field( - default=2022, converter=int, validator=must_equal(2022) + default=2022, converter=int, validator=validators.in_([2022]) ) # TODO update based on feedback from SAM team def __attrs_post_init__(self): diff --git a/h2integrate/core/validators.py b/h2integrate/core/validators.py deleted file mode 100644 index 661327c7c..000000000 --- a/h2integrate/core/validators.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -This module contains validator functions for use with `attrs` class definitions. -""" - - -def must_equal(required_value): - """Validates that an item equals a specific value""" - - def validator(instance, attribute, value): - if value != required_value: - msg = ( - f"{attribute.name} cannot be {value}, {attribute.name} " - f"must have value of {required_value}" - ) - raise ValueError(msg) - - return validator