diff --git a/CHANGELOG.md b/CHANGELOG.md index f7799af..9b5e5b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ## Unreleased ### Added +- Congestion result [#198](https://github.com/ie3-institute/pypsdm/issues/198) ### Changed - Adapt Coordinate type to use WKBElement Geography [#287](https://github.com/ie3-institute/pypsdm/issues/287) diff --git a/pypsdm/models/enums.py b/pypsdm/models/enums.py index dfb6702..8269aa9 100644 --- a/pypsdm/models/enums.py +++ b/pypsdm/models/enums.py @@ -40,6 +40,7 @@ def get_plot_name(self): def get_result_type(self) -> type[TimeSeries]: # locally to avoid circular imports from pypsdm import FlexOption + from pypsdm.models.result.grid.congestions import CongestionResult from pypsdm.models.result.grid.connector import ConnectorCurrent from pypsdm.models.result.grid.switch import SwitchResult from pypsdm.models.result.grid.transformer import Transformer2WResult @@ -66,6 +67,8 @@ def get_result_type(self) -> type[TimeSeries]: return ConnectorCurrent case RawGridElementsEnum.SWITCH: return SwitchResult + case RawGridElementsEnum.CONGESTION: + return CongestionResult case _: raise NotImplementedError( f"Result type {self} not implemented yet!" @@ -74,6 +77,7 @@ def get_result_type(self) -> type[TimeSeries]: raise ValueError(f"Entity type {self} not supported!") def get_result_dict_type(self) -> Type["EntitiesResultDictMixin"]: + from pypsdm.models.result.grid.congestions import CongestionsResult from pypsdm.models.result.grid.line import LinesResult from pypsdm.models.result.grid.node import NodesResult from pypsdm.models.result.grid.switch import SwitchesResult @@ -100,6 +104,8 @@ def get_result_dict_type(self) -> Type["EntitiesResultDictMixin"]: return Transformers2WResult case RawGridElementsEnum.SWITCH: return SwitchesResult + case RawGridElementsEnum.CONGESTION: + return CongestionsResult case SystemParticipantsEnum.ELECTRIC_VEHICLE: return EvsResult case SystemParticipantsEnum.EV_CHARGING_STATION: @@ -161,6 +167,7 @@ class RawGridElementsEnum(EntitiesEnum): TRANSFROMER_3_W = "transformer_3_w" SWITCH = "switch" MEASUREMENT_UNIT = "measurement_unit" + CONGESTION = "congestion" class ThermalGridElementsEnum(EntitiesEnum): diff --git a/pypsdm/models/gwr.py b/pypsdm/models/gwr.py index 3c31d5d..578ec01 100644 --- a/pypsdm/models/gwr.py +++ b/pypsdm/models/gwr.py @@ -112,6 +112,10 @@ def transformers_2_w_res(self): def switches_res(self): return self.raw_grid_res.switches + @property + def congestions_res(self): + return self.results.raw_grid.congestions + @property def participants_res(self): return self.results.participants diff --git a/pypsdm/models/result/container/grid.py b/pypsdm/models/result/container/grid.py index 49753f9..136ee97 100644 --- a/pypsdm/models/result/container/grid.py +++ b/pypsdm/models/result/container/grid.py @@ -41,6 +41,10 @@ def transformers_2w(self): def switches(self): return self.raw_grid.switches + @property + def congestions(self): + return self.raw_grid.congestions + @property def ems(self): return self.participants.ems diff --git a/pypsdm/models/result/container/raw_grid.py b/pypsdm/models/result/container/raw_grid.py index 365aac4..a2323ed 100644 --- a/pypsdm/models/result/container/raw_grid.py +++ b/pypsdm/models/result/container/raw_grid.py @@ -10,6 +10,7 @@ from pypsdm.models.result.grid.node import NodesResult from pypsdm.models.result.grid.switch import SwitchesResult from pypsdm.models.result.grid.transformer import Transformers2WResult +from pypsdm.models.result.grid.congestions import CongestionsResult from pypsdm.models.ts.base import EntityKey @@ -19,6 +20,7 @@ class RawGridResultContainer(ResultContainerMixin): lines: LinesResult transformers_2w: Transformers2WResult switches: SwitchesResult + congestions: CongestionsResult def __init__(self, dct): def get_or_empty(key: RawGridElementsEnum, dict_type): @@ -37,6 +39,9 @@ def get_or_empty(key: RawGridElementsEnum, dict_type): RawGridElementsEnum.TRANSFORMER_2_W, Transformers2WResult ) self.switches = get_or_empty(RawGridElementsEnum.SWITCH, SwitchesResult) + self.congestions = get_or_empty( + RawGridElementsEnum.CONGESTION, CongestionsResult + ) def __len__(self): return sum(len(v) for v in self.to_dict().values()) @@ -55,6 +60,7 @@ def to_dict(self, include_empty: bool = False) -> dict: RawGridElementsEnum.LINE: self.lines, RawGridElementsEnum.TRANSFORMER_2_W: self.transformers_2w, RawGridElementsEnum.SWITCH: self.switches, + RawGridElementsEnum.CONGESTION: self.congestions, } if not include_empty: res = {k: v for k, v in res.items() if v} diff --git a/pypsdm/models/result/grid/__init__.py b/pypsdm/models/result/grid/__init__.py index 6d97443..cb45b98 100644 --- a/pypsdm/models/result/grid/__init__.py +++ b/pypsdm/models/result/grid/__init__.py @@ -1,3 +1,4 @@ +from .congestions import CongestionResult from .connector import ConnectorCurrent, ConnectorCurrentDict from .line import LinesResult from .node import NodesResult @@ -5,6 +6,7 @@ from .transformer import Transformer2WResult, Transformers2WResult __all__ = [ + "CongestionResult", "ConnectorCurrent", "ConnectorCurrentDict", "NodesResult", diff --git a/pypsdm/models/result/grid/congestions.py b/pypsdm/models/result/grid/congestions.py new file mode 100644 index 0000000..dd17cac --- /dev/null +++ b/pypsdm/models/result/grid/congestions.py @@ -0,0 +1,98 @@ +from dataclasses import dataclass +from datetime import datetime +from enum import Enum + +from pandas import DataFrame + +from pypsdm.models.result.participant.dict import EntitiesResultDictMixin +from pypsdm.models.enums import RawGridElementsEnum +from pypsdm.models.ts.base import ( + EntityKey, + TimeSeries, + TimeSeriesDict, + TimeSeriesDictMixin, +) + + +class InputModelType(Enum): + NODE = "node" + LINE = "line" + TRANSFORMER_2_W = "transformer_2w" + TRANSFORMER_3_W = "transformer_3w" + + +@dataclass +class CongestionResult(TimeSeries): + def __init__(self, data: DataFrame, end: datetime | None = None): + super().__init__(data, end) + + def __eq__(self, other: object) -> bool: + return super().__eq__(other) + + def __add__(self, _): + return NotImplemented + + @property + def subgrid(self) -> int: + return self.data["subgrid"].drop_duplicates()[0] + + @property + def input_model_type(self) -> InputModelType: + match self.data["type"].drop_duplicates()[0]: + case "node": + return InputModelType.NODE + case "line": + return InputModelType.LINE + case "transformer_2w": + return InputModelType.TRANSFORMER_2_W + case "transformer_3w": + return InputModelType.TRANSFORMER_3_W + + @property + def value(self) -> float: + return self.data["value"].drop_duplicates()[0] + + @property + def min(self) -> float: + return self.data["min"].drop_duplicates()[0] + + @property + def max(self) -> float: + return self.data["max"].drop_duplicates()[0] + + @staticmethod + def attributes() -> list[str]: + return ["subgrid", "type", "value", "min", "max"] + + +class CongestionsResult( + TimeSeriesDict[EntityKey, CongestionResult], + TimeSeriesDictMixin, + EntitiesResultDictMixin, +): + def __eq__(self, other: object) -> bool: + return super().__eq__(other) + + @property + def subgrid(self) -> DataFrame: + return self.attr_df("subgrid") + + @property + def type(self) -> DataFrame: + return self.attr_df("type") + + @property + def value(self) -> DataFrame: + return self.attr_df("value") + + @property + def min(self) -> DataFrame: + return self.attr_df("min") + + @property + def max(self) -> DataFrame: + return self.attr_df("max") + + @classmethod + def entity_type(cls) -> RawGridElementsEnum: + return RawGridElementsEnum.CONGESTION