def test_is_connected_component_edges_direction_is_ignored(self): """ Check that edges direction is ignored when checking for the connectivity. """ graph = Graph() node_names = list(range(1, 5)) graph.add_nodes_from(node_names) graph.add_edges_from([(2, 1), (2, 3), (4, 3)]) self.assertTrue(is_connected_component(graph, node_names)) self.assertTrue(is_connected_component(graph, [2, 1])) self.assertTrue(is_connected_component(graph, [4, 2, 3]))
def test_is_connected_component_two_separate_sub_graphs(self): """ Check that if there are two separate sub-graphs the function returns False. """ graph = Graph() graph.add_nodes_from(list(range(1, 7))) graph.add_edges_from([(1, 2), (2, 3), (4, 5), (5, 6)]) self.assertFalse(is_connected_component(graph, list(range(1, 7)))) self.assertFalse(is_connected_component(graph, [1, 3])) self.assertFalse(is_connected_component(graph, [6, 4])) self.assertFalse(is_connected_component(graph, [2, 5]))
def test_is_connected_component_edges_direction_is_ignored_not_connected( self): """ Check that edges direction is ignored when checking for the connectivity. In this case the graph is not connected. """ graph = Graph() graph.add_nodes_from(list(range(1, 5))) graph.add_edges_from([(2, 1), (2, 3), (4, 3)]) self.assertFalse(is_connected_component(graph, [1, 2, 4])) self.assertFalse(is_connected_component(graph, [1, 4])) self.assertFalse(is_connected_component(graph, [2, 4])) self.assertFalse(is_connected_component(graph, [3, 4, 1]))
def find_and_replace_pattern(self, graph: Graph): replacement_descriptions = CustomReplacementRegistry( ).get_custom_replacement_description(self.replacement_id) if replacement_descriptions is None: log.info( "Failed to find custom replacement description with id '{}'". format(self.replacement_id)) return # there are a list of custom replacements descriptions that have the same replacement id for replacement_description in replacement_descriptions: sub_graph_matcher = SubgraphMatcher(replacement_description) matched_instances = list( sub_graph_matcher.matched_sub_graph_instances(graph)) if not len(matched_instances): log.error( "Failed to match nodes from custom replacement description with id '{}':\nIt means model and " "custom replacement description are incompatible.\nTry to correct custom replacement " "description according to documentation with respect to model node names" "".format(self.replacement_id)) for match in matched_instances: if not is_connected_component(graph, match.matched_nodes_names()): log.warning( "The following nodes don't form connected sub-graph: {}" .format(match.matched_nodes_names())) # graph.dump_graph_for_graphviz(match.matched_nodes_names()) self.replace_sub_graph(graph, match)
def test_is_connected_component_connected(self): """ Check that if the sub-graph is connected. """ graph = Graph() node_names = list(range(1, 8)) graph.add_nodes_from(node_names) graph.add_edges_from([(1, 2), (2, 3), (4, 5), (5, 6), (1, 7), (7, 4)]) self.assertTrue(is_connected_component(graph, list(range(1, 8))))
def test_is_connected_component_two_separate_sub_graphs_divided_by_ignored_node( self): """ Check that if there are two separate sub-graphs the function connected by an edge going through the ignored node then the function returns False. """ graph = Graph() node_names = list(range(1, 8)) graph.add_nodes_from(node_names) graph.add_edges_from([(1, 2), (2, 3), (4, 5), (5, 6), (1, 7), (7, 4)]) self.assertFalse(is_connected_component(graph, list(range(1, 7))))
def merge_nodes(graph: Graph, nodes_to_merge_names: list, inputs_desc: list = None, outputs_desc: list = None): """ Merges nodes specified in the set 'nodes_to_merge_names' into one mega-node, creating new edges between mega-node and inputs/outputs nodes of the mega-node. The added edges contain name of input/output nodes which will be used for generation of placeholders and will be saved to the IR xml so IE plug-in know how to map input/output data for the layer. Also the function adds protobufs of the nodes of the sub-graph and 'Const' ops consumed by nodes in the sub-graph to the node's attribute 'pbs'. :param graph: the graph object to operate on. :param nodes_to_merge_names: list of nodes names that should be merged into a single node. :param inputs_desc: optional list describing input nodes order. :param outputs_desc: optional list describing output nodes order. """ if not is_connected_component(graph, nodes_to_merge_names): log.warning( "The following nodes do not form connected sub-graph: {}".format( nodes_to_merge_names)) # graph.dump_graph_for_graphviz(nodes_to_dump=nodes_to_merge_names) new_node_name = graph.unique_id("TFSubgraphCall_") log.info("Create new node with name '{}' for nodes '{}'".format( new_node_name, ', '.join(nodes_to_merge_names))) graph.add_node(new_node_name) new_node_attrs = graph.node[new_node_name] new_node_attrs['name'] = new_node_name set_tf_custom_call_node_attrs(new_node_attrs) new_node = Node(graph, new_node_name) added_input_tensors_names = set( ) # set of tensors that are were added as input to the sub-graph added_new_node_output_tensors = dict( ) # key - tensor name, value - out port for node_name in nodes_to_merge_names: node = Node(graph, node_name) add_node_pb_if_not_yet_added(node, new_node) # TODO: any improvements? for in_node_name, edge_attrs in Node(graph, node_name).get_inputs(): in_node = Node(graph, in_node_name) # internal edges between nodes of the sub-graph if in_node_name in nodes_to_merge_names: add_node_pb_if_not_yet_added(in_node, new_node) continue # edge outside of sub-graph into sub-graph if in_node_name not in nodes_to_merge_names: # we cannot use the 'in_node_name' as a protobuf operation name here # because the 'in_node_name' could be a sub-graph matched before. input_tensor_name = node.pb.input[edge_attrs['in']] if input_tensor_name not in added_input_tensors_names: if not new_node.has_port('in', edge_attrs['in']): new_node.add_input_port(edge_attrs['in']) graph.add_edge( in_node_name, new_node_name, **merge_edge_props( { 'in': find_input_port(new_node, inputs_desc, node_name, edge_attrs['in']), 'out': edge_attrs['out'], 'internal_input_node_name': input_tensor_name, 'original_dst_node_name': node_name, 'original_dst_port': edge_attrs['in'], 'in_attrs': [ 'in', 'internal_input_node_name', 'original_dst_node_name', 'original_dst_port', 'placeholder_name' ], 'out_attrs': ['out'] }, edge_attrs)) log.debug( "Creating edge from outside of sub-graph to inside sub-graph: {} -> {}" .format(in_node_name, new_node_name)) added_input_tensors_names.add(input_tensor_name) # edge from inside sub-graph to outside sub-graph for out_node_name, edge_attrs in Node(graph, node_name).get_outputs(): if out_node_name not in nodes_to_merge_names: log.debug( "Creating edge from inside of sub-graph to outside sub-graph: {} -> {}" .format(new_node_name, out_node_name)) out_name = internal_output_name_for_node( node_name, edge_attrs['out']) if out_name not in added_new_node_output_tensors.keys(): added_new_node_output_tensors[out_name] = find_output_port( new_node, outputs_desc, node_name, edge_attrs['out']) if not new_node.has_port( 'out', added_new_node_output_tensors[out_name]): new_node.add_output_port( added_new_node_output_tensors[out_name]) graph.add_edge( new_node_name, out_node_name, **merge_edge_props( { 'in': edge_attrs['in'], 'out': added_new_node_output_tensors[out_name], 'internal_output_node_name': out_name, 'in_attrs': ['in', 'internal_input_node_name'], 'out_attrs': ['out', 'internal_output_node_name'] }, edge_attrs)) new_node['output_tensors_names'] = [ val for val in {v: k for k, v in added_new_node_output_tensors.items()}.values() ] # add nodes using the same order as in initial GraphDef so we can dump them to IR in "correct" order new_node['nodes_order'] = [ node for node in graph.graph['initial_nodes_order'] if node in new_node['pbs'].keys() ] for n in nodes_to_merge_names: if graph.has_node( n): # check if not deleted by another (similar) pattern graph.remove_node(n) return Node(graph, new_node_name)