diff --git a/kadmos/graph/graph_kadmos.py b/kadmos/graph/graph_kadmos.py index 9cd9016a1ab0aebd1eabd24e9840ab4f1ed6017e..a23298e9c6a7932384c696e0d66fc587ca26acdf 100644 --- a/kadmos/graph/graph_kadmos.py +++ b/kadmos/graph/graph_kadmos.py @@ -1911,7 +1911,7 @@ class KadmosGraph(nx.DiGraph, VispackMixin): return function_nodes - def get_graph_nodes_indegree(self): + def get_nodes_indegree(self): """Function to get the indegree of all the graph nodes and store them in a dictionary. :return: dictionary with node name key and indegree integer value. @@ -1921,7 +1921,7 @@ class KadmosGraph(nx.DiGraph, VispackMixin): nid_dict = dict(list(self.in_degree_iter())) return nid_dict - def get_graph_nodes_outdegree(self): + def get_nodes_outdegree(self): """Function to get the outdegree of all the graph nodes and store them in a dictionary. :return: dictionary with node name key and outdegree integer value. @@ -1931,6 +1931,157 @@ class KadmosGraph(nx.DiGraph, VispackMixin): nod_dict = dict(list(self.out_degree_iter())) return nod_dict + def get_nodes_subcategory(self): + """Method to analyse all nodes and to update the subcategory attributes of the nodes.""" + + for node in self.nodes_iter(): + self.get_node_subcategory(node) + + return + + def get_node_subcategory(self, node): + """Method to analyse a node and to update the subcategory attribute of the node. + + The following table illustrates how the subcategory is determined based on the category, indegree and outdegree: + + +-------------------+-----------------------------------+----------+-----------+ + | CATEGORY OF NODE | SUBCATEGORY | INDEGREE | OUTDEGREE | + +===================+===================================+==========+===========+ + | variable | hole | 0 | 0 | + | +-----------------------------------+----------+-----------+ + | | supplied input | 0 | 1 | + | +-----------------------------------+----------+-----------+ + | | supplied shared input | 0 | >1 | + | +-----------------------------------+----------+-----------+ + | | output | 1 | 0 | + | +-----------------------------------+----------+-----------+ + | | collision | >1 | 0 | + | +-----------------------------------+----------+-----------+ + | | coupling | | | + | | or | 1 | 1 | + | | pure circular coupling | | | + | +-----------------------------------+----------+-----------+ + | | shared coupling | | | + | | or | 1 | >1 | + | | shared circular coupling | | | + | +-----------------------------------+----------+-----------+ + | | collided coupling | | | + | | or | >1 | 1 | + | | collided circular coupling | | | + | +-----------------------------------+----------+-----------+ + | | collided shared coupling | | | + | | or | >1 | >1 | + | | collided shared circular coupling | | | + +-------------------+-----------------------------------+----------+-----------+ + | function | hole | 0 | 0 | + | +-----------------------------------+----------+-----------+ + | | inputless | 0 | >0 | + | +-----------------------------------+----------+-----------+ + | | outputless | >0 | 0 | + | +-----------------------------------+----------+-----------+ + | | complete | >0 | >0 | + +-------------------+-----------------------------------+----------+-----------+ + + :param node: node in the graph + :type node: basestring + :return: subcategory of the node + :rtype: basestring + """ + + # Check node + assert self.has_node(node), 'Node %s is not present in the graph.' % node + + # Get node indegree and outdegree + idx = self.in_degree(node) + od = self.out_degree(node) + + # Get node data + data = self.node[node] + + # Analyse node and set subcategory + if data['category'] == 'variable': + if idx == 0 and od == 0: + data['subcategory'] = 'hole' + elif idx == 0 and od == 1: + data['subcategory'] = 'supplied input' + elif idx == 0 and od > 1: + data['subcategory'] = 'supplied shared input' + elif idx == 1 and od == 0: + data['subcategory'] = 'output' + elif idx > 1 and od == 0: + data['subcategory'] = 'collision' + elif idx == 1 and od == 1: + in_function = self.in_edges(node)[0][0] + if in_function == self.out_edges(node)[0][1]: + data['subcategory'] = 'pure circular coupling' + data['circularity_info'] = dict() + data['circularity_info']['level'] = 1 + data['circularity_info']['circular_functions'] = [in_function] + else: + data['subcategory'] = 'coupling' + elif idx == 1 and od > 1: + in_function = self.in_edges(node)[0][0] + if in_function in [edge[1] for edge in self.out_edges(node)]: + data['subcategory'] = 'shared circular coupling' + data['circularity_info'] = dict() + data['circularity_info']['level'] = 1 + data['circularity_info']['circular_functions'] = [in_function] + else: + data['subcategory'] = 'shared coupling' + elif idx > 1 and od == 1: + out_function = self.out_edges(node)[0][1] + if out_function in [edge[0] for edge in self.in_edges(node)]: + data['subcategory'] = 'collided circular coupling' + data['circularity_info'] = dict() + data['circularity_info']['level'] = 1 + data['circularity_info']['circular_functions'] = [out_function] + else: + data['subcategory'] = 'collided coupling' + elif idx > 1 and od > 1: + in_nodes = [edge[0] for edge in self.in_edges(node)] + out_nodes = [edge[1] for edge in self.out_edges(node)] + common_nodes = set(in_nodes).intersection(set(out_nodes)) + if common_nodes: + data['subcategory'] = 'collided shared circular coupling' + data['circularity_info'] = dict() + data['circularity_info']['level'] = len(common_nodes) + data['circularity_info']['circular_functions'] = list(common_nodes) + else: + data['subcategory'] = 'collided shared coupling' + else: + raise NotImplementedError('Variable subcategory could not be determined based on combination of ' + 'indegree {} and outdegree {}.'.format(idx, od)) + if data['category'] == 'variable group': + if idx == 0 and od == 0: + data['subcategory'] = 'hole group' + elif idx == 0 and od == 1: + data['subcategory'] = 'supplied input group' + elif idx == 0 and od > 1: + data['subcategory'] = 'supplied shared input group' + elif idx > 0 and od == 0: + data['subcategory'] = 'output group' + elif idx > 0 and od == 1: + data['subcategory'] = 'coupling group' + elif idx > 0 and od > 1: + data['subcategory'] = 'shared coupling group' + else: + raise NotImplementedError('Group variable subcategory could not be determined based on combination ' + 'of indegree {} and outdegree {}.'.format(idx, od)) + if data['category'] == 'function': + if idx == 0 and od == 0: + data['subcategory'] = 'independent' + elif idx == 0 and od > 0: + data['subcategory'] = 'inputless' + elif idx > 0 and od == 0: + data['subcategory'] = 'outputless' + elif idx > 0 and od > 0: + data['subcategory'] = 'complete' + else: + raise NotImplementedError('Function subcategory could not be determined based on combination of ' + 'indegree {} and outdegree {}.'.format(idx, od)) + + return data['subcategory'] + def get_categorized_nodes(self, print_in_log=False): """Function that returns a dictionary with graph nodes grouped according to category and subcategory. @@ -1940,7 +2091,7 @@ class KadmosGraph(nx.DiGraph, VispackMixin): :rtype: dict """ - self.add_nodes_subcategory() + self.get_nodes_subcategory() result = deepcopy(self.NODE_CAT_TREE) for node, data in self.nodes_iter(data=True): @@ -1962,21 +2113,6 @@ class KadmosGraph(nx.DiGraph, VispackMixin): return result - def get_node_subcategory(self, node, initiate=True): - """Method to get the subcategory of a node. - - :param node: node in the graph - :type node: basestring - :return: subcategory of the node - :rtype: basestring - """ - - assert self.has_node(node), 'Node %s is not present in the graph.' % node - if initiate: - self.add_nodes_subcategory() - - return self.node[node]['subcategory'] - def get_node_attributes(self, node, attr_list, print_in_log=False): """Function to get and print certain attributes of a node from the graph. @@ -2677,7 +2813,7 @@ class KadmosGraph(nx.DiGraph, VispackMixin): raise AssertionError('The subgraph contains collided nodes %s.' % collided_nodes) # noinspection PyUnboundLocalVariable - def split_variables(self, *args, **kwargs): + def split_variables(self, *args): """Method to split a problematic variable node into multiple separate valid nodes. The following variables are considered problematic and will be handled by this function: @@ -2748,10 +2884,7 @@ class KadmosGraph(nx.DiGraph, VispackMixin): :param args: problematic node in the graph :type args: basestring, list - :param kwargs: print_in_log: keyword setting to print in log - :type kwargs: print_in_log: bool """ - # TODO: Remove print_in_log but use logger instead # Input assertions if len(args) == 0: @@ -2768,42 +2901,19 @@ class KadmosGraph(nx.DiGraph, VispackMixin): assert isinstance(arg, basestring) nodes.append(arg) - if 'print_in_log' in kwargs: - assert isinstance(kwargs['print_in_log'], bool) - print_in_log = kwargs['print_in_log'] - else: - print_in_log = False + logger.info('Splitting problematic variables...') + # Get relevant categories relevant_node_subcats = self.NODE_GROUP_SUBCATS['all splittable variables'] function_order_required_cats = relevant_node_subcats[2:7] - if print_in_log: - step_size = 5 - progress_steps = [int(round(i/100.0*len(nodes))) for i in range(0, 101, step_size)] - - progress_bar = progressbar.ProgressBar(max_value=len(nodes)) - progress_bar.start() - n_key = 0 - - self.add_nodes_subcategory() - test_list = [] + # Loop through the provided nodes for idx, node in enumerate(nodes): - progress_bar.update(n_key) - n_key += 1 - - # The following assertion is already part of get_node_subcategory - # TODO: Check were it should ideally be, but do not run it multiple times if possible - # assert self.has_node(node), 'Node %s is not present in the graph' % node - - # TODO: Reduce execution time - start = time.time() - # Start slow - subcat = self.get_node_subcategory(node, initiate=False) - test_list.append(subcat) - # End slow - end = time.time() - #print 'B (should ideally be 0): ' + str(end - start) + # Get subcat + subcat = self.get_node_subcategory(node) + + # Check subcategory assert subcat in relevant_node_subcats, 'Node %s is not a problematic variable.' % node # Get functions for which the node is also output (except the circular function) @@ -2812,7 +2922,7 @@ class KadmosGraph(nx.DiGraph, VispackMixin): # Get functions for which the node is an input target_funcs = self.get_targets(node) - # Determine / Get function order + # Get function order if subcat in function_order_required_cats: assert 'function_order' in self.graph['problem_formulation'], \ 'Function order should be formulated in order to split this problematic node.' @@ -2851,76 +2961,34 @@ class KadmosGraph(nx.DiGraph, VispackMixin): 'Problematic node is still connected somehow.' self.remove_node(node) - progress_bar.finish() - print test_list - - if print_in_log: - max_nodes = 20 - print '\nSplitted nodes (' + str(len(nodes)) + '):' - if nodes: - if len(nodes) > max_nodes: - print '(only first ' + str(max_nodes) + ' nodes are printed)' - print_nodes = nodes[0:max_nodes] - else: - print_nodes = nodes - for node in print_nodes: - print 'Node: ' + str(node) - if len(nodes) > max_nodes: - print '...and ' + str(len(nodes)-max_nodes) + ' additional nodes.' - else: - print 'None' - print '' + # Log some info about the splitted nodes + log_max_nodes = 20 + log_nodes = log_max_nodes if len(nodes) >= log_max_nodes else len(nodes) + logger.info('Successfully splitted ' + str(len(nodes)) + ' problematic variables.') + if nodes: + logger.info('The first ' + str(len(nodes)) + ' splitted problematic variables are: ' + + (', '.join(nodes[0:log_nodes]))) return - def split_all_variables(self, print_in_log=False): - """Method to simply split all problematic variables that can be split using the split_variables() method. + def make_all_variables_valid(self): + """Method to analyze all variables in a graph and make any problematic variables valid. - :param print_in_log: setting on whether to print split variables in the log - :type print_in_log: bool - :return: Manipulated graph with split variables - :rtype: KadmosGraph + Problematic variable are holes and splittables. Splittable variables are split and holes will simply be removed. """ - # TODO: Remove print_in_log but use logger instead - - # Find all splittable variables - nodes = self.find_all_nodes(subcategory='all splittable variables') - - # Split all variables - self.split_variables(nodes, print_in_log=print_in_log) - - return - - def make_all_variables_valid(self, print_in_log=False): - """Method to analyze all variables in a graph and make any problematic variables (holes, split-ables) valid. - - Splittable variables are split and holes will simply be removed. - - :param print_in_log: setting on whether to print split variables in the log - :type print_in_log: bool - :return: Manipulated graph with split and removed variables - :rtype: KadmosGraph - """ - # TODO: Remove print_in_log but use logger instead # First fix the splittable variables - logger.info('Splitting variables...') - self.split_all_variables(print_in_log=print_in_log) - logger.info('Successfully splitted variables.') + splittables = self.find_all_nodes(subcategory='all splittable variables') + if splittables: + self.split_variables(splittables) - # Find hole variables and remove them - if print_in_log: - print 'Removing hole nodes...' + # Then find hole variables and remove them holes = self.find_all_nodes(category='variable', subcategory='hole') - self.remove_nodes_from(holes) - if print_in_log: - print '\nRemoved hole nodes (' + str(len(holes)) + '):' - if holes: - for node in holes: - print 'Node: ' + str(node) - else: - print 'None' - print '' + if holes: + logger.info('Removing hole nodes...') + self.remove_nodes_from(holes) + logger.info('Successfully removed ' + str(len(holes)) + ' hole nodes.') + logger.info('The removed hole nodes were: ' + (', '.join(holes))) return @@ -2950,7 +3018,7 @@ class KadmosGraph(nx.DiGraph, VispackMixin): source_nodes = self.get_sources(function) if disconnect_shared_sources else [] # Add node subcategories - self.add_nodes_subcategory() + self.get_nodes_subcategory() # Disconnect if required for target_node in target_nodes: @@ -3020,139 +3088,6 @@ class KadmosGraph(nx.DiGraph, VispackMixin): return len(self.find_all_nodes(category='function')) - def add_nodes_subcategory(self): - """Function to analyze and sort the graph nodes according to different variable descriptions. - - +-------------------+-----------------------------------+----------+-----------+ - | CATEGORY OF NODE | SUBCATEGORY | INDEGREE | OUTDEGREE | - +===================+===================================+==========+===========+ - | variable | hole | 0 | 0 | - | +-----------------------------------+----------+-----------+ - | | supplied input | 0 | 1 | - | +-----------------------------------+----------+-----------+ - | | supplied shared input | 0 | >1 | - | +-----------------------------------+----------+-----------+ - | | output | 1 | 0 | - | +-----------------------------------+----------+-----------+ - | | collision | >1 | 0 | - | +-----------------------------------+----------+-----------+ - | | coupling | | | - | | or | 1 | 1 | - | | pure circular coupling | | | - | +-----------------------------------+----------+-----------+ - | | shared coupling | | | - | | or | 1 | >1 | - | | shared circular coupling | | | - | +-----------------------------------+----------+-----------+ - | | collided coupling | | | - | | or | >1 | 1 | - | | collided circular coupling | | | - | +-----------------------------------+----------+-----------+ - | | collided shared coupling | | | - | | or | >1 | >1 | - | | collided shared circular coupling | | | - +-------------------+-----------------------------------+----------+-----------+ - | function | hole | 0 | 0 | - | +-----------------------------------+----------+-----------+ - | | inputless | 0 | >0 | - | +-----------------------------------+----------+-----------+ - | | outputless | >0 | 0 | - | +-----------------------------------+----------+-----------+ - | | complete | >0 | >0 | - +-------------------+-----------------------------------+----------+-----------+ - - :return: dictionary with sorted variables - :rtype: dict - """ - # TODO: This function can be made shorter by splitting the functions. - - for node, data in self.nodes_iter(data=True): - idx = self.in_degree(node) - od = self.out_degree(node) - - if data['category'] == 'variable': - if idx == 0 and od == 0: - data['subcategory'] = 'hole' - elif idx == 0 and od == 1: - data['subcategory'] = 'supplied input' - elif idx == 0 and od > 1: - data['subcategory'] = 'supplied shared input' - elif idx == 1 and od == 0: - data['subcategory'] = 'output' - elif idx > 1 and od == 0: - data['subcategory'] = 'collision' - elif idx == 1 and od == 1: - in_function = self.in_edges(node)[0][0] - if in_function == self.out_edges(node)[0][1]: - data['subcategory'] = 'pure circular coupling' - data['circularity_info'] = dict() - data['circularity_info']['level'] = 1 - data['circularity_info']['circular_functions'] = [in_function] - else: - data['subcategory'] = 'coupling' - elif idx == 1 and od > 1: - in_function = self.in_edges(node)[0][0] - if in_function in [edge[1] for edge in self.out_edges(node)]: - data['subcategory'] = 'shared circular coupling' - data['circularity_info'] = dict() - data['circularity_info']['level'] = 1 - data['circularity_info']['circular_functions'] = [in_function] - else: - data['subcategory'] = 'shared coupling' - elif idx > 1 and od == 1: - out_function = self.out_edges(node)[0][1] - if out_function in [edge[0] for edge in self.in_edges(node)]: - data['subcategory'] = 'collided circular coupling' - data['circularity_info'] = dict() - data['circularity_info']['level'] = 1 - data['circularity_info']['circular_functions'] = [out_function] - else: - data['subcategory'] = 'collided coupling' - elif idx > 1 and od > 1: - in_nodes = [edge[0] for edge in self.in_edges(node)] - out_nodes = [edge[1] for edge in self.out_edges(node)] - common_nodes = set(in_nodes).intersection(set(out_nodes)) - if common_nodes: - data['subcategory'] = 'collided shared circular coupling' - data['circularity_info'] = dict() - data['circularity_info']['level'] = len(common_nodes) - data['circularity_info']['circular_functions'] = list(common_nodes) - else: - data['subcategory'] = 'collided shared coupling' - else: - raise NotImplementedError('Variable subcategory could not be determined based on combination of ' - 'indegree {} and outdegree {}.'.format(idx, od)) - if data['category'] == 'variable group': - if idx == 0 and od == 0: - data['subcategory'] = 'hole group' - elif idx == 0 and od == 1: - data['subcategory'] = 'supplied input group' - elif idx == 0 and od > 1: - data['subcategory'] = 'supplied shared input group' - elif idx > 0 and od == 0: - data['subcategory'] = 'output group' - elif idx > 0 and od == 1: - data['subcategory'] = 'coupling group' - elif idx > 0 and od > 1: - data['subcategory'] = 'shared coupling group' - else: - raise NotImplementedError('Group variable subcategory could not be determined based on combination ' - 'of indegree {} and outdegree {}.'.format(idx, od)) - if data['category'] == 'function': - if idx == 0 and od == 0: - data['subcategory'] = 'independent' - elif idx == 0 and od > 0: - data['subcategory'] = 'inputless' - elif idx > 0 and od == 0: - data['subcategory'] = 'outputless' - elif idx > 0 and od > 0: - data['subcategory'] = 'complete' - else: - raise NotImplementedError('Function subcategory could not be determined based on combination of ' - 'indegree {} and outdegree {}.'.format(idx, od)) - - return - def find_all_nodes(self, category='all', subcategory='all', attr_cond=None, attr_include=None, attr_exclude=None, print_in_log=False, print_attributes='all'): """ @@ -3555,7 +3490,7 @@ class KadmosGraph(nx.DiGraph, VispackMixin): node_size=node_sizes[a_shape]) elif color_setting == 'categories': # Group graph nodes - self.add_nodes_subcategory() + self.get_nodes_subcategory() grouped_nodes = self.get_categorized_nodes() # Specify node colors diff --git a/kadmos/graph/mixin_rce.py b/kadmos/graph/mixin_rce.py index 0fd07d94feef976a1d52729076d7b666e74beb9e..77e82a959fe6d68c2d704a3e7d7a12bb99bf94b5 100644 --- a/kadmos/graph/mixin_rce.py +++ b/kadmos/graph/mixin_rce.py @@ -344,7 +344,7 @@ class RceMixin(object): # Reconnect outgoing edges of first node first_node_out_edges = rce_graph.out_edges(first_iter_node) - rce_graph.add_nodes_subcategory() + rce_graph.get_nodes_subcategory() for edge in first_node_out_edges: var_node = edge[1] var_node_cat = rce_graph.node[var_node]['category'] diff --git a/kadmos/graph/mixin_vispack.py b/kadmos/graph/mixin_vispack.py index 6b2c7d2769dfb6cea126190f905e106cface1d53..ffe6f43ddceb7ba8a437c7c90d0177cd9668bf12 100644 --- a/kadmos/graph/mixin_vispack.py +++ b/kadmos/graph/mixin_vispack.py @@ -241,7 +241,7 @@ class VispackMixin(object): circular_coor_output_cats = get_list_entries(circular_cats, 0, 2) coordinator_str = self.COORDINATOR_STRING - self.add_nodes_subcategory() + self.get_nodes_subcategory() # CREATE FULL_GRAPH_DATA JSON # full_graph is a dictionary with the following structure {node_key:[input_nodes to node_key]}