Пример #1
0
def diff(A, B, context=False, mods=False):
    '''Given two graphs A and B, where it is generally assumed that B is a "newer" version of A,
    returns a new graph which captures information about which nodes and edges of A were
    removed, added, and remain the same in B.

    Specifically, it returns A ∪ B, such that:
    1. Nodes in A - B are given the "diffstatus" attribute "removed"
    2. Nodes in B - A are given the "diffstatus" attribute "added"
    3. Nodes in A ∩ B are given the "diffstatus" attribute "same"

    Notice that the union of 1 - 3 equals A ∪ B.

    The optional parameter context, when true, will prune the graph so that nodes/edges which are
    the same are only present in the diff graph if:
    1. An edge incident on/to/from it has been changed, or
    2. it is connected to a changed node.

    The optional parameter mods, when true, will check for attribute modifications on nodes and
    edges, in addition to new/removed nodes. Any nodes/edges that have had their attributes
    changed between A and B are marked with the "diffstatus" attribute as "modified."

    WARNING: Currently, this method only works if both A and B were generated with unique IDs in a
    deterministic fashion; i.e., two identical nodes are given the same ID at both points in time.
    This means that diff() will not work on graphs which were generated with automatic random UUIDs.
    '''
    # must take their union first, then mark appropriate nodes/edges
    AB = sn.union(A, B)

    # any edges incident on, to, or from the removed nodes will not be in the removed graph,
    # since we cannot have edges incident on, to, or from non-existent nodes
    removed = sn.difference(A, B)
    _mark_nodes_edges_as(AB, removed, 'removed')
    _mark_incident_edges_as(AB, removed, 'removed')

    added = sn.difference(B, A)
    _mark_nodes_edges_as(AB, added, 'added')
    _mark_incident_edges_as(AB, added, 'added')

    same = sn.intersection(B, A)
    _mark_nodes_edges_as(AB, same, 'same')
    _check_changed_edges(A, B, AB, same)

    if mods:
        _check_mods(A, B, AB, same)

    if context:
        _clear_clutter(AB)

    return AB
Пример #2
0
def test_difference(populated_digraph):
    A = populated_digraph
    B = populated_digraph.copy()

    # remove node C. Consequently, also removes edges (A, C) and (B, C)
    B.remove_node('3cd197c2cf5e42dc9ccd0c2adcaf4bc2')
    d = B.add_node({"type": "D"}, 'da30015efe3c44dbb0b3b3862cef704a') # add another node D
    B.add_edge(d, '3caaa8c09148493dbdf02c574b95526c') # add an edge from D to A

    e = A.add_node({"type": "E"}, 'b1b1c6bbbce74a6fb40ee2486cebef26') # add another node
    f = A.add_node({"type": "F"}, '3a668c22b43e4521b3c9f042fb2380c2') # add another node
    A.add_edge(e, f, {"type": "irregular"}, 'a216de41cca8412fa4b3f432b5d3b0e4') # add edge between the two new nodes

    C = sn.difference(A, B) # A - B

    correct_nodes = {
        uuid.UUID('3cd197c2cf5e42dc9ccd0c2adcaf4bc2'): {
            "type": "C",
            "id": uuid.UUID('3cd197c2cf5e42dc9ccd0c2adcaf4bc2')
        },
        uuid.UUID('b1b1c6bbbce74a6fb40ee2486cebef26'): {
            "type": "E",
            "id": uuid.UUID('b1b1c6bbbce74a6fb40ee2486cebef26')
        },
        uuid.UUID('3a668c22b43e4521b3c9f042fb2380c2'): {
            "type": "F",
            "id": uuid.UUID('3a668c22b43e4521b3c9f042fb2380c2')
        }
    }
    assert C.get_nodes() == correct_nodes

    correct_edges = {
        # e,f
        uuid.UUID('a216de41cca8412fa4b3f432b5d3b0e4'): {
            "type": "irregular",
            "src": uuid.UUID('b1b1c6bbbce74a6fb40ee2486cebef26'),
            "dst": uuid.UUID('3a668c22b43e4521b3c9f042fb2380c2'),
            "id": uuid.UUID('a216de41cca8412fa4b3f432b5d3b0e4')
        }
    }
    assert C.get_edges() == correct_edges
Пример #3
0
def test_difference_custom_lambda(populated_digraph):
    # add some attributes
    populated_digraph.set_node_attribute('3caaa8c09148493dbdf02c574b95526c', 'depth', 0)
    populated_digraph.set_node_attribute('2cdfebf3bf9547f19f0412ccdfbe03b7', 'depth', 0)
    populated_digraph.set_node_attribute('3cd197c2cf5e42dc9ccd0c2adcaf4bc2', 'depth', 1)

    # add a node of type D
    d = populated_digraph.add_node({'type': 'D', 'depth': 1}, '63cf70d2762043c29eb5e3e958383f4a')

    populated_digraph.set_edge_attribute('5f5f44ec7c0144e29c5b7d513f92d9ab', 'weight', 1)
    populated_digraph.set_edge_attribute('f3674fcc691848ebbd478b1bfb3e84c3', 'weight', 2) # (B, A)
    populated_digraph.set_edge_attribute('7eb91be54d3746b89a61a282bcc207bb', 'weight', 3)
    populated_digraph.set_edge_attribute('c172a3599b7d4ef3bbb688277276b763', 'weight', 5) # (B, C)

    # make a copy to change around
    new_populated_digraph = populated_digraph.copy()

    # add an edge between the new node d and node B with weight 8
    new_populated_digraph.add_edge(d, '2cdfebf3bf9547f19f0412ccdfbe03b7', {'weight': 8},
        '8ccd176a48284915828e5ac7e13bc43a')

    # remove node C. Will remove the edges (B, C) and (A, C) as well
    new_populated_digraph.remove_node('3cd197c2cf5e42dc9ccd0c2adcaf4bc2')
    new_populated_digraph.remove_edge('f3674fcc691848ebbd478b1bfb3e84c3') # remove edge (B, A)

    # custom lambda that defines membership in the usual way, but only for nodes with
    # a depth greater than 0, and edges with a weight greater than 2
    node_depth_gt_0 = lambda nid, G: sn.node_in(nid, G) and G.get_node_attribute(nid, 'depth') > 0
    edge_weight_gt_2 = lambda eid, G: sn.edge_in(eid, G) and G.get_edge_attribute(eid, 'weight') > 2

    dg = sn.difference(populated_digraph, new_populated_digraph, node_depth_gt_0, edge_weight_gt_2)

    # the first two nodes are "not in B" as we have defined it because their
    # depth is not greater than 0
    correct_nodes = {
        uuid.UUID('2cdfebf3-bf95-47f1-9f04-12ccdfbe03b7'): {
            'depth': 0,
            'id': uuid.UUID('2cdfebf3-bf95-47f1-9f04-12ccdfbe03b7'),
            'type': 'B'
        },
        uuid.UUID('3caaa8c0-9148-493d-bdf0-2c574b95526c'): {
            'depth': 0,
            'id': uuid.UUID('3caaa8c0-9148-493d-bdf0-2c574b95526c'),
            'type': 'A'
        },
        uuid.UUID('3cd197c2cf5e42dc9ccd0c2adcaf4bc2'): {
            "id": uuid.UUID('3cd197c2cf5e42dc9ccd0c2adcaf4bc2'),
            "type": "C",
            "depth": 1
        }
    }
    assert dg.get_nodes() == correct_nodes

    correct_edges = {
        # the new graph DOES have edge (B, A), but its weight 1, which is not > 2,
        # so by our definition, (B, A) is "not in" the new graph
        uuid.UUID('5f5f44ec-7c01-44e2-9c5b-7d513f92d9ab'): {
            'dst': uuid.UUID('2cdfebf3-bf95-47f1-9f04-12ccdfbe03b7'),
            'id': uuid.UUID('5f5f44ec-7c01-44e2-9c5b-7d513f92d9ab'),
            'src': uuid.UUID('3caaa8c0-9148-493d-bdf0-2c574b95526c'),
            'type': 'normal',
            'weight': 1
        },
        # the new graph removed edge (B, A)
        uuid.UUID('f3674fcc691848ebbd478b1bfb3e84c3'): {
            'id': uuid.UUID('f3674fcc691848ebbd478b1bfb3e84c3'),
            'src': uuid.UUID('2cdfebf3bf9547f19f0412ccdfbe03b7'),
            'dst': uuid.UUID('3caaa8c09148493dbdf02c574b95526c'),
            'weight': 2,
            'type': 'normal'
        },
        # the new graph removed edge (A, C)
        uuid.UUID('7eb91be5-4d37-46b8-9a61-a282bcc207bb'): {
            'dst': uuid.UUID('3cd197c2-cf5e-42dc-9ccd-0c2adcaf4bc2'),
            'id': uuid.UUID('7eb91be5-4d37-46b8-9a61-a282bcc207bb'),
            'src': uuid.UUID('3caaa8c0-9148-493d-bdf0-2c574b95526c'),
            'type': 'normal',
            'weight': 3
        },
        # new graph removed edge (B, C)
        uuid.UUID('c172a359-9b7d-4ef3-bbb6-88277276b763'): {
            'dst': uuid.UUID('3cd197c2-cf5e-42dc-9ccd-0c2adcaf4bc2'),
            'id': uuid.UUID('c172a359-9b7d-4ef3-bbb6-88277276b763'),
            'src': uuid.UUID('2cdfebf3-bf95-47f1-9f04-12ccdfbe03b7'),
            'type': 'irregular',
            'weight': 5
        }
    }
    assert dg.get_edges() == correct_edges