Spaces:
Paused
Paused
| """View of Graphs as SubGraph, Reverse, Directed, Undirected. | |
| In some algorithms it is convenient to temporarily morph | |
| a graph to exclude some nodes or edges. It should be better | |
| to do that via a view than to remove and then re-add. | |
| In other algorithms it is convenient to temporarily morph | |
| a graph to reverse directed edges, or treat a directed graph | |
| as undirected, etc. This module provides those graph views. | |
| The resulting views are essentially read-only graphs that | |
| report data from the original graph object. We provide an | |
| attribute G._graph which points to the underlying graph object. | |
| Note: Since graphviews look like graphs, one can end up with | |
| view-of-view-of-view chains. Be careful with chains because | |
| they become very slow with about 15 nested views. | |
| For the common simple case of node induced subgraphs created | |
| from the graph class, we short-cut the chain by returning a | |
| subgraph of the original graph directly rather than a subgraph | |
| of a subgraph. We are careful not to disrupt any edge filter in | |
| the middle subgraph. In general, determining how to short-cut | |
| the chain is tricky and much harder with restricted_views than | |
| with induced subgraphs. | |
| Often it is easiest to use .copy() to avoid chains. | |
| """ | |
| import networkx as nx | |
| from networkx.classes.coreviews import ( | |
| FilterAdjacency, | |
| FilterAtlas, | |
| FilterMultiAdjacency, | |
| UnionAdjacency, | |
| UnionMultiAdjacency, | |
| ) | |
| from networkx.classes.filters import no_filter | |
| from networkx.exception import NetworkXError | |
| from networkx.utils import deprecate_positional_args, not_implemented_for | |
| __all__ = ["generic_graph_view", "subgraph_view", "reverse_view"] | |
| def generic_graph_view(G, create_using=None): | |
| """Returns a read-only view of `G`. | |
| The graph `G` and its attributes are not copied but viewed through the new graph object | |
| of the same class as `G` (or of the class specified in `create_using`). | |
| Parameters | |
| ---------- | |
| G : graph | |
| A directed/undirected graph/multigraph. | |
| create_using : NetworkX graph constructor, optional (default=None) | |
| Graph type to create. If graph instance, then cleared before populated. | |
| If `None`, then the appropriate Graph type is inferred from `G`. | |
| Returns | |
| ------- | |
| newG : graph | |
| A view of the input graph `G` and its attributes as viewed through | |
| the `create_using` class. | |
| Raises | |
| ------ | |
| NetworkXError | |
| If `G` is a multigraph (or multidigraph) but `create_using` is not, or vice versa. | |
| Notes | |
| ----- | |
| The returned graph view is read-only (cannot modify the graph). | |
| Yet the view reflects any changes in `G`. The intent is to mimic dict views. | |
| Examples | |
| -------- | |
| >>> G = nx.Graph() | |
| >>> G.add_edge(1, 2, weight=0.3) | |
| >>> G.add_edge(2, 3, weight=0.5) | |
| >>> G.edges(data=True) | |
| EdgeDataView([(1, 2, {'weight': 0.3}), (2, 3, {'weight': 0.5})]) | |
| The view exposes the attributes from the original graph. | |
| >>> viewG = nx.graphviews.generic_graph_view(G) | |
| >>> viewG.edges(data=True) | |
| EdgeDataView([(1, 2, {'weight': 0.3}), (2, 3, {'weight': 0.5})]) | |
| Changes to `G` are reflected in `viewG`. | |
| >>> G.remove_edge(2, 3) | |
| >>> G.edges(data=True) | |
| EdgeDataView([(1, 2, {'weight': 0.3})]) | |
| >>> viewG.edges(data=True) | |
| EdgeDataView([(1, 2, {'weight': 0.3})]) | |
| We can change the graph type with the `create_using` parameter. | |
| >>> type(G) | |
| <class 'networkx.classes.graph.Graph'> | |
| >>> viewDG = nx.graphviews.generic_graph_view(G, create_using=nx.DiGraph) | |
| >>> type(viewDG) | |
| <class 'networkx.classes.digraph.DiGraph'> | |
| """ | |
| if create_using is None: | |
| newG = G.__class__() | |
| else: | |
| newG = nx.empty_graph(0, create_using) | |
| if G.is_multigraph() != newG.is_multigraph(): | |
| raise NetworkXError("Multigraph for G must agree with create_using") | |
| newG = nx.freeze(newG) | |
| # create view by assigning attributes from G | |
| newG._graph = G | |
| newG.graph = G.graph | |
| newG._node = G._node | |
| if newG.is_directed(): | |
| if G.is_directed(): | |
| newG._succ = G._succ | |
| newG._pred = G._pred | |
| # newG._adj is synced with _succ | |
| else: | |
| newG._succ = G._adj | |
| newG._pred = G._adj | |
| # newG._adj is synced with _succ | |
| elif G.is_directed(): | |
| if G.is_multigraph(): | |
| newG._adj = UnionMultiAdjacency(G._succ, G._pred) | |
| else: | |
| newG._adj = UnionAdjacency(G._succ, G._pred) | |
| else: | |
| newG._adj = G._adj | |
| return newG | |
| def subgraph_view(G, *, filter_node=no_filter, filter_edge=no_filter): | |
| """View of `G` applying a filter on nodes and edges. | |
| `subgraph_view` provides a read-only view of the input graph that excludes | |
| nodes and edges based on the outcome of two filter functions `filter_node` | |
| and `filter_edge`. | |
| The `filter_node` function takes one argument --- the node --- and returns | |
| `True` if the node should be included in the subgraph, and `False` if it | |
| should not be included. | |
| The `filter_edge` function takes two (or three arguments if `G` is a | |
| multi-graph) --- the nodes describing an edge, plus the edge-key if | |
| parallel edges are possible --- and returns `True` if the edge should be | |
| included in the subgraph, and `False` if it should not be included. | |
| Both node and edge filter functions are called on graph elements as they | |
| are queried, meaning there is no up-front cost to creating the view. | |
| Parameters | |
| ---------- | |
| G : networkx.Graph | |
| A directed/undirected graph/multigraph | |
| filter_node : callable, optional | |
| A function taking a node as input, which returns `True` if the node | |
| should appear in the view. | |
| filter_edge : callable, optional | |
| A function taking as input the two nodes describing an edge (plus the | |
| edge-key if `G` is a multi-graph), which returns `True` if the edge | |
| should appear in the view. | |
| Returns | |
| ------- | |
| graph : networkx.Graph | |
| A read-only graph view of the input graph. | |
| Examples | |
| -------- | |
| >>> G = nx.path_graph(6) | |
| Filter functions operate on the node, and return `True` if the node should | |
| appear in the view: | |
| >>> def filter_node(n1): | |
| ... return n1 != 5 | |
| ... | |
| >>> view = nx.subgraph_view(G, filter_node=filter_node) | |
| >>> view.nodes() | |
| NodeView((0, 1, 2, 3, 4)) | |
| We can use a closure pattern to filter graph elements based on additional | |
| data --- for example, filtering on edge data attached to the graph: | |
| >>> G[3][4]["cross_me"] = False | |
| >>> def filter_edge(n1, n2): | |
| ... return G[n1][n2].get("cross_me", True) | |
| ... | |
| >>> view = nx.subgraph_view(G, filter_edge=filter_edge) | |
| >>> view.edges() | |
| EdgeView([(0, 1), (1, 2), (2, 3), (4, 5)]) | |
| >>> view = nx.subgraph_view(G, filter_node=filter_node, filter_edge=filter_edge,) | |
| >>> view.nodes() | |
| NodeView((0, 1, 2, 3, 4)) | |
| >>> view.edges() | |
| EdgeView([(0, 1), (1, 2), (2, 3)]) | |
| """ | |
| newG = nx.freeze(G.__class__()) | |
| newG._NODE_OK = filter_node | |
| newG._EDGE_OK = filter_edge | |
| # create view by assigning attributes from G | |
| newG._graph = G | |
| newG.graph = G.graph | |
| newG._node = FilterAtlas(G._node, filter_node) | |
| if G.is_multigraph(): | |
| Adj = FilterMultiAdjacency | |
| def reverse_edge(u, v, k=None): | |
| return filter_edge(v, u, k) | |
| else: | |
| Adj = FilterAdjacency | |
| def reverse_edge(u, v, k=None): | |
| return filter_edge(v, u) | |
| if G.is_directed(): | |
| newG._succ = Adj(G._succ, filter_node, filter_edge) | |
| newG._pred = Adj(G._pred, filter_node, reverse_edge) | |
| # newG._adj is synced with _succ | |
| else: | |
| newG._adj = Adj(G._adj, filter_node, filter_edge) | |
| return newG | |
| def reverse_view(G): | |
| """View of `G` with edge directions reversed | |
| `reverse_view` returns a read-only view of the input graph where | |
| edge directions are reversed. | |
| Identical to digraph.reverse(copy=False) | |
| Parameters | |
| ---------- | |
| G : networkx.DiGraph | |
| Returns | |
| ------- | |
| graph : networkx.DiGraph | |
| Examples | |
| -------- | |
| >>> G = nx.DiGraph() | |
| >>> G.add_edge(1, 2) | |
| >>> G.add_edge(2, 3) | |
| >>> G.edges() | |
| OutEdgeView([(1, 2), (2, 3)]) | |
| >>> view = nx.reverse_view(G) | |
| >>> view.edges() | |
| OutEdgeView([(2, 1), (3, 2)]) | |
| """ | |
| newG = generic_graph_view(G) | |
| newG._succ, newG._pred = G._pred, G._succ | |
| # newG._adj is synced with _succ | |
| return newG | |