From 4570fa9134d7b646c79f3d4417136a3f68659587 Mon Sep 17 00:00:00 2001
From: "christoph.von.oy" <christoph.von.oy@rwth-aachen.de>
Date: Wed, 27 Sep 2023 11:46:30 +0200
Subject: [PATCH] Implemented GlobalDynamic

---
 dynamics/Dynamic.py | 270 ++++++++++++++++++++++++++++++++++++--------
 1 file changed, 222 insertions(+), 48 deletions(-)

diff --git a/dynamics/Dynamic.py b/dynamics/Dynamic.py
index 2bf1155..e4af138 100644
--- a/dynamics/Dynamic.py
+++ b/dynamics/Dynamic.py
@@ -25,6 +25,59 @@ THE SOFTWARE.
 import pandas as pd
 import pyomo.environ as pyo
 
+class GlobalDynamic:
+    def __init__(self, d_steps):
+        self.d_steps = d_steps
+        self.root_dynamic = TrivialDynamic(self.d_steps, self)
+        self.dynamics = dict()
+        self.dynamics[self.root_dynamic] = (dict(), dict())
+        self.symbol_table = dict()
+        self.symbol_table[self.root_dynamic] = 0
+        self.assignments = dict()
+    
+    def root(self):
+        return self.root_dynamic
+    
+    # indices are relative to the root dynamic
+    def sub_dynamic(self, dynamic, indices):
+        if isinstance(dynamic, PartialDynamic):
+            dynamic = dynamic.reference
+        if tuple(indices) in self.dynamics[dynamic][0].keys():
+            return self.dynamics[dynamic][0][tuple(indices)]
+        sub_dynamic = BackedDynamic(dynamic, indices, self)
+        self.dynamics[dynamic][0][tuple(indices)] = sub_dynamic
+        self.dynamics[sub_dynamic] = (dict(), dict())
+        self.symbol_table[sub_dynamic] = len(self.symbol_table)
+        return sub_dynamic
+    
+    # start/end are relative to dynamic
+    def partial_dynamic(self, dynamic, start, end):
+        if start == 0 and end == dynamic.number_of_steps():
+            return dynamic
+        if isinstance(dynamic, PartialDynamic):
+            start += dynamic.start
+            end += dynamic.start
+            dynamic = dynamic.reference
+        if (start, end) in self.dynamics[dynamic][1].keys():
+            return self.dynamics[dynamic][1][(start, end)]
+        partial_dynamic = PartialDynamic(dynamic, start, end, self)
+        self.dynamics[dynamic][1][(start, end)] = partial_dynamic
+        self.dynamics[partial_dynamic] = (dict(), dict())
+        self.symbol_table[partial_dynamic] = len(self.symbol_table)
+        return partial_dynamic
+    
+    def display(self):
+        output = ''
+        for dynamic, (sub_dynamics, partial_dynamics) in self.dynamics.items():
+            if len(sub_dynamics) == 0 and len(partial_dynamics) == 0:
+                continue
+            output += f'{self.symbol_table[dynamic]}:\n'
+            for indices, sub_dynamic in sub_dynamics.items():
+                output += f'\t{indices}: {self.symbol_table[sub_dynamic]}\n'
+            for (start, end), partial_dynamic in partial_dynamics.items():
+                output += f'\t{start}-{end}: {self.symbol_table[partial_dynamic]}\n'
+        return output
+
 # The root dynamic is
 #   for TrivialDynamic: self
 #   for BackedDynamic: root dynamic of its reference
@@ -48,6 +101,10 @@ class Dynamic:
     def time_steps(self):
         pass
 
+    # returns all indices in this dynamic
+    def all_indices(self):
+        pass
+
     # return true if the dynamic contains the given index
     # index is relative to the root dynamic
     def has_index(self, index):
@@ -107,20 +164,115 @@ class Dynamic:
     def partial_dynamic(self, i_start, i_end):
         pass
 
-    def to_string(self):
-        to_print = "["
-        for p in range(self.number_of_steps()):
-            to_print += "(" + str(self.index_of(p)) + ", " + str(self.step_size_p(p)) + ")"
-            if p != self.number_of_steps() - 1:
-                to_print += ", "
-        to_print += "]"
-        return to_print
+    def display(self):
+        numbers = self.all_indices()
+        rows = [[]]
+        row_ends = [0]
+        for number in numbers:
+            local_position = number - numbers[0]
+            row_index = 0
+            while row_index < len(rows) and row_ends[row_index] > local_position:
+                row_index += 1
+            if row_index == len(rows):
+                rows.append([])
+                row_ends.append(0)
+            rows[row_index].append(number)
+            row_ends[row_index] = local_position + len(str(number)) + 1
+        row_strings = []
+        for items in rows:
+            row_string = ''
+            string_end = 0
+            for number in items:
+                local_position = number - numbers[0]
+                row_string += ' ' * (local_position - string_end)
+                row_string += str(number)
+                string_end = local_position + len(str(number))
+            row_strings.append(row_string)
+        output = ''
+        for row_index in range(len(row_strings)):
+            output += row_strings[len(row_strings) - row_index - 1] + '\n'
+        string_end = 0
+        for number in numbers:
+            local_position = number - numbers[0]
+            output += ' ' * (local_position - string_end)
+            output += '|'
+            string_end = local_position + 1
+        output += '\n'
+        return output
+    
+    def display_alignment(self, other):
+        p_self = 0
+        p_other = 0
+        numbers = []
+        while p_self <= self.number_of_steps() and p_other <= other.number_of_steps():
+            i_self = self.index_of(p_self)
+            i_other = other.index_of(p_other)
+            if i_self < i_other:
+                numbers.append(i_self)
+                p_self += 1
+            elif i_self > i_other:
+                numbers.append(i_other)
+                p_other += 1
+            else:
+                numbers.append(i_self)
+                p_self += 1
+                p_other += 1
+        while p_self < self.number_of_steps():
+            numbers.append(self.index_of(p_self))
+            p_self += 1
+        else:
+            while p_other < other.number_of_steps():
+                numbers.append(other.index_of(p_other))
+                p_other += 1
+        rows = [[]]
+        row_ends = [0]
+        for number in numbers:
+            local_position = number - numbers[0]
+            row_index = 0
+            while row_index < len(rows) and row_ends[row_index] > local_position:
+                row_index += 1
+            if row_index == len(rows):
+                rows.append([])
+                row_ends.append(0)
+            rows[row_index].append(number)
+            row_ends[row_index] = local_position + len(str(number)) + 1
+        row_strings = []
+        for items in rows:
+            row_string = ''
+            self_string_end = 0
+            for number in items:
+                local_position = number - numbers[0]
+                row_string += ' ' * (local_position - self_string_end)
+                row_string += str(number)
+                self_string_end = local_position + len(str(number))
+            row_strings.append(row_string)
+        output = ''
+        for row_index in range(len(row_strings)):
+            output += row_strings[len(row_strings) - row_index - 1] + '\n'
+        self_string = ' ' * (self.index_of(0) - numbers[0])
+        self_string_end = self.index_of(0) - numbers[0]
+        for index in self.all_indices():
+            local_position = index - numbers[0]
+            self_string += ' ' * (local_position - self_string_end)
+            self_string += '|'
+            self_string_end = local_position + 1
+        output += self_string + '\n'
+        other_string = ' ' * (other.index_of(0) - numbers[0])
+        other_string_end = other.index_of(0) - numbers[0]
+        for index in other.all_indices():
+            local_position = index - numbers[0]
+            other_string += ' ' * (local_position - other_string_end)
+            other_string += '|'
+            other_string_end = local_position + 1
+        output += other_string + '\n'
+        return output
 
 # a dynamic defined by the length of each time step
 class TrivialDynamic(Dynamic):
     # d_step: the length all time_steps
-    def __init__(self, d_steps):
+    def __init__(self, d_steps, global_dynamic):
         self.d_steps = d_steps
+        self.global_dynamic = global_dynamic
 
     def root(self):
         return self
@@ -134,6 +286,9 @@ class TrivialDynamic(Dynamic):
     def time_steps(self):
         return range(len(self.d_steps))
     
+    def all_indices(self):
+        return range(len(self.d_steps) + 1)
+    
     def has_index(self, index):
         return 0 <= index and index <= len(self.d_steps)
     
@@ -159,25 +314,26 @@ class TrivialDynamic(Dynamic):
         return self.d_steps
 
     def sub_dynamic_p(self, positions):
-        return BackedDynamic(self, positions)
+        return self.global_dynamic.sub_dynamic(self, positions)
 
     def sub_dynamic(self, indices):
-        return BackedDynamic(self, indices)
+        return self.global_dynamic.sub_dynamic(self, indices)
     
     def partial_dynamic_p(self, p_start, p_end):
-        return PartialDynamic(self, p_start, p_end)
+        return self.global_dynamic.partial_dynamic(self, p_start, p_end)
 
     def partial_dynamic(self, i_start, i_end):
-        return PartialDynamic(self, i_start, i_end)
+        return self.global_dynamic.partial_dynamic(self, i_start, i_end)
 
 # a dynamic definied by taking certain time steps form a reference dynamic
 class BackedDynamic(Dynamic):
     # reference: the dynamic that backs this dynamic
     # indices: the indicies of the time steps contained in this dynamic with the last index representing the end of the last time step
     # indices are relative to the reference dynamic
-    def __init__(self, reference, indices):
+    def __init__(self, reference, indices, global_dynamic):
         self.reference = reference
         self.indices = indices
+        self.global_dynamic = global_dynamic
 
     def root(self):
         return self.reference.root()
@@ -191,6 +347,9 @@ class BackedDynamic(Dynamic):
     def time_steps(self):
         return self.indices[:-1]
     
+    def all_indices(self):
+        return self.indices
+    
     def has_index(self, index):
         return index in self.indices
     
@@ -230,22 +389,22 @@ class BackedDynamic(Dynamic):
     def sub_dynamic_p(self, positions):
         if any(position < 0 or len(self.indices) <= position for position in positions):
             raise IndexError("The dynamic does not have all requested indices!")
-        return BackedDynamic(self.reference, [self.indices[position] for position in positions])
+        return self.global_dynamic.sub_dynamic(self, [self.indices[position] for position in positions])
 
     def sub_dynamic(self, indices):
         if any(index not in self.indices for index in indices):
             raise IndexError("The dynamic does not have all requested indices for the sub dynamic!")
-        return BackedDynamic(self.reference, indices)
+        return self.global_dynamic.sub_dynamic(self, indices)
     
     def partial_dynamic_p(self, p_start, p_end):
         if p_start < 0 or len(self.indices) <= p_start or p_end < 0 or len(self.indices) <= p_end:
             raise IndexError("The dynamic does not have all requested positions for the sub dynamic!")
-        return PartialDynamic(self, p_start, p_end)
+        return self.global_dynamic.partial_dynamic(self, p_start, p_end)
 
     def partial_dynamic(self, i_start, i_end):
         if i_start not in self.indices or i_end not in self.indices:
             raise IndexError("The dynamic does not have all requested indices for the sub dynamic!")
-        return PartialDynamic(self, self.indices.index(i_start), self.indices.index(i_end))
+        return self.global_dynamic.partial_dynamic(self, self.indices.index(i_start), self.indices.index(i_end))
     
 # a dynamic defined by taking a continuus intervall of time steps from a reference dynamic
 class PartialDynamic(Dynamic):
@@ -253,10 +412,11 @@ class PartialDynamic(Dynamic):
     # start: the position in the reference dynamic of the first time step contained in this dynamic
     # end: the position in the reference dynamic of the end of the last time step contained in this dynamic
     # start/end are relative to the reference dynamic
-    def __init__(self, reference, start, end):
+    def __init__(self, reference, start, end, global_dynamic):
         self.reference = reference
         self.start = start
         self.end = end
+        self.global_dynamic = global_dynamic
 
     def root(self):
         return self.reference.root()
@@ -270,6 +430,9 @@ class PartialDynamic(Dynamic):
     def time_steps(self):
         return self.reference.time_steps()[self.start:self.end]
     
+    def all_indices(self):
+        return self.reference.all_indices()[self.start:self.end + 1]
+    
     def has_index(self, index):
         if self.reference.has_index(index):
             reference_position = self.reference.position_of(index)
@@ -316,7 +479,7 @@ class PartialDynamic(Dynamic):
     def sub_dynamic_p(self, positions):
         if any(position < 0 or self.end - self.start < position for position in positions):
             raise IndexError("The dynamic does not have all requested positions for the sub dynamic!")
-        return self.reference.sub_dynamic_p([self.start + position for position in positions])
+        return self.global_dynamic.sub_dynamic(self, [self.index_of(position) for position in positions])
 
     def sub_dynamic(self, indices):
         def filter(index):
@@ -324,19 +487,19 @@ class PartialDynamic(Dynamic):
             return reference_position < self.start or self.end < reference_position
         if any(filter(index) for index in indices):
             raise IndexError("The does not have all requested indices for the sub dynamic!")
-        return self.reference.sub_dynamic(indices)
+        return self.global_dynamic.sub_dynamic(self, indices)
     
     def partial_dynamic_p(self, p_start, p_end):
         if p_start < 0 or self.end - self.start < p_start or p_end < 0 or self.end - self.start < p_end:
             raise IndexError("The dynamic does not have all requested positions for the sub dynamic!")
-        return PartialDynamic(self.reference, self.start + p_start, self.start + p_end)
+        return self.global_dynamic.partial_dynamic(self, p_start, p_end)
 
     def partial_dynamic(self, i_start, i_end):
         reference_start = self.reference.position_of(i_start)
         reference_end = self.reference.position_of(i_end)
         if reference_start < self.start or self.end < reference_start or reference_end < self.start or self.end < reference_end:
             raise IndexError("The dynamic does not have all requested indices for the sub dynamic!")
-        return PartialDynamic(self.reference, reference_start, reference_end)
+        return self.global_dynamic.partial_dynamic(self, self.position_of(i_start), self.position_of(i_end))
 
 # only works if both dynamics share the same root dynamic
 def resample(values, dynamic, target_dynamic):
@@ -702,28 +865,39 @@ def test_single_resampling(dynamic, target_dynamic):
         print('hey')
 
 def test_resampling():
-    dynamics = {}
-    dynamics['d'] = TrivialDynamic([1 for i in range(36)])
-    dynamics['d_1'] = PartialDynamic(dynamics['d'], 0, 36)
-    dynamics['d_2'] = PartialDynamic(dynamics['d'], 1, 35)
-    dynamics['d_3'] = BackedDynamic(dynamics['d'], [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36])
-    dynamics['d_3_1'] = PartialDynamic(dynamics['d_3'], 0, 18)
-    dynamics['d_3_2'] = PartialDynamic(dynamics['d_3'], 1, 17)
-    dynamics['d_3_3'] = BackedDynamic(dynamics['d_3'], [0, 4, 8, 12, 16, 20, 24, 28, 32, 36])
-    dynamics['d_3_3_1'] = PartialDynamic(dynamics['d_3_3'], 0, 9)
-    dynamics['d_3_3_2'] = PartialDynamic(dynamics['d_3_3'], 1, 8)
-    dynamics['d_3_4'] = BackedDynamic(dynamics['d_3'], [2, 6, 10, 14, 18, 22, 26, 30, 34])
-    dynamics['d_3_4_1'] = PartialDynamic(dynamics['d_3_4'], 0, 8)
-    dynamics['d_3_4_2'] = PartialDynamic(dynamics['d_3_4'], 1, 7)
-    dynamics['d_4'] = BackedDynamic(dynamics['d'], [3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33])
-    dynamics['d_4_1'] = PartialDynamic(dynamics['d_4'], 0, 10)
-    dynamics['d_4_2'] = PartialDynamic(dynamics['d_4'], 1, 9)
-    dynamics['d_4_3'] = BackedDynamic(dynamics['d_4'], [3, 9, 15, 21, 27, 33])
-    dynamics['d_4_3_1'] = PartialDynamic(dynamics['d_4_3'], 0, 5)
-    dynamics['d_4_3_2'] = PartialDynamic(dynamics['d_4_3'], 1, 4)
-    dynamics['d_4_4'] = BackedDynamic(dynamics['d_4'], [6, 12, 18, 24, 30])
-    dynamics['d_4_4_1'] = PartialDynamic(dynamics['d_4_4'], 0, 4)
-    dynamics['d_4_4_2'] = PartialDynamic(dynamics['d_4_4'], 1, 3)
-    dynamics['d_5'] = BackedDynamic(dynamics['d'], [0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36])
-
-    test_single_resampling(dynamics['d'], dynamics['d'])
+    import random
+    random.seed(0)
+    global_dynamic = GlobalDynamic([1 for i in range(100)])
+    dynamics = []
+    dynamics.append(global_dynamic.root())
+    for i in range(100):
+        dynamic_number = random.randint(0, len(dynamics) - 1)
+        dynamic = dynamics[dynamic_number]
+        if random.random() < 0.75:
+            original_indices = list(dynamic.all_indices())
+            number = random.randint(2, len(original_indices))
+            indices = []
+            for i in range(number):
+                choice = random.choice(original_indices)
+                original_indices.remove(choice)
+                indices.append(choice)
+            indices.sort()
+            sub_dynamic = dynamic.sub_dynamic(indices)
+            if sub_dynamic not in dynamics:
+                dynamics.append(sub_dynamic)
+        else:
+            original_positions = list(range(dynamic.number_of_steps() + 1))
+            positions = []
+            for i in range(2):
+                choice = random.choice(original_positions)
+                original_positions.remove(choice)
+                positions.append(choice)
+            positions.sort()
+            p_start = positions[0]
+            p_end = positions[1]
+            partial_dynamic = dynamic.partial_dynamic_p(p_start, p_end)
+            if partial_dynamic not in dynamics:
+                dynamics.append(partial_dynamic)
+    print(global_dynamic.display(), end='')
+    test_single_resampling(dynamics[0], dynamics[0])
+    print(dynamics[0].display_alignment(dynamics[0]), end='')
-- 
GitLab