From 7f0021b55d5f0f2ec60d618e88f0bafea86c1684 Mon Sep 17 00:00:00 2001
From: "christoph.von.oy" <christoph.von.oy@rwth-aachen.de>
Date: Tue, 21 May 2024 22:26:40 +0200
Subject: [PATCH] Refactored results extraction and storage

---
 dynamics.py           |   4 +-
 optimization_model.py | 174 ++++++++++++++++++++++++++++++------------
 topology.py           |  29 +++----
 3 files changed, 143 insertions(+), 64 deletions(-)

diff --git a/dynamics.py b/dynamics.py
index 11c8416..50d441f 100644
--- a/dynamics.py
+++ b/dynamics.py
@@ -1501,7 +1501,9 @@ def resample_first_state_into(
             raise ValueError(
                 f"The period dynamic and the aggregated dynamic have to be part of the same period aggregation!"
             )
-        target_values[:, dynamic.period_index] = values
+        target_values[:, dynamic.period_index] = values[
+            :, 0
+        ]  # TODO: the first dimension of values and target_values is 0, somehow values[:,0] is necessary, and values on its own does not work
 
 
 def resample_variable(variable, dynamic: Dynamic, target_dynamic: Dynamic):
diff --git a/optimization_model.py b/optimization_model.py
index 9108e29..dbf54a2 100644
--- a/optimization_model.py
+++ b/optimization_model.py
@@ -133,23 +133,25 @@ class OptimizationModel(OptimizationBlock):
             == TerminationCondition.optimal
         )
 
-class EntityResult:
-    def __init__(self, var_names, architecture, dynamic):
+
+class DynamicResult:
+    def __init__(self, dynamic, var_names):
         n_u_vars = 0
         n_i_vars = 0
         n_i_prime_vars = 0
-        self.var_kinds = dict()
+        self.var_map = dict()
+
         for var_name, var_kind in var_names:
             if var_kind == VariableKind.INDEXED:
-                self.var_kinds[var_name] = ('i_result', n_i_vars)
+                self.var_map[var_name] = (1, n_i_vars)
                 n_i_vars += 1
             elif var_kind == VariableKind.UNINDEXED:
-                self.var_kinds[var_name] = ('u_result', n_u_vars)
+                self.var_map[var_name] = (0, n_u_vars)
                 n_u_vars += 1
             elif var_kind == VariableKind.EXTENDED_INDEXED:
-                self.var_kinds[var_name] = ('i_prime_result', n_i_prime_vars)
+                self.var_map[var_name] = (2, n_i_prime_vars)
                 n_i_prime_vars += 1
-                
+
         self.n_u_vars = n_u_vars
         self.u_result = np.empty(n_u_vars, dtype=float)
 
@@ -157,67 +159,141 @@ class EntityResult:
         self.i_result = np.empty((n_i_vars, dynamic.shape()), dtype=float)
 
         self.n_i_prime_vars = n_i_prime_vars
-        self.i_prime_first_result = np.empty((n_i_prime_vars, dynamic.first_state_shape()), dtype=float)
+        self.i_prime_first_result = np.empty(
+            (n_i_prime_vars, dynamic.first_state_shape()), dtype=float
+        )
         self.i_prime_result = np.empty((n_i_prime_vars, dynamic.shape()), dtype=float)
 
-        self.architecture = architecture
         self.dynamic = dynamic
 
-        self.sub_results = dict()
-
     def extract_result(self, block):
         # TODO: when the resampling is simple copying, then detect that here and extract in the loop over self.var_kinds the values directly into the EntityResult instead of creating new ndarrays
         u_values = np.empty(self.n_u_vars, dtype=float)
         i_values = np.empty((self.n_i_vars, block.dynamic.shape()), dtype=float)
-        i_prime_first_values = np.empty((self.n_i_prime_vars, block.dynamic.first_state_shape()), dtype=float)
-        i_prime_values = np.empty((self.n_i_prime_vars, block.dynamic.shape()), dtype=float)
-        for var_name, (var_kind, index) in self.var_kinds.items():
-            if var_kind == 'i_result':
+        i_prime_first_values = np.empty(
+            (self.n_i_prime_vars, block.dynamic.first_state_shape()), dtype=float
+        )
+        i_prime_values = np.empty(
+            (self.n_i_prime_vars, block.dynamic.shape()), dtype=float
+        )
+
+        for var_name, (outer_index, inner_index) in self.var_map.items():
+            if outer_index == 1:
                 value_object = block.component_dict[var_name].get_values()
-                i_values[index] = np.fromiter((value_object[t] for t in block.T), float)
-            elif var_kind == 'u_result':
-                u_values[index] = pyo.value(block.component_dict[var_name])
-            elif var_kind == 'i_prime_result':
+                i_values[inner_index] = np.fromiter(
+                    (value_object[t] for t in block.T), float
+                )
+            elif outer_index == 0:
+                u_values[inner_index] = pyo.value(block.component_dict[var_name])
+            elif outer_index == 2:
                 value_object = block.component_dict[var_name].get_values()
-                i_prime_first_values[index] = value_object[block.T_prime.first()]
-                i_prime_values[index] = np.fromiter((value_object[t] for t in block.T), float)
+                i_prime_first_values[inner_index] = value_object[block.T_prime.first()]
+                i_prime_values[inner_index] = np.fromiter(
+                    (value_object[t] for t in block.T), float
+                )
 
         self.u_result = u_values
         resample_into(i_values, block.dynamic, self.i_result, self.dynamic)
-        resample_first_state_into(i_prime_first_values, block.dynamic, self.i_prime_first_result, self.dynamic)
+        resample_first_state_into(
+            i_prime_first_values, block.dynamic, self.i_prime_first_result, self.dynamic
+        )
         resample_into(i_prime_values, block.dynamic, self.i_prime_result, self.dynamic)
 
-    def add_sub_result(self, key, result):
-        self.sub_results[key] = result
-
     def __getitem__(self, var_name):
-        if var_name in self.var_kinds:
-            res_name, index = self.var_kinds[var_name]
-            if res_name == 'i_result':
-                return Profile(self.i_result[index], self.dynamic)
-            elif res_name == 'u_result':
-                return self.u_result[index]
-            elif res_name == 'i_prime_result':
-                return Profile(self.i_prime_result[index], self.dynamic)
+        if var_name in self.var_map:
+            outer_index, inner_index = self.var_map[var_name]
+            if outer_index == 1:
+                return Profile(self.i_result[inner_index], self.dynamic)
+            elif outer_index == 0:
+                return self.u_result[inner_index]
+            elif outer_index == 2:
+                return Profile(self.i_prime_result[inner_index], self.dynamic)
             else:
                 return None
+
+    def to_excel(self, excel_writer, **kwargs):
+        sheet_name = kwargs.get("sheet_name", "")
+
+        kwargs["sheet_name"] = "u" if sheet_name == "" else sheet_name + "_u"
+        df = pd.Series(
+            self.u_result,
+            index=(
+                var_name
+                for var_name, (outer_index, _) in self.var_map.items()
+                if outer_index == 0
+            ),
+        )
+        df.to_excel(excel_writer, **kwargs)
+
+        kwargs["sheet_name"] = "i" if sheet_name == "" else sheet_name + "_i"
+        df = pd.DataFrame(
+            np.transpose(self.i_result),
+            index=self.dynamic.pandas_index(),
+            columns=(
+                var_name
+                for var_name, (outer_index, _) in self.var_map.items()
+                if outer_index == 1
+            ),
+        )
+        df.to_excel(excel_writer, **kwargs)
+
+        kwargs["sheet_name"] = (
+            "i_prime" if sheet_name == "" else sheet_name + "_i_prime"
+        )
+        df_first = pd.DataFrame(
+            np.transpose(self.i_prime_first_result),
+            index=self.dynamic.first_state_pandas_index(),
+            columns=(
+                var_name
+                for var_name, (outer_index, _) in self.var_map.items()
+                if outer_index == 2
+            ),
+        )
+        df = pd.DataFrame(
+            np.transpose(self.i_prime_result),
+            index=self.dynamic.pandas_index(),
+            columns=(
+                var_name
+                for var_name, (outer_index, _) in self.var_map.items()
+                if outer_index == 2
+            ),
+        )
+        df = pd.concat([df, df_first])
+        df.sort_index(inplace=True)
+        df.to_excel(excel_writer, **kwargs)
+
+
+class EntityResult:
+    def __init__(self, architecture):
+        self.architecture = architecture
+        self.registred_dynamics = []
+        self.names = []
+        self.var_map = dict()
+
+    def register_dynamic(self, dynamic, name, var_names):
+        self.registred_dynamics.append((dynamic, var_names))
+        self.names.append(name)
+        for var_name, _ in var_names:
+            self.var_map[var_name] = dynamic
+
+    def compile(self):
+        self.data = dict()
+
+        for dynamic, var_names in self.registred_dynamics:
+            self.data[dynamic] = DynamicResult(dynamic, var_names)
+
+    def extract_result(self, block, dynamic):
+        self.data[dynamic].extract_result(block)
+
+    def __getitem__(self, var_name):
+        if var_name in self.var_map:
+            return self.data[self.var_map[var_name]][var_name]
         else:
             return None
 
     def to_excel(self, excel_writer, **kwargs):
-        sheet_name = kwargs.get('sheet_name', '')
-        kwargs['sheet_name'] = 'unindexed' if sheet_name == '' else sheet_name + '_unindexed'
-        u_result = pd.Series(self.u_result, index=(var_name for var_name, (var_kind, _) in self.var_kinds.items() if var_kind == 'u_result'))
-        u_result.to_excel(excel_writer, **kwargs)
-        kwargs['sheet_name'] = 'T' if sheet_name == '' else sheet_name + '_T'
-        i_result = pd.DataFrame(np.transpose(self.i_result), index=self.dynamic.pandas_index(), columns=(var_name for var_name, (var_kind, _) in self.var_kinds.items() if var_kind == 'i_result'))
-        i_result.to_excel(excel_writer, **kwargs)
-        kwargs['sheet_name'] = 'T_prime' if sheet_name == '' else sheet_name + '_T_prime'
-        i_prime_first_result = pd.DataFrame(np.transpose(self.i_prime_first_result), index=self.dynamic.first_state_pandas_index(), columns=(var_name for var_name, (var_kind, _) in self.var_kinds.items() if var_kind == 'i_prime_result'))
-        i_prime_result = pd.DataFrame(np.transpose(self.i_prime_result), index=self.dynamic.pandas_index(), columns=(var_name for var_name, (var_kind, _) in self.var_kinds.items() if var_kind == 'i_prime_result'))
-        i_prime_result = pd.concat([i_prime_first_result, i_prime_result])
-        i_prime_result.sort_index(inplace=True)
-        i_prime_result.to_excel(excel_writer, **kwargs)
-        for key, sub_result in self.sub_results.items():
-            kwargs['sheet_name'] = str(key) if sheet_name == '' else sheet_name + '_' + str(key)
-            sub_result.to_excel(excel_writer, **kwargs)
+        sheet_name = kwargs.get("sheet_name", "")
+
+        for (_, result), name in zip(self.data.items(), self.names):
+            kwargs["sheet_name"] = name if sheet_name == "" else sheet_name + "_" + name
+            result.to_excel(excel_writer, **kwargs)
diff --git a/topology.py b/topology.py
index e942c3c..3a1ecb5 100644
--- a/topology.py
+++ b/topology.py
@@ -511,9 +511,8 @@ class Topology:
             for flow in self._flows:
                 base_variable_names.append((flow, VariableKind.INDEXED))
 
-            result = EntityResult(
-                base_variable_names, architecture, architecture.dynamic
-            )
+            result = EntityResult(architecture)
+            result.register_dynamic(architecture.dynamic, "", base_variable_names)
 
         elif isinstance(architecture, PeriodAggregation):
             design_base_variable_names = []
@@ -522,8 +521,9 @@ class Topology:
                     self._components[component].design_base_variable_names()
                 )
 
-            result = EntityResult(
-                design_base_variable_names, architecture, architecture.dynamic
+            result = EntityResult(architecture)
+            result.register_dynamic(
+                architecture.dynamic, "", design_base_variable_names
             )
 
             operational_base_variable_names = []
@@ -535,16 +535,17 @@ class Topology:
             for flow in self._flows:
                 operational_base_variable_names.append((flow, VariableKind.INDEXED))
 
-            for period_index, period_dynamic in enumerate(architecture.period_dynamics):
-                period_result = EntityResult(
-                    operational_base_variable_names, None, period_dynamic
-                )
-
-                result.add_sub_result(period_index, period_result)
+            result.register_dynamic(
+                architecture.value_dynamic,
+                "aggregated",
+                operational_base_variable_names,
+            )
 
         else:
             raise ValueError(f"Invalid architecture type {type(architecture)}")
 
+        result.compile()
+
         self._results[key] = result
         self._last_result_key = key
 
@@ -556,15 +557,15 @@ class Topology:
         result = self._results[key]
 
         if isinstance(result.architecture, TrivialArchitecture):
-            result.extract_result(block)
+            result.extract_result(block, result.architecture.dynamic)
 
         elif isinstance(result.architecture, PeriodAggregation):
-            result.extract_result(block)
+            result.extract_result(block, result.architecture.dynamic)
 
             for period_index in range(result.architecture.number_of_periods()):
                 period_block = block.blocks[str(period_index)]
 
-                result.sub_results[period_index].extract_result(period_block)
+                result.extract_result(period_block, result.architecture.value_dynamic)
 
         else:
             raise ValueError(f"Invalid architecture type {type(result.architecture)}")
-- 
GitLab