"""Decision Tree"""
from __future__ import annotations
from dataclasses import dataclass
from functools import cached_property
from itertools import chain
from typing import TYPE_CHECKING, Literal
from .._core._dimension import _Dimension
from ..modeling.indices.domain import Domain
from ..modeling.variables.control import Control
if TYPE_CHECKING:
from ..modeling.variables.aspect import Aspect
from ..modeling.variables.states import Consequence, State, Stream
from .space import Space
from .time import Time
[docs]
@dataclass
class Problem(_Dimension):
"""
Aspect (Decision) Tree.
All the aspects are attached to this object.
:param model: Model to which the representation belongs.
:type model: Model
:ivar name: Name of the dimension, generated based on the class and model name.
:vartype name: str
:ivar controls: List of controls. Defaults to empty list.
:vartype controls: list[Decision]
:ivar streams: List of streams. Defaults to empty list.
:vartype streams: list[Stream]
:ivar consequences: List of consequences. Defaults to empty list.
:vartype consequences: list[Impact]
:ivar players: List of players. Defaults to empty list.
:vartype players: list[Player]
.. note::
- name is generated based on Model name
- controls, streams, consequences, and players are populated as model is defined
"""
def __post_init__(self):
self.states: list[State] = []
self.controls: list[Control] = []
self.streams: list[Stream] = []
self.consequences: list[Consequence] = []
_Dimension.__post_init__(self)
[docs]
@cached_property
def time(self) -> Time:
"""Time"""
return self.model.time
[docs]
@cached_property
def space(self) -> Space:
"""Space"""
return self.model.space
@property
def aspects(self) -> list[Consequence | Stream | Control | State]:
"""All Decisions"""
return self.states + self.controls + self.streams + self.consequences
@property
def domains(self) -> list[Domain]:
"""All Domains"""
return list(set(chain.from_iterable(d.domains for d in self.aspects)))
[docs]
def get(
self,
keys: Literal[
"aspects",
"states",
"controls",
"streams",
"consequences",
"domains",
] = "aspects",
values: Literal[
"aspects",
"states",
"controls",
"streams",
"consequences",
"domains",
] = "domains",
) -> dict[Aspect | Domain, list[Aspect | Domain]]:
"""Get a dictionary of the tree with a particular structure
:param keys: Keys to use for the dictionary. Defaults to 'aspects'.
:type keys: Literal[ 'aspects', 'states', 'controls', 'streams', 'consequences', 'domains' ], optional
:param values: Values to use for the dictionary. Defaults to 'domains'.
:type values: Literal[ 'aspects', 'states', 'controls', 'streams', 'consequences', 'domains' ], optional
:returns: dictionary with particular structure
:rtype: dict[Aspect | Domain, list[Aspect | Domain]]
:raises ValueError: If keys is not one of the allowed values
"""
# This function will essentially return a dictionary
# with any choice of keys and values
# can come very handy
if keys in ["aspects", "states", "controls", "streams", "consequences"]:
keysset: list[Aspect] = getattr(self, keys)
if values == "domains":
return {d: [dom.tup for dom in d.domains] for d in keysset if d.domains}
return {d: getattr(d, values) for d in keysset if getattr(d, values)}
if keys in ["domains"]:
dict_ = {d: [] for d in self.domains}
for dom in self.domains:
for val in self.get(keys=values, values=keys):
if dom in self.get(keys=values, values=keys)[val]:
dict_[dom].append(val)
return {k.tup: v for k, v in dict_.items() if v}
raise ValueError(
"keys must be one of aspects, states, controls, streams, consequences, domains",
)