def graph_update(graph1, graph2, update_edges=True, update_nodes=True):
    """
    Update graph1 with the content of graph2

    Requires graph2 to be fully contained in graph1 based on graph topology
    measured as equality between nodes and edges assessed by node and edge ID.

    :param graph1:  target graph
    :type graph1:   Graph
    :param graph2:  source graph
    :type graph2:   Graph
    :param update_edges: update edge data
    :type update_edges:  bool
    :param update_nodes: update node data
    :type update_nodes:  bool
    """

    # Validate if all arguments are Graphs
    check_graphbase_instance(graph1, graph2)

    if graph2 in graph1:
        if update_edges:
            for edge, value in graph2.edges.items():
                graph1.edges[edge].update(value)
        if update_nodes:
            for node, value in graph2.nodes.items():
                graph1.nodes[node].update(value)

    return graph1
def graph_symmetric_difference(graph1,
                               graph2,
                               edge_diff=False,
                               return_copy=False):
    """
    Return a new graph with the symmetric difference in nodes and edges of two
    graphs.

    The symmetric difference represents the nodes and edges connecting them
    that are present in `graph1` but not in `graph2` and vice versa.
    It is thus the opposite of the `graph_intersection`.
    The difference is node driven resulting in edges being removed when the
    nodes on either end are removed. Use the `edge_diff` argument to switch
    to an edge driven difference calculation,

    If the graphs share a common origin graph and `return_copy` is False
    the returned intersection graph will be a view on the origin, else it is
    a new graph.

    :param graph1:      first graph
    :type graph1:       :graphit:Graph
    :param graph2:      second graph
    :type graph2:       :graphit:Graph
    :param edge_diff:   switch from node to edge driven difference calculation
    :type edge_diff:    :py:bool
    :param return_copy: force return a new grpah as deep copy based on graph1
    :type return_copy:  :py:bool

    :return:            symmetric difference graph
    :rtype:             :graphit:Graph
    :raises:            AttributeError, if arguments no instance of Graph class
    """

    # Validate if all arguments are Graphs
    check_graphbase_instance(graph1, graph2)

    # Compute node or edge symmetric difference.
    if edge_diff:
        symdiff_edges = graph1.edges.symmetric_difference(graph2.edges)
    else:
        symdiff_nodes = graph1.nodes.symmetric_difference(graph2.nodes)

    if share_common_origin(graph1, graph2) and not return_copy:
        if edge_diff:
            return graph1.origin.getedges(symdiff_edges)
        return graph1.origin.getnodes(symdiff_nodes)
    else:
        # Get node or edge difference for both and join them in a new graph
        if edge_diff:
            result = graph1.getedges(symdiff_edges.difference(
                graph2.edges)).copy(deep=True, copy_view=False)
            graph_join(result,
                       graph2.getedges(symdiff_edges.difference(graph1.edges)))
        else:
            result = graph1.getnodes(symdiff_nodes.difference(
                graph2.nodes)).copy(deep=True, copy_view=False)
            graph_join(result,
                       graph2.getnodes(symdiff_nodes.difference(graph1.nodes)))

        return result
Esempio n. 3
0
def graph_join(graph1, graph2, links=None, run_node_new=True, run_edge_new=True):
    """
    Add graph2 as subgraph to graph1

    All nodes and edges of graph 2 are added to graph 1. Final links between
    nodes in graph 1 and newly added nodes of graph 2 are defined by the edges
    in the `links` list.

    :param graph1:       graph to add to
    :type graph1:        GraphAxis
    :param graph2:       graph added
    :type graph2:        GraphAxis
    :param links:        links between nodes in graph1 and graph2
    :type links:         :py:list
    :param run_edge_new: run the custom initiation method (new method) of
                         the new edge once.
    :type run_edge_new:  py:bool
    :param run_node_new: run the custom initiation method (new method) of
                         the new node once.
    :type run_node_new:  :py:bool

    :return:             node mapping
    :rtype:              :py:dict
    """

    # Validate if all arguments are Graphs
    check_graphbase_instance(graph1, graph2)

    # Check if graph 1 link nodes exist
    if links:
        for link in links:
            if not link[0] in graph1.nodes:
                raise GraphitException('Link node {0} not in graph1'.format(link[0]))
            if not link[1] in graph2.nodes:
                raise GraphitException('Link node {0} not in graph1'.format(link[1]))

    # Add all nodes and attributes of graph 2 to 1 and register node mapping
    mapping = {}
    for nid, attr in graph2.nodes.items():
        newnid = graph1.add_node(node=nid, run_node_new=run_node_new, **attr)
        mapping[nid] = newnid

    # Transfer edges and attributes from graph 2 to graph 1 and map node IDs
    for eid, attr in graph2.edges.items():
        if eid[0] in mapping and eid[1] in mapping:
            graph1.add_edge(mapping[eid[0]], mapping[eid[1]], run_edge_new=run_edge_new, directed=True, **attr)

    # Link graph 2 nodes to graph 1
    attach_nids = []
    if links:
        for link in links:
            graph1.add_edge(link[0], mapping[link[1]], run_edge_new=run_edge_new, directed=graph1.directed)
            attach_nids.append(mapping[link[1]])

    return mapping
def graph_difference(graph1, graph2, edge_diff=False, return_copy=False):
    """
    Return a graph with the difference in nodes and edges of `graph1` with
    respect to `graph2`.

    The difference represents the nodes and edges connecting them that are
    present in `graph1` but not in `graph2`. Difference is based on ID.
    The difference is node driven resulting in edges being removed when the
    nodes on either end are removed. Use the `edge_diff` argument to switch
    to an edge driven difference calculation,

    If the graphs share a common origin graph and `return_copy` is False
    the returned intersection graph will be a view on the origin, else it is
    a new graph.

    :param graph1:      first graph
    :type graph1:       :graphit:Graph
    :param graph2:      second graph
    :type graph2:       :graphit:Graph
    :param edge_diff:   switch from node to edge driven difference calculation
    :type edge_diff:    :py:bool
    :param return_copy: force return a new graph as deep copy based on graph1
    :type return_copy:  :py:bool

    :return:            difference graph
    :rtype:             :graphit:Graph
    :raises:            AttributeError, if arguments no instance of Graph class
    """

    # Validate if all arguments are Graphs
    check_graphbase_instance(graph1, graph2)

    # Compute edge or node difference.
    if edge_diff:
        difference_edges = graph1.edges.difference(graph2.edges)
    else:
        difference_nodes = graph1.nodes.difference(graph2.nodes)

    if share_common_origin(graph1, graph2) and not return_copy:
        if edge_diff:
            return graph1.origin.getedges(difference_edges)
        return graph1.origin.getnodes(difference_nodes)
    else:
        if edge_diff:
            result = graph1.getedges(difference_edges)
        else:
            result = graph1.getnodes(difference_nodes)
        return result.copy(deep=True, copy_view=False)
def graph_issuperset(graph1, graph2):
    """
    Test if graph1 is a superset of graph2 two in terms of nodes and edges.

    :param graph1:  first graph
    :type graph1:   :graphit:Graph
    :param graph2:  second graph
    :type graph2:   :graphit:Graph

    :return:        issuperset or not
    :rtype:         :py:bool
    :raises:        AttributeError, if arguments no instance of Graph class
    """

    # Validate if all arguments are Graphs
    check_graphbase_instance(graph1, graph2)

    return graph1.nodes.issuperset(graph2.nodes) and graph1.edges.issuperset(graph2.edges)
def graph_intersection(graph1, graph2, return_copy=False):
    """
    Return a graph with the intersection in nodes and edges between the two
    input graphs

    The intersection represents the nodes and edges connecting them present
    in both graphs. Node and edge similarity is based on ID.

    If the graphs share a common origin graph and `return_copy` is False
    the returned intersection graph will be a view on the origin, else it is
    a new graph.

    :param graph1:      first graph
    :type graph1:       :graphit:Graph
    :param graph2:      second graph
    :type graph2:       :graphit:Graph
    :param return_copy: force return a new graph as deep copy based on graph1
    :type return_copy:  :py:bool

    :return:            intersection graph
    :rtype:             :graphit:Graph
    :raises:            AttributeError, if arguments no instance of Graph class
    """

    # Validate if all arguments are Graphs
    check_graphbase_instance(graph1, graph2)

    # Compute node/edge intersection
    intersect_nodes = graph1.nodes.intersection(graph2.nodes)
    intersect_edges = graph1.edges.intersection(graph2.edges)

    if share_common_origin(graph1, graph2) and not return_copy:

        # First get node intersection then edges
        result = graph1.origin.getnodes(intersect_nodes)
        result.edges.set_view(intersect_edges)
        return result
    else:
        result = graph1.getnodes(intersect_nodes)
        result.edges.set_view(intersect_edges)
        return result.copy(deep=True, copy_view=False)
def graph_subtract(graph1, graph2):
    """
    Subtract the second graph from the first

    Subtracting equals calculating the graph difference between graph1 and
    graph2 using a node oriented difference and returning a copy.

    :param graph1:      first graph
    :type graph1:       :graphit:Graph
    :param graph2:      second graph
    :type graph2:       :graphit:Graph

    :return:            new Graph with differences between graph1, graph2
    :rtype:             :graphit:Graph
    """

    # Validate if all arguments are Graphs
    check_graphbase_instance(graph1, graph2)

    # Subtract equals topological difference computed by graph_difference
    return graph_difference(graph1, graph2, edge_diff=False, return_copy=True)
def graph_add(*args, **kwargs):
    """
    Add graphs together

    Using the first graph as base graph, add all nodes and edges in the
    remaining graphs to the first graph based on node and edge ID.
    Attributes are updated for nodes and edges already in the graph one or
    multiple times.
    Node and edge attribute update can be switched on/off using the
    `update_node_attributes` and `update_edge_attributes` respectively.

    .. note:: in case multiple graphs are added together that have overlapping
              nodes or edges defining similar attributes the final value of the
              attribute in the result graph equals the last input graph that
              defines that attribute.

    Please use the `graph_setlike_operations.graph_union` function to create a
    union between input graphs that will not update attributes and return
    `views` in case of a common origin graph.

    .. note:: This function will not create any new nodes or edges that are not
              already available in on of the input graphs. This may yield a
              result graph with isolated sub graphs.

    :param args:                   graphs to add together
    :type args:                    :py:list
    :param update_node_attributes: update existing node attributes
    :type update_node_attributes:  :py:bool
    :param update_edge_attributes: update existing edge attributes
    :type update_edge_attributes:  :py:bool

    :return:                       new Graph joining input graphs
    :rtype:                        :graphit:Graph
    """

    if not len(args) > 1:
        raise AttributeError('At least two input Graphs required')

    # Validate if all arguments are Graphs
    check_graphbase_instance(*args)

    # Make a deep copy of the first graph to serve as base graph for adding
    result = args[0].copy(deep=True)

    # Temporary turn off auto_nid to ensure true addition based on node and
    # edge ID instead of auto generated node ID's
    auto_nid = result.auto_nid
    result.auto_nid = False

    update_node_attributes = kwargs.get('update_node_attributes', True)
    update_edge_attributes = kwargs.get('update_edge_attributes', True)
    for graph in args[1:]:
        for nid, attr in graph.nodes.items():
            if nid in result.nodes and not update_node_attributes:
                continue
            result.add_node(nid, **attr)

        for eid, attr in graph.edges.items():
            if eid in result.edges and not update_edge_attributes:
                continue
            result.add_edge(*eid, **attr)

    result.auto_nid = auto_nid

    return result
def graph_union(*args, **kwargs):
    """
    Return a graph with the union of the input graphs

    The union represents the combined unique nodes and edges present in
    the graphs. Similarity is based on ID.

    Nodes and edges are added without updating the respective attributes.
    The latter can be done by calling the `update` function afterwards.

    If the graphs share a common origin graph and `return_copy` is False
    the returned intersection graph will be a view on the origin, else it is
    a new graph.
    In both cases, the first graph in the argument list is used as basis
    for the returned union graph.

    :param args:        graphs to return the union for
    :type args:         :py:list
    :param return_copy: force return a new graph as deep copy based on graph1
    :type return_copy:  :py:bool

    :return:            union graph
    :rtype:             :graphit:Graph
    :raises:            AttributeError, if arguments no instance of Graph class
    """

    if not len(args) > 1:
        raise AttributeError('At least two input Graphs required')

    # Validate if all arguments are Graphs
    check_graphbase_instance(*args)

    all_share_common_origin = all(
        [share_common_origin(args[0], n) for n in args[1:]])
    if all_share_common_origin and not kwargs.get('return_copy', False):

        nids = []
        for graph in args:
            nids.extend([n for n in graph.nodes if n not in nids])

        eids = []
        for graph in args:
            eids.extend([e for e in graph.edges if e not in eids])

        result = args[0].origin.getnodes(nids)
        result.edges.set_view(eids)
        return result
    else:

        # make a deep copy of the first graph
        result = args[0].copy(deep=True, copy_view=False)

        # we need control over the node ID to add
        # temporary turn off auto_nid if needed
        auto_nid = result.data.auto_nid
        result.data.auto_nid = False

        for graph in args[1:]:
            for node, attrib in graph.nodes.items():
                if node not in result.nodes:
                    result.add_node(node, **attrib)

            for edge, attrib in graph.edges.items():
                if edge not in result.edges:
                    result.add_edge(*edge, **attrib)

        # Restore auto_nid
        result.data.auto_nid = auto_nid

        return result