Exemplo n.º 1
0
 def update_input_edges_attrs(graph: Graph, node: Node, added_nodes: list):
     """
     Copy edge attributes from 'old' input edges of node 'node' to new input sub-graph edges.
     :param graph: graph to operate on
     :param node: Node object that was replaced.
     :param added_nodes: list of nodes names added.
     :return: None
     """
     for old_u, old_v, old_edge_attrs in graph.in_edges(node.id, data=True):
         for new_u, new_v, new_edge_attrs in graph.in_edges(added_nodes, data=True):
             if new_u not in added_nodes:  # external input to the sub-graph
                 if old_u == new_u and old_edge_attrs['out'] == new_edge_attrs['out']:
                     merge_edge_props(new_edge_attrs, old_edge_attrs)  # copy old edge attributes
Exemplo n.º 2
0
def is_connected_component(graph: Graph, node_names: list):
    """
    Checks that specified list of nodes forms a connected sub-graph. It ignores edges direction.
    The algorithm is the following. Run BFS from one of the nodes from the node_names list ignoring edges order and
    visiting only nodes from the node_names list. Prepare list of visited nodes. If this list is equal to the
    node_names list (we actually check that the node_names set is sub-set of 'visited' set that is equivalent) then the
    sub-graph is connected.
    :param graph: graph to operate on.
    :param node_names: list of node names to be checked.
    :return: Result of the check.
    """
    if len(node_names) == 0:
        return True

    d = deque([node_names[0]])
    visited = set([node_names[0]])
    while len(d) != 0:
        cur_node_name = d.popleft()
        visited.add(cur_node_name)
        # find adjacent nodes from the list of node_names. Ignoring edges direction
        adj_nodes = [src_node for src_node, _ in graph.in_edges(cur_node_name) if src_node in node_names] + \
                    [dst_node for _, dst_node in graph.out_edges(cur_node_name) if dst_node in node_names]
        for adj_node in adj_nodes:
            if adj_node not in visited:
                d.append(adj_node)
                visited.add(adj_node)
    return set(node_names).issubset(visited)
Exemplo n.º 3
0
def bfs_search(graph: Graph, start_nodes: list = list()):
    """
    Performs breadth-first search over a graph and returns a list of nodes in the BFS order.
    :param graph: networkx graph to traverse.
    :param start_nodes: list of start nodes of the graph. If the list is empty then start from all nodes that do not
    have input nodes.
    :return: the list of nodes in the BFS order.
    """
    result = list()
    if len(start_nodes) == 0:
        start_nodes = [
            node_name for node_name in graph.nodes()
            if len(graph.in_edges(node_name)) == 0
        ]

    visited = set(start_nodes)
    d = deque(start_nodes)

    while len(d) != 0:
        cur_node_name = d.popleft()
        result.append(cur_node_name)
        for src_node, dst_node in graph.out_edges(cur_node_name):
            if dst_node not in visited:
                d.append(dst_node)
                visited.add(dst_node)
    return result
Exemplo n.º 4
0
def control_flow_infer(graph: Graph, node_name: str):
    """
       Executes constant control flow. Propagates nodes executability
    """
    if graph.node[node_name]['kind'] == 'data':
        return

    def mark_executability(node_id: str, is_executable: bool):
        if is_executable and not graph.node[node_id]['executable']:
            return
        graph.node[node_id]['executable'] = is_executable

    in_edges_with_data = graph.in_edges(node_name, data=True)
    in_df_edges_with_data = [(u, v, attrs) for u, v, attrs in in_edges_with_data
                             if 'control_flow_edge' not in attrs or not attrs['control_flow_edge']]
    in_cf_edges_with_data = [(u, v, attrs) for u, v, attrs in in_edges_with_data
                             if 'control_flow_edge' in attrs and attrs['control_flow_edge']]
    is_executable_df = all([graph.node[u]['executable'] for u, _, attrs in in_df_edges_with_data]
                           if len(in_df_edges_with_data) else [True])
    is_executable_cf = all([graph.node[u]['executable'] for u, _, attrs in in_cf_edges_with_data]
                           if len(in_cf_edges_with_data) else [True])
    is_executable = is_executable_df and is_executable_cf

    node = Node(graph, node_name)
    if 'cf_infer' in graph.node[node_name] and callable(node.cf_infer):
        node.cf_infer(node, is_executable, mark_executability)
    else:
        for _, out_data in graph.out_edges(node_name):
            mark_executability(out_data, is_executable)
Exemplo n.º 5
0
 def replace_pattern(self, graph: Graph, match: dict):
     if len(graph.in_edges(match['merge'].id)) <= 1:
         remove_op_node_with_data_node(
             graph, match['merge'],
             list(match['merge'].in_nodes().values())[0])
         log.info(
             "Useles Merge op and data nodes was deleted op='{}'".format(
                 match['merge'].id))
Exemplo n.º 6
0
def node_incoming_neighbourhood(graph: Graph, node_name: str, depth: int):
    """
    Find input neighbourhood of the node.
    :param graph: graph to operate on.
    :param node_name: name of the node to find neighbourhood for.
    :param depth: maximum depth of input nodes.
    :return: list of names of nodes in the neighbourhood.
    """
    return node_neighbourhood(
        node_name, depth,
        lambda node_name: [u for u, v in graph.in_edges([node_name])])
Exemplo n.º 7
0
def invert_sub_graph_between_nodes(graph: Graph,
                                   start_nodes: list,
                                   end_nodes: list,
                                   detect_extra_start_node: callable = None):
    """
    Finds nodes of the sub-graph between 'start_nodes' and 'end_nodes'. But doing it from start_nodes stepping
    backward by in edges.

    Input nodes for the sub-graph nodes are also added to the sub-graph. Constant inputs of the 'start_nodes'
    are also added to the sub-graph.
    :param graph: graph to operate on.
    :param start_nodes: list of nodes names that specifies start nodes.
    :param end_nodes: list of nodes names that specifies end nodes.
    :return: list of nodes of the identified sub-graph or None if the sub-graph cannot be extracted.
    """
    sub_graph_nodes = list()
    visited = set(start_nodes)
    d = deque(start_nodes)
    extra_start_nodes = []

    nx.set_node_attributes(G=graph, name='prev', values=None)
    while len(d) != 0:
        cur_node_name = d.popleft()
        sub_graph_nodes.append(cur_node_name)
        if cur_node_name not in start_nodes and \
                detect_extra_start_node is not None and detect_extra_start_node(Node(graph, cur_node_name)):
            extra_start_nodes.append(cur_node_name)
        else:
            if cur_node_name not in end_nodes:  # do not add output nodes of the end_nodes
                for src_node_name, _ in graph.in_edges(cur_node_name):
                    if src_node_name not in visited:
                        d.append(src_node_name)
                        visited.add(src_node_name)
                        graph.node[cur_node_name]['prev'] = src_node_name

    for node_name in sub_graph_nodes:
        # sub-graph should not contain Input nodes
        if graph.node[node_name].get('op', '') == 'Parameter':
            path = list()
            cur_node = node_name
            while cur_node and 'prev' in graph.node[cur_node]:
                path.append(str(cur_node))
                cur_node = graph.node[cur_node]['prev']
            log.debug("The path from input node is the following: {}".format(
                '\n'.join(path)))
            raise Error(
                'The matched sub-graph contains network input node "{}". '.
                format(node_name) + refer_to_faq_msg(75))
    if detect_extra_start_node is None:
        return sub_graph_nodes
    else:
        return sub_graph_nodes, extra_start_nodes
Exemplo n.º 8
0
 def find_and_replace_pattern(self, graph: Graph):
     nodes_to_remove = set()
     for node_name, node_attrs in list(graph.nodes(data=True)):
         if node_attrs[
                 'kind'] == 'data' and 'executable' in node_attrs and not node_attrs[
                     'executable']:
             [
                 nodes_to_remove.add(op)
                 for op, _ in graph.in_edges(node_name)
             ]
             nodes_to_remove.add(node_name)
     log.debug('Removing the following not executable nodes: {}'
               ''.format('\n'.join(sorted(map(str, nodes_to_remove)))))
     graph.remove_nodes_from(nodes_to_remove)
Exemplo n.º 9
0
 def replace_sub_graph(self, graph: Graph, match: dict):
     """
     There are Concat and ConcatV2 operations in TensorFlow
     The main difference is incoming port of tensor representing axis of concatenation
     In Concat it is the 0 port, in ConcatV2 it is the last port
     To reuse ConcatV2 logic (infer) that already exists in the Model Optimizer here we renumber ports of Concat
     """
     in_edges = list(graph.in_edges(match['concat'].id, data=True))
     for u, v, attrs in in_edges:
         in_port = attrs['in']
         attrs['in'] = len(in_edges) - 1 if in_port == 0 else attrs['in'] - 1
     if match['concat'].has('axis'):
         # we delete axis parameter here (it was set by default by Concat Op) to carefully get it from the last
         # input in Concat infer function
         del graph.node[match['concat'].id]['axis']
Exemplo n.º 10
0
def reverse_dfs(graph: Graph, node_name: str, stop_nodes: list, inputs: list, visited: set = None):
    d = deque()

    if visited is None:
        visited = set()
    visited.add(node_name)
    d.appendleft(node_name)
    while len(d) != 0:
        cur_node = d.popleft()
        for in_node_name, _ in graph.in_edges(cur_node):
            if in_node_name not in visited:
                if op_type(graph, in_node_name) not in stop_nodes:
                    visited.add(in_node_name)
                    d.append(in_node_name)
                else:
                    update_inputs(graph, inputs, in_node_name)
Exemplo n.º 11
0
 def get_internal_output_nodes(self, graph: Graph):
     """
     Gets list of node names producing output outside of the sub-graph. This function checks whether output nodes
     specified in the configuration file should be added to the sub-graph or not. If they should not be added to the
     sub-graph then output nodes of the sub-graph are parents of these nodes.
     :param graph: graph to operate on.
     :return: list of output node names.
     """
     if not self.include_outputs_to_sub_graph:
         log.debug('Do not include outputs of sub-graph for replacement with id {}'.format(self.replacement_id))
         new_end_nodes = set()
         for end_node in self.instances['end_points']:
             for in_node_name, _ in graph.in_edges(end_node):
                 new_end_nodes.add(in_node_name)
         end_nodes = list(new_end_nodes)
         log.debug('New outputs are: {}'.format(end_nodes))
         return end_nodes
     else:
         return self.instances['end_points']
 def dfs(graph: Graph, node_name: str, stop_nodes: tuple, reverse: bool = False) -> list:
     d = deque()
     res = []
     visited = set()
     visited.add(node_name)
     d.appendleft(node_name)
     while len(d) != 0:
         cur_node = d.popleft()
         if reverse:
             nodes = graph.in_edges(cur_node)
         else:
             nodes = graph.out_edges(cur_node)
         for in_node_name, _ in nodes:
             if in_node_name not in visited:
                 if op_type(graph, in_node_name) not in stop_nodes:
                     visited.add(in_node_name)
                     d.append(in_node_name)
                 else:
                     res.append(in_node_name)
     return res
Exemplo n.º 13
0
def sub_graph_between_nodes(graph: Graph,
                            start_nodes: list,
                            end_nodes: list,
                            detect_extra_start_node: callable = None,
                            include_control_flow=True,
                            allow_non_reachable_end_nodes=False):
    """
    Finds nodes of the sub-graph between 'start_nodes' and 'end_nodes'. Input nodes for the sub-graph nodes are also
    added to the sub-graph. Constant inputs of the 'start_nodes' are also added to the sub-graph.
    :param graph: graph to operate on.
    :param start_nodes: list of nodes names that specifies start nodes.
    :param end_nodes: list of nodes names that specifies end nodes.
    :param detect_extra_start_node: callable function to add additional nodes to the list of start nodes instead of
    traversing the graph further. The list of additional start nodes is returned of the function is not None.
    :param include_control_flow: flag to specify whether to follow the control flow edges or not
    :param allow_non_reachable_end_nodes: do not fail if the end nodes are not reachable from the start nodes
    :return: list of nodes of the identified sub-graph or None if the sub-graph cannot be extracted.
    """
    sub_graph_nodes = list()
    visited = set(start_nodes)
    d = deque(start_nodes)
    extra_start_nodes = []

    nx.set_node_attributes(G=graph, name='prev', values=None)
    while len(d) != 0:
        cur_node_id = d.popleft()
        sub_graph_nodes.append(cur_node_id)
        if cur_node_id not in end_nodes:  # do not add output nodes of the end_nodes
            for _, dst_node_name, attrs in graph.out_edges(cur_node_id,
                                                           data=True):
                if dst_node_name not in visited and (
                        include_control_flow
                        or not attrs.get('control_flow_edge', False)):
                    d.append(dst_node_name)
                    visited.add(dst_node_name)
                    graph.node[dst_node_name]['prev'] = cur_node_id

        for src_node_name, _, attrs in graph.in_edges(cur_node_id, data=True):
            # add input nodes for the non-start_nodes
            if cur_node_id not in start_nodes and src_node_name not in visited and\
                    (include_control_flow or not attrs.get('control_flow_edge', False)):
                if detect_extra_start_node is not None and detect_extra_start_node(
                        Node(graph, cur_node_id)):
                    extra_start_nodes.append(cur_node_id)
                else:
                    d.append(src_node_name)
                    graph.node[src_node_name]['prev'] = cur_node_id
                    visited.add(src_node_name)

    # use forward dfs to check that all end nodes are reachable from at least one of input nodes
    forward_visited = set()
    for start_node in start_nodes:
        graph.dfs(start_node, forward_visited)
    for end_node in end_nodes:
        if not allow_non_reachable_end_nodes and end_node not in forward_visited:
            raise Error('End node "{}" is not reachable from start nodes: {}. '
                        .format(end_node, start_nodes) + refer_to_faq_msg(74))

    for node_id in sub_graph_nodes:
        # sub-graph should not contain Placeholder nodes
        if graph.node[node_id].get('op', '') == 'Parameter':
            path = list()
            cur_node = node_id
            while cur_node and 'prev' in graph.node[cur_node]:
                path.append(str(cur_node))
                cur_node = graph.node[cur_node]['prev']
            log.debug("The path from input node is the following: {}".format(
                '\n'.join(path)))
            raise Error(
                'The matched sub-graph contains network input node "{}". '.
                format(node_id) + refer_to_faq_msg(75))
    if detect_extra_start_node is None:
        return sub_graph_nodes
    else:
        return sub_graph_nodes, extra_start_nodes
Exemplo n.º 14
0
def build_graph_with_attrs(nodes_with_attrs: list,
                           edges_with_attrs: list,
                           new_nodes_with_attrs: list = [],
                           new_edges_with_attrs: list = [],
                           update_edge_attrs: dict = None,
                           update_nodes_attributes: list = None,
                           nodes_with_edges_only: bool = False,
                           add_nodes_from_edges: bool = False):
    """
    Build the Graph with specific nodes and edges. Also update of edge and node parameters is supported.
    :param nodes_with_attrs: list of tuples ('node_name', {node_attrs})
    :param edges_with_attrs: list of tuples like (start node, end node, (optional) {attrs of the edge}).
    :param new_nodes_with_attrs: analogically nodes_with_attrs
    :param new_edges_with_attrs: analogically new_edges
    :param update_edge_attrs: optional dictionary like {('from_node', 'to_node', key): {edge_attrs}}.
    :param update_nodes_attributes: optional list of tuples which specifies nodes names and their attributes to be
    updated. The first element is a node name to update attribute and the second element is a dictionary with attribute
    name and its value.
    :param nodes_with_edges_only: add nodes which has at least one incoming or outcoming edge.
    :param add_nodes_from_edges: whether nodes that is not listed in all_nodes but are in all_edges is allowed.
    :return: generated graph.
    """
    if not_all_new([node[0] for node in nodes_with_attrs],
                   [node[0] for node in new_nodes_with_attrs]):
        raise Error(
            'Some nodes from new_nodes_with_attrs are already in nodes.'
            ' Please, add to new_nodes_with_attrs only NEW nodes.')

    if not_all_new([(edge[0], edge[1]) for edge in edges_with_attrs],
                   [(edge[0], edge[1]) for edge in new_edges_with_attrs]):
        raise Error(
            'Some edges from new_edges_with_attrs are already in edges.'
            ' Please, add to new_edges_with_attrs only NEW edges.')

    # Check that all nodes from list of edges are in nodes
    all_nodes = nodes_with_attrs + new_nodes_with_attrs
    all_edges = edges_with_attrs + new_edges_with_attrs
    all_nodes_names = [node[0] for node in all_nodes]
    if not add_nodes_from_edges and not all_edges_in_nodes(
            nodes=all_nodes_names, edges=all_edges):
        raise Error(
            "Some nodes from list of edges is not in nodes. Please, add all necessary nodes."
        )

    graph = Graph()

    # Create dict for nodes with attrs
    nodes_attrs = {}
    for node_name, attrs in all_nodes:
        nodes_attrs[node_name] = attrs
        if 'name' not in attrs:
            attrs['name'] = node_name

    if nodes_with_edges_only:
        # filter nodes to keep only ones with edges connected
        filtered_nodes = {}
        for edge in all_edges:
            node_1, node_2 = edge[0], edge[1]
            filtered_nodes[node_1] = nodes_attrs[node_1]
            filtered_nodes[node_2] = nodes_attrs[node_2]
        nodes_attrs = filtered_nodes

    # Create all nodes
    for node, attrs in nodes_attrs.items():
        graph.add_node(node, **deepcopy(attrs))

    # Connect nodes with edges (also unpack edge params)
    for edge in all_edges:
        node_1, node_2 = edge[0], edge[1]
        edge_attrs = edge[2] if len(edge) == 3 else {}
        graph.add_edge(node_1, node_2, **edge_attrs)

    # Update attributes of edges
    if update_edge_attrs:
        # it will work in 2.x networkx only
        for edge, attr in update_edge_attrs.items():
            for k, v in attr.items():
                nx.set_edge_attributes(G=graph, name=k, values={edge: v})

    # Update attributes of nodes
    if update_nodes_attributes is not None:
        for node_name, new_attrs in update_nodes_attributes:
            assert (node_name in graph.nodes())
            for attr, value in new_attrs.items():
                graph.node[node_name][attr] = value

    for node_id in graph.nodes():
        node = Node(graph, node_id)
        check_and_update_ports(node, [
            graph.get_edge_data(edge[0], node_id)[0]
            for edge in graph.in_edges(node_id)
        ], True)
        check_and_update_ports(node, [
            graph.get_edge_data(node_id, edge[1])[0]
            for edge in graph.out_edges(node_id)
        ], False)

    for node in graph.get_op_nodes():
        # Add in_ports attribute
        in_edges = node.in_edges()
        for i in range(len(in_edges)):
            node.add_input_port(idx=i)

        # Add out_ports attribute
        out_edges = node.out_edges()
        for i in range(len(out_edges)):
            node.add_output_port(idx=i)
    return graph
Exemplo n.º 15
0
def build_graph(nodes_attrs: dict,
                edges: list,
                update_attributes: dict = None,
                nodes_with_edges_only: bool = False,
                cli: Namespace = None):
    """
    Build the Graph with specific nodes and edges.
    :param nodes_attrs: dictionary where key is the node name and the value is the dictionary with node attributes.
    :param edges: list of pairs with start and end node names of the edge.
    :param update_attributes: optional dictionary which specifies nodes names and their attributes to be updated. The
    key is a node name to update attribute and the value is a dictionary with attribute name and its value.
    :param nodes_with_edges_only: add nodes which has at least one incoming or outcoming edge.
    :param cli: Namespace with cli keys to associate with the graph
    :return: generated graph.
    """
    # no mutable values must be set as default function argument
    cli = Namespace(static_shape=False,
                    data_type='FP32') if cli is None else cli
    graph = Graph()

    for node_name, attrs in nodes_attrs.items():
        if 'name' not in attrs:
            attrs['name'] = node_name

    if nodes_with_edges_only:
        # filter nodes to keep only ones with edges connected
        filtered_nodes = {}
        for item in edges:
            if len(
                    item
            ) == 2:  # TODO: is there any better way in python to do that?
                node1, node2 = item
            else:
                node1, node2, _ = item
            filtered_nodes[node1] = nodes_attrs[node1]
            filtered_nodes[node2] = nodes_attrs[node2]
        nodes_attrs = filtered_nodes

    # create all nodes first
    for node, attrs in nodes_attrs.items():
        assert node not in graph.nodes()
        graph.add_node(node, **deepcopy(attrs))

    # connect nodes with edges
    for item in edges:
        if len(item
               ) == 2:  # TODO: is there any better way in python to do that?
            node_1, node_2 = item
            edge_attrs = {}
        else:
            node_1, node_2, edge_attrs = item

        common_attrs = {
            'in': len(graph.in_edges(node_2)),
            'out': len(graph.out_edges(node_1)),
            'name': nodes_attrs[node_1]['name']
        }
        common_attrs.update(edge_attrs)
        graph.add_edge(node_1, node_2, **common_attrs)

    if update_attributes is not None:
        for node_name, new_attrs in update_attributes.items():
            assert (node_name in graph.nodes(
            )), 'Node with name "{}" is not in the graph'.format(node_name)
            for attr, value in new_attrs.items():
                graph.node[node_name][attr] = value

    for node in graph.get_op_nodes():
        # Add in_ports attribute
        in_edges = node.in_edges(control_flow=True)
        for attr in in_edges.values():
            control_flow = True if 'control_flow_edge' in attr and attr[
                'control_flow_edge'] is True else False
            node.add_input_port(idx=attr['in'], control_flow=control_flow)

        # Add out_ports attribute
        out_edges = node.out_edges(control_flow=True)
        for attr in out_edges.values():
            control_flow = True if 'control_flow_edge' in attr and attr[
                'control_flow_edge'] is True else False
            node.add_output_port(idx=attr['out'], control_flow=control_flow)

    graph.graph['cmd_params'] = cli
    return graph