def shortest_path(nm_graph, src, dst): # TODO: move to utils # TODO: use networkx boundary nodes directly: does the same thing graph = unwrap_graph(nm_graph) src_id = unwrap_nodes(src) dst_id = unwrap_nodes(dst) #TODO: check path works for muli-edge graphs too path = nx.shortest_path(graph, src_id, dst_id) return wrap_nodes(nm_graph, path)
def shortest_path(nm_graph, src, dst): # TODO: move to utils # TODO: use networkx boundary nodes directly: does the same thing graph = unwrap_graph(nm_graph) src_id = unwrap_nodes(src) dst_id = unwrap_nodes(dst) # TODO: check path works for muli-edge graphs too path = nx.shortest_path(graph, src_id, dst_id) return wrap_nodes(nm_graph, path)
def neigh_average( NmGraph, node, attribute, attribute_graph=None, ): """ averages out attribute from neighbors in specified NmGraph attribute_graph is the graph to read the attribute from if property is numeric, then return mean else return most frequently occuring value """ graph = unwrap_graph(NmGraph) if attribute_graph: attribute_graph = unwrap_graph(attribute_graph) else: attribute_graph = graph # use input graph node = unwrap_nodes(node) values = [attribute_graph.node[n].get(attribute) for n in graph.neighbors(node)] # TODO: use neigh_attr try: values = [float(val) for val in values] return sum(values) / len(values) except ValueError: return most_frequent(values)
def aggregate_nodes(nm_graph, nodes, retain=None): """Combines connected into a single node""" if retain is None: retain = [] try: retain.lower() retain = [retain] # was a string, put into list except AttributeError: pass # already a list nodes = list(unwrap_nodes(nodes)) graph = unwrap_graph(nm_graph) subgraph = graph.subgraph(nodes) if not len(subgraph.edges()): # print "Nothing to aggregate for %s: no edges in subgraph" pass total_added_edges = [] if graph.is_directed(): component_nodes_list = nx.strongly_connected_components(subgraph) else: component_nodes_list = nx.connected_components(subgraph) for component_nodes in component_nodes_list: if len(component_nodes) > 1: component_nodes = [nm_graph.node(n) for n in component_nodes] # TODO: could choose most connected, or most central? # TODO: refactor so use nodes_to_remove nodes_to_remove = list(component_nodes) base = nodes_to_remove.pop() # choose a base device to retain log.debug("Retaining %s, removing %s", base, nodes_to_remove) external_edges = [] for node in nodes_to_remove: external_edges += [e for e in node.edges() if e.dst not in component_nodes] # all edges out of component log.debug("External edges %s", external_edges) edges_to_add = [] for edge in external_edges: dst = edge.dst data = dict((key, edge._data.get(key)) for key in retain) ports = edge.raw_interfaces dst_int_id = ports[dst.node_id] # TODO: bind to (and maybe add) port on the new switch? data["_ports"] = {dst.node_id: dst_int_id} append = (base.node_id, dst.node_id, data) edges_to_add.append(append) nm_graph.add_edges_from(edges_to_add) total_added_edges += edges_to_add nm_graph.remove_nodes_from(nodes_to_remove) return wrap_edges(nm_graph, total_added_edges)
def set_node_default(nm_graph, nbunch=None, **kwargs): """Sets all nodes in nbunch to value if key not already set Note: this won't apply to future nodes added >>> anm = autonetkit.topos.house() >>> g_phy = anm['phy'] >>> r1 = g_phy.node("r1") >>> r1.color = "blue" >>> [(n, n.color) for n in g_phy] [(r4, None), (r5, None), (r1, 'blue'), (r2, None), (r3, None)] >>> set_node_default(g_phy, color="red") >>> [(n, n.color) for n in g_phy] [(r4, 'red'), (r5, 'red'), (r1, 'blue'), (r2, 'red'), (r3, 'red')] Can also set for a specific bunch of nodes >>> nodes = ["r1", "r2", "r3"] >>> set_node_default(g_phy, nodes, role="core") >>> [(n, n.role) for n in g_phy] [(r4, None), (r5, None), (r1, 'core'), (r2, 'core'), (r3, 'core')] """ # work with the underlying NetworkX graph for efficiency graph = unwrap_graph(nm_graph) if nbunch is None: nbunch = graph.nodes() else: nbunch = unwrap_nodes(nbunch) for node in nbunch: for (key, val) in kwargs.items(): if key not in graph.node[node]: graph.node[node][key] = val
def explode_nodes(overlay_graph, nodes, retain = []): """Explodes all nodes in nodes TODO: explain better TODO: Add support for digraph - check if overlay_graph.is_directed() """ log.debug("Exploding nodes") try: retain.lower() retain = [retain] # was a string, put into list except AttributeError: pass # already a list graph = unwrap_graph(overlay_graph) nodes = unwrap_nodes(nodes) added_edges = [] #TODO: need to keep track of edge_ids here also? nodes = list(nodes) for node in nodes: log.debug("Exploding from %s" % node) neighbors = graph.neighbors(node) neigh_edge_pairs = ( (s,t) for s in neighbors for t in neighbors if s != t) edges_to_add = [] for (src, dst) in neigh_edge_pairs: src_to_node_data = dict( (key, graph[src][node][key]) for key in retain) node_to_dst_data = dict( (key, graph[node][dst][key]) for key in retain) src_to_node_data.update(node_to_dst_data) edges_to_add.append((src, dst, src_to_node_data)) graph.add_edges_from(edges_to_add) added_edges.append(edges_to_add) graph.remove_node(node) return wrap_edges(overlay_graph, added_edges)
def explode_nodes(overlay_graph, nodes, retain = []): """Explodes all nodes in nodes TODO: explain better """ try: retain.lower() retain = [retain] # was a string, put into list except AttributeError: pass # already a list graph = unwrap_graph(overlay_graph) nodes = unwrap_nodes(nodes) added_edges = [] #TODO: need to keep track of edge_ids here also? nodes = list(nodes) for node in nodes: neighbors = graph.neighbors(node) neigh_edge_pairs = ( (s,t) for s in neighbors for t in neighbors if s != t) edges_to_add = [] for (src, dst) in neigh_edge_pairs: data = dict( (key, graph[src][dst][key]) for key in retain) edges_to_add.append((src, dst, data)) graph.add_edges_from(edges_to_add) added_edges.append(edges_to_add) graph.remove_node(node) return wrap_edges(overlay_graph, added_edges)
def explode_nodes(overlay_graph, nodes, retain=[]): """Explodes all nodes in nodes TODO: explain better """ try: retain.lower() retain = [retain] # was a string, put into list except AttributeError: pass # already a list graph = unwrap_graph(overlay_graph) nodes = unwrap_nodes(nodes) added_edges = [] #TODO: need to keep track of edge_ids here also? nodes = list(nodes) for node in nodes: neighbors = graph.neighbors(node) neigh_edge_pairs = ((s, t) for s in neighbors for t in neighbors if s != t) edges_to_add = [] for (src, dst) in neigh_edge_pairs: data = dict((key, graph[src][dst][key]) for key in retain) edges_to_add.append((src, dst, data)) graph.add_edges_from(edges_to_add) added_edges.append(edges_to_add) graph.remove_node(node) return wrap_edges(overlay_graph, added_edges)
def connected_subgraphs(nm_graph, nodes=None): """ >>> anm = autonetkit.topos.house() >>> g_phy = anm['phy'] >>> connected_subgraphs(g_phy) [[r4, r5, r1, r2, r3]] >>> edges = [("r2", "r4"), ("r3", "r5")] >>> g_phy.remove_edges_from(edges) >>> connected_subgraphs(g_phy) [[r1, r2, r3], [r4, r5]] """ if nodes is None: nodes = nm_graph.nodes() else: nodes = list(unwrap_nodes(nodes)) graph = unwrap_graph(nm_graph) subgraph = graph.subgraph(nodes) if not len(subgraph.edges()): # print "Nothing to aggregate for %s: no edges in subgraph" pass if graph.is_directed(): component_nodes_list = nx.strongly_connected_components(subgraph) else: component_nodes_list = nx.connected_components(subgraph) wrapped = [] for component in component_nodes_list: wrapped.append(list(wrap_nodes(nm_graph, component))) return wrapped
def set_node_default(OverlayGraph, nbunch, **kwargs): """Sets all nodes in nbunch to value if key not already set""" graph = unwrap_graph(OverlayGraph) nbunch = unwrap_nodes(nbunch) for node in nbunch: for key, val in kwargs.items(): if key not in graph.node[node]: graph.node[node][key] = val
def set_node_default(overlay_graph, nbunch, **kwargs): """Sets all nodes in nbunch to value if key not already set""" graph = unwrap_graph(overlay_graph) nbunch = unwrap_nodes(nbunch) for node in nbunch: for key, val in kwargs.items(): if key not in graph.node[node]: graph.node[node][key] = val
def neigh_most_frequent(OverlayGraph, node, attribute, attribute_graph = None): """Used to explicitly force most frequent - useful if integers such as ASN which would otherwise return mean""" graph = unwrap_graph(OverlayGraph) if attribute_graph: attribute_graph = unwrap_graph(attribute_graph) else: attribute_graph = graph # use input graph node = unwrap_nodes(node) values = [attribute_graph.node[n].get(attribute) for n in graph.neighbors(node)] return most_frequent(values)
def neigh_most_frequent(overlay_graph, node, attribute, attribute_graph = None): """Used to explicitly force most frequent - useful if integers such as ASN which would otherwise return mean""" graph = unwrap_graph(overlay_graph) if attribute_graph: attribute_graph = unwrap_graph(attribute_graph) else: attribute_graph = graph # use input graph node = unwrap_nodes(node) values = [attribute_graph.node[n].get(attribute) for n in graph.neighbors(node)] return most_frequent(values)
def boundary_nodes(graph, nodes): # TODO: move to utils #TODO: use networkx boundary nodes directly: does the same thing """ returns nodes at boundary of G based on edge_boundary from networkx """ graph = unwrap_graph(graph) nodes = list(nodes) nbunch = list(unwrap_nodes(nodes)) # find boundary b_edges = nx.edge_boundary(graph, nbunch) # boundary edges internal_nodes = [s for (s, t) in b_edges] assert(all(n in nbunch for n in internal_nodes)) # check internal return wrap_nodes(graph, internal_nodes)
def set_node_default(NmGraph, nbunch = None, **kwargs): """Sets all nodes in nbunch to value if key not already set Note: this won't apply to future nodes added """ graph = unwrap_graph(NmGraph) if nbunch is None: nbunch = graph.nodes() else: nbunch = unwrap_nodes(nbunch) for node in nbunch: for key, val in kwargs.items(): if key not in graph.node[node]: graph.node[node][key] = val
def neigh_attr(NmGraph, node, attribute, attribute_graph = None): #TODO: tidy up parameters to take attribute_graph first, and then evaluate if attribute_graph set, if not then use attribute_graph as attribute #TODO: explain how NmGraph and attribute_graph work, eg for G_ip and G_phy graph = unwrap_graph(NmGraph) node = unwrap_nodes(node) if attribute_graph: attribute_graph = unwrap_graph(attribute_graph) else: attribute_graph = graph # use input graph #Only look at nodes which exist in attribute_graph neighs = (n for n in graph.neighbors(node)) valid_nodes = (n for n in neighs if n in attribute_graph) return (attribute_graph.node[node].get(attribute) for node in valid_nodes)
def neigh_most_frequent(NmGraph, node, attribute, attribute_graph = None, allow_none = False): """Used to explicitly force most frequent - useful if integers such as ASN which would otherwise return mean""" #TODO: rename to median? graph = unwrap_graph(NmGraph) if attribute_graph: attribute_graph = unwrap_graph(attribute_graph) else: attribute_graph = graph # use input graph node = unwrap_nodes(node) values = [attribute_graph.node[n].get(attribute) for n in graph.neighbors(node)] values = sorted(values) if not allow_none: values = [v for v in values if v is not None] return most_frequent(values)
def set_node_default(NmGraph, nbunch=None, **kwargs): """Sets all nodes in nbunch to value if key not already set Note: this won't apply to future nodes added """ graph = unwrap_graph(NmGraph) if nbunch is None: nbunch = graph.nodes() else: nbunch = unwrap_nodes(nbunch) for node in nbunch: for (key, val) in kwargs.items(): if key not in graph.node[node]: graph.node[node][key] = val
def neigh_attr(OverlayGraph, node, attribute, attribute_graph = None): #TODO: tidy up parameters to take attribute_graph first, and then evaluate if attribute_graph set, if not then use attribute_graph as attribute #TODO: explain how OverlayGraph and attribute_graph work, eg for G_ip and G_phy graph = unwrap_graph(OverlayGraph) node = unwrap_nodes(node) if attribute_graph: attribute_graph = unwrap_graph(attribute_graph) else: attribute_graph = graph # use input graph #Only look at nodes which exist in attribute_graph neighs = (n for n in graph.neighbors(node)) valid_nodes = (n for n in neighs if n in attribute_graph) return (attribute_graph.node[node].get(attribute) for node in valid_nodes)
def connected_subgraphs(NmGraph, nodes): nodes = list(unwrap_nodes(nodes)) graph = unwrap_graph(NmGraph) subgraph = graph.subgraph(nodes) if not len(subgraph.edges()): #print "Nothing to aggregate for %s: no edges in subgraph" pass total_added_edges = [] if graph.is_directed(): component_nodes_list = nx.strongly_connected_components(subgraph) else: component_nodes_list = nx.connected_components(subgraph) wrapped = [] for component in component_nodes_list: wrapped.append(list(wrap_nodes(NmGraph, component))) return wrapped
def aggregate_nodes(overlay_graph, nodes, retain=[]): """Combines connected into a single node""" try: retain.lower() retain = [retain] # was a string, put into list except AttributeError: pass # already a list nodes = list(unwrap_nodes(nodes)) graph = unwrap_graph(overlay_graph) subgraph = graph.subgraph(nodes) if not len(subgraph.edges()): #print "Nothing to aggregate for %s: no edges in subgraph" pass total_added_edges = [] for component_nodes in nx.connected_components(subgraph): if len(component_nodes) > 1: base = component_nodes.pop() # choose one base device to retain nodes_to_remove = set( component_nodes ) # remaining nodes, set for fast membership test external_edges = nx.edge_boundary(graph, component_nodes) edges_to_add = [] for src, dst in external_edges: # src is the internal node to remove if src == base or dst == base: continue # don't alter edges from base else: if src in nodes_to_remove: # edge from component to outside data = dict( (key, graph[src][dst][key]) for key in retain) edges_to_add.append((base, dst, data)) else: # edge from outside into component data = dict( (key, graph[dst][src][key]) for key in retain) edges_to_add.append((base, src, data)) graph.add_edges_from(edges_to_add) total_added_edges += edges_to_add graph.remove_nodes_from(nodes_to_remove) return wrap_edges(overlay_graph, total_added_edges)
def neigh_most_frequent(nm_graph, node, attribute, attribute_graph=None, allow_none=False): """Used to explicitly force most frequent - useful if integers such as ASN which would otherwise return mean""" # TODO: rename to median? graph = unwrap_graph(nm_graph) if attribute_graph: attribute_graph = unwrap_graph(attribute_graph) else: attribute_graph = graph # use input graph node = unwrap_nodes(node) values = [attribute_graph.node[n].get(attribute) for n in graph.neighbors(node)] values = sorted(values) if not allow_none: values = [v for v in values if v is not None] return most_frequent(values)
def explode_nodes(OverlayGraph, nodes, retain = []): """Explodes all nodes in nodes TODO: explain better TODO: Add support for digraph - check if OverlayGraph.is_directed() """ log.debug("Exploding nodes") try: retain.lower() retain = [retain] # was a string, put into list except AttributeError: pass # already a list graph = unwrap_graph(OverlayGraph) nodes = unwrap_nodes(nodes) added_edges = [] #TODO: need to keep track of edge_ids here also? nodes = list(nodes) #TODO: if graph is bidirectional, need to explode here too #TODO: how do we handle explode for multi graphs? for node in nodes: log.debug("Exploding from %s" % node) neighbors = graph.neighbors(node) neigh_edge_pairs = ( (s,t) for s in neighbors for t in neighbors if s != t) neigh_edge_pairs = list(neigh_edge_pairs) edges_to_add = [] for (src, dst) in neigh_edge_pairs: src_to_node_data = dict( (key, graph[src][node][key]) for key in retain) node_to_dst_data = dict( (key, graph[node][dst][key]) for key in retain) # copy interfaces src_int_id = graph[src][node]["_interfaces"][src] dst_int_id = graph[node][dst]["_interfaces"][dst] src_to_node_data["_interfaces"] = {src: src_int_id, dst: dst_int_id} src_to_node_data.update(node_to_dst_data) #TODO: handle interfaces for explode edges_to_add.append((src, dst, src_to_node_data)) graph.add_edges_from(edges_to_add) added_edges += edges_to_add graph.remove_node(node) return wrap_edges(OverlayGraph, added_edges)
def connected_subgraphs(NmGraph, nodes): nodes = list(unwrap_nodes(nodes)) graph = unwrap_graph(NmGraph) subgraph = graph.subgraph(nodes) if not len(subgraph.edges()): # print "Nothing to aggregate for %s: no edges in subgraph" pass if graph.is_directed(): component_nodes_list = \ nx.strongly_connected_components(subgraph) else: component_nodes_list = nx.connected_components(subgraph) wrapped = [] for component in component_nodes_list: wrapped.append(list(wrap_nodes(NmGraph, component))) return wrapped
def aggregate_nodes(overlay_graph, nodes, retain = []): """Combines connected into a single node""" try: retain.lower() retain = [retain] # was a string, put into list except AttributeError: pass # already a list nodes = list(unwrap_nodes(nodes)) graph = unwrap_graph(overlay_graph) subgraph = graph.subgraph(nodes) if not len(subgraph.edges()): #print "Nothing to aggregate for %s: no edges in subgraph" pass total_added_edges = [] for component_nodes in nx.connected_components(subgraph): if len(component_nodes) > 1: base = component_nodes.pop() # choose one base device to retain nodes_to_remove = set(component_nodes) # remaining nodes, set for fast membership test external_edges = nx.edge_boundary(graph, component_nodes) edges_to_add = [] for src, dst in external_edges: # src is the internal node to remove if src == base or dst == base: continue # don't alter edges from base else: if src in nodes_to_remove: # edge from component to outside data = dict( (key, graph[src][dst][key]) for key in retain) edges_to_add.append((base, dst, data)) else: # edge from outside into component data = dict( (key, graph[dst][src][key]) for key in retain) edges_to_add.append((base, src, data)) graph.add_edges_from(edges_to_add) total_added_edges += edges_to_add graph.remove_nodes_from(nodes_to_remove) return wrap_edges(overlay_graph, total_added_edges)
def _init_interfaces(self, nbunch = None): #TODO: make this more efficient by taking in the newly added node ids as a parameter if not nbunch: nbunch = [n for n in self._graph.nodes()] try: nbunch = list(unwrap_nodes(nbunch)) except AttributeError: pass # don't need to unwrap phy_graph = self._anm._overlays["phy"] for node in nbunch: #TODO: tidy up this hardcoded logic try: phy_interfaces = phy_graph.node[node]["_interfaces"] data = dict( (key, {}) for key in phy_interfaces) self._graph.node[node]['_interfaces'] = data except KeyError: # no counterpart in physical graph, initialise log.debug("Initialise interfaces for %s in %s" % (node, self._overlay_id)) self._graph.node[node]['_interfaces'] = {0: {'description': 'loopback'}}
def connected_subgraphs(nm_graph, nodes = None): """ >>> anm = autonetkit.topos.house() >>> g_phy = anm['phy'] >>> connected_subgraphs(g_phy) [[r4, r5, r1, r2, r3]] >>> edges = [("r2", "r4"), ("r3", "r5")] >>> g_phy.remove_edges_from(edges) >>> connected_subgraphs(g_phy) [[r1, r2, r3], [r4, r5]] """ if nodes is None: nodes = nm_graph.nodes() else: nodes = list(unwrap_nodes(nodes)) graph = unwrap_graph(nm_graph) subgraph = graph.subgraph(nodes) if not len(subgraph.edges()): # print "Nothing to aggregate for %s: no edges in subgraph" pass if graph.is_directed(): component_nodes_list = \ nx.strongly_connected_components(subgraph) else: component_nodes_list = nx.connected_components(subgraph) wrapped = [] for component in component_nodes_list: wrapped.append(list(wrap_nodes(nm_graph, component))) return wrapped
def aggregate_nodes(NmGraph, nodes, retain = []): """Combines connected into a single node""" try: retain.lower() retain = [retain] # was a string, put into list except AttributeError: pass # already a list nodes = list(unwrap_nodes(nodes)) graph = unwrap_graph(NmGraph) subgraph = graph.subgraph(nodes) if not len(subgraph.edges()): #print "Nothing to aggregate for %s: no edges in subgraph" pass total_added_edges = [] if graph.is_directed(): component_nodes_list = nx.strongly_connected_components(subgraph) else: component_nodes_list = nx.connected_components(subgraph) for component_nodes in component_nodes_list: if len(component_nodes) > 1: base = component_nodes.pop() # choose one base device to retain nodes_to_remove = set(component_nodes) # remaining nodes, set for fast membership test external_edges = nx.edge_boundary(graph, component_nodes) edges_to_add = [] for src, dst in external_edges: # src is the internal node to remove if src == base or dst == base: continue # don't alter edges from base else: if src in nodes_to_remove: # edge from component to outside interfaces = graph[src][dst]["_interfaces"] dst_int_id = interfaces[dst] data = dict( (key, graph[src][dst][key]) for key in retain) data['_interfaces'] = {dst: dst_int_id} edges_to_add.append((base, dst, data)) if graph.is_directed(): # other direction #TODO: check which data should be copied dst_data = dict( (key, graph[src][dst][key]) for key in retain) dst_data['_interfaces'] = {dst: dst_int_id} edges_to_add.append((dst, base, dst_data)) else: # edge from outside into component interfaces = graph[dst][src]["_interfaces"] src_int_id = interfaces[src] data = dict( (key, graph[dst][src][key]) for key in retain) data['_interfaces'] = {src: src_int_id} edges_to_add.append((base, src, data)) if graph.is_directed(): # other direction #TODO: check which data should be copied dst_data = dict( (key, graph[src][dst][key]) for key in retain) dst_data['_interfaces'] = {src: src_int_id} edges_to_add.append((src, base, dst_data)) graph.add_edges_from(edges_to_add) total_added_edges += edges_to_add graph.remove_nodes_from(nodes_to_remove) return wrap_edges(NmGraph, total_added_edges)
def aggregate_nodes(nm_graph, nodes, retain=None): """Combines connected into a single node""" if retain is None: retain = [] try: retain.lower() retain = [retain] # was a string, put into list except AttributeError: pass # already a list nodes = list(unwrap_nodes(nodes)) graph = unwrap_graph(nm_graph) subgraph = graph.subgraph(nodes) if not len(subgraph.edges()): # print "Nothing to aggregate for %s: no edges in subgraph" pass total_added_edges = [] if graph.is_directed(): component_nodes_list = \ nx.strongly_connected_components(subgraph) else: component_nodes_list = nx.connected_components(subgraph) for component_nodes in component_nodes_list: if len(component_nodes) > 1: component_nodes = [nm_graph.node(n) for n in component_nodes] # TODO: could choose most connected, or most central? # TODO: refactor so use nodes_to_remove nodes_to_remove = list(component_nodes) base = nodes_to_remove.pop() # choose a base device to retain log.debug('Retaining %s, removing %s', base, nodes_to_remove) external_edges = [] for node in nodes_to_remove: external_edges += [e for e in node.edges() if e.dst not in component_nodes] # all edges out of component log.debug('External edges %s', external_edges) edges_to_add = [] for edge in external_edges: dst = edge.dst data = dict((key, edge._data.get(key)) for key in retain) ports = edge.raw_interfaces dst_int_id = ports[dst.node_id] # TODO: bind to (and maybe add) port on the new switch? data['_ports'] = {dst.node_id: dst_int_id} append = (base.node_id, dst.node_id, data) edges_to_add.append(append) nm_graph.add_edges_from(edges_to_add) total_added_edges += edges_to_add nm_graph.remove_nodes_from(nodes_to_remove) return wrap_edges(nm_graph, total_added_edges)
def aggregate_nodes(OverlayGraph, nodes, retain = []): """Combines connected into a single node""" try: retain.lower() retain = [retain] # was a string, put into list except AttributeError: pass # already a list nodes = list(unwrap_nodes(nodes)) graph = unwrap_graph(OverlayGraph) subgraph = graph.subgraph(nodes) if not len(subgraph.edges()): #print "Nothing to aggregate for %s: no edges in subgraph" pass total_added_edges = [] if graph.is_directed(): component_nodes_list = nx.strongly_connected_components(subgraph) else: component_nodes_list = nx.connected_components(subgraph) for component_nodes in component_nodes_list: if len(component_nodes) > 1: base = component_nodes.pop() # choose one base device to retain nodes_to_remove = set(component_nodes) # remaining nodes, set for fast membership test external_edges = nx.edge_boundary(graph, component_nodes) edges_to_add = [] for src, dst in external_edges: # src is the internal node to remove if src == base or dst == base: continue # don't alter edges from base else: if src in nodes_to_remove: # edge from component to outside interfaces = graph[src][dst]["_interfaces"] dst_int_id = interfaces[dst] data = dict( (key, graph[src][dst][key]) for key in retain) data['_interfaces'] = {dst: dst_int_id} edges_to_add.append((base, dst, data)) if graph.is_directed(): # other direction #TODO: check which data should be copied dst_data = dict( (key, graph[src][dst][key]) for key in retain) dst_data['_interfaces'] = {dst: dst_int_id} edges_to_add.append((dst, base, dst_data)) else: # edge from outside into component interfaces = graph[dst][src]["_interfaces"] src_int_id = interfaces[src] data = dict( (key, graph[dst][src][key]) for key in retain) data['_interfaces'] = {src: src_int_id} edges_to_add.append((base, src, data)) if graph.is_directed(): # other direction #TODO: check which data should be copied dst_data = dict( (key, graph[src][dst][key]) for key in retain) dst_data['_interfaces'] = {src: src_int_id} edges_to_add.append((src, base, dst_data)) graph.add_edges_from(edges_to_add) total_added_edges += edges_to_add graph.remove_nodes_from(nodes_to_remove) return wrap_edges(OverlayGraph, total_added_edges)