h2integrate.core.h2integrate_model#

Classes

H2IntegrateModel(config_input)

State(value[, names, module, qualname, ...])

class h2integrate.core.h2integrate_model.State(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)#
INITIALIZED = 0#
SETUP = 1#
RUN = 2#
POST_PROCESS = 3#
class h2integrate.core.h2integrate_model.H2IntegrateModel(config_input)#
_load_component_config(config_key, config_value, config_path, validator_func)#

Helper method to load and validate a component configuration.

Parameters:
  • config_key (str) -- Key name for the configuration (e.g., "driver_config")

  • config_value (dict | str) -- Configuration value from main config

  • config_path (Path | None) -- Path to main config file (None if dict)

  • validator_func (callable) -- Validation function to apply

Returns:

tuple --

(validated_config, config_file_path, parent_path)
  • validated_config: Validated configuration dictionary

  • config_file_path: Path to config file (None if dict)

  • parent_path: Parent directory of config file (None if dict)

load_config(config_input)#

Load and validate configuration files for the H2I model.

This method loads the main configuration and the component configuration files (driver, technology, and plant). Each configuration can be provided either as a dictionary or as a file path. When file paths are provided, the method resolves them using multiple search strategies.

Parameters:

config_input (dict | str | Path) --

Main configuration containing references to driver, technology, and plant configurations. This can be:

  • A dictionary containing the configuration data directly.

  • A string or Path pointing to a YAML file containing the configuration.

Behavior

  • If config_input is a dict, uses it directly as the main configuration.

  • If config_input is a path, uses get_path() to resolve and load the YAML file from multiple search locations (absolute path, relative to CWD, relative to the H2Integrate package).

  • For component configs provided as dicts, validates them directly using load_driver_yaml, load_tech_yaml, and load_plant_yaml.

  • For component configs provided as paths and a file-based main config, uses find_file() to search relative to the main config directory first, then falls back to other search locations (CWD, H2Integrate package, glob patterns).

  • For component configs provided as paths and a dict-based main config, uses get_path() with standard search locations (absolute, CWD, H2Integrate package).

Sets:

self.name (str): Name of the system from main config. self.system_summary (str): Summary description from main config. self.driver_config (dict): Validated driver configuration. self.technology_config (dict): Validated technology configuration. self.plant_config (dict): Validated plant configuration. self.driver_config_path (Path | None): Path to driver config file (None if dict). self.tech_config_path (Path | None): Path to technology config file (None if dict). self.plant_config_path (Path | None): Path to plant config file (None if dict). self.tech_parent_path (Path | None): Parent directory of technology config file. self.plant_parent_path (Path | None): Parent directory of plant config file.

Note

The parent path attributes (tech_parent_path, plant_parent_path) are used later to resolve relative paths to custom models and other referenced files within the technology and plant configurations.

Example

>>> # Using filepaths
>>> model = H2IntegrateModel("main_config.yaml")
>>> # Using mixed dict and filepaths
>>> config = {
...     "name": "my_system",
...     "driver_config": "driver.yaml",
...     "technology_config": {"technologies": {...}},
...     "plant_config": "plant.yaml",
... }
>>> model = H2IntegrateModel(config)
create_custom_models(model_config, config_parent_path, model_types, prefix='')#

This method loads custom models from the specified directory and adds them to the supported models dictionary.

Parameters:
  • model_config (dict) -- dictionary containing models, such as technology_config["technologies"].

  • config_parent_path (Path) -- parent path of the input file that model_config comes from. Should either be plant_config_path.parent or tech_config_path.parent.

  • model_types (list[str]) -- list of key names to search for in model_config.values(). Should be ["performance_model", "cost_model", "financial_model"] if model_config is technology_config["technologies"].

  • prefix (str, optional) -- Prefix of model_class_name, model_location and model. Defaults to "". Should be "finance_" if looking for custom system finance models.

collect_custom_models()#

Collect custom models from the technology configuration and system finance models found in the plant configuration.

create_site_model()#

Create and configure site component(s) for the system.

This method initializes a site group for each site provided in self.plant_config["sites"].

This method creates an OpenMDAO Group for each site that contains the location definition and resources models (if provided in the configuration) for that site.

create_site_group(plant_config_dict, site_config)#

Create and configure a site Group for the input site configuration.

Parameters:
  • plant_config_dict (dict) -- The plant config dictionary formatted for the resource models

  • site_config (dict) -- Information that defines each site, such as latitude, longitude, and resource models.

Returns:

om.Group -- OpenMDAO group for a site

create_plant_model()#

Create the plant-level model.

This method creates an OpenMDAO group that contains all the technologies. It uses the plant configuration but not the driver or technology configuration.

Information at this level might be used by any technology and info stored here is the same for each technology. This includes site information, project parameters, control strategy, and finance parameters.

_classify_slc_technologies()#

Classify technologies for system-level control.

Uses self.tech_control_classifiers (populated by create_technology_models()) to partition technologies into fixed, flexible, dispatchable, and storage lists. Also identifies the single demand technology and its commodity.

SLC demand is supplied by a demand component (for example, GenericDemandComponent). When SLC is enabled, only one demand component is currently supported.

Returns:

dict --

Classification dictionary (slc_topology) with keys:

  • "demand_tech" (str): Name of the demand technology (the tech whose performance model is a DemandComponent).

  • "demand_commodity" (str): Commodity the demand technology consumes (e.g. "electricity", "hydrogen").

  • "demand_commodity_rate_units" (str | None): Units string for the demand commodity rate (e.g. "kW", "kg/h"), or None if not specified in the demand tech config.

  • "tech_to_commodity" (set[tuple[str, str]]): Set of (tech_name, commodity) pairs for every technology that the SLC controls or reads from. Built from outgoing edges of the technology graph and filtered to fixed, flexible, dispatchable, storage, and feedstock classifiers.

  • "technology_graph" (nx.DiGraph): Directed graph of technology interconnections, with edge attribute commodity indicating the commodity carried on each edge. Used by cost-aware controllers to trace upstream feedstocks.

  • "tech_control_classifiers" (dict[str, str]): Mapping of tech name to its _control_classifier (one of "fixed", "flexible", "dispatchable", "storage", "feedstock"). Determines how the SLC interacts with each tech.

add_system_level_controller(slc_topology)#

Add a system-level controller component and connect it within the plant.

Instantiates the controller specified by control_strategy in the plant configuration, adds it as an OpenMDAO subsystem named "system_level_controller", configures solvers on the plant group to resolve the feedback loop, and creates all necessary OpenMDAO connections between the controller and the technology models it dispatches.

The method executes in five sequential steps:

  1. Select and instantiate the controller - Looks up the class from supported_models using the control_strategy string (e.g. "DemandFollowingControl", "ProfitMaximizationControl"). Raises ValueError if the strategy name is not found. The instantiated component is added to self.plant as "system_level_controller".

  2. Configure the plant-level nonlinear solver - Because the controller creates a feedback loop (controller outputs become technology inputs, whose outputs feed back to the controller), a nonlinear solver is required. Solver type and options are read from plant_config["system_level_control"]["solver_options"] via SLCSolverOptionsConfig. A DirectSolver is set as the linear solver and is largely inconsequential as we're not propagating derivatives at this time.

  3. Connect technology outputs to controller inputs - For each (tech_name, commodity) pair in slc_topology["tech_to_commodity"]:

    • Feedstock techs: Only the commodity output ({tech_name}_source.{commodity}_out) is connected to the controller. Feedstocks have no demand-input connection.

    • Fixed techs: Only the commodity output ({tech_name}.{commodity}_out) is connected to the controller. Fixed techs always produce and receive no demand-input connection.

    • Flexible / dispatchable / storage techs: Both the commodity output ({tech_name}.{commodity}_out) and rated production ({tech_name}.rated_{commodity}_production) are connected as controller inputs. The controller's per-tech {tech_name}_{commodity}_set_point output is then connected to the tech group's {commodity}_set_point input. Every controlled tech group is expected to expose this input — either via a user-defined control_strategy or via the auto-injected PassthroughController — which converts the set-point signal into the appropriate performance-model command value.

  4. Connect marginal-cost inputs for cost-aware strategies - Only executed when control_strategy is "CostMinimizationControl" or "ProfitMaximizationControl". Additional cost-aware control strategies would need to be added here. For each dispatchable tech, the cost_per_tech specification determines which cost signal is connected:

    • "VarOpEx": connects the tech's own VarOpEx output.

    • "feedstock": uses graph traversal (nx.ancestors) on the technology_graph to find all upstream feedstock technologies at any depth and connects each feedstock's VarOpEx output. This is consistent with the _find_feedstock_techs method used by the controller component internally.

    • "buy_price": the controller's {tech_name}_buy_price input is connected input-to-input to the technology's own buy-price input (electricity_buy_price for Grid, price for Feedstock) so a single prob.set_val() on the tech propagates to the SLC. The default value still comes from the tech config.

    • Numeric scalar: no connection needed; the value is used directly as a constant marginal cost.

  5. Connect the demand profile - Connects the demand technology's output ({demand_tech}.{demand_commodity}_demand_out) to the controller's demand input (system_level_controller.{demand_commodity}_demand). This relies on the current SLC constraint that exactly one demand component is defined.

Parameters:

slc_topology (dict) --

Pre-computed dictionary produced by _classify_slc_technologies(). Expected keys:

  • "demand_tech" (str): Name of the demand technology.

  • "demand_commodity" (str): Commodity the demand consumes.

  • "tech_to_commodity" (set[tuple[str, str]]): Set of (tech_name, commodity) pairs for all controlled techs.

  • "tech_control_classifiers" (dict[str, str]): Mapping of tech name to classifier ("fixed", "flexible", "dispatchable", "storage", "feedstock").

  • "storage_techs_to_control" (dict[str, bool]): Whether each storage tech has its own sub-controller.

  • "technology_graph" (nx.DiGraph): Directed graph of technology interconnections.

Raises:

ValueError -- If control_strategy is not found in self.supported_models.

Side Effects

  • Adds "system_level_controller" subsystem to self.plant.

  • Sets self.plant.nonlinear_solver and self.plant.linear_solver.

  • Creates OpenMDAO connections within self.plant.

create_technology_models()#
_process_model(model_type, individual_tech_config, tech_group)#
_check_time_step(model_name, model_object)#
_check_control_classifier(model_name, model_object)#
_add_passthrough_controller(tech_group, perf_comp, individual_tech_config)#

Automatically add a PassthroughController to a tech group if appropriate.

A controller is auto-inserted only when:

  • the technology has no user-defined control_strategy in its config,

  • the performance model exposes a _control_classifier of "flexible", "dispatchable", or "storage",

  • the performance model has set commodity and commodity_rate_units attributes (typically set in its initialize()), or those values can be read from the individual tech config.

The controller's {commodity}_set_point input becomes the tech group's external set-point-input promoted at the tech group level, and its {commodity}_command_value output is auto-connected (via promotion) to the performance model's {commodity}_command_value input if one exists.

create_finance_model()#

Create and configure the finance model(s) for the plant.

This method initializes finance subsystems for the plant based on the configuration provided in self.plant_config["finance_parameters"]. It supports both default (single-model) setups and multiple/distinct (subgroup-specific) finance models.

Within this framework, a finance subgroup serves as a flexible grouping mechanism for calculating finance metrics across different subsets of technologies. These groupings can draw on varying finance inputs or models within the same simulation. To support a wide range of use cases, such as evaluating metrics for only part of a larger system, finance subgroups may reference multiple finance_groups and may overlap partially or fully with the technologies included in other finance subgroups.

Behavior

  • If finance_parameters is not defined in the plant configuration, no finance model is created.

  • If no subgroups are defined, all technologies are grouped together under a default finance group. commodity and finance_model are required in this case.

  • If subgroups are provided, each subgroup defines its own set of technologies, associated commodity, and finance model(s). Each subgroup is nested under a unique name of your choice under ["finance_parameters"]["subgroups"] in the plant configuration.

  • Subsystems such as AdjustedCapexOpexComp and GenericProductionSummerPerformanceModel, and the selected finance models are added to each subgroup's finance group.

  • If commodity_stream is provided for a subgroup, the output of the technology specified as the commodity_stream must be the same as the specified commodity for that subgroup.

  • Supports both global finance models and technology-specific finance models. Technology-specific finance models are defined in the technology configuration.

Raises:
  • ValueError -- If ["finance_parameters"]["finance_group"] is incomplete (e.g., missing commodity or finance_model) when no subgroups are defined.

  • ValueError -- If a subgroup has an invalid technology.

  • ValueError -- If a specified finance model is not found in self.supported_models.

Side Effects

  • Updates self.plant_config["finance_parameters"]["finance_group"] if only a single finance model is provided (wraps it in a default finance subgroup).

  • Constructs and attaches OpenMDAO finance subsystem groups to the plant model under names finance_subgroup_<subgroup_name>.

  • Stores processed subgroup configurations in self.finance_subgroups.

Example

Suppose plant_config["finance_parameters"]["finance_group"] defines a single finance model without subgroups:

>>> self.plant_config["finance_parameters"]["finance_group"] = {
...     "commodity": "hydrogen",
...     "finance_model": "ProFastLCO",
...     "model_inputs": {"discount_rate": 0.08},
... }
>>> self.create_finance_model()
# Creates a default subgroup containing all technologies and
# attaches a ProFAST finance model component to the plant.
_connect_multivariable_stream(source_tech, dest_tech, stream_name, combiner_counts, splitter_counts)#

Connect a multivariable stream between source and destination technologies.

Handles combiner indexing (numbered inputs), splitter indexing (numbered outputs), and direct connections. Updates combiner_counts/splitter_counts dicts in-place.

Parameters:
  • source_tech (str) -- Name of the source technology.

  • dest_tech (str) -- Name of the destination technology.

  • stream_name (str) -- Name of the multivariable stream (key in multivariable_streams).

  • combiner_counts (dict) -- Tracks the next input index per combiner technology.

  • splitter_counts (dict) -- Tracks the next output index per splitter technology.

connect_technologies()#
create_driver_model()#

Add the driver to the OpenMDAO model and add recorder.

setup()#

Extremely light wrapper to setup the OpenMDAO problem and track setup status.

run()#
post_process(print_results=True, summarize_sql=False, show_plots=False)#

Post-process the results of the OpenMDAO model.

Prints the inputs and outputs to all systems in the model, excluding any variables with "resource_data" in the name since those are large dictionary variables that are not correctly formatted when printing.

Parameters:
  • print_results (bool) -- If True, print a summary of all model inputs and outputs. Defaults to True.

  • summarize_sql (bool) -- If True and a recorder file was written, convert the SQL recorder file to a CSV summary. Defaults to False.

  • show_plots (bool) -- If True, run post-processing plots for any performance models that support them. Defaults to False.

static print_results(model, includes=None, excludes=None, show_units=True)#

Print hierarchical inputs plus explicit/implicit outputs (means only) using Rich.

Order of rows preserves OpenMDAO's original ordering from list_inputs/list_outputs. Group rows are emitted lazily the first time a variable within that path appears.

create_xdsm(outfile='connections_xdsm')#

Create an XDSM diagram from the plant technology interconnections.

This method reads technology_interconnections from self.plant_config and delegates diagram generation to h2integrate.core.utilities.create_xdsm_from_config().

Parameters:

outfile (str, optional) -- Base filename for the generated XDSM output. The default is "connections_xdsm".

Raises:

ValueError -- If technology_interconnections is empty or missing from the plant configuration.

create_technology_graph(tech_interconnections)#

Create a directed graph of the technology interconnections.

Builds a NetworkX directed graph where nodes represent technologies and edges represent connections between them. If a connection includes a commodity (length-4 entry), it is stored as an edge attribute.

Parameters:

tech_interconnections (list) -- list of technology interconnections

Returns:

nx.DiGraph --

A directed graph with technologies as nodes and

interconnections as edges.

_check_tech_connections()#

Check that commodity streams between technologies are valid.

Validates that each commodity in a length-4 technology interconnection is output by the source technology and accepted as input by the destination technology. Does not check length-3 connections or missing input commodity streams.

Raises:

ValueError -- If any commodity connection is invalid.

static _split_indices_from_connected_parameter_definition(connected_parameter)#

Extract and parse slice indices from connected parameter definitions for OpenMDAO connections.

This function processes parameter names containing slice patterns in square brackets (e.g., "power[0:8760]") and generates OpenMDAO-compatible src_indices for connections between variables of different shapes.

Parameters:

connected_parameter (list[str]) --

A two-element list containing: - [0] source parameter name, optionally with pattern like "var[slice_spec]" - [1] destination parameter name, optionally with pattern like "var[slice_spec]"

Example: ["power[0:8760]", "demand[:]"]

Returns:

tuple --

A two-element tuple containing:
  • connected_parameter (list[str]): The parameter names with slices removed (e.g., ["power", "demand"])

  • src_indices: OpenMDAO slicer object for indexing source outputs to match destination input shapes. Returns om.slicer[slice] for indexing.

Note

If the destination has a slice pattern, it must include the length ":N" (e.g., "[0:N]"), the function extracts N as the destination length and multiplies the source slice by this factor to create properly scaled indices. The length is required because the length is not known in the OpenMDAO model until prob.setup() has been called.