Exemplo n.º 1
0
def rem(g: nx.MultiGraph) -> None:
    node_attributes = nx.get_node_attributes(G, 'elderly')
    nodes = list(g.nodes)
    for node in nodes:
        neigh = list(g.neighbors(node))

        if len(neigh) == 2 and (g.degree(node) % 2) == 0:
            try:
                set1 = {
                    edge['label']
                    for edge in g.get_edge_data(node, neigh[0]).values()
                }
            except:
                print(g.get_edge_data(node, neigh[0]))
            set2 = {
                edge['label']
                for edge in g.get_edge_data(node, neigh[1]).values()
            }
            if set1 == set2:
                #prec=node_attributes[neigh[0]]+0.001
                succ = node_attributes[neigh[1]] + 0.001
                node_attr = node_attributes[node] + 0.001
                # if (abs((node_attr-prec))<1000) and (abs((node_attr-succ))<1000):
                if (abs((node_attr - succ)) < 1000):
                    g.remove_node(node)
                    for edge_label in set1:
                        g.add_edge(neigh[0], neigh[1], label=edge_label)
Exemplo n.º 2
0
def auto_neb(m1,
             m2,
             graph: MultiGraph,
             model: models.ModelWrapper,
             config: config.AutoNEBConfig,
             callback: callable = None):
    # Continue existing cycles or start from scratch
    if m2 in graph[m1]:
        existing_edges = graph[m1][m2]
        previous_cycle_idx = max(existing_edges)
        connection_data = graph[m1][m2][previous_cycle_idx]
        start_cycle_idx = previous_cycle_idx + 1
    else:
        connection_data = {
            "path_coords":
            torch.cat([graph.nodes[m]["coords"].view(1, -1)
                       for m in (m1, m2)]),
            "target_distances":
            torch.ones(1)
        }
        start_cycle_idx = 1
    assert start_cycle_idx <= config.cycle_count

    # Run NEB and add to graph
    for cycle_idx in helper.pbar(
            range(start_cycle_idx, config.cycle_count + 1), "AutoNEB"):
        cycle_config = config.neb_configs[cycle_idx - 1]
        connection_data = neb(connection_data, model, cycle_config)
        graph.add_edge(m1,
                       m2,
                       key=cycle_idx,
                       **helper.move_to(connection_data, "cpu"))
        if callback is not None:
            callback()
Exemplo n.º 3
0
def connect_routes(lab, lst, g: nx.MultiGraph):
    links = list(lst.linkid)
    if len(links) == 0:
        return
    for i in range(len(links)-1):
        conn = g.edges.get((links[i], links[i+1], 0))
        if conn is None or conn['label'] != lab.iloc[0]:
            g.add_edge(links[i], links[i+1], label=lab.iloc[0])
Exemplo n.º 4
0
def graph_minus(g: MultiGraph, w: set) -> MultiGraph:
	gx = MultiGraph()
	for (n1, n2) in g.edges():
		if n1 not in w and n2 not in w:
			gx.add_edge(n1, n2)
	for n in g.nodes():
		if n not in w:
			gx.add_node(n)
	return gx
Exemplo n.º 5
0
def add_edge_journal_in_graph(graph: nwx.MultiGraph, journal_edge):

    for item in journal_edge:
        if item['edge_list']:
            for edge in item['edge_list']:
                graph.add_edge(edge[0],
                               edge[1],
                               journal=item['journal'],
                               date=item['date'])

    return graph
Exemplo n.º 6
0
def add_edge_clinical_in_graph(graph: nwx.MultiGraph, clinical_edge):

    for item in clinical_edge:
        if item['edge_list']:
            for edge in item['edge_list']:
                graph.add_edge(edge[0],
                               edge[1],
                               id_clinical=item['id_clinical'],
                               title_clinical=item['title_clinical'],
                               date=item['date'])

    return graph
Exemplo n.º 7
0
def add_edge_pubmed_in_graph(graph: nwx.MultiGraph, pubmed_edge):

    for item in pubmed_edge:
        if item['edge_list']:
            for edge in item['edge_list']:
                graph.add_edge(edge[0],
                               edge[1],
                               id_pubmed=item['id_pubmed'],
                               title_pubmed=item['title_pubmed'],
                               date=item['date'])

    return graph
Exemplo n.º 8
0
def add_weak_gene(gene: EdgeGene, graph: nx.MultiGraph) -> bool:
    key = gene.A1 + gene.A2
    edge = (gene.P1, gene.P2, key)
    if any(starmap(
            lambda p, a: p in graph and not any(x == a for x in get_node_space(graph, p)),
            [(gene.P1, gene.A1), (gene.P2, gene.A2)])):
        return False
    if graph.has_edge(gene.P1, gene.P2):
        keys = graph[gene.P1][gene.P2]
        if len(keys) > 1:
            return False
        graph.remove_edge(gene.P1, gene.P2, next(iter(keys)))
    graph.add_edge(*edge, gene=gene)
    return True
Exemplo n.º 9
0
    def test_sequence(self):
        graph = MultiGraph()
        graph.add_node(1, value=1)  # Global minimum
        graph.add_node(2, value=2)
        graph.add_node(3, value=3)
        graph.add_node(4, value=4)

        def weight(id_pair):
            return sum(node ** 2 for node in id_pair)

        unfinished_edge = (1, 3)
        config = LandscapeExplorationConfig("value", "weight", [], None, AutoNEBConfig([None, None]))

        # Disconnect suggest
        correct_order = [
            (1, 2), (1, 3), (1, 4),
        ]
        config.suggest_methods = [disconnected]
        while True:
            pair = suggest_pair(graph, config)
            if pair[0] is None:
                break
            self.assertGreater(len(correct_order), 0, "disconnected_suggest gives more pairs than necessary")
            assert pair == correct_order.pop(0)
            graph.add_edge(*pair, key=1, weight=weight(pair))
            if pair != unfinished_edge:
                # Skip the second edge for this pair, to test unfinished suggest
                graph.add_edge(*pair, key=2, weight=weight(pair))
        self.assertEqual(len(correct_order), 0, "disconnected_suggest missing suggestions!")

        # Unfinished suggest
        correct_order = [
            unfinished_edge
        ]
        config.suggest_methods = [unfinished]
        while True:
            pair = suggest_pair(graph, config)
            if pair[0] is None:
                break
            self.assertGreater(len(correct_order), 0, "unfinished_suggest gives more pairs than necessary")
            assert pair == correct_order.pop(0)
            graph.add_edge(*pair, key=2, weight=weight(pair))
        self.assertEqual(len(correct_order), 0, "unfinished_suggest missing suggestions!")

        # Core: MST suggest
        correct_order = [
            (2, 4), (3, 4),  # Replace (1, 4)
            (2, 3),  # Replace (1, 3)
        ]
        config.suggest_methods = [mst]
        while True:
            pair = suggest_pair(graph, config)
            if pair[0] is None:
                break
            self.assertGreater(len(correct_order), 0, "mst_suggest gives more pairs than necessary")
            assert pair == correct_order.pop(0)
            graph.add_edge(*pair, key=1, weight=weight(pair))
            graph.add_edge(*pair, key=2, weight=weight(pair))
        self.assertEqual(len(correct_order), 0, "mst_suggest missing suggestions!")
Exemplo n.º 10
0
def reduction3(g: MultiGraph, w: set, h: MultiGraph, k: int) -> (int, int, bool):
	for v in h.nodes():
		if g.degree(v) == 2:
			# If v has a neighbour in H, short-curcuit it.
			if len(h[v]) >= 1:
				# Delete v and make its neighbors adjacent.
				[n1, n2] = g.neighbors(v)
				g.remove_node(v)
				g.add_edge(n1, n2)
				# Update H accordingly.
				h.remove_nodes_from([v])
				if n1 not in w and n2 not in w:
					h.add_edge(n1, n2)
				return (k, None, True)
	return (k, None, False)
Exemplo n.º 11
0
def test_run_walks():
    mg = MultiGraph()
    mg.add_edge('a', 'b')
    mg.add_edge('b', 'c')
    dw = DeepWalk(mg, 10, 100)
    dw.get_walks(workers=1)
    # Number of neighbors for all nodes together is 4, times niter: 400
    assert len(dw.walks) == 400, len(dw.walks)
    assert len([w for w in dw.walks if w[0] == 'a']) == 100
    assert len([w for w in dw.walks if w[0] == 'b']) == 200

    dw.get_walks(workers=2)
    # Number of neighbors for all nodes together is 4, times niter: 400
    assert len(dw.walks) == 400, len(dw.walks)
    assert len([w for w in dw.walks if w[0] == 'a']) == 100
    assert len([w for w in dw.walks if w[0] == 'b']) == 200
Exemplo n.º 12
0
 def add_node_edge(graph: nx.MultiGraph, from_node, to_nodes, c):
     graph.add_node(from_node)
     for i in to_nodes.split(';'):
         graph.add_node(i)
         graph.add_edge(
             from_node,
             i,
             viz={'color': {
                 'r': c[0],
                 'g': c[1],
                 'b': c[2],
                 'a': 1.0
             }})
         graph.add_edge()
         # graph.add_edge(frm, i)
     return graph
Exemplo n.º 13
0
def main():
    g = MultiGraph()

    analyzer = TraceAnalyzer()
    analyzer.parse("../pintool.log")

    # Get all the chunks used by the application
    chunks = analyzer.getChunks()
    for chunk in chunks:
        g.add_node("%x-%x" % (chunk.chunk_addr, chunk.timestamp))

    writes = analyzer.getMemoryWrites()
    for write in writes:
        b = "C:%x-%x" % (write.chunk_addr, write.timestamp)
        a = "W:%x-%x" % (write.write_addr, write.content)

        g.add_edge(a, b)

    draw_shell(g)
    plt.show()
Exemplo n.º 14
0
    def connect_matching_pair_edges(self, Gu: nx.MultiGraph,
                                    ls, rs, obj_node_id='obj',
                                    connect_obj_rel_edges=False) -> nx.Graph:
        """
        Ground truth (_gt_)generator function for a combined graph. Used for training S_0

        :param Gu: A (unconnected) composed graph of Gs, Gt. No relational links between head nodes,
        thus, the number_connected_components(Gs|Gt) = number of object nodes in each graph.


        :param Gs: The source Graph, i.e., the text graph representation
        :param Gt: The target Graph, i.e., the grounding (image features) graph representation
        :param obj_node_id: the identifier determining a obj (or head) node
        :return: matching_pairs: List of matching pair tuples between Gs, Gt
        """
        matching_pairs, unmatched_pairs = self.get_matching_pairs_in_bipartite_graph(Gu, ls, rs, obj_node_id)
        NDV = Gu.nodes(data=True)
        # Connect the matching pairs if not connected #
        for pair in matching_pairs:
            s_node, t_node = pair
            if not Gu.has_edge(s_node, t_node):
                Gu.add_edge(s_node, t_node, '<gt>')
            # Connect Attr Nodes #
            is_head_node = lambda x: obj_node_id in x
            Ns = nx.neighbors(Gu, s_node)
            Nt = list(nx.neighbors(Gu, t_node))
            for ns in Ns:
                if is_head_node(ns):
                    continue
                # Check label equality only, 'val' equality already verified
                ns_label = NDV[ns]['label']
                #logger.debug(f'Source attr node label = {ns_label}')
                # TODO: potential issue here, Nt should always have a matching attr node
                for nt in filter(lambda x: NDV[x]['label'] == ns_label, Nt):
                    if not Gu.has_edge(ns, nt):
                        Gu.add_edge(ns, nt, key='<gt>')
        if connect_obj_rel_edges:
            # TODO: Add a obj relation edge among all Gs, Gt obj nodes. Actually
            # should be done earlier in the parse cycle.
            pass
        return Gu
 def reduction3(self, g: MultiGraph, w: set, h: MultiGraph, k: int) -> (int, int, bool):
     """
     If there is a node v ∈ V(H) of degree 2 in G such
     that at least one neighbor of v in G is from V (H), then delete this node
     and make its neighbors adjacent (even if they were adjacent before; the graph
     could become a multigraph now).
     """
     for v in h.nodes():
         if g.degree(v) == 2:
             # If v has a neighbour in H, short-curcuit it.
             if len(h[v]) >= 1:
                 # Delete v and make its neighbors adjacent.
                 [n1, n2] = g.neighbors(v)
                 g.remove_node(v)
                 g.add_edge(n1, n2)
                 # Update H accordingly.
                 h.remove_nodes_from([v])
                 if n1 not in w and n2 not in w:
                     h.add_edge(n1, n2)
                 return k, None, True
     return k, None, False
Exemplo n.º 16
0
    def reduction4(self, g: MultiGraph, k: int) -> (int, List[int], bool):
        """
         If there is a vertex v of degree 2, delete v and connect its two neighbors by a new edge.
        """
        for v in g.nodes():
            if g.degree(v) == 2:
                # Delete v and make its neighbors adjacent.
                ne = g.neighbors(v)

                # We must check whether v has 2 neighbors, or just one but connected to v by multiple edges
                if len(ne) == 2:
                    [n1, n2] = ne
                else:
                    [n1] = ne
                    n2 = n1
                g.remove_node(v)

                # Only add the edge if there are currently less than 2 edges between these two nodes
                es = g[n1].get(n2, {})
                if len(es) < 2:
                    g.add_edge(n1, n2)
                return k, None, True
        return k, None, False
Exemplo n.º 17
0
    def reduction4(self, g: MultiGraph, k: int) -> (int, List[int], bool):
        """
         If there is a vertex v of degree 2, delete v and connect its two neighbors by a new edge.
        """
        for v in g.nodes():
            if g.degree(v) == 2:
                # Delete v and make its neighbors adjacent.
                ne = g.neighbors(v)

                # We must check whether v has 2 neighbors, or just one but connected to v by multiple edges
                if len(ne) == 2:
                    [n1, n2] = ne
                else:
                    [n1] = ne
                    n2 = n1
                g.remove_node(v)

                # Only add the edge if there are currently less than 2 edges between these two nodes
                es = g[n1].get(n2, {})
                if len(es) < 2:
                    g.add_edge(n1, n2)
                return k, None, True
        return k, None, False
Exemplo n.º 18
0
def add_strong_gene(gene: EdgeGene, graph: nx.MultiGraph, coupling_threshold: float) -> bool:
    key = gene.A1 + gene.A2
    edge = (gene.P1, gene.P2, key)
    if not graph.has_edge(gene.P1, gene.P2):
        graph.add_edge(*edge, gene=gene)
        return True
    keys = graph[gene.P1][gene.P2]
    if key in keys:
        return False
    if len(keys) > 1:
        graph.add_edge(*edge, gene=gene)
        return True
    if graph[gene.P1][gene.P2][next(iter(keys))]['gene'].C < coupling_threshold:
        graph.remove_edge(gene.P1, gene.P2, next(iter(keys)))
    graph.add_edge(*edge, gene=gene)
    return True
Exemplo n.º 19
0
class ShuttlingGraph(list):
    def __init__(self, shuttlingEdges=list() ):
        super(ShuttlingGraph, self).__init__(shuttlingEdges) 
        self.currentPosition = None
        self.currentPositionName = None
        self.nodeLookup = dict()
        self.currentPositionObservable = Observable()
        self.graphChangedObservable = Observable()
        self.initGraph()
        self._hasChanged = True
        
    def initGraph(self):
        self.shuttlingGraph = MultiGraph()
        for edge in self:
            self.shuttlingGraph.add_node(edge.startName)
            self.shuttlingGraph.add_node(edge.stopName)
            self.shuttlingGraph.add_edge(edge.startName, edge.stopName, key=hash(edge), edge=edge,
                                         weight=abs(edge.stopLine-edge.startLine))
            self.nodeLookup[edge.startLine] = edge.startName
            self.nodeLookup[edge.stopLine] = edge.stopName

    def rgenerateNodeLookup(self):
        self.nodeLookup.clear()
        for edge in self:
            self.nodeLookup[edge.startLine] = edge.startName
            self.nodeLookup[edge.stopLine] = edge.stopName

    @property
    def hasChanged(self):
        return self._hasChanged
    
    @hasChanged.setter
    def hasChanged(self, value):
        self._hasChanged = value
            
    def position(self, line):
        return self.nodeLookup.get(line)
    
    def setPosition(self, line):
        if self.currentPosition!=line:
            self.currentPosition = line
            self.currentPositionName = self.position(line)
            self.currentPositionObservable.fire( line=line, text=firstNotNone(self.currentPositionName, "") )

    def getMatchingPosition(self,graph):
        """Try to match node name/position to the current settings in the provided ShuttlingGraph."""
        if not graph:
            return self.currentPosition # no change
        # Matching node name. Need to set the corresponding position
        for edge in self:
            if edge.startName == graph.currentPositionName:
                return edge.startLine
            if edge.stopName == graph.currentPositionName:
                return edge.stopLine
        #if graph.currentPosition:
        #    return graph.currentPosition #just use the graph's position
        return self.currentPosition

    def addEdge(self, edge):
        self._hasChanged = True
        self.append(edge)
        self.shuttlingGraph.add_edge(edge.startName, edge.stopName, key=hash(edge), edge=edge, weight=abs(edge.stopLine-edge.startLine))
        self.nodeLookup[edge.startLine] = edge.startName
        self.nodeLookup[edge.stopLine] = edge.stopName
        self.graphChangedObservable.firebare()
        self.setPosition(self.currentPosition)
            
    def isValidEdge(self, edge):
        return ((edge.startLine not in self.nodeLookup or self.nodeLookup[edge.startLine] == edge.startName)
                and (edge.stopLine not in self.nodeLookup or self.nodeLookup[edge.stopLine] == edge.stopName))
        
    def getValidEdge(self):
        index = 0
        while self.shuttlingGraph.has_node("Start_{0}".format(index)):
            index += 1
        startName = "Start_{0}".format(index)
        index = 0
        while self.shuttlingGraph.has_node("Stop_{0}".format(index)):
            index += 1
        stopName = "Stop_{0}".format(index)
        index = 0
        startLine = (max( self.nodeLookup.keys() )+1) if self.nodeLookup else 1
        stopLine = startLine + 1
        return ShuttleEdge(startName, stopName, startLine, stopLine, 0, 0, 0, 0)
    
    def removeEdge(self, edgeno):
        self._hasChanged = True
        edge = self.pop(edgeno)
        self.shuttlingGraph.remove_edge(edge.startName, edge.stopName, hash(edge))
        if self.shuttlingGraph.degree(edge.startName) == 0:
            self.shuttlingGraph.remove_node(edge.startName)
        if self.shuttlingGraph.degree(edge.stopName) == 0:
            self.shuttlingGraph.remove_node(edge.stopName)
        self.graphChangedObservable.firebare()
        self.rgenerateNodeLookup()
        self.setPosition(self.currentPosition)
    
    def setStartName(self, edgeno, startName):
        self._hasChanged = True
        startName = str(startName)
        edge = self[edgeno]
        if edge.startName != startName:
            self.shuttlingGraph.remove_edge(edge.startName, edge.stopName, key=hash(edge))
            if self.shuttlingGraph.degree(edge.startName) == 0:
                self.shuttlingGraph.remove_node(edge.startName)
            edge.startName = startName
            self.shuttlingGraph.add_edge(edge.startName, edge.stopName, key=hash(edge), edge=edge,
                                         weight=abs(edge.stopLine-edge.startLine) )
            self.graphChangedObservable.firebare()
            self.setPosition(self.currentPosition)
            self.rgenerateNodeLookup()
        return True
    
    def setStopName(self, edgeno, stopName):
        self._hasChanged = True
        stopName = str(stopName)
        edge = self[edgeno]
        if edge.stopName != stopName:
            self.shuttlingGraph.remove_edge(edge.startName, edge.stopName, key=hash(edge))
            if self.shuttlingGraph.degree(edge.stopName) == 0:
                self.shuttlingGraph.remove_node(edge.stopName)
            edge.stopName = stopName
            self.shuttlingGraph.add_edge(edge.startName, edge.stopName, key=hash(edge), edge=edge,
                                         weight=abs(edge.stopLine-edge.startLine) )
            self.graphChangedObservable.firebare()
            self.rgenerateNodeLookup()
            self.setPosition(self.currentPosition)
        return True
    
    def setStartLine(self, edgeno, startLine):
        self._hasChanged = True
        edge = self[edgeno]
        if startLine != edge.startLine and (startLine not in self.nodeLookup or self.nodeLookup[startLine] == edge.startName):
            self.nodeLookup.pop(edge.startLine)
            edge.startLine = startLine
            self.shuttlingGraph.edge[edge.startName][edge.stopName][hash(edge)]['weight'] = abs(edge.stopLine-edge.startLine)
            self.rgenerateNodeLookup()
            self.graphChangedObservable.firebare()
            self.setPosition(self.currentPosition)
            return True    
        return False  
    
    def setStopLine(self, edgeno, stopLine):
        self._hasChanged = True
        edge = self[edgeno]
        if stopLine != edge.stopLine and (stopLine not in self.nodeLookup or self.nodeLookup[stopLine] == edge.stopName):
            self.nodeLookup.pop(edge.stopLine)
            edge.stopLine = stopLine
            self.shuttlingGraph.edge[edge.startName][edge.stopName][hash(edge)]['weight'] = abs(edge.stopLine-edge.startLine)
            self.rgenerateNodeLookup()
            self.graphChangedObservable.firebare()
            self.setPosition(self.currentPosition)
            return True  
        return False
    
    def setIdleCount(self, edgeno, idleCount):
        self._hasChanged = True
        self[edgeno].idleCount = idleCount
        return True      

    def setSteps(self, edgeno, steps):
        self._hasChanged = True
        self[edgeno].steps = steps
        return True      
    
    def shuttlePath(self, fromName, toName ):
        fromName = firstNotNone(fromName, self.currentPositionName)
        fromName = fromName if fromName else self.position(float(self.currentPosition))
        if fromName not in self.shuttlingGraph:
            raise ShuttlingGraphException("Shuttling failed, origin '{0}' is not a valid shuttling node".format(fromName))
        if toName not in self.shuttlingGraph:
            raise ShuttlingGraphException("Shuttling failed, target '{0}' is not a valid shuttling node".format(toName))
        sp = shortest_path(self.shuttlingGraph, fromName, toName)
        path = list()
        for a, b in pairs_iter(sp):
            edge = sorted(self.shuttlingGraph.edge[a][b].values(), key=itemgetter('weight'))[0]['edge']
            path.append((a, b, edge, self.index(edge)))
        return path
    
    def nodes(self):
        return self.shuttlingGraph.nodes()
    
    def toXmlElement(self, root):
        mydict = dict( ( (key, str(getattr(self, key))) for key in ('currentPosition', 'currentPositionName') if getattr(self, key) is not None  ) ) 
        myElement = ElementTree.SubElement(root, "ShuttlingGraph", attrib=mydict )
        for edge in self:
            edge.toXmlElement( myElement )
        return myElement
    
    def setStartType(self, edgeno, Type):
        self._hasChanged = True
        self[edgeno].startType = str(Type)
        return True
    
    def setStopType(self, edgeno, Type):
        self._hasChanged = True
        self[edgeno].stopType = str(Type)
        return True
    
    def setStartLength(self, edgeno, length):
        edge = self[edgeno]
        if length!=edge.startLength:
            if length+edge.stopLength<edge.sampleCount:
                self._hasChanged = True
                edge.startLength = int(length)
            else:
                return False
        return True
    
    def setStopLength(self, edgeno, length):
        edge = self[edgeno]
        if length!=edge.stopLength:
            if edge.startLength+length<edge.sampleCount:
                self._hasChanged = True
                edge.stopLength = int(length)
            else:
                return False
        return True
    
    @staticmethod
    def fromXmlElement( element ):
        edgeElementList = element.findall("ShuttleEdge")
        edgeList = [ ShuttleEdge.fromXmlElement(e) for e in edgeElementList ]
        return ShuttlingGraph(edgeList)
Exemplo n.º 20
0
class MultiEdgeUndirectedTopology(BaseTopology):
    """Class representing a topology with mutltiple undirected edges.

    :param iter nodes: Nodes in the graph
    :param iter edges: Edges in the graph
    """

    # Names used to hold the node longitude and latitude
    NODE_LONG = 'long'
    NODE_LAT = 'lat'

    # Name used to hold the edge type
    EDGE_TYPE = 'type'

    def __init__(self, nodes=None, edges=None):

        self.graph = MultiGraph()

        # Nodes
        nodes = nodes or {}
        for node_id, (lon, lat) in nodes.items():
            self.add_node(node_id, lon, lat)

        # Edges
        edges = edges or {}
        for (n1, n2), (edge_type, dict_attrs) in edges.items():
            self.add_edge(n1, n2, edge_type, **dict_attrs)

    @property
    def num_of_nodes(self):
        """The number of nodes in the topology."""
        return self.graph.number_of_nodes()

    @property
    def num_of_edges(self):
        """The number of nodes in the topology."""
        return self.graph.number_of_edges()

    @property
    def nodes(self):
        """The nodes in the graph."""
        return self.graph.nodes

    def add_node(self, node_id, longitude, latitude, **node_attrs):
        """Add a node defined by its label.

        :param int node_id: Node id.
        :param dict node_attrs: Node attributes.

        :raises:
            :py:class:`RuntimeError`: if a node with the same ID already exists.
        """

        # If there is no node with the same ID already, raise exception
        if self.graph.has_node(node_id):
            raise RuntimeError("Node %s already exists." % node_id)

        # Adding longitude and latitude to attributes
        node_attrs[self.NODE_LONG] = longitude
        node_attrs[self.NODE_LAT] = latitude
        # Create node
        self.graph.add_node(node_id, **node_attrs)

    def get_node(self, node_id):
        """Return node from its ID."""

        try:
            return self.graph.nodes[node_id]
        except KeyError:
            raise KeyError("Node %s does not exist." % node_id)

    def distance(self, n1, n2):
        """
        Calculate the distance between two nodes on the Earth.

        :param int n1: ID of the first node.
        :param int n2: ID of the second node.
        :returns: Distance in cm
        :rtype: float
        """
        n1 = self.get_node(n1)
        n2 = self.get_node(n2)

        return utility_distance(
            n1[self.NODE_LONG],
            n1[self.NODE_LAT],
            n2[self.NODE_LONG],
            n2[self.NODE_LAT]
        )

    def add_edge(self, node1, node2, edge_type, **edge_attrs):
        """Add an edge between two nodes in the graph.

        :param obj node1: Label of the first node.
        :param obj node2: Label of the second node.
        :param obj edge_type: Edge type.
        :param dict edge_attrs: Additional attributes of the edge.

        :raises:
            :py:class:`RuntimeError`: if an edge with the same type already exists.
        """

        _ = self.get_node(node1)
        _ = self.get_node(node2)

        # If an edge of the given type between the two nodes already exits: error
        # We take into account that the graph in undirected
        with suppress(KeyError):
            types_existing_edges = [e[self.EDGE_TYPE] == edge_type
                                    for e in self.get_edges(node1, node2).values()]

            if any(types_existing_edges):
                raise RuntimeError(
                    'Already existing edge %s between nodes %s and %s' % (edge_type, node1, node2)
                )

        # Adding type to attributes
        edge_attrs[self.EDGE_TYPE] = edge_type
        # Create edge
        self.graph.add_edge(node1, node2, **edge_attrs)

    def get_edges(self, node1, node2, edge_types=None):
        """Return all the edges between two nodes.

        :param obj node1: Label of the first node.
        :param obj node2: Label of the second node.
        :param iterable edge_types: If provided, return only the edges for these types.
        :returns: A dictionary where the keys are the edge number
            and the values a dictionary of the edges attributes.
        :rtype: dict
        :raises: :py:class:`ValueError`: if there is no edge between the nodes.
        """
        try:
            edges = dict(self.graph[node1][node2])
        except KeyError:
            # undirected graph
            try:
                edges = dict(self.graph[node2][node1])
            except KeyError:
                raise KeyError('No edge between nodes %s and %s' % (node1, node2))

        # Filter if necessary
        if edge_types:
            edges = {e: v for e, v in edges.items()
                     if edges[e][self.EDGE_TYPE] in edge_types}

            # If dict is empty, there is no edge
            if not edges:
                raise KeyError('No edge between nodes %s and %s' % (node1, node2))

        return edges

    def _get_min_weight(self, weight, allowed_types, best_types, node1, node2, _d):
        """
        Find the edge with the minimum weight between two nodes.

        :param str weight: Weight name.
        :param allowed_types: Edge type(s) allowed to build path.
        :type allowed_types: dict-like object with TransportType as keys.
        :param dict best_types: dictionnary containing the best type for each pair of nodes.

        :note: other arguments are the ones needed by the NetworkX API.
        """

        # The algorithm uses the full (symmetric) matrix
        # instead of considering the upper triangular only.
        # This means that for two nodes u ≠ v, the weight is extracted twice:
        # once for (u,v), once for (v,u). We do not need to do the work for both.
        if (node2, node1) in best_types:
            return None

        # Get all edges between the two nodes
        edges = self.get_edges(node1, node2)

        # Get all the weights but keep only those that are allowed
        # This will throw a KeyError is some edges do not have the weight specified
        try:
            weights = [(attrs[self.EDGE_TYPE], attrs[weight]) for attrs in edges.values()
                       if attrs[self.EDGE_TYPE] in allowed_types]
        except KeyError:
            raise KeyError(
                "No edge with attribute %s found between nodes %s and %s" % (
                    weight, node1, node2
                ))

        # If there is no valid edge
        if not weights:
            return None

        # If the weights need to be weighted. Skip if they are all None.
        if list(set(allowed_types.values()))[0]:
            weights = [(t, w * allowed_types[t]) for t, w in weights]

        # Otherwise, save type and return minimum weight
        min_type, min_weight = min(weights, key=lambda t: t[1])
        best_types[(node1, node2)] = min_type
        return min_weight

    def get_shortest_path(self, node1, node2, criterion, allowed_types, edge_data=None):
        """Find the shortest path between two nodes using specified edges.

        :param obj node1: Label of the first node.
        :param obj node2: Label of the second node.
        :param str criterion: Criterion used to find shortest path. Must be an edge attribute.
        :param dict allowed_types: Edge type(s) allowed to build path.
            The keys are the edge types, the values (if any) indicate the preference for each type.
        :param iter(str) edge_data: Edge attributes for which data along the path is requested.
        :returns: A 3-tuple containing in this order

            * the score of the optimal path
            * the list of nodes along the path
            * a dict containing the edge type and values for the attributes specified in edge_data
              (data are stored in numpy arrays)

        :rtype: tuple
        :raises:
            :py:class:`.ValueError`: if nodes dont exists or no path has been found between them.

        :note: If `allowed_types` is a dict-like object, the weight of an edge will be weighted
            by the value of the given edge type.
        """

        # Check that the nodes exist
        for node in node1, node2:
            if not self.graph.has_node(node):
                raise ValueError("Node %s does not exist." % node)

        # Using partial function to pass the edge types
        # The weight_func will be called for each edge on the graph
        # Even those which will not been part of the optimial path
        # So best_types cannot be a simple list to which we append values
        # Instead it will be a dict where the key is a pair of nodes
        # We will extract the relevant values and attributes afterwards
        best_types = {}
        weight_func = partial(self._get_min_weight, criterion, allowed_types, best_types)

        # Calculate path
        try:
            score, path = single_source_dijkstra(
                self.graph, node1, node2, weight=weight_func)
        except NetworkXNoPath:
            raise RuntimeError("No path found with type %s between %s and %s." %
                               (allowed_types, node1, node2))

        # Build the dict containing the attributes data
        # Because we do not want to assume anything about the data,
        # we build an intermediate list first.
        data = {}
        # Always extract the edge types
        edge_types = []
        for u, v in zip(path, path[1:]):
            # The key could be (u,v) or (v,u)
            try:
                edge_types.append(best_types[(u, v)])
            except KeyError:
                edge_types.append(best_types[(v, u)])
        data[self.EDGE_TYPE] = np.array(edge_types)

        # Additional data if needed
        edge_data = edge_data or []
        for attr in edge_data:
            try:
                temp_list = [
                    list(self.get_edges(u, v, [t]).values())[0][attr]
                    for u, v, t in zip(
                        path, path[1:], data[self.EDGE_TYPE])]
                data[attr] = np.array(temp_list)
            except KeyError:
                raise KeyError("Some edges do not have the attribute '%s'." % attr)

        return (score, path, data)

    def add_energy_based_edges(
            self, edge_types, num_edges_per_step, max_iterations,
            degree_energy_factor, distance_energy_factor, rng,
            attribute_name='distance'):
        """
        This algorithm creates edges based on an energy sampling mechanism.
        At each iteration, the total energy (degree energy + potential energy)
        is calculated between all nodes and transformed into a Boltzmann distribution.
        Edges are more likely to be created between nodes which are:

        * close
        * already connected to other nodes

        A fixed number of random connections are then created based on this distribution,
        masking previous connections and self-edges.
        The process is repeated until either the maximum number of iterations is reached,
        or all nodes have been connected at least once to another node.

        :param iter edge_types: Types of the edges to add.
        :param int num_edges_per_step: Number of edges to create at each step
        :param int max_iterations: Maximum number of iterations.
            If None, the algorithm stops when each node has been connected at least once.
        :param float degree_energy_factor: Multiplier applied to the degree enery component
            during the search for new edges (higher means more prominent).
        :param float distance_energy_factor: Multiplier applied to the distance energy component
            during the search for new edges (higher means more prominent).
        :param rng: Random number generator.
        :type rng: :py:class:`RandomGenerator<city_graph.utils.RandomGenerator>`

        :note: The algorithm does take into account already exisiting edges
            to compute the probabilities.
        """

        print("[Topology] Starting energy sampling algorithm to build edges.")

        # Step 1: prepare
        # Compute all distances: graph is undirected so we want all combinations with replacements
        # so we should have in total C(2,n) + n distances
        # TODO: We compute things twice for now, so things might be improved
        array_shape = (self.num_of_nodes,) * 2
        distances = np.zeros(shape=array_shape)
        array_size = distances.size
        for (i, n1), (j, n2) in product(enumerate(self.nodes), repeat=2):
            distances[i, j] = self.distance(n1, n2)

        # Normalize by maximum distance - save scaling factor as it will be needed
        max_distance = distances.max()
        distances /= max_distance

        # Adjacency matrix (not taking into account previously existing edges)
        adjacency_matrix = np.zeros(shape=array_shape)

        # Step 2: sampling
        step = 0
        termination = TerminationReason.NO_TERMINATION
        while termination == TerminationReason.NO_TERMINATION:

            # This loop could be put in a separate function.
            # We will see if it is necessary

            # Sampling step
            # a) compute the degree energies
            degree_energy = -1 / 2 * (
                adjacency_matrix.sum(0, keepdims=True) +
                adjacency_matrix.sum(1, keepdims=True)
            )
            if degree_energy_factor:
                degree_energy = degree_energy_factor * degree_energy

            # b) compute the distance energies
            distance_energy = distances
            if distance_energy_factor:
                distance_energy = distance_energy_factor * distances

            # c) compute edge sampling probability distribution
            total_energy = degree_energy + distance_energy

            # mask existing edges
            total_energy[adjacency_matrix > EPS_PRECISION] = np.inf
            # mask self-edges
            np.fill_diagonal(total_energy, np.inf)

            # compute Boltzmann distribution and normalize it
            bolz_dist = np.exp(-total_energy)
            bolz_dist /= bolz_dist.sum()

            # d) sample step
            # sample indices to activate
            sample_indices = rng.choice(
                np.arange(array_size), size=(num_edges_per_step,),
                p=bolz_dist.ravel(), replace=True)

            # unravel indices - get a tuple
            unravel_indices = np.unravel_index(sample_indices, array_shape)

            # add sample edges to the adjacency matrix
            # and make matric symmetric
            adjacency_matrix[unravel_indices] = 1
            adjacency_matrix[unravel_indices[::-1]] = 1

            # Increase counter
            step += 1

            # Check termination
            # Maximum step number reached
            if max_iterations:
                if step >= max_iterations:
                    termination = TerminationReason.MAX_ITER
            # All nodes are connected
            if adjacency_matrix.sum(0).min() > EPS_PRECISION:
                termination = TerminationReason.ALL_NODES_CONNECTED

        # Inform the user why the algorithm has stopped
        print("[Topology] Sampling finished after", step, "steps. Reason:", termination)

        # Build edges from the adjacency matrix
        # Rescale the distances
        distances *= max_distance

        # Get pair of nodes to connect
        # Here we use indices because we will need them later on for the distances
        pairs = [(i, j) for i, j in zip(*adjacency_matrix.nonzero())]

        # Create edges
        old_num_edges = self.num_of_edges
        node_ids = list(self.nodes)
        for i, j in pairs:
            for edge_type in edge_types:

                # TODO: exception might be raised here because we now check
                # That there is no outgoing/incoming edges.
                # Should we fix when we use an actual triangular matrix
                with suppress(RuntimeError):
                    self.add_edge(node_ids[i], node_ids[j], edge_type,
                                  **{attribute_name: distances[i, j]})

        # Inform that edges have been built
        print("[Topology] %i edges have been created" % (self.num_of_edges - old_num_edges))

    def add_edges_between_centroids(self, edge_types, num_centroids, rng, attribute_name='distance'):
        """
        This algorithm creates edges between central nodes. These central nodes
        are determined by a clustering algorithm (here the Fluid Communities algorithm).

        :param iter edge_types: Types of the edges to add.
        :param int num_centroids: Number of centroids.
        :param rng: Random number generator.
        :type rng: :py:class:`RandomGenerator<city_graph.utils.RandomGenerator>`
        """

        print("[Topology] Starting building edges between %s central nodes." % num_centroids)

        # Calculate clusters
        # TODO: I think it would make sense to instead use e.g. a k-means for two reasons:
        #  * this algo assumes that the clusters have the same density, which is not necessarily
        # application to cities (some areas are more crowded)
        #  *  the implementation needs the graph to be fully connected to begin with. It seems
        # to be a limitation
        clusters = (list(c) for c in asyn_fluidc(
            self.graph, k=num_centroids, seed=rng.rand_int()))

        # Extract centroids: we take the node with the highest degree
        centroids = [c[int(np.argmax([self.graph.degree[n] for n in c]))] for c in clusters]

        # Create temporary graph for the centroids
        tmp_graph = Graph()
        tmp_graph.add_nodes_from(centroids)
        # We need the combinations because the graph is undirected and we dont want self-edges

        for n1, n2 in combinations(centroids, 2):
            tmp_graph.add_edge(n1, n2, **{attribute_name: self.distance(n1, n2)})

        # Calculate subgraph with the minimum sum of edge weights
        # TODO: to investigate why we do this here...
        subgraph = minimum_spanning_tree(tmp_graph, weight=attribute_name)

        # Build edges
        # Here we can reuse the previously calculated distances
        old_num_edges = self.num_of_edges
        for (n1, n2) in subgraph.edges:
            for edge_type in edge_types:

                # TODO: exception might be raised here because we now check
                # That there is no outgoing/incoming edges.
                # Should we fix when we use an actual triangular matrix
                with suppress(RuntimeError):
                    self.add_edge(n1, n2, edge_type, **subgraph[n1][n2])

        # Inform that edges have been built
        print("[Topology] %i edges have been created" % (self.num_of_edges - old_num_edges))
Exemplo n.º 21
0
        return '10Gb'
    elif 'OC3' in label:
        return '155Mb'
    elif 'OC12' in label:
        return '622Mb'
    elif 'OC48' in label:
        return '2.4Gb'
    else:
        return '1Gb'


for n1, n2, ed in graph.edges_iter(data=True):
    # print n1, n2, ed
    n1d = graph.node[n1]
    n2d = graph.node[n2]
    # print n1d,n2d
    dist = distance(n1d['Longitude'], n1d['Latitude'], n2d['Longitude'],
                    n2d['Latitude'])
    # print dist
    loc1 = '{}, {}'.format(n1d['label'], n1d['Country'])
    loc2 = '{}, {}'.format(n2d['label'], n2d['Country'])
    span = '{} to {}'.format(loc1, loc2)
    newgraph.add_node(n1, autoack='False', location=loc1)
    newgraph.add_node(n2, autoack='False', location=loc2)
    cap = get_cap(ed['LinkLabel'])
    newgraph.add_edge(n1, n2, weight=1, capacity=cap, delay=dist, span=span)

add_measurement(newgraph)
add_traffic(newgraph)
write_dot(newgraph, outname)
Exemplo n.º 22
0
f.close()

fh = open("trash","w")
call(["./blossom4","-3", "-x","data.dat", "-w", "output.dat"], stdout=fh)
fh.close()

result = genfromtxt('output.dat', delimiter=' ', usecols = (0, 1))
result = result[1:]
#MINMAXMATCHING END

H = MultiGraph()
H.add_nodes_from(T.nodes())
H.add_edges_from(T.edges())
for (key, value) in a.items():
	if N < N:
		H.add_edge(N, N)

t = Set()
for (i, j) in eulerian_circuit(H):
	if i in t:
		continue
	t.add(i)
	print i

#the result is t
#t_e = zip(t[:-1], t[1:])
#distance = 0
#for (i, j) in t_e:
#	distance += Y[i, j]
	
Exemplo n.º 23
0
def get_cap(label):
    if 'OC192/STM64' in label:
        return '10Gb'
    elif 'OC3' in label:
        return '155Mb'
    elif 'OC12' in label:
        return '622Mb'
    elif 'OC48' in label:
        return '2.4Gb'
    else:
        return '1Gb'

for n1,n2,ed in graph.edges_iter(data=True):
    # print n1, n2, ed
    n1d = graph.node[n1]
    n2d = graph.node[n2]
    # print n1d,n2d
    dist = distance(n1d['Longitude'],n1d['Latitude'],n2d['Longitude'],n2d['Latitude'])
    # print dist
    loc1 = '{}, {}'.format(n1d['label'], n1d['Country'])
    loc2 = '{}, {}'.format(n2d['label'], n2d['Country'])
    span = '{} to {}'.format(loc1, loc2)
    newgraph.add_node(n1, autoack='False', location=loc1)
    newgraph.add_node(n2, autoack='False', location=loc2)
    cap = get_cap(ed['LinkLabel'])
    newgraph.add_edge(n1, n2, weight=1, capacity=cap, delay=dist, span=span)

add_measurement(newgraph)
add_traffic(newgraph)
write_dot(newgraph, outname)
# with or without fee is hereby granted, provided that the above copyright notice
# and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
# OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
# DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
# SOFTWARE.

from networkx import MultiGraph

# create a new graph
my_graph = MultiGraph()

# create four nodes
my_graph.add_node(1)
my_graph.add_node(2)
my_graph.add_node(3)
my_graph.add_node(4)

# create seven edges
my_graph.add_edge(1, 2)
my_graph.add_edge(1, 2)
my_graph.add_edge(1, 3)
my_graph.add_edge(1, 4)
my_graph.add_edge(1, 4)
my_graph.add_edge(2, 3)
my_graph.add_edge(3, 4)
Exemplo n.º 25
0
with open('excel_files', 'r') as ef:

    for each_excel_file in ef:
        each_excel_file = each_excel_file.strip()

        excel_file_object = pd.read_excel(
            r'C:\\Network Programmability and Automation\network_diagram\\' +
            each_excel_file)
        #print(excel_file_object)
        #filter specific columns from excel sheet
        remote_device_id = pd.DataFrame(excel_file_object,
                                        columns=['remote device-Id'])
        Local_device_id = pd.DataFrame(excel_file_object,
                                       columns=['Local device-Id'])

        #print(list(remote_device_id))
        #print('\n\n')
        #print(remote_device_id)
        #print('\n\n\n')
        #print(Local_device_id)
        #print('test')
        #print()

        local_device_name = Local_device_id.values[0][0]
        remote_device_list = remote_device_id.values

        for i in remote_device_list:
            graph.add_edge(local_device_name, i[0])
        draw(graph, with_labels=True)
        show()
Exemplo n.º 26
0
	f.write("\n")
f.close()

fh = open("trash.dat","w")
call(["./blossom4","-3", "-x","data.dat", "-w", "output.dat"], stdout=fh)
fh.close()

result = genfromtxt('output.dat', delimiter=' ', usecols = (0, 1))
result = result[1:]
#MINMAXMATCHING END

H = MultiGraph()
H.add_nodes_from(T.nodes())
H.add_edges_from(T.edges())
for (key, value) in result:
	H.add_edge(N[int(key)], N[int(value)])

f = open("data.dat", "wb")
t = Set()
for (i, j) in eulerian_circuit(H):
	if i in t:
		continue
	t.add(i)
	f.write(str(i) + " ")
f.close()

fh = open("trash.dat", "w")
call(["./exe", 'input.dat'], stdout = fh)
fh.close()

#the result is t
Exemplo n.º 27
0
def graph_from_dataframe(
    dataframe,
    threshold_by_percent_unique=0.1,
    threshold_by_count_unique=None,
    node_id_columns=[],
    node_property_columns=[],
    edge_property_columns=[],
    node_type_key="type",
    edge_type_key="type",
    collapse_edges=True,
    edge_agg_key="weight",
):
    """
    Build an undirected graph from a pandas dataframe.

    This function attempts to infer which cells should become nodes
    based on either:

        a. what percentage of the column are unique values (defaults to 10%)
        b. an explicit count of unique values (i.e. any column with 7 unique
           values or less)
        c. an explicit list of column keys (i.e.
           ['employee_id', 'location_code'])

    Column headers are preserved as node and edge 'types'. By default, this is
    stored using the key 'type' which is used by some graph import processes
    but can be reconfigured.

    This function uses a MultiGraph structure during the build phase so that it
    is possible to make multiple connections between nodes. By default, at the
    end of the build phase, the MultiGraph is converted to a Graph and the
    count of edges between each node-pair is written as a 'weight' property.

    :param pandas.DataFrame dataframe: A pandas dataframe containing the data
        to be converted into a graph.
    :param float threshold_by_percent_unique: A percent value used to determine
        whether a column should be used to generate nodes based on its
        cardinality (i.e. in a dataframe with 100 rows, treat any column with
        10 or less unique values as a node)
    :param int threshold_by_count_unique: A numeric value used to determine
        whether a column should be used to generate nodes based on its
        cardinality (i.e. if 7 is supplied, treat any column with 7 or less
        unique values as a node) - supplying a value will take priority over
        percent_unique
    :param list node_id_columns: A list of column headers to use for generating
        nodes. Suppyling any value will take precedence over
        threshold_by_percent_unique or threshold_by_count_unique. Note: this
        can cause the size of the graph to expand significantly since every
        unique value in a column will become a node.
    :param list node_property_columns: A list of column headers to use for
        generating properties of nodes. These can include the same column
        headers used for the node id.
    :param list edge_property_columns: A list of column headers to use for
        generating properties of edges.
    :param str node_type_key: A string that sets the key will be used to
        preserve the column name as node property (this is useful for importing
        networkx graphs to databases that distinguish between node 'types' or
        for visually encoding those types in plots).
    :param str edge_type_key: A string that sets the key will be used to keep
        track of edge relationships an 'types' (this is useful for importing
        networkx graphs to databases that distinguish between edge'types' or
        for visually encoding those types in plots). Edge type values are
        automatically set to <node_a_id>_<node_b_id>.
    :param bool collapse_edges: Graphs are instantiated as a 'MultiGraph'
        (allow multiple edges between nodes) and then collapsed into a 'Graph'
        which only has a single edge between any two nodes. Information is
        preserved by aggregating the count of those edges as a 'weight' value.
        Set this value to False to return the MultiGraph. Note: this can cause
        the size of the graph to expand significantly since each row can
        potentially have n! edges where n is the number of columns in the
        dataframe.
    :param str edge_agg_key: A string that sets the key the edge count will be
        assigned to when edges are aggregated.
    :returns: A networkx Graph (or MultiGraph if collapse_edges is set to
        False).
    """

    assert isinstance(
        dataframe,
        pd.DataFrame), "{} is not a pandas DataFrame".format(dataframe)

    M = MultiGraph()

    # if explicit specification of node_id_columns is provided, use those
    if len(node_id_columns) > 0:
        node_columns = node_id_columns
    else:
        # otherwise, compute with thresholds based on the dataframe
        if threshold_by_count_unique:
            node_columns = sorted([
                col for col in dataframe.columns
                if dataframe[col].nunique() <= threshold_by_count_unique
            ])
        else:
            node_columns = sorted([
                col for col in dataframe.columns
                if dataframe[col].nunique() / dataframe.shape[0] <=
                threshold_by_percent_unique  # NOQA to preserve meaningful variable names
            ])

    # use the unique values for each node column as node types
    for node_type in node_columns:
        M.add_nodes_from([(node, {
            node_type_key: node_type
        }) for node in dataframe[node_type].unique()])

    # iterate over the rows and generate an edge for each pair of node columns
    for i, row in dataframe.iterrows():
        # assemble the edge properties as a dictionary
        edge_properties = {k: row[k] for k in edge_property_columns}

        # iterate over the node_ids in each node_column of the dataframe row
        node_buffer = []
        for node_type in node_columns:
            node_id = row[node_type]

            # get a reference to the node and assign any specified properties
            node = M.nodes[node_id]
            for k in node_property_columns:
                # if values are not identical, append with a pipe delimiter
                if k not in node:
                    node[k] = row[k]
                elif isinstance(node[k], str) and str(row[k]) not in node[k]:
                    node[k] += "|{}".format(str(row[k]))
                elif str(row[k]) not in str(node[k]):
                    node[k] = str(node[k]) + "|{}".format(str(row[k]))

            # build edges using precomputed edge properties
            for other_node_id, other_node_type in node_buffer:
                # sort node_type so undirected edges all share the same type
                ordered_name = "_".join(sorted([node_type, other_node_type]))
                edge_properties[edge_type_key] = ordered_name
                M.add_edge(node_id, other_node_id, **edge_properties)

            # store the node from this column in the buffer for future edges
            node_buffer.append((node_id, node_type))

    if collapse_edges:
        # convert the MultiGraph to a Graph
        G = Graph(M)
        k = edge_agg_key
        # preserve the edge count as a sum of the weight values
        for u, v, data in M.edges(data=True):
            w = data[k] if k in data else 1.0
            edge = G[u][v]
            edge[k] = (w + edge[k]) if k in edge else w
        return G

    return M