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
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