From 0f8e35ad4473e8f6192d4ded1fd7729847333a24 Mon Sep 17 00:00:00 2001
From: Anne-Liza <a.m.r.m.bruggeman@student.tudelft.nl>
Date: Thu, 23 Nov 2017 13:32:08 +0100
Subject: [PATCH] Added function to sort pre and post coupling functions and
 changed get_coupling_matrix() such that it accepts a selection of nodes

Former-commit-id: a99012bc228329d45851fc836bdbfe0dad1c47b5
---
 kadmos/graph/graph_data.py | 66 +++++++++++++++++++++++++++++++++-----
 1 file changed, 58 insertions(+), 8 deletions(-)

diff --git a/kadmos/graph/graph_data.py b/kadmos/graph/graph_data.py
index 13e5c2c9e..a94828b44 100644
--- a/kadmos/graph/graph_data.py
+++ b/kadmos/graph/graph_data.py
@@ -397,16 +397,58 @@ class DataGraph(KadmosGraph):
                     function_graph = nx.contracted_nodes(function_graph, 'super_node', function_id)
                     function_graph.remove_edges_from(function_graph.selfloop_edges())
 
-        # Find function order
+        # Find a topological function order
         function_order = nx.topological_sort(function_graph)
+
+        # Get pre-coupling functions and sort
+        pre_coupling_functions = function_order[:function_order.index('super_node')]
+        pre_coupling_functions_order = self.sort_non_coupled_nodes(pre_coupling_functions)
+
+        # Sort coupled functions to minimize feedback
         coupled_functions_order = self.minimize_feedback(coupled_functions, method, multi_start=multi_start)
 
-        # Replace super node with coupled functions in function order
-        function_order[function_order.index('super_node'):function_order.index('super_node') + 1] = \
-            coupled_functions_order
+        # Get post-coupling functions and sort
+        post_coupling_functions = function_order[function_order.index('super_node') + 1:]
+        post_coupling_functions_order = self.sort_non_coupled_nodes(post_coupling_functions)
+
+        # Get function_order
+        function_order = pre_coupling_functions_order + coupled_functions_order + post_coupling_functions_order
 
         return function_order
 
+    def sort_non_coupled_nodes(self, nodes):
+        """ Function to sort the pre and post coupling nodes
+
+        :param nodes: nodes that need to be sorted
+        :type nodes: list
+        :return nodes in sorted order
+        :rtype list
+        """
+
+        # Check if all nodes are in graph and no feedback loops exists
+        for func in nodes:
+            assert func in self, "Function node {} must be present in graph.".format(func)
+        subgraph = self.get_subgraph_by_function_nodes(nodes)
+        assert nx.is_directed_acyclic_graph(subgraph)
+
+        # Make sure the nodes are in a topological sort
+        if self.check_for_coupling(nodes, only_feedback=True):
+            nodes = nx.topological_sort(subgraph)
+
+        nodes_to_sort = list(nodes)
+        sorted_nodes = []
+        while len(nodes_to_sort) != 0:
+            # Get the coupling matrix of the nodes that need to be sorted
+            coupling_matrix = self.get_coupling_matrix(node_selection=nodes_to_sort)
+            # Find the columns that are zero
+            idxs = np.where(~coupling_matrix.any(axis=0))[0]
+            # Add sorted node to sorted list, and delete from to be sorted list
+            for idx in sorted(idxs, reverse=True):
+                sorted_nodes.append(nodes_to_sort[idx])
+                del nodes_to_sort[idx]
+
+        return sorted_nodes
+
     def minimize_feedback(self, nodes, method, multi_start=None):
         """Function to find the function order with minimum feedback
 
@@ -2179,17 +2221,22 @@ class FundamentalProblemGraph(DataGraph, KeChainMixin):
 
         return
 
-    def get_coupling_matrix(self, function_order_method='manual'):
+    def get_coupling_matrix(self, function_order_method='manual', node_selection=None):
         """Function to determine the role of the different functions in the FPG.
 
         :param function_order_method: algorithm to be used for the order in which the functions are executed.
         :type function_order_method: basestring
+        :param node_selection: selection of nodes for which the coupling matrix will be calculated only
+        :type node_selection: list
         :return: graph with enriched function node attributes and function problem role dictionary
         :rtype: FundamentalProblemGraph
         """
 
         # Make a copy of the graph, check it and remove all inputs and outputs
-        graph = self.cleancopy()
+        if node_selection:
+            graph = self.get_subgraph_by_function_nodes(node_selection)
+        else:
+            graph = self.cleancopy()
         nodes_to_remove = list()
         # TODO: Consider using the check function
         assert not graph.find_all_nodes(subcategory='all problematic variables'), 'Graph still has problematic variables.'
@@ -2200,8 +2247,11 @@ class FundamentalProblemGraph(DataGraph, KeChainMixin):
         # Determine and check function ordering method
         assert function_order_method in self.OPTIONS_FUNCTION_ORDER_METHOD
         if function_order_method == 'manual':
-            assert 'function_order' in graph.graph['problem_formulation'], 'function_order must be given as attribute.'
-            function_order = graph.graph['problem_formulation']['function_order']
+            if node_selection:
+                function_order = node_selection
+            else:
+                assert 'function_order' in graph.graph['problem_formulation'], 'function_order must be given as attribute.'
+                function_order = graph.graph['problem_formulation']['function_order']
         elif function_order_method == 'random':
             function_order = graph.find_all_nodes(category='function')
 
-- 
GitLab