From c567f43540bbe3168efe59e34122ab5b315ae348 Mon Sep 17 00:00:00 2001
From: Anne-Liza <a.m.r.m.bruggeman@student.tudelft.nl>
Date: Tue, 31 Jul 2018 10:42:56 +0200
Subject: [PATCH] Improved performance of get_runtime_sequence() to reduce its
 calculation time

Former-commit-id: e7853586b567b273a2fcf925cb35b29f37388f7d
---
 kadmos/graph/graph_data.py | 90 ++++++++++++++++++--------------------
 1 file changed, 43 insertions(+), 47 deletions(-)

diff --git a/kadmos/graph/graph_data.py b/kadmos/graph/graph_data.py
index afe796ecf..e7abc5fa0 100644
--- a/kadmos/graph/graph_data.py
+++ b/kadmos/graph/graph_data.py
@@ -786,17 +786,18 @@ class DataGraph(KadmosGraph):
 
         return function_order
 
-    def get_time_line(self, sequence, use_runtime_info=False, coupling_dict=None):
-        """Function to get the time line of a sequence of nodes. At each time step it is indicated which nodes are
-        waiting for data, which nodes are running and which nodes are finished. Each node starts running as soon as all
-        its required input data is available.
+    def get_runtime_sequence(self, sequence, use_runtime_info=False, coupling_dict=None, get_time_line=False):
+        """Function to get the runtime of a sequence of nodes. Each node starts running as soon as all its required
+        input data is available.
 
-        :param sequence: Sequence of nodes
+        :param sequence: sequence of nodes
         :type sequence: list
-        :param use_runtime_info: option to use the runtime of the disciplines, if False the runtime is set to 1
+        :param use_runtime_info: option to use the runtime of the nodes, if False the runtime for each node is set to 1
         :type use_runtime_info: bool
         :param coupling_dict: coupling dictionary of the graph
         :type coupling_dict: dict
+        :param get_time_line: option to return a time line which indicates at each time step which nodes are waiting
+        for input, which nodes are running and which nodes are finished
         """
 
         # Input assertion
@@ -807,67 +808,60 @@ class DataGraph(KadmosGraph):
 
         # If zero nodes, return zero runtime
         if not sequence:
-            return [[0, [], [], []]]
+            if not get_time_line:
+                return [[0, [], [], []]]
+            else:
+                return 0
 
         # Get coupling dictionary
         if not coupling_dict:
             coupling_dict = self.get_coupling_dictionary()
 
-        # Calculate runtime
-        waiting_list = list(sequence)
+        # Initialize variables
+        waiting_list = dict()
         running = dict()
-        finished = []
+        finished = set()
         time_line = []
         total_runtime = 0
 
+        # Add required input and runtime to each node in the waiting list
+        for node in sequence:
+            required_input = set(sequence[:sequence.index(node)]).intersection(coupling_dict[node])
+            runtime = self.nodes[node]['performance_info']['run_time'] if use_runtime_info else 1
+            waiting_list[node] = [required_input, runtime]
+
         while waiting_list or running:
             if waiting_list:
-                # Determine for which nodes in the waiting list all info is known, such that they can be executed
+                updated_waiting_list = dict(waiting_list)
                 for node in waiting_list:
-                    required_input = set(sequence[:sequence.index(node)]).intersection(coupling_dict[node])
-                    execute = True if all([input_node in finished for input_node in required_input]) else False
-                    if execute:
-                        # If all input is known the node can be moved to the run list
-                        running[node] = self.nodes[node]['performance_info']['run_time'] if use_runtime_info else 1
-                # Remove the activated nodes from the waiting list
-                [waiting_list.pop(waiting_list.index(node)) for node in running if node in waiting_list]
+                    # Check if all input data is available
+                    if waiting_list[node][0].issubset(finished):
+                        # If all input is known the node can be moved to the run list and deleted from the waiting list
+                        running[node] = updated_waiting_list.pop(node)[1]
+                waiting_list = dict(updated_waiting_list)
             # Add timestep to the time line
-            time_line.append([total_runtime, list(waiting_list), running.keys(), list(finished)])
+            if get_time_line:
+                time_line.append([total_runtime, list(waiting_list), running.keys(), list(finished)])
             # When the running list is updated, the next time step can be determined
-            # Determine the shortest runtime in the running list
-            time_step = min(running.items(), key=lambda x: x[1])[1]
+            time_step = min(running.values())
             # Let all modules run for the time of the time step
+            updated_running_list = dict(running)
             for node in running:
                 running[node] -= time_step
                 # If node is finished, move it to the finished list
                 if running[node] == 0:
-                    finished.append(node)
+                    finished.add(node)
+                    del updated_running_list[node]
+            running = dict(updated_running_list)
             # Add the time step to the total time
             total_runtime += time_step
-            # Remove the finished nodes from the running list
-            for node in finished:
-                if node in running:
-                    del running[node]
-
-        # Add final step to time line
-        time_line.append([total_runtime, list(waiting_list), running.keys(), list(finished)])
-
-        return time_line
-
-    def get_runtime_sequence(self, sequence, use_runtime_info=False, coupling_dict=None):
-        """Function to get the total runtime of a sequence of nodes. Each node starts running as soon as all its
-        required input data is available.
 
-        :param sequence: Sequence of nodes for which the runtime needs to be calculated
-        :type sequence: list
-        :param use_runtime_info: option to use the runtime of the disciplines, if False the runtime is set to 1
-        :type use_runtime_info: bool
-        :param coupling_dict: coupling dictionary of the graph
-        :type coupling_dict: dict
-        """
-
-        time_line = self.get_time_line(sequence, use_runtime_info, coupling_dict)
-        return time_line[-1][0]
+        if get_time_line:
+            # Add final step to time line
+            time_line.append([total_runtime, list(waiting_list), running.keys(), list(finished)])
+            return time_line
+        else:
+            return total_runtime
 
     def minimize_feedback(self, nodes, method, multi_start=None, get_evaluations=False, coupling_dict=None, rcb=1,
                           use_runtime_info=False):
@@ -1245,8 +1239,9 @@ class DataGraph(KadmosGraph):
 
     def _get_lower_bound_branch_and_bound(self, branch, nodes, coupling_dict, use_runtime_info):
         """Function to calculate the lower bound of a branch in the branch and bound algorithm.
-        The lower bound is defined as the amount of feedback loops that are guaranteed to occur if the
-        selected nodes are placed at the beginning of the order
+        The lower bound for the number of feedback loops is defined as the amount of feedback loops that are guaranteed
+        to occur if the current branch is placed at the beginning of the order. The lower bound for the runtime is
+        defined as the runtime of the current branch.
 
         :param branch: the nodes in the branch
         :type branch: list
@@ -1603,6 +1598,7 @@ class RepositoryConnectivityGraph(DataGraph):
         # Create H-matrix: relation between global design variables and global constraints
         H = np.array([[float(random.randint(-5, 5)) for _ in range(n_global_var)] for _ in
                       range(n_global_constraints)]) if 'H' not in kwargs else kwargs['H']
+        mathematical_problem['H-matrix'] = H
 
         # Create I-matrix: relation between local design variables and global constraints
         I = np.array([[float(random.randint(-5, 5)) for _ in range(sum(n_local_var))] for _ in
-- 
GitLab