Source code for energia.modeling.variables.aspect

"""Variable"""

from __future__ import annotations

from dataclasses import dataclass
from functools import cached_property
from typing import TYPE_CHECKING, Self, Type

from ...components.commodities.commodity import Commodity
from ...components.game.couple import Interact
from ...components.game.player import Player
from ...components.impact.indicator import Indicator
from ...components.operations.process import Process
from ...components.operations.storage import Storage
from ...components.operations.transport import Transport
from ...components.spatial.linkage import Linkage
from ...components.spatial.location import Location
from ...components.temporal.lag import Lag
from ...components.temporal.modes import Modes
from ...components.temporal.periods import Periods
from ...dimensions.space import Space
from ...dimensions.time import Time
from ...utils.dictionary import merge_tree_levels
from ..constraints.balance import Balance as BalCons
from ..constraints.vmap import Map as MapCons
from ..indices.domain import Domain
from ..indices.sample import Sample

if TYPE_CHECKING:
    from gana import I as Idx
    from gana import Prg
    from gana import V as Var
    from gana.sets.constraint import C

    from ..._core._component import _Component
    from ..._core._x import _X
    from ...dimensions.problem import Problem
    from ...represent.model import Model


[docs] @dataclass class Aspect: r""" A particular facet of the system under consideration. A sample of an aspect at a specific disposition is represented by a variable (:math:`\overset{\*}{v} \in \overset{\*}{\mathcal{V}}`). The range of values which the :math:`\overset{\*}{v}` is bounded such that: .. math:: \overset{\*}{v} \in [\underline{\theta}, \overline{\theta}] Aspects can be decision-variables (:math:`\dot{v}`) that prescribe a control action or a set point for the state, or derived (calculated) variables (:math:`\hat{v}`) that represent the state of the system. States can be operation capacity, production levels, purchase levels, emissions, consumption levels, etc. :param primary_types: associated components type(s). Defaults to None. :type primary_types: Type[_Component] | tuple[Type[_Component], ...] :param nn: If True, the decision is a non-negative decision. Defaults to True. :type nn: bool :param ispos: If True, the decision is positive (non-negative). Defaults to True. :type ispos: bool :param neg: Negative form or representation of the decision, if any. Defaults to "". :type neg: str :param latex: LaTeX string. Defaults to "". :type latex: str :param bound: if the aspect is bounded by another. Defaults to "". :type bound: str :param label: Label for the decision. Defaults to "". :type label: str :param use_multiplier: Use a scaler (such as distance) for calculations :type use_multiplier: bool :ivar model: Model to which the Aspect belongs. :vartype model: Model :ivar name: Name of the Aspect. :vartype name: str :ivar indices: List of indices (Location, Periods) associated with the Aspect. :vartype indices: list[Location | Linkage, Periods] :ivar bound_spaces: Spaces where the Aspect has been already bound. :vartype bound_spaces: dict[Commodity | Process | Storage | Transport, list[Location | Linkage]] :ivar domains: List of domains associated with the Aspect. :vartype domains: list[Domain] :raises ValueError: If `primary_type` is not defined. """ primary_type: Type[_Component] | tuple[Type[_Component], ...] nn: bool = True ispos: bool = True neg: str = "" latex: str = "" bound: str = "" label: str = "" use_multiplier: bool = False def __post_init__(self): # will be set when added to model self.name: str = "" # name of the decision self.model: Model | None = None if self.label: self.label += " [+]" if self.nn else " [-]" # spaces where the aspect has been already bound self.bound_spaces: dict[ Commodity | Process | Storage | Transport, list[Location | Linkage], ] = {} # Domains of the decision self.domains: list[Domain] = [] self.indices: list[tuple[Idx]] = [] # a dictionary of domains and their maps from higher order domains # reporting variable self.reporting: Var | None = None self.constraints: set[str] = set()
[docs] @cached_property def maps(self) -> dict[str, dict[Domain, list[Domain]]]: """Maps of the decision""" self.model.maps[self] = { "time": {}, "space": {}, "modes": {}, "samples": {}, } return self.model.maps[self]
[docs] @cached_property def maps_report(self) -> dict[str, dict[Domain, list[Domain]]]: """Maps of the decision""" self.model.maps_report[self] = { "time": {}, "space": {}, "modes": {}, "samples": {}, } return self.model.maps_report[self]
[docs] @cached_property def isneg(self) -> bool: """Does this remove from the domain?""" return not self.ispos
[docs] @cached_property def sign(self) -> float: """Gives the multiplier in balances""" if self.ispos: return 1.0 else: return -1.0
[docs] @cached_property def space(self) -> Space: """Space""" return self.model.space
[docs] @cached_property def program(self) -> Prg: """Mathematical Program""" return self.model.program
[docs] @cached_property def problem(self) -> Problem: """Tree""" return self.model.problem
[docs] @cached_property def time(self) -> Time: """Time""" return self.model.time
@property def I(self) -> Idx: """gana index set (I)""" return getattr(self.program, self.name) @property def V(self) -> Var: """Variable""" return getattr(self.program, self.name) @property def cons(self) -> list[C]: """Constraints""" return [getattr(self.program, c) for c in self.constraints] @property def network(self) -> Location: """Circumscribing Location (Spatial Scale)""" return self.model.network @property def horizon(self) -> Periods: """Circumscribing Periods (Temporal Scale)""" return self.model.horizon @property def dispositions(self) -> dict[ Self, dict[ Commodity | Process | Storage | Transport, dict[ Periods | Location | Linkage, dict[Location | Periods | Linkage, bool], ], ], ]: """Dispositions dict""" return self.model.dispositions[self] @property def sizes(self): """dict of domain sizes""" _sizes = {} for d in self.domains: if d.size in _sizes: _sizes[d.size].append(d.index) else: _sizes[d.size] = [d.index] return _sizes @property def box(self): """Box of domain indices""" return [b.index_short for b in self.domains]
[docs] def crumple_domains(self): return merge_tree_levels(self.dispositions)
[docs] def alias(self, *names: str): """ Create aliases for the decision :param names: Names of the aliases :type names: str """ self.model.alias(*names, of=self.name)
[docs] def update(self, domain: Domain, reporting: bool = False): """Each inherited object has their own"""
[docs] def show(self, descriptive=False): """Pretty print the component""" for c in self.cons: c.show(descriptive)
[docs] def output( self, n_sol: int = 0, aslist: bool = False, asdict: bool = False, compare: bool = False, ) -> list[float] | dict[tuple[Idx, ...], float] | None: """ Solution :param n_sol: Solution number. Defaults to 0. :type n_sol: int, optional :param compare: Compares the solution with the previous one. Defaults to False. :type compare: bool, optional :param asdict (bool, optional): Returns values taken as dict. Defaults to False. :type asdict: bool, optional :param aslist (bool, optional): Returns values taken as list. Defaults to False. :type aslist: bool, optional :return: List of values taken by the decision. :rtype: list[float] | None """ var: Var = getattr(self.program, self.name) return var.output(n_sol, aslist=aslist, asdict=asdict, compare=compare)
[docs] def gettime(self, *index) -> list[Periods]: """Finds the sparsest time scale in the domains""" ds = [i for i in self.indices if all([x in i for x in index])] t = [t for t in ds if isinstance(t, Periods)] return t
[docs] def Map(self, domain: Domain, reporting: bool = False): """Map the aspect to the domain""" MapCons(aspect=self, domain=domain, reporting=reporting)
[docs] def Balance(self, domain: Domain): """Add a general resource balance for the aspect over the domain""" BalCons(aspect=self, domain=domain)
def __neg__(self): """Negative Consequence""" dscn = type(self)( nn=False, primary_type=self.primary_type, ) dscn.neg, self.neg = self, dscn dscn.ispos = not self.ispos return dscn def __len__(self): return len(self.domains) def __eq__(self, other: Self) -> bool: return str(self) == str(other) def __getitem__(self, item: _X) -> Sample: return self.dispositions[item] def __call__( self, *index: _X, domain: Domain | None = None, report=False ) -> Sample: if not domain: args = { "indicator": None, "commodity": None, "player": None, "process": None, "storage": None, "transport": None, "periods": None, "couple": None, "location": None, "linkage": None, "lag": None, "modes": None, } type_map = { Periods: ("periods", "timed", False), Lag: ("lag", "timed", False), Location: ("location", "spaced", False), Linkage: ("linkage", "spaced", False), Process: ("process", None, True), Storage: ("storage", None, True), Transport: ("transport", None, True), Player: ("player", None, False), Interact: ("couple", None, False), Indicator: ("indicator", None, False), Modes: ("modes", None, False), Commodity: ("commodity", None, True), } samples: list[Sample] = [] timed, spaced = False, False for comp in index: if isinstance(comp, Sample): samples.append(comp) for b in samples: if b.domain.samples: samples.extend(b.domain.samples) samples = list(set(samples)) continue for typ, (attr, flag, require_primary) in type_map.items(): if isinstance(comp, typ): if require_primary and ( not self.primary_type or not isinstance(comp, self.primary_type) ): raise ValueError( f"For component {self} of type {type(self)}: " f"{comp} of type {type(comp)} not recognized as an index", ) if not args[attr]: args[attr] = comp if flag == "timed": timed = True elif flag == "spaced": spaced = True break else: raise ValueError( f"For component {self} of type {type(self)}: " f"{comp} of type {type(comp)} not recognized as an index", ) args = {k: v for k, v in args.items() if v is not None} if samples: args["samples"] = samples domain = Domain(**args) else: timed = spaced = True return Sample( aspect=self, domain=domain, timed=timed, spaced=spaced, report=report ) def __str__(self): return self.name def __repr__(self): return self.name def __hash__(self): return hash(self.name) def __iter__(self): """Iterate over domains""" for d in self.domains: yield self(domain=d) def __init_subclass__(cls): cls.__repr__ = Aspect.__repr__ cls.__hash__ = Aspect.__hash__ cls.__eq__ = Aspect.__eq__