From 63ae42ca571e448ef0168c5a8077a4f1b2277faf Mon Sep 17 00:00:00 2001
From: "christoph.von.oy" <christoph.von.oy@rwth-aachen.de>
Date: Thu, 16 May 2024 13:34:18 +0200
Subject: [PATCH] Extended Dynamic interface 2

---
 dynamics.py | 95 +++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 74 insertions(+), 21 deletions(-)

diff --git a/dynamics.py b/dynamics.py
index e991542..5a0992f 100644
--- a/dynamics.py
+++ b/dynamics.py
@@ -40,9 +40,19 @@ class Dynamic(abc.ABC):
     def shape(self) -> int:
         pass
 
-    # returns the length that a ndarray needs in order to hold state values for this dynamic
+    # returns the length that a ndarray needs in order to hold the first state values for this dynamic
     @abc.abstractmethod
-    def state_shape(self) -> int:
+    def first_state_shape(self) -> int:
+        pass
+
+    # returns the index that a pandas Series or DataFrame needs in order to hold values for this dynamic
+    @abc.abstractmethod
+    def pandas_index(self) -> pd.Index:
+        pass
+
+    # returns the index that a pandas Series or DataFrame needs in order to hold first state values for this dynamic
+    @abc.abstractmethod
+    def first_state_pandas_index(self) -> pd.Index:
         pass
 
     # returns the length of the time step at the given position in hours
@@ -108,8 +118,14 @@ class TrivialDynamic(Dynamic):
     def shape(self) -> int:
         return len(self.d_steps)
 
-    def state_shape(self) -> int:
-        return len(self.d_steps) + 1
+    def first_state_shape(self) -> int:
+        return 1
+
+    def pandas_index(self) -> pd.Index:
+        return pd.Index(np.arange(0, len(self.d_steps), dtype=int), dtype=int)
+
+    def first_state_pandas_index(self) -> pd.Index:
+        return pd.Index([-1], dtype=int)
 
     def step_size(self, position) -> float:
         return self.d_steps[position] / 3600.0
@@ -269,8 +285,14 @@ class RootDynamic(TreeDynamic):
     def shape(self) -> int:
         return len(self.d_steps)
 
-    def state_shape(self) -> int:
-        return len(self.d_steps) + 1
+    def first_state_shape(self) -> int:
+        return 1
+
+    def pandas_index(self) -> pd.Index:
+        return pd.Index(np.arange(0, len(self.d_steps), dtype=int), dtype=int)
+
+    def first_state_pandas_index(self) -> pd.Index:
+        return pd.Index([-1], dtype=int)
 
     def step_size(self, position) -> float:
         return self.d_steps[position] / 3600.0
@@ -339,8 +361,14 @@ class BackedDynamic(TreeDynamic):
     def shape(self) -> int:
         return len(self.indices) - 1
 
-    def state_shape(self) -> int:
-        return len(self.indices)
+    def first_state_shape(self) -> int:
+        return 1
+
+    def pandas_index(self) -> pd.Index:
+        return pd.Index(self.indices[:-1], dtype=int)
+
+    def first_state_pandas_index(self) -> pd.Index:
+        return pd.Index([-1], dtype=int)
 
     def step_size(self, position) -> float:
         if position < 0 or len(self.indices) <= position:
@@ -458,8 +486,14 @@ class PartialDynamic(TreeDynamic):
     def shape(self) -> int:
         return self.end - self.start
 
-    def state_shape(self) -> int:
-        return self.end - self.start + 1
+    def first_state_shape(self) -> int:
+        return 1
+
+    def pandas_index(self) -> pd.Index:
+        return pd.Index(self.reference._all_indices()[self.start : self.end], dtype=int)
+
+    def first_state_pandas_index(self) -> pd.Index:
+        return pd.Index([-1], dtype=int)
 
     def step_size(self, position) -> float:
         if position < 0 or self.end - self.start < position:
@@ -1250,7 +1284,7 @@ class AggregatedDynamic(Dynamic):
         self.period_dynamics = []
         self.n_p = []
         running_index = 0
-        self.offsets_ = []
+        self.offsets = []
         for i, period in enumerate(periods):
             index = 0
             period_indices = [0]
@@ -1265,12 +1299,12 @@ class AggregatedDynamic(Dynamic):
             self.n_p.append(
                 sum(1 for period_index in period_order if period_index == i)
             )
-            self.offsets_.append((running_index, running_index + len(period)))
+            self.offsets.append((running_index, running_index + len(period)))
             running_index += len(period)
         self.period_order = period_order
         self.n = len(period_order)
         self.shape_ = running_index
-        self.state_shape_ = running_index + len(periods)
+        self.frist_state_shape_ = len(periods)
 
         # sanity check 5: all elements in period order are valid period indices (above n_p[i] is the number of times that period index i appears in period order -> sum(n_p) == len(period_order))
         if sum(n_p for n_p in self.n_p) != len(period_order):
@@ -1281,8 +1315,8 @@ class AggregatedDynamic(Dynamic):
     def number_of_periods(self) -> int:
         return len(self.period_dynamics)
 
-    def offsets(self, period_index) -> Tuple[int]:
-        return self.offsets_[period_index]
+    def offset(self, period_index) -> Tuple[int]:
+        return self.offsets[period_index]
 
     def number_of_steps(self) -> int:
         return self.dynamic.number_of_steps()
@@ -1290,8 +1324,21 @@ class AggregatedDynamic(Dynamic):
     def shape(self) -> int:
         return self.shape_
 
-    def state_shape(self) -> int:
-        return self.state_shape_
+    def first_state_shape(self) -> int:
+        return self.frist_state_shape_
+
+    def pandas_index(self) -> pd.Index:
+        first_level = np.empty(self.shape(), dtype=int)
+        second_level = np.empty(self.shape(), dtype=int)
+        for i, (start, end) in enumerate(self.offsets):
+            first_level[start:end] = i
+            second_level[start:end] = np.arange(0, end - start, dtype=int)
+        return pd.MultiIndex.from_arrays([first_level, second_level])
+
+    def first_state_pandas_index(self) -> pd.Index:
+        first_level = np.arange(0, self.number_of_periods(), dtype=int)
+        second_level = np.full(self.first_state_shape(), -1, dtype=int)
+        return pd.MultiIndex.from_arrays([first_level, second_level])
 
     def step_size(self, position) -> float:
         return self.dynamic.step_size(position)
@@ -1323,8 +1370,14 @@ class PeriodDynamic(Dynamic):
     def shape(self) -> int:
         return self.dynamic.shape()
 
-    def state_shape(self) -> int:
-        return self.dynamic.state_shape()
+    def first_state_shape(self) -> int:
+        return self.dynamic.first_state_shape()
+
+    def pandas_index(self) -> pd.Index:
+        return self.dynamic.pandas_index()
+
+    def first_state_pandas_index(self) -> pd.Index:
+        return self.dynamic.first_state_pandas_index()
 
     def step_size(self, position) -> float:
         return self.dynamic.step_size(position)
@@ -1377,7 +1430,7 @@ def resample_aggregation_down(
         raise ValueError(
             f"The period dynamic has to be part of the aggregated dynamic!"
         )
-    offset = dynamic.offsets(target_dynamic.period_index)
+    offset = dynamic.offset(target_dynamic.period_index)
     return values[:, offset[0] : offset[1]]
 
 
@@ -1422,7 +1475,7 @@ def resample_aggregation_up(
         raise ValueError(
             f"The period dynamic has to be part of the aggregated dynamic!"
         )
-    offset = target_dynamic.offsets(dynamic.period_index)
+    offset = target_dynamic.offset(dynamic.period_index)
     target_values[:, offset[0] : offset[1]] = values
 
 
-- 
GitLab