Ejemplo n.º 1
0
def add_nugget_to_action_graph(hie, nug_id, ag_id, partial_typing, move=True):
    nug_gr = hie.node[nug_id].graph
    ag_gr = hie.node[ag_id].graph
    shared_typings = [
        typing for typing in hie.successors(ag_id)
        if typing in hie.successors(nug_id)
    ]
    # necessary_typings = [typing for typing in hie.successors(ag_id)
    #                      if hie.edge[ag_id][typing].total]
    necessary_typings = [typing for typing in hie.successors(ag_id)]
    for typing in necessary_typings:
        for node in nug_gr:
            if node not in partial_typing:
                mapping = hie.get_typing(nug_id, typing)
                if mapping is None or node not in mapping:
                    raise ValueError("Node {} is not typed by {}".format(
                        node, typing))

    for node in nug_gr:
        if node not in partial_typing:
            new_node = prim.add_node_new_id(ag_gr, node,
                                            copy.deepcopy(nug_gr.node[node]))
            partial_typing[node] = new_node
            for typing in necessary_typings:
                mapping = hie.get_typing(nug_id, typing)
                hie.edge[ag_id][typing].mapping[new_node] = mapping[node]

    for (source, target) in nug_gr.edges():
        prim.add_edge(ag_gr, partial_typing[source], partial_typing[target])

    if move:
        for typing in hie.successors(nug_id):
            hie.remove_edge(nug_id, typing)
        hie.add_typing(nug_id, ag_id, partial_typing, total=True)
        hie.node[nug_id].attrs["type"] = "nugget"
Ejemplo n.º 2
0
def pullback(b, c, d, b_d, c_d, inplace=False):
    """Find the pullback from b -> d <- c.

    Given h1 : B -> D; h2 : C -> D returns A, rh1, rh2
    with rh1 : A -> B; rh2 : A -> C and A the pullback.
    """
    if inplace is True:
        a = b
    else:
        a = type(b)()

    # Check homomorphisms
    check_homomorphism(b, d, b_d)
    check_homomorphism(c, d, c_d)

    hom1 = {}
    hom2 = {}

    f = b_d
    g = c_d

    for n1 in b.nodes():
        for n2 in c.nodes():
            if f[n1] == g[n2]:
                new_attrs = merge_attributes(b.node[n1],
                                             c.node[n2],
                                             'intersection')
                if n1 not in a.nodes():
                    add_node(a, n1, new_attrs)
                    hom1[n1] = n1
                    hom2[n1] = n2
                else:
                    i = 1
                    new_name = str(n1) + str(i)
                    while new_name in a.nodes():
                        i += 1
                        new_name = str(n1) + str(i)
                    # if n2 not in a.nodes():
                    add_node(a, new_name, new_attrs)
                    hom1[new_name] = n1
                    hom2[new_name] = n2

    for n1 in a.nodes():
        for n2 in a.nodes():
            if (hom1[n1], hom1[n2]) in b.edges() or \
               ((not a.is_directed()) and (hom1[n2], hom1[n1]) in b.edges()):
                if (hom2[n1], hom2[n2]) in c.edges() or \
                   ((not a.is_directed) and (hom2[n2], hom2[n1]) in c.edges()):
                    add_edge(a, n1, n2)
                    set_edge(
                        a,
                        n1,
                        n2,
                        merge_attributes(
                            get_edge(b, hom1[n1], hom1[n2]),
                            get_edge(c, hom2[n1], hom2[n2]),
                            'intersection'))
    check_homomorphism(a, b, hom1)
    check_homomorphism(a, c, hom2)
    return (a, hom1, hom2)
Ejemplo n.º 3
0
    def inject_add_edge(self, n1, n2, attrs=None):
        """Inject addition of a new edge to the rule.

        This method adds an edge between two nodes of the
        `rhs`.

        Parameters
        ----------
        n1 : hashable
            Id of an edge's source node in `rhs`.
        n2 : hashable
            Id of an edge's target node in `rhs`.

        Raises
        ------
        RuleError
            If some of the nodes (`n1` or `n2`) do not exist
            or if there is already an edge between them in `rhs`.
        """
        if n1 not in self.rhs.nodes():
            raise RuleError(
                "Node with the id '%s' does not exist in the "
                "right-hand side of the rule" % n1)
        if n2 not in self.rhs.nodes():
            raise RuleError(
                "Node with the id '%s' does not exist in the "
                "right-hand side of the rule" % n2)
        if (n1, n2) in self.rhs.edges():
            raise RuleError(
                "Edge '%s->%s' already exists in the right-"
                "hand side of the rule" %
                (n1, n2)
            )
        primitives.add_edge(self.rhs, n1, n2, attrs)
        return
Ejemplo n.º 4
0
def paste_nodes(hie, top, graph_id, parent_path, nodes, mouse_x, mouse_y):
    """paste the selected nodes from graph at parent_path to graph_id"""
    path_list = [s for s in parent_path.split("/") if s and not s.isspace()]
    other_id = child_from_path(hie, top, path_list)
    gr = hie.node[graph_id].graph
    other_gr = hie.node[other_id].graph
    old_to_new = {}

    # check that all copied nodes exist in the graph
    for node in nodes:
        if node not in other_gr:
            raise ValueError(
                "copied node {} does not exist anymore".format(node))

    if hie.has_edge(graph_id, other_id):
        mapping = hie.edge[graph_id][other_id].mapping
        for node in nodes:
            n_id = prim.unique_node_id(gr, node)
            prim.add_node(gr, n_id, other_gr.node[node])
            old_to_new[node] = n_id
            mapping[n_id] = node
        for (source, target) in other_gr.subgraph(nodes).edges():
            prim.add_edge(gr, old_to_new[source], old_to_new[target],
                          other_gr.edge[source][target])
    else:
        # check that all necessary typings are there
        necessary_typings = [typing for typing in hie.successors(graph_id)]
        #  if hie.edge[graph_id][typing].total]
        # until UI can handle partial typings
        typings = [typing for typing in hie.successors(graph_id)
                   if typing in hie.successors(other_id)]
        for typing in necessary_typings:
            if typing not in typings:
                raise ValueError("copied nodes not typed by {}".format(typing))
            for node in nodes:
                if node not in hie.edge[other_id][typing].mapping:
                    raise ValueError("copied node {} is not typed by {}"
                                     .format(node, typing))
        for node in nodes:
            node_id = prim.unique_node_id(gr, node)
            old_to_new[node] = node_id
            prim.add_node(gr, node_id, other_gr.node[node])
            for typing in typings:
                other_mapping = hie.edge[other_id][typing].mapping
                if node in other_mapping:
                    hie.edge[graph_id][typing].mapping[old_to_new[node]] =\
                        other_mapping[node]
        for (source, target) in other_gr.subgraph(nodes).edges():
            prim.add_edge(gr, old_to_new[source], old_to_new[target],
                          other_gr.edge[source][target])

    if "positions" in hie.node[other_id].attrs:
        if "positions" not in hie.node[graph_id].attrs:
            hie.node[graph_id].attrs["positions"] = {}
        positions_old = hie.node[other_id].attrs["positions"]
        positions_new = hie.node[graph_id].attrs["positions"]
        add_positions(mouse_x, mouse_y, positions_old, positions_new,
                      old_to_new)
Ejemplo n.º 5
0
def pullback(b, c, d, b_d, c_d, inplace=False):
    """Find the pullback from b -> d <- c.

    Given h1 : B -> D; h2 : C -> D returns A, rh1, rh2
    with rh1 : A -> B; rh2 : A -> C and A the pullback.
    """
    if inplace is True:
        a = b
    else:
        a = type(b)()

    # Check homomorphisms
    check_homomorphism(b, d, b_d)
    check_homomorphism(c, d, c_d)

    hom1 = {}
    hom2 = {}

    f = b_d
    g = c_d

    for n1 in b.nodes():
        for n2 in c.nodes():
            if f[n1] == g[n2]:
                new_attrs = merge_attributes(b.node[n1], c.node[n2],
                                             'intersection')
                if n1 not in a.nodes():
                    add_node(a, n1, new_attrs)
                    hom1[n1] = n1
                    hom2[n1] = n2
                else:
                    i = 1
                    new_name = str(n1) + str(i)
                    while new_name in a.nodes():
                        i += 1
                        new_name = str(n1) + str(i)
                    # if n2 not in a.nodes():
                    add_node(a, new_name, new_attrs)
                    hom1[new_name] = n1
                    hom2[new_name] = n2

    for n1 in a.nodes():
        for n2 in a.nodes():
            if (hom1[n1], hom1[n2]) in b.edges() or \
               ((not a.is_directed()) and (hom1[n2], hom1[n1]) in b.edges()):
                if (hom2[n1], hom2[n2]) in c.edges() or \
                   ((not a.is_directed) and (hom2[n2], hom2[n1]) in c.edges()):
                    add_edge(a, n1, n2)
                    set_edge(
                        a, n1, n2,
                        merge_attributes(get_edge(b, hom1[n1], hom1[n2]),
                                         get_edge(c, hom2[n1], hom2[n2]),
                                         'intersection'))
    check_homomorphism(a, b, hom1)
    check_homomorphism(a, c, hom2)
    return (a, hom1, hom2)
Ejemplo n.º 6
0
def link_components(hie, g_id, comp1, comp2, kami_id):
    """ link two componenst together with brk, bnd"""
    typing = hie.edge[g_id][kami_id].mapping
    graph = hie.node[g_id].graph
    print(graph)

    bnd_name = unique_node_id(graph, "bnd %s-%s" % (comp1, comp2))
    typing[bnd_name] = "bnd"
    add_node(graph, bnd_name)

    #brk_name = unique_node_id(graph, "brk")
    #add_node(graph, brk_name)
    #typing[brk_name] = "brk"

    #loc1 = unique_node_id(graph, "loc")
    #add_node(graph, loc1)
    #typing[loc1] = "locus"
    #loc2 = unique_node_id(graph, "loc")
    #add_node(graph, loc2)
    #typing[loc2] = "locus"

    add_edge(graph, comp1, bnd_name)
    add_edge(graph, comp2, bnd_name)
    #add_edge(graph, loc1, comp1)
    #add_edge(graph, loc1, bnd_name)
    #add_edge(graph, loc1, brk_name)
    #add_edge(graph, loc2, comp2)
    #add_edge(graph, loc2, bnd_name)
    #add_edge(graph, loc2, brk_name)

    if "positions" in hie.node[g_id].attrs:
        positions = hie.node[g_id].attrs["positions"]
        if comp1 in positions.keys():
            xpos1 = positions[comp1].get("x", 0)
            ypos1 = positions[comp1].get("y", 0)
        else:
            (xpos1, ypos1) = (0, 0)
        if comp2 in positions.keys():
            xpos2 = positions[comp2].get("x", 0)
            ypos2 = positions[comp2].get("y", 0)
        else:
            (xpos2, ypos2) = (0, 0)
        difx = xpos2 - xpos1
        dify = ypos2 - ypos1
        if (difx, dify) != (0, 0):
            distance = sqrt(difx * difx + dify * dify)
            vect = (difx / distance, dify / distance)
            #positions[loc1] = {"x": xpos1+vect[0]*distance/3,
            #                   "y": ypos1+vect[1]*distance/3}
            #positions[loc2] = {"x": xpos1+vect[0]*distance/3*2,
            #                   "y": ypos1+vect[1]*distance/3*2}
            positions[bnd_name] = {
                "x": (xpos1 + vect[0] * distance / 2),  # +
                #vect[1]*60),
                "y": (ypos1 + vect[1] * distance / 2)
            }  # -
Ejemplo n.º 7
0
def add_edge(hie, g_id, parent, node1, node2):
    """add an edge to a node of the hierarchy"""
    if isinstance(hie.node[g_id], GraphNode):
        _valid_edge(hie, g_id, node1, node2)
        prim.add_edge(hie.node[g_id].graph, node1, node2)
    elif isinstance(hie.node[g_id], RuleNode):
        tmp_rule = copy.deepcopy(hie.node[g_id].rule)
        tmp_rule.add_edge_rhs(node1, node2)
        typings = [(hie.node[typing].graph, hie.edge[g_id][typing])
                   for _, typing in hie.out_edges(g_id)]
        check_rule_typings(typings, tmp_rule)
        hie.node[g_id].rule = tmp_rule
    else:
        raise ValueError("node is neither a rule nor a graph")
Ejemplo n.º 8
0
def add_edge(hie, g_id, parent, node1, node2):
    """add an edge to a node of the hierarchy"""
    if isinstance(hie.node[g_id], GraphNode):
        _valid_edge(hie, g_id, node1, node2)
        prim.add_edge(hie.node[g_id].graph, node1, node2)
    elif isinstance(hie.node[g_id], RuleNode):
        tmp_rule = copy.deepcopy(hie.node[g_id].rule)
        tmp_rule.add_edge_rhs(node1, node2)
        typings = [(hie.node[typing].graph, hie.edge[g_id][typing])
                   for _, typing in hie.out_edges(g_id)]
        check_rule_typings(typings, tmp_rule)
        hie.node[g_id].rule = tmp_rule
    else:
        raise ValueError("node is neither a rule nor a graph")
Ejemplo n.º 9
0
def pushout_from_relation(g1, g2, relation, inplace=False):
    """Find the pushout from a relation."""

    left_dict = left_relation_dict(relation)
    right_dict = right_relation_dict(relation)

    if inplace is True:
        g12 = g1
    else:
        g12 = copy.deepcopy(g1)

    g1_g12 = id_of(g12.nodes())
    g2_g12 = dict()

    for node in g1.nodes():
        if node in left_dict.keys():
            for g2_node in left_dict[node]:
                g2_g12[g2_node] = node

    for node in g2.nodes():
        if node not in right_dict.keys():
            add_node(g12, node, g2.node[node])
            g2_g12[node] = node
        elif len(right_dict[node]) == 1:
            node_attrs_diff = dict_sub(
                g2.node[node],
                g1.node[list(right_dict[node])[0]])
            add_node_attrs(
                g12, list(right_dict[node])[0], node_attrs_diff)
        elif len(right_dict[node]) > 1:
            new_name = merge_nodes(g12, right_dict[node])
            for g1_node in right_dict[node]:
                g1_g12[g1_node] = new_name
            g2_g12[node] = new_name
            node_attrs_diff = dict_sub(
                g2.node[node],
                g12.node[new_name])
            add_node_attrs(g12, new_name, node_attrs_diff)

    for u, v in g2.edges():
        if (g2_g12[u], g2_g12[v]) not in g12.edges():
            add_edge(g12, g2_g12[u], g2_g12[v], get_edge(g2, u, v))
        else:
            edge_attrs_diff = dict_sub(
                g2.edge[u][v],
                g12.edge[g2_g12[u]][g2_g12[v]])
            add_edge_attrs(g12, g2_g12[u], g2_g12[v], edge_attrs_diff)
    return (g12, g1_g12, g2_g12)
Ejemplo n.º 10
0
def pushout_from_relation(g1, g2, relation, inplace=False):
    """Find the pushout from a relation."""

    left_dict = left_relation_dict(relation)
    right_dict = right_relation_dict(relation)

    if inplace is True:
        g12 = g1
    else:
        g12 = copy.deepcopy(g1)

    g1_g12 = id_of(g12.nodes())
    g2_g12 = dict()

    for node in g1.nodes():
        if node in left_dict.keys():
            for g2_node in left_dict[node]:
                g2_g12[g2_node] = node

    for node in g2.nodes():
        if node not in right_dict.keys():
            add_node(g12, node, g2.node[node])
            g2_g12[node] = node
        elif len(right_dict[node]) == 1:
            node_attrs_diff = dict_sub(g2.node[node],
                                       g1.node[list(right_dict[node])[0]])
            add_node_attrs(g12, list(right_dict[node])[0], node_attrs_diff)
        elif len(right_dict[node]) > 1:
            new_name = merge_nodes(g12, right_dict[node])
            for g1_node in right_dict[node]:
                g1_g12[g1_node] = new_name
            g2_g12[node] = new_name
            node_attrs_diff = dict_sub(g2.node[node], g12.node[new_name])
            add_node_attrs(g12, new_name, node_attrs_diff)

    for u, v in g2.edges():
        if (g2_g12[u], g2_g12[v]) not in g12.edges():
            add_edge(g12, g2_g12[u], g2_g12[v], get_edge(g2, u, v))
        else:
            edge_attrs_diff = dict_sub(g2.edge[u][v],
                                       g12.edge[g2_g12[u]][g2_g12[v]])
            add_edge_attrs(g12, g2_g12[u], g2_g12[v], edge_attrs_diff)
    return (g12, g1_g12, g2_g12)
Ejemplo n.º 11
0
    def test_lifting(self):
        pattern = nx.DiGraph()
        primitives.add_nodes_from(pattern, [
            ("student", {"sex": {"male", "female"}}),
            "prof"
        ])
        primitives.add_edge(pattern, "prof", "student")

        p = nx.DiGraph()
        primitives.add_nodes_from(p, [
            ("girl", {"sex": "female"}),
            ("boy", {"sex": "male"}),
            ("generic")
        ])
        p_lhs = {
            "girl": "student",
            "boy": "student",
            "generic": "student"
        }
        rule = Rule(p, pattern, p_lhs=p_lhs)

        # Test non-canonical rule lifting
        rule_hierarchy1, lhs_instances1 = self.hierarchy.get_rule_propagations(
            "b", rule, p_typing={"c": {"Alice": {"girl", "generic"}, "Bob": "boy"}})

        new_hierarchy, rhs_instances1 = self.hierarchy.apply_rule_hierarchy(
            rule_hierarchy1, lhs_instances1, inplace=False)

        pattern = nx.DiGraph()
        primitives.add_nodes_from(pattern, [
            "school",
            "institute"
        ])
        rule = Rule.from_transform(pattern)
        rule.inject_add_node("phd")
        rule.inject_add_edge("phd", "institute", {"type": "internship"})

        rule_hierarchy2, lhs_instances2 = self.hierarchy.get_rule_propagations(
            "b", rule, rhs_typing={"a": {"phd": "red"}})

        new_hierarchy, rhs_instances2 = self.hierarchy.apply_rule_hierarchy(
            rule_hierarchy2, lhs_instances2, inplace=False)
Ejemplo n.º 12
0
 def add_edge(self, n1, n2, attrs=None):
     """Add an edge in the graph."""
     # Find nodes in p mapping to n1 & n2
     p_keys_1 = keys_by_value(self.p_lhs, n1)
     p_keys_2 = keys_by_value(self.p_lhs, n2)
     for k1 in p_keys_1:
         if k1 not in self.p.nodes():
             raise RuleError(
                 "Node with the id '%s' does not exist in the "
                 "preserved part of the rule" % k1
             )
         for k2 in p_keys_2:
             if k2 not in self.p.nodes():
                 raise RuleError(
                     "Node with the id '%s' does not exist in the "
                     "preserved part of the rule" % k2
                 )
             rhs_key_1 = self.p_rhs[k1]
             rhs_key_2 = self.p_rhs[k2]
             if self.rhs.is_directed():
                 if (rhs_key_1, rhs_key_2) in self.rhs.edges():
                     raise RuleError(
                         "Edge '%s->%s' already exists in the right "
                         "hand side of the rule" %
                         (rhs_key_1, rhs_key_2)
                     )
                 primitives.add_edge(self.rhs, rhs_key_1, rhs_key_2, attrs)
             else:
                 if (rhs_key_1, rhs_key_2) in self.rhs.edges() or\
                    (rhs_key_2, rhs_key_1) in self.rhs.edges():
                     raise RuleError(
                         "Edge '%s->%s' already exists in the right "
                         "hand side of the rule" %
                         (rhs_key_1, rhs_key_2)
                     )
                 primitives.add_edge(self.rhs, rhs_key_1, rhs_key_2, attrs)
     return
Ejemplo n.º 13
0
    def __init__(self):
        """Initialize test."""
        # Define the left hand side of the rule
        self.pattern = nx.DiGraph()
        self.pattern.add_node(1)
        self.pattern.add_node(2)
        self.pattern.add_node(3)
        prim.add_node(self.pattern, 4, {'a': 1})

        self.pattern.add_edges_from([
            (1, 2),
            (3, 2),
            (4, 1)
        ])
        prim.add_edge(self.pattern, 2, 3, {'a': {1}})

        # Define preserved part of the rule
        self.p = nx.DiGraph()
        self.p.add_node('a')
        self.p.add_node('b')
        self.p.add_node('c')
        prim.add_node(self.p, 'd', {'a': 1})

        self.p.add_edges_from([
            ('a', 'b'),
            ('d', 'a')
        ])
        prim.add_edge(self.p, 'b', 'c', {'a': {1}})

        # Define the right hand side of the rule
        self.rhs = nx.DiGraph()
        self.rhs.add_node('x')
        self.rhs.add_node('y')
        self.rhs.add_node('z')
        # self.rhs.add_node('s', {'a': 1})
        prim.add_node(self.rhs, 's', {'a': 1})
        self.rhs.add_node('t')

        self.rhs.add_edges_from([
            ('x', 'y'),
            # ('y', 'z', {'a': {1}}),
            ('s', 'x'),
            ('t', 'y')
        ])
        prim.add_edge(self.rhs, 'y', 'z', {'a': {1}})

        # Define mappings
        self.p_lhs = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
        self.p_rhs = {'a': 'x', 'b': 'y', 'c': 'z', 'd': 's'}
        return
Ejemplo n.º 14
0
    def __init__(self):
        """Initialize test."""
        # Define the left hand side of the rule
        self.pattern = nx.DiGraph()
        self.pattern.add_node(1)
        self.pattern.add_node(2)
        self.pattern.add_node(3)
        prim.add_node(self.pattern, 4, {'a': 1})

        self.pattern.add_edges_from([
            (1, 2),
            (3, 2),
            (4, 1)
        ])
        prim.add_edge(self.pattern, 2, 3, {'a': {1}})

        # Define preserved part of the rule
        self.p = nx.DiGraph()
        self.p.add_node('a')
        self.p.add_node('b')
        self.p.add_node('c')
        prim.add_node(self.p, 'd', {'a': 1})

        self.p.add_edges_from([
            ('a', 'b'),
            ('d', 'a')
        ])
        prim.add_edge(self.p, 'b', 'c', {'a': {1}})

        # Define the right hand side of the rule
        self.rhs = nx.DiGraph()
        self.rhs.add_node('x')
        self.rhs.add_node('y')
        self.rhs.add_node('z')
        # self.rhs.add_node('s', {'a': 1})
        prim.add_node(self.rhs, 's', {'a': 1})
        self.rhs.add_node('t')

        self.rhs.add_edges_from([
            ('x', 'y'),
            # ('y', 'z', {'a': {1}}),
            ('s', 'x'),
            ('t', 'y')
        ])
        prim.add_edge(self.rhs, 'y', 'z', {'a': {1}})

        # Define mappings
        self.p_lhs = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
        self.p_rhs = {'a': 'x', 'b': 'y', 'c': 'z', 'd': 's'}
        return
Ejemplo n.º 15
0
 def _add_edge_lhs(self, source, target, attrs=None):
     if (source, target) not in self.lhs.edges():
         if source in self.lhs.nodes() and target in self.rhs.nodes():
             primitives.add_edge(self.lhs, source, target, attrs)
             p_sources = keys_by_value(self.p_lhs, source)
             p_targets = keys_by_value(self.p_lhs, target)
             if len(p_sources) > 0 and len(p_targets) > 0:
                 for p_s in p_sources:
                     for p_t in p_targets:
                         primitives.add_edge(self.p, p_s, p_t, attrs)
                         primitives.add_edge(
                             self.rhs, self.p_rhs[p_s],
                             self.p_rhs[p_t], attrs)
         else:
             raise RuleError(
                 "Cannot add an edge between nodes '{}' and '{}': "
                 "one of the nodes does not exist".format(source, target))
     else:
         raise RuleError(
             "Edge '{}'->'{}' already exists in the left-hand side "
             "of the rule".format(source, target))
Ejemplo n.º 16
0
def paste_nodes(hie, top, graph_id, parent_path, nodes, mouse_x, mouse_y):
    """paste the selected nodes from graph at parent_path to graph_id"""
    path_list = [s for s in parent_path.split("/") if s and not s.isspace()]
    other_id = child_from_path(hie, top, path_list)
    gr = hie.node[graph_id].graph
    other_gr = hie.node[other_id].graph
    old_to_new = {}

    # check that all copied nodes exist in the graph
    for node in nodes:
        if node not in other_gr:
            raise ValueError(
                "copied node {} does not exist anymore".format(node))

    if hie.has_edge(graph_id, other_id):
        mapping = hie.edge[graph_id][other_id].mapping
        for node in nodes:
            n_id = prim.unique_node_id(gr, node)
            prim.add_node(gr, n_id, other_gr.node[node])
            old_to_new[node] = n_id
            mapping[n_id] = node
        for (source, target) in other_gr.subgraph(nodes).edges():
            prim.add_edge(gr, old_to_new[source], old_to_new[target],
                          other_gr.edge[source][target])
    else:
        # check that all necessary typings are there
        necessary_typings = [typing for typing in hie.successors(graph_id)]
        #  if hie.edge[graph_id][typing].total]
        # until UI can handle partial typings
        typings = [
            typing for typing in hie.successors(graph_id)
            if typing in hie.successors(other_id)
        ]
        for typing in necessary_typings:
            if typing not in typings:
                raise ValueError("copied nodes not typed by {}".format(typing))
            for node in nodes:
                if node not in hie.edge[other_id][typing].mapping:
                    raise ValueError(
                        "copied node {} is not typed by {}".format(
                            node, typing))
        for node in nodes:
            node_id = prim.unique_node_id(gr, node)
            old_to_new[node] = node_id
            prim.add_node(gr, node_id, other_gr.node[node])
            for typing in typings:
                other_mapping = hie.edge[other_id][typing].mapping
                if node in other_mapping:
                    hie.edge[graph_id][typing].mapping[old_to_new[node]] =\
                        other_mapping[node]
        for (source, target) in other_gr.subgraph(nodes).edges():
            prim.add_edge(gr, old_to_new[source], old_to_new[target],
                          other_gr.edge[source][target])

    if "positions" in hie.node[other_id].attrs:
        if "positions" not in hie.node[graph_id].attrs:
            hie.node[graph_id].attrs["positions"] = {}
        positions_old = hie.node[other_id].attrs["positions"]
        positions_new = hie.node[graph_id].attrs["positions"]
        add_positions(mouse_x, mouse_y, positions_old, positions_new,
                      old_to_new)
Ejemplo n.º 17
0
 def add_edge_rhs(self, n1, n2, attrs=None):
     """Add an edge in the rhs."""
     primitives.add_edge(self.rhs, n1, n2, attrs)
Ejemplo n.º 18
0
    def inject_clone_node(self, n, new_node_id=None):
        """Inject cloning of a node by the rule.

        This procedure clones `n` in the preserved part
        and the right-hand side.

        Parameters
        ----------
        n : hashable
            Node from `lhs` to clone
        new_node_id : hashable
            Id for the clone

        Returns
        -------
        p_new_node_id : hashable
            Id of the new clone node in the preserved part
        rhs_new_node_id : hashable
            Id of the new clone node in the right-hand side


        Raises
        ------
        RuleError
            If the node to clone is already being removed by the rule
            or if node with the specified clone id already exists in p.
        """
        p_nodes = keys_by_value(self.p_lhs, n)
        if len(p_nodes) == 0:
            raise RuleError(
                "Cannot inject cloning: node '%s' is already "
                "being removed by the rule, revert its removal "
                "first" % n)
        else:
            if new_node_id is not None and new_node_id in self.p.nodes():
                raise RuleError(
                    "Node with id '%s' already exists in the "
                    "preserved part!")
            some_p_node = p_nodes[0]
            p_new_node_id = primitives.clone_node(
                self.p, some_p_node, new_node_id)
            self.p_lhs[p_new_node_id] = n
            # add it to the rhs
            # generate a new id for rhs
            rhs_new_node_id = p_new_node_id
            if rhs_new_node_id in self.rhs.nodes():
                rhs_new_node_id = primitives.unique_node_id(
                    self.rhs, rhs_new_node_id)
            primitives.add_node(
                self.rhs, rhs_new_node_id, self.p.node[p_new_node_id])
            self.p_rhs[p_new_node_id] = rhs_new_node_id
            # reconnect the new rhs node with necessary edges
            for pred in self.p.predecessors(p_new_node_id):
                if (self.p_rhs[pred], rhs_new_node_id) not in self.rhs.edges():
                    primitives.add_edge(
                        self.rhs,
                        self.p_rhs[pred], rhs_new_node_id,
                        self.p.edge[pred][p_new_node_id])
            for suc in self.p.successors(p_new_node_id):
                if (rhs_new_node_id, self.p_rhs[suc]) not in self.rhs.edges():
                    primitives.add_edge(
                        self.rhs,
                        rhs_new_node_id, self.p_rhs[suc],
                        self.p.edge[p_new_node_id][suc])

        return (p_new_node_id, rhs_new_node_id)
Ejemplo n.º 19
0
def pushout(a, b, c, a_b, a_c, inplace=False):
    """Find the pushour of the span b <- a -> c."""
    check_homomorphism(a, b, a_b)
    check_homomorphism(a, c, a_c)

    if inplace is True:
        d = b
    else:
        d = copy.deepcopy(b)

    b_d = id_of(b.nodes())
    c_d = dict()

    # Add/merge nodes
    for c_n in c.nodes():
        a_keys = keys_by_value(a_c, c_n)
        # Add nodes
        if len(a_keys) == 0:
            add_node(d, c_n, c.node[c_n])
            c_d[c_n] = c_n
        # Keep nodes
        elif len(a_keys) == 1:
            c_d[a_c[a_keys[0]]] = a_b[a_keys[0]]
        # Merge nodes
        else:
            nodes_to_merge = []
            for k in a_keys:
                nodes_to_merge.append(a_b[k])
            new_name = merge_nodes(d, nodes_to_merge)
            c_d[c_n] = new_name
            for node in nodes_to_merge:
                b_d[node] = new_name

    # Add edges
    for (n1, n2) in c.edges():
        if b.is_directed():
            if (c_d[n1], c_d[n2]) not in d.edges():
                add_edge(d, c_d[n1], c_d[n2], get_edge(c, n1, n2))
        else:
            if (c_d[n1], c_d[n2]) not in d.edges() and\
               (c_d[n2], c_d[n1]) not in d.edges():
                add_edge(d, c_d[n1], c_d[n2], get_edge(c, n1, n2))

    # Add node attrs
    for c_n in c.nodes():
        a_keys = keys_by_value(a_c, c_n)
        # Add attributes to the nodes which stayed invariant
        if len(a_keys) == 1:
            attrs_to_add = dict_sub(c.node[c_n], a.node[a_keys[0]])
            add_node_attrs(d, c_d[c_n], attrs_to_add)
        # Add attributes to the nodes which were merged
        elif len(a_keys) > 1:
            merged_attrs = {}
            for k in a_keys:
                merged_attrs = merge_attributes(merged_attrs, a.node[k])
            attrs_to_add = dict_sub(c.node[c_n], merged_attrs)
            add_node_attrs(d, c_d[c_n], attrs_to_add)

    # Add edge attrs
    for (n1, n2) in c.edges():
        d_n1 = c_d[n1]
        d_n2 = c_d[n2]
        if d.is_directed():
            attrs_to_add = dict_sub(get_edge(c, n1, n2),
                                    get_edge(d, d_n1, d_n2))
            add_edge_attrs(d, c_d[n1], c_d[n2], attrs_to_add)
        else:
            attrs_to_add = dict_sub(get_edge(c, n1, n2),
                                    get_edge(d, d_n1, d_n2))
            add_edge_attrs(d, c_d[n1], c_d[n2], attrs_to_add)
    return (d, b_d, c_d)
Ejemplo n.º 20
0
def unfold_nugget(hie, nug_id, ag_id, mm_id, test=False):
    """unfold a nugget with conflicts to create multiple nuggets"""
    nug_gr = copy.deepcopy(hie.node[nug_id].graph)
    mm_typing = copy.deepcopy(hie.get_typing(nug_id, mm_id))
    ag_typing = copy.deepcopy(hie.get_typing(nug_id, ag_id))

    # create one new locus for each linked agent, region or residue linked to
    #  a locus
    new_ports = {}  # new_port remember the loci/state it is created from
    old_ports = []
    non_comp_neighbors = {}
    for node in nug_gr.nodes():

        # move the state test to explicit "is_equal" nodes
        if mm_typing[node] == "state" and "val" in nug_gr.node[node]:
            for val in nug_gr.node[node]["val"]:
                id_prefix = "{}_{}".format(val, node)
                test_id = unique_node_id(nug_gr, id_prefix)
                add_node(nug_gr, test_id, {"val": val})
                mm_typing[test_id] = "is_equal"
                add_edge(nug_gr, test_id, node)

                # for testing
                if test:
                    ag = hie.node[ag_id].graph
                    ag_test_id = unique_node_id(ag, id_prefix)
                    add_node(ag, ag_test_id, {"val": val})
                    add_edge(ag, ag_test_id, ag_typing[node])
                    hie.edge[ag_id][mm_id].mapping[ag_test_id] = "is_equal"

                    real_nugget = hie.node[nug_id].graph
                    old_test_id = unique_node_id(real_nugget, id_prefix)
                    add_node(real_nugget, old_test_id, {"val": val})
                    add_edge(real_nugget, old_test_id, node)
                    hie.edge[nug_id][ag_id].mapping[old_test_id] = ag_test_id

        if mm_typing[node] in ["locus", "state"]:
            comp_neighbors = [
                comp for comp in nug_gr.successors(node)
                if mm_typing[comp] in ["agent", "region", "residue"]
            ]
            other_neighbors = [
                other for other in (nug_gr.successors(node) +
                                    nug_gr.predecessors(node))
                if other not in comp_neighbors
            ]
            old_ports.append(node)
            for comp in comp_neighbors:
                id_prefix = "{}_{}".format(node, comp)
                port_id = unique_node_id(nug_gr, id_prefix)
                add_node(nug_gr, port_id)
                mm_typing[port_id] = mm_typing[node]
                ag_typing[port_id] = ag_typing[node]
                new_ports[port_id] = node
                add_edge(nug_gr, port_id, comp)
                for other in other_neighbors:
                    if mm_typing[other] in ["mod", "is_equal"]:
                        add_edge(nug_gr, other, port_id)
                    else:
                        add_edge(nug_gr, port_id, other)
                non_comp_neighbors[port_id] = set(other_neighbors)

    # remove the old potentially shared between agents/region/residues loci
    for port in old_ports:
        remove_node(nug_gr, port)
        del mm_typing[port]
        del ag_typing[port]

    # associate the components nodes (agent,region, residue) to the ports
    components = {}
    for port in new_ports:
        components[port] = _agents_of_components(nug_gr, mm_typing, port)

    def _nonconflicting(port1, action_node1, port2, action_node2):
        typ1 = mm_typing[action_node1]
        typ2 = mm_typing[action_node2]
        if port1 == port2:
            if typ1 == typ2:
                return False
            if mm_typing[port1] == "state":
                return True
            if {typ1, typ2} & {"is_free", "is_bnd"}:
                return False
            different_loci = set(nug_gr.predecessors(action_node1)) !=\
                set(nug_gr.predecessors(action_node2))
            return different_loci

        elif action_node1 != action_node2:
            return True
        elif typ1 in ["mod", "is_equal", "is_free"]:
            return False
        else:
            return new_ports[port1] != new_ports[port2]

    def replace(node):
        """identify is_equal and mod nodes with same values"""
        if mm_typing[node] == "is_equal":
            return ("is_equal", str(nug_gr.node[node]["val"]))
        if mm_typing[node] == "mod":
            return ("mod", str(nug_gr.node[node]["val"]))
        return node

    def reduce_subsets(set_list):
        return set_list

    def subset_up_to_equivalence(set1, set2):
        set1 = {frozenset(map(replace, s)) for s in set1}
        set2 = {frozenset(map(replace, s)) for s in set2}
        return set1.issubset(set2)

    def replace2(node):
        """identify is_equal and mod nodes with same values"""
        if mm_typing[node] == "is_equal":
            return ("is_equal", str(nug_gr.node[node]["val"]),
                    frozenset(nug_gr.successors(node)))
        if mm_typing[node] == "mod":
            return ("mod", str(nug_gr.node[node]["val"]),
                    frozenset(nug_gr.successors(node)))
        return node

    def _equivalent_actions(act1, act2, edge_list):
        l1 = [(port, replace(node)) for (port, node) in edge_list
              if node == act1]
        l2 = [(port, replace(node)) for (port, node) in edge_list
              if node == act2]
        return l1 == l2

    def _equivalent_edge(p1, a1, p2, a2):
        return p1 == p2 and replace2(a1) == replace2(a2)

    def _valid_subsets(memo_dict, set_list):
        """build non conflicting sets of sets of nodes"""
        if set_list == []:
            return [[]]
        memo_key = frozenset(set_list)
        if memo_key in memo_dict:
            return memo_dict[memo_key]
        (port, a_node) = set_list[0]
        conflicting_edges = [
            (port2, a_node2) for (port2, a_node2) in set_list[1:]
            if not _nonconflicting(port, a_node, port2, a_node2)
        ]

        nonconflicting_sets =\
            [(port2, a_node2) for (port2, a_node2) in set_list[1:]
             if _nonconflicting(port, a_node, port2, a_node2)]
        equivalent_edges = [
            (p2, n2) for (p2, n2) in set_list
            if p2 == port and _equivalent_actions(a_node, n2, set_list)
        ]

        new_set_list = [
            (p2, n2) for (p2, n2) in set_list[1:]
            if p2 != port or not _equivalent_actions(a_node, n2, set_list)
        ]

        cond1 = (len([node
                      for (_, node) in set_list[1:] if node == a_node]) == 0
                 and all(
                     replace(n2) == replace(a_node)
                     for (p2, n2) in set_list[1:] if p2 == port))

        if nonconflicting_sets == new_set_list or cond1:
            memo_dict[memo_key] =\
                [sub + [(port, a_node)]
                 for sub in _valid_subsets(memo_dict, nonconflicting_sets)]
            return memo_dict[memo_key]
        else:
            without_current_edge = _valid_subsets(memo_dict, new_set_list)

            def conflict_with_removed_edges(edge_list):
                return all(
                    any(not _nonconflicting(p1, a_node1, p2, a_node2)
                        for (p2, a_node2) in edge_list)
                    for (p1, a_node1) in equivalent_edges)

            # with_conflict = list(filter(conflict_with_current_edge, without_current_edge))
            with_conflict = list(
                filter(conflict_with_removed_edges, without_current_edge))
            memo_dict[memo_key] =\
                with_conflict +\
                [sub + [(port, a_node)]
                 for sub in _valid_subsets(memo_dict, nonconflicting_sets)]
            return memo_dict[memo_key]

    def _complete_subsets(set_list):
        print(set_list)
        return [components[port] | {a_node} for (port, a_node) in set_list]

    def _remove_uncomplete_actions(set_list):
        """remove actions and test which are not connected to enough
         components"""
        labels = {node: 0 for node in nug_gr.nodes()}
        for nodes in set_list:
            for node in nodes:
                labels[node] += 1

        to_remove = set()
        for node in nug_gr.nodes():
            if (mm_typing[node] in ["bnd", "brk", "is_bnd"]
                    and labels[node] < 2):
                to_remove.add(node)
            if (mm_typing[node] in ["is_free", "mod", "is_equal"]
                    and labels[node] < 1):
                to_remove.add(node)

        return [nodes for nodes in set_list if not nodes & to_remove]

    port_action_list = [(port, a_node)
                        for (port, a_nodes) in non_comp_neighbors.items()
                        for a_node in a_nodes]

    # build globally non conflicting subsets and remove the uncomplete actions
    memo_dict = {}
    valid_ncss = {
        frozenset(
            map(frozenset,
                _remove_uncomplete_actions(_complete_subsets(set_list))))
        for set_list in _valid_subsets(memo_dict, port_action_list)
    }
    maximal_valid_ncss = valid_ncss

    # add the nodes that where not considered at all
    # because they are not connected to a locus or state
    nodes_with_ports = set.union(
        set.union(*(list(non_comp_neighbors.values()) + [set()])),
        set.union(*(list(components.values()) + [set()])))

    nodes_without_ports = set(nug_gr.nodes()) - nodes_with_ports

    # build the nuggets and add them to the hierarchy
    # as children of the old one for testing
    def _graph_of_ncs(ncs):
        sub_graphs = [(subgraph(nug_gr, nodes), {node: node
                                                 for node in nodes})
                      for nodes in ncs]
        sub_graphs.append((subgraph(nug_gr, nodes_without_ports),
                           {node: node
                            for node in nodes_without_ports}))
        return multi_pullback_pushout(nug_gr, sub_graphs)

    valid_graphs = map(_graph_of_ncs, maximal_valid_ncss)
    new_nuggets = []
    for (new_nugget, new_typing) in valid_graphs:
        if test:
            typing_by_old_nugget = {}
            for node in new_nugget.nodes():
                if new_typing[node] in hie.node[nug_id].graph.nodes():
                    typing_by_old_nugget[node] = new_typing[node]
                else:
                    typing_by_old_nugget[node] = new_ports[new_typing[node]]
            new_nuggets.append((new_nugget, typing_by_old_nugget))
        else:
            new_ag_typing = compose_homomorphisms(ag_typing, new_typing)
            new_mm_typing = compose_homomorphisms(mm_typing, new_typing)
            new_nuggets.append((new_nugget, new_ag_typing, new_mm_typing))
    return new_nuggets
Ejemplo n.º 21
0
    def test_propagation_node_adds(self):
        """Test propagation down of additions."""
        p = NXGraph()
        primitives.add_nodes_from(p, ["B"])

        l = NXGraph()
        primitives.add_nodes_from(l, ["B"])

        r = NXGraph()
        primitives.add_nodes_from(r, ["B", "B_res_1", "X", "Y"])
        primitives.add_edge(r, "B_res_1", "B")

        rule = Rule(p, l, r)

        instance = {"B": "B"}

        rhs_typing = {
            "mm": {
                "B_res_1": "residue"
            },
            "mmm": {
                "X": "component"
            },
            "colors": {
                "Y": "red"
            }
        }
        try:
            self.hierarchy.rewrite("n1",
                                   rule,
                                   instance,
                                   rhs_typing=rhs_typing,
                                   strict=True)
            raise ValueError("Error was not caught!")
        except RewritingError:
            pass

        new_hierarchy = NXHierarchy.copy(self.hierarchy)

        new_hierarchy.rewrite("n1", rule, instance, rhs_typing=rhs_typing)

        # test propagation of node adds
        assert ("B_res_1" in new_hierarchy.get_graph("n1").nodes())
        assert ("B_res_1" in new_hierarchy.get_graph("ag").nodes())
        assert (new_hierarchy.get_typing("n1", "ag")["B_res_1"] == "B_res_1")
        assert (new_hierarchy.get_typing("ag", "mm")["B_res_1"] == "residue")
        assert (("B_res_1", "B") in new_hierarchy.get_graph("n1").edges())
        assert (("B_res_1", "B") in new_hierarchy.get_graph("ag").edges())

        assert ("X" in new_hierarchy.get_graph("n1").nodes())
        assert ("X" in new_hierarchy.get_graph("ag").nodes())
        assert ("X" in new_hierarchy.get_graph("mm").nodes())
        assert ("X" in new_hierarchy.get_graph("colors").nodes())
        assert (new_hierarchy.get_typing("n1", "ag")["X"] == "X")
        assert (new_hierarchy.get_typing("ag", "mm")["X"] == "X")
        assert (new_hierarchy.get_typing("mm", "mmm")["X"] == "component")
        assert (new_hierarchy.get_typing("mm", "colors")["X"] == "X")

        assert ("Y" in new_hierarchy.get_graph("n1").nodes())
        assert ("Y" in new_hierarchy.get_graph("ag").nodes())
        assert ("Y" in new_hierarchy.get_graph("mm").nodes())
        assert ("Y" in new_hierarchy.get_graph("mm").nodes())
        assert (new_hierarchy.get_typing("n1", "ag")["Y"] == "Y")
        assert (new_hierarchy.get_typing("ag", "mm")["Y"] == "Y")
        assert (new_hierarchy.get_typing("mm", "mmm")["Y"] == "Y")
        assert (new_hierarchy.get_typing("mm", "colors")["Y"] == "red")
Ejemplo n.º 22
0
    def test_propagation_node_adds(self):
        """Test propagation down of additions."""
        p = nx.DiGraph()
        primitives.add_nodes_from(
            p, ["B"]
        )

        l = nx.DiGraph()
        primitives.add_nodes_from(
            l, ["B"]
        )

        r = nx.DiGraph()
        primitives.add_nodes_from(
            r, ["B", "B_res_1", "X", "Y"]
        )
        primitives.add_edge(r, "B_res_1", "B")

        rule = Rule(p, l, r)

        instance = {"B": "B"}

        rhs_typing = {
            "mm": {"B_res_1": "residue"},
            "mmm": {"X": "component"}, "colors": {"Y": "red"}
        }
        try:
            self.hierarchy.rewrite(
                "n1", rule, instance, lhs_typing=None, rhs_typing=rhs_typing)
            raise ValueError("Error was not caught!")
        except RewritingError:
            pass

        new_hierarchy, _ = self.hierarchy.rewrite(
            "n1", rule, instance,
            lhs_typing=None, rhs_typing=rhs_typing,
            strict=False, inplace=False)

        # test propagation of node adds
        assert("B_res_1" in new_hierarchy.graph["n1"].nodes())
        assert("B_res_1" in new_hierarchy.graph["ag"].nodes())
        assert(new_hierarchy.typing["n1"]["ag"]["B_res_1"] == "B_res_1")
        assert(new_hierarchy.typing["ag"]["mm"]["B_res_1"] == "residue")
        assert(("B_res_1", "B") in new_hierarchy.graph["n1"].edges())
        assert(("B_res_1", "B") in new_hierarchy.graph["ag"].edges())

        assert("X" in new_hierarchy.graph["n1"].nodes())
        assert("X" in new_hierarchy.graph["ag"].nodes())
        assert("X" in new_hierarchy.graph["mm"].nodes())
        assert("X" in new_hierarchy.graph["colors"].nodes())
        assert(new_hierarchy.typing["n1"]["ag"]["X"] == "X")
        assert(new_hierarchy.typing["ag"]["mm"]["X"] == "X")
        assert(new_hierarchy.typing["mm"]["mmm"]["X"] == "component")
        assert(new_hierarchy.typing["mm"]["colors"]["X"] == "X")

        assert("Y" in new_hierarchy.graph["n1"].nodes())
        assert("Y" in new_hierarchy.graph["ag"].nodes())
        assert("Y" in new_hierarchy.graph["mm"].nodes())
        assert("Y" in new_hierarchy.graph["mm"].nodes())
        assert(new_hierarchy.typing["n1"]["ag"]["Y"] == "Y")
        assert(new_hierarchy.typing["ag"]["mm"]["Y"] == "Y")
        assert(new_hierarchy.typing["mm"]["mmm"]["Y"] == "Y")
        assert(new_hierarchy.typing["mm"]["colors"]["Y"] == "red")
Ejemplo n.º 23
0
def pushout(a, b, c, a_b, a_c, inplace=False):
    """Find the pushour of the span b <- a -> c."""
    check_homomorphism(a, b, a_b)
    check_homomorphism(a, c, a_c)

    if inplace is True:
        d = b
    else:
        d = copy.deepcopy(b)

    b_d = id_of(b.nodes())
    c_d = dict()

    # Add/merge nodes
    for c_n in c.nodes():
        a_keys = keys_by_value(a_c, c_n)
        # Add nodes
        if len(a_keys) == 0:
            add_node(d, c_n, c.node[c_n])
            c_d[c_n] = c_n
        # Keep nodes
        elif len(a_keys) == 1:
            c_d[a_c[a_keys[0]]] = a_b[a_keys[0]]
        # Merge nodes
        else:
            nodes_to_merge = []
            for k in a_keys:
                nodes_to_merge.append(a_b[k])
            new_name = merge_nodes(d, nodes_to_merge)
            c_d[c_n] = new_name
            for node in nodes_to_merge:
                b_d[node] = new_name

    # Add edges
    for (n1, n2) in c.edges():
        if b.is_directed():
            if (c_d[n1], c_d[n2]) not in d.edges():
                add_edge(
                    d, c_d[n1], c_d[n2],
                    get_edge(c, n1, n2))
        else:
            if (c_d[n1], c_d[n2]) not in d.edges() and\
               (c_d[n2], c_d[n1]) not in d.edges():
                add_edge(
                    d, c_d[n1], c_d[n2],
                    get_edge(c, n1, n2)
                )

    # Add node attrs
    for c_n in c.nodes():
        a_keys = keys_by_value(a_c, c_n)
        # Add attributes to the nodes which stayed invariant
        if len(a_keys) == 1:
            attrs_to_add = dict_sub(
                c.node[c_n],
                a.node[a_keys[0]]
            )
            add_node_attrs(d, c_d[c_n], attrs_to_add)
        # Add attributes to the nodes which were merged
        elif len(a_keys) > 1:
            merged_attrs = {}
            for k in a_keys:
                merged_attrs = merge_attributes(
                    merged_attrs,
                    a.node[k]
                )
            attrs_to_add = dict_sub(c.node[c_n], merged_attrs)
            add_node_attrs(d, c_d[c_n], attrs_to_add)

    # Add edge attrs
    for (n1, n2) in c.edges():
        d_n1 = c_d[n1]
        d_n2 = c_d[n2]
        if d.is_directed():
            attrs_to_add = dict_sub(
                get_edge(c, n1, n2),
                get_edge(d, d_n1, d_n2)
            )
            add_edge_attrs(
                d, c_d[n1], c_d[n2],
                attrs_to_add
            )
        else:
            attrs_to_add = dict_sub(
                get_edge(c, n1, n2),
                get_edge(d, d_n1, d_n2)
            )
            add_edge_attrs(
                d, c_d[n1], c_d[n2],
                attrs_to_add
            )
    return (d, b_d, c_d)