Source code for dove.core.system

# Copyright 2024, Battelle Energy Alliance, LLC
# ALL RIGHTS RESERVED
"""
``dove.core.system``
====================

Module containing the System class for modeling energy systems.

The System class forms the central component of DOVE models, connecting
resources and components in a coherent framework for optimization and simulation.
The System manages time series data, component interactions, and provides
methods for building and solving optimization problems.

Classes
--------
    - `System`: Main class for creating and managing DOVE energy system models.
"""

from __future__ import annotations

from typing import Any, Self

from dove.models import BUILDER_REGISTRY

from . import Component, Resource, Storage


[docs] class System: """ The main class for creating and managing energy system models. A System represents a collection of components and resources that form an energy system. It provides methods for adding components and resources, validating the system configuration, and solving optimization problems. Attributes ---------- components : list[Component] The components in the system. resources : list[Resource] The resources in the system. time_index : list[int] The time periods for simulation. comp_map : dict[str, Component] A mapping of component names to component objects. res_map : dict[str, Resource] A mapping of resource names to resource objects. """
[docs] def __init__( self, components: list[Component] | None = None, resources: list[Resource] | None = None, time_index: list[int] | None = None, ) -> None: """ Initialize a System instance. Parameters ---------- components : list[Component] | None, optional The components to include in the system. If None, an empty list is used. resources : list[Resource] | None, optional The resources to include in the system. If None, an empty list is used. time_index : list[int] | None, optional The time periods for simulation. If None, [0] is used. """ self.components: list[Component] = [] if components is None else components self.verify_components_definition() self.resources: list[Resource] = [] if resources is None else resources self.verify_resources_definition() self.time_index = [0] if time_index is None else time_index self.comp_map: dict[str, Component] = {comp.name: comp for comp in self.components} self.res_map: dict[str, Resource] = {res.name: res for res in self.resources} self.verify_time_series()
@property def non_storage_comp_names(self) -> list[str]: """The names of all non-storage components in the system.""" return [cn for cn in self.comp_map if not isinstance(self.comp_map[cn], Storage)] @property def storage_comp_names(self) -> list[str]: """The names of all storage components in the system.""" return [cn for cn in self.comp_map if isinstance(self.comp_map[cn], Storage)]
[docs] def summary(self) -> None: """ Print a summary of the system configuration. Displays information about the number and names of components and resources, categorizes components into storage and non-storage types, and shows the time horizon length. """ info = { "num_components": len(self.components), "component_names": [c.name for c in self.components], "non_storage_components": self.non_storage_comp_names, "storage_components": self.storage_comp_names, "num_resources": len(self.resources), "resource_names": [r.name for r in self.resources], "time_horizon": len(self.time_index), } print(info)
[docs] def verify_components_definition(self) -> None: """ Verify that components are valid. Checks for: - Uniqueness of component names Raises ------ ValueError If component names are not unique """ # Check for unique component names component_names = [comp.name for comp in self.components] if len(component_names) != len(set(component_names)): raise ValueError("Component names must be unique!")
[docs] def verify_resources_definition(self) -> None: """ Verify that resources are valid. Checks for: - Type of resources - Uniqueness of resource names Raises ------ TypeError If any resource is not of type Resource ValueError If resource names are not unique """ # Check the types of the resources for res in self.resources: if not isinstance(res, Resource): raise TypeError(f"Type of {res} is not Resource.") # Check for unique resource names resource_names = [res.name for res in self.resources] if len(resource_names) != len(set(resource_names)): raise ValueError("Resource names must be unique!")
[docs] def verify_time_series(self) -> None: """ Verify the integrity of the system's time series. Checks for: - Consistency of time series lengths with the system time index Raises ------ ValueError If component capacity profile length doesn't match time index length """ # Check that time index length matches component capacity profiles for comp in self.components: if len(comp.max_capacity_profile) != len(self.time_index): raise ValueError( f"Component '{comp.name}' has a capacity profile length " "that does not match the time index length!" )
[docs] def add_component(self, comp: Component) -> Self: """ Add a component to the system. Parameters ---------- comp : Component The component to add. Returns ------- Self The system instance for method chaining. Raises ------ ValueError If the component validation fails (via verify method). """ self.components.append(comp) self.verify_components_definition() self.verify_time_series() self.comp_map[comp.name] = comp return self
[docs] def add_resource(self, res: Resource) -> Self: """ Add a resource to the system. Parameters ---------- res : Resource The resource to add. Returns ------- Self The system instance for method chaining. """ self.resources.append(res) self.verify_resources_definition() self.res_map[res.name] = res return self
[docs] def build(self) -> None: """ Build the system model. This method is intended to construct the necessary optimization problem based on the system configuration. Raises ------ NotImplementedError This method is not yet implemented. """ # TODO: Implement the build method raise NotImplementedError("System method 'build' is not yet implemented!")
[docs] def solve(self, model_type: str = "price_taker", **kw: dict[str, Any]) -> Any: """ Solve the system optimization problem. Parameters ---------- model_type : str, default="price_taker" The type of optimization model to use. **kw : dict[str, Any] Additional keyword arguments to pass to the solver. Returns ------- Any The optimization results. Raises ------ ValueError If the specified model_type is not registered. """ try: builder_cls = BUILDER_REGISTRY[model_type] except KeyError as err: raise ValueError( f"Unknown model type: '{model_type}'! Available: {list(BUILDER_REGISTRY)}" ) from err builder = builder_cls(self) builder.build() builder.solve(**kw) return builder.extract_results()