def get_path_graph( self, graph: nx.Graph, source: str, target: str, enforce_directionality: bool = True, try_reverse: bool = True, ): # pylint: disable=too-many-arguments """Make a new graph with the shortest paths between two nodes""" new_graph = graph if enforce_directionality else self._make_undirected( graph) try: nodes = set( sum( map(list, nx.all_shortest_paths(new_graph, source, target)), [])) return graph.__class__(graph.subgraph(nodes)) except (nx.NetworkXError, nx.NetworkXException): warn(traceback.format_exc()) if try_reverse: return self.get_path_graph( graph=graph, source=target, target=source, enforce_directionality=enforce_directionality, try_reverse=False, ) return graph.__class__()
def read_geograph_with_coordinates_attributes(graph: nx.Graph, x_key='x', y_key='y', **attr) -> GeoGraph: """Parse a `networkx` graph which have node's coordinates as attribute. This method can be useful to parse an output graph of the `osmnx` package. Parameters ---------- graph : nx.Graph Given graph to parse. All nodes must have the ``x_key`` and ``y_key`` attributes. x_key : x-coordinates attribute to parse (Default value = 'x') y_key : y-coordinates attribute to parse (Default value = 'y') **attr : Optional geograph spatial keys. Returns ------- GeoGraph, GeoDiGraph, GeoMultiGraph, GeoMultiDiGraph The parsed geograph (shallow copy of the input graph). """ graph = graph.__class__(graph) x_coords = nx.get_node_attributes(graph, x_key) y_coords = nx.get_node_attributes(graph, y_key) nodes_geometry_key = attr.pop("nodes_geometry_key", settings.NODES_GEOMETRY_DEFAULT_KEY) edges_geometry_key = attr.pop("edges_geometry_key", settings.EDGES_GEOMETRY_DEFAULT_KEY) for n in graph.nodes: if n not in x_coords or n not in y_coords: raise ValueError("Unable to find coordinates for node : '%s'" % str(n)) point = Point([x_coords[n], y_coords[n]]) graph.nodes[n][nodes_geometry_key] = point graph.nodes_geometry_key = nodes_geometry_key graph.edges_geometry_key = edges_geometry_key return parse_graph_as_geograph(graph, **attr)
def get_spanning_graph( self, graph: nx.Graph, seeds: ty.Union[list, set, tuple], max_distance: int = 2, enforce_directionality: bool = True, ): if not enforce_directionality: graph = self._make_undirected(graph) seed_nodes = {id_: max_distance for id_ in seeds if id_ in graph.nodes} seed_elements = [ self.model.elements[id_] for id_ in seeds if id_ not in seed_nodes and id_ in self.model.elements ] seed_edges = [(element.source, element.target) for element in seed_elements if element._is_relationship] distances = { node: max_distance - 1 for node in set(sum(seed_edges, [])) if node in graph } distances.update(seed_nodes) max_iter = 100 while sum(distances.values()) > 0 and max_iter > 0: max_iter -= 1 for node_id, distance in tuple(distances.items()): if distance < 1: continue for neighbor in graph.neighbors(node_id): distances[neighbor] = distances.get(neighbor, distance - 1) distances[node_id] -= 1 nodes = tuple(distances) return graph.__class__(graph.subgraph(nodes))
def nx_copy(from_graph: nx.Graph, to_graph: Union[nx.Graph, Type[nx.Graph]], *, node_transform: Optional[Callable[[NodeGenerator], NodeGenerator]] = None, edge_transform: Optional[Callable[[EdgeGenerator], EdgeGenerator]] = None, global_transform: Optional[Callable[[NodeGenerator], NodeGenerator]] = None, global_attr_key: str = None, deepcopy: bool = False) -> nx.Graph: """Copies node, edges, node_data, and edge_data from graph `g1` to graph `g2`. If `g2` is None, a new graph of type `g1.__class__` is created. If `g2` is a class or subclass of `nx.Graph`, a new graph of that type is created. If `deepcopy` is set to True, the copy will perform a deepcopy of all node and edge data. If false, only shallow copies will be created. `node_transform` and `edge_transform` can be provided to perform a transform on the on `g1.nodes(data=True)` and `g1.edges(data=True)` iterators during copy. The node transform should return a `Generator[Tuple[T, dict], None, None]` while the edge transform should return a `Generator[Tuple[T, T, dict], None, None]`. These transforms may include skipping certain nodes or edges, transforming the node or edge data, or transforming the node keys themselves. Some example transforms include: .. code-block:: def node_to_str(gen): for n, ndata in gen: yield (str(n), ndata) def remove_self_loops(gen): for n1, n2, edata in gen: if n1 == n2: yield (n1, n2, edata) nx_copy(g1, None, node_transform=node_to_str, edge_transform=remove_self_loops, deepcopy=True) :param from_graph: graph to copy from :param to_graph: graph to copy to :param node_transform: optional transform applied to the `from_graph.nodes(data=True)` iterator :param edge_transform: optional transform applied to the `from_graph.edges(data=True)` iterator :param literal_transform: if True, node_transform will *not* be applied following the edge_transform. This may result in unintentional edges being created. :param deepcopy: :return: """ if to_graph is None: to_graph = from_graph.__class__() elif isinstance(to_graph, type) and issubclass(to_graph, nx.Graph): to_graph = to_graph() node_iter = from_graph.nodes(data=True) if node_transform: niter1, niter2 = itertools.tee(node_iter) node_iter = list(node_transform(niter1)) _node_mapping = {} for x1, x2 in zip(niter2, node_iter): _node_mapping[x1[0]] = x2[0] def map_node(x): return _node_mapping[x] else: def map_node(x): return x for n, ndata in node_iter: if deepcopy: n, ndata = do_deepcopy((n, ndata)) to_graph.add_node(n, **ndata) edge_iter = from_graph.edges(data=True) if edge_transform: edge_iter = edge_transform(edge_iter) for n1, n2, edata in edge_iter: if deepcopy: n1, n2, edata = do_deepcopy((n1, n2, edata)) to_graph.add_edge(map_node(n1), map_node(n2), **edata) if hasattr(from_graph, GraphWithGlobal.get_global.__name__) and hasattr( to_graph, GraphWithGlobal.get_global.__name__): giter = from_graph.globals(data=True, global_key=global_attr_key) if global_transform: giter = global_transform(giter) for gkey, gdata in giter: if deepcopy: gdata = do_deepcopy(gdata) else: gdata = dict(gdata) to_graph.set_global(gdata, gkey) return to_graph
def edge_weight_percolation(network: nx.Graph, order, connecting='strong') -> np.ndarray: '''Percolation with given weight on given network. Parameters --------------- network : `nx.Graph` (or `nx.DiGraph`) The network which will be percolated. order : `str` or `numpy.ndarray` The string name of the weight to be used. If type of `order` is numpy array, percolation will be performed with the same order. Return --------------- `np.ndarray` or `list` From each node as each cluster, the return is a merging ordered list of edges and its cluster. The form of each element is ('weight', startnode, endnode, cluster1, cluster2). If cluster is negative, it means there is no merging of clusters between such a edge. Examples ----------- ```!python >>> network = nx.Graph() >>> network.add_edge(1,2, 'value' = 0.2 ) >>> network.add_edge(2,3, 'value' = 0.5 ) >>> network.add_edge(3,4, 'value' = 0.3 ) >>> >>> edge_weight_percolation(network, 'value') ((0.2, 1, 2, 1, 2) (0.3, 3, 4, 3, 4) (0.5, 2, 3, 1, 3)) ``` First state : 1, 2, 3, 4 (each node is a single cluster). Edge (1,2) was merged. Current state : 1, 1, 3, 4 (cluster 1 contains node 1 and 2.). Edge (3,4) was merged. Current state : 1, 1, 3, 3 (cluster 3 contains node 3 and 4.). Finally, edge (2,3) was merged. Current state : 1, 1, 1, 1 (cluster 1 contains all nodes.). ''' strong = True if connecting == 'strong' else False if not isinstance(network, nx.DiGraph) and not isinstance( network, nx.Graph): raise ValueError( "'network' must be a instance of networkx `DiGraph` or `Graph`.") if strong and not isinstance(network, nx.DiGraph): strong = False raise Warning( "Undirected graph cannot be operated on strongly connected component. Weakly connected component will be calculated." ) edges = np.array([[wt, u, v] for (u, v, wt) in network.edges.data(order) ]) # container for sorting #order = np.argsort(edges[:,0]) # sort by weight class cluster: def __init__(self, data): self.head = None # the representative element of cluster self.data = data # the representative data of cluster def leader(self): current = self while current.head is not None: #print(current.data) current = current.head return current def merge(self, other): target, cl = (self, other) if self > other else (other, self) target.leader().head = cl.leader() def __gt__(self, other): return self.leader().data > other.leader( ).data # for deciding which is more representative #make empty Graph percolation = [] percolnet = network.__class__() clusters = { i: cluster(i) for i in range(len(network.nodes)) } # initial cluster state is that each node is a cluster itself. for o in order: weight, st_node, end_node = edges[ o] # weight, start node, end node, respectively. cl1, cl2 = clusters[st_node], clusters[ end_node] # cl1 and cl2 are cluster1 and cluster2, respectively. percolnet.add_edge( st_node, end_node) # Make percolation by adding new edge by edge. merged = False if strong: if isinstance(percolnet[end_node].get(st_node), dict): merged = True else: merged = True if merged and cl1.leader().data != cl2.leader().data: percolation.append([ weight, st_node, end_node, cl1.leader().data, cl2.leader().data ]) cl1.merge(cl2) else: percolation.append([weight, st_node, end_node, -1, -1]) return percolation