Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
510
venv/Lib/site-packages/networkx/algorithms/minors.py
Normal file
510
venv/Lib/site-packages/networkx/algorithms/minors.py
Normal file
|
@ -0,0 +1,510 @@
|
|||
"""Provides functions for computing minors of a graph."""
|
||||
from itertools import chain
|
||||
from itertools import combinations
|
||||
from itertools import permutations
|
||||
from itertools import product
|
||||
|
||||
import networkx as nx
|
||||
from networkx import density
|
||||
from networkx.exception import NetworkXException
|
||||
from networkx.utils import arbitrary_element
|
||||
|
||||
__all__ = ["contracted_edge", "contracted_nodes", "identified_nodes", "quotient_graph"]
|
||||
|
||||
chaini = chain.from_iterable
|
||||
|
||||
|
||||
def equivalence_classes(iterable, relation):
|
||||
"""Returns the set of equivalence classes of the given `iterable` under
|
||||
the specified equivalence relation.
|
||||
|
||||
`relation` must be a Boolean-valued function that takes two argument. It
|
||||
must represent an equivalence relation (that is, the relation induced by
|
||||
the function must be reflexive, symmetric, and transitive).
|
||||
|
||||
The return value is a set of sets. It is a partition of the elements of
|
||||
`iterable`; duplicate elements will be ignored so it makes the most sense
|
||||
for `iterable` to be a :class:`set`.
|
||||
|
||||
"""
|
||||
# For simplicity of implementation, we initialize the return value as a
|
||||
# list of lists, then convert it to a set of sets at the end of the
|
||||
# function.
|
||||
blocks = []
|
||||
# Determine the equivalence class for each element of the iterable.
|
||||
for y in iterable:
|
||||
# Each element y must be in *exactly one* equivalence class.
|
||||
#
|
||||
# Each block is guaranteed to be non-empty
|
||||
for block in blocks:
|
||||
x = arbitrary_element(block)
|
||||
if relation(x, y):
|
||||
block.append(y)
|
||||
break
|
||||
else:
|
||||
# If the element y is not part of any known equivalence class, it
|
||||
# must be in its own, so we create a new singleton equivalence
|
||||
# class for it.
|
||||
blocks.append([y])
|
||||
return {frozenset(block) for block in blocks}
|
||||
|
||||
|
||||
def quotient_graph(
|
||||
G,
|
||||
partition,
|
||||
edge_relation=None,
|
||||
node_data=None,
|
||||
edge_data=None,
|
||||
relabel=False,
|
||||
create_using=None,
|
||||
):
|
||||
"""Returns the quotient graph of `G` under the specified equivalence
|
||||
relation on nodes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
The graph for which to return the quotient graph with the
|
||||
specified node relation.
|
||||
|
||||
partition : function or list of sets
|
||||
If a function, this function must represent an equivalence
|
||||
relation on the nodes of `G`. It must take two arguments *u*
|
||||
and *v* and return True exactly when *u* and *v* are in the
|
||||
same equivalence class. The equivalence classes form the nodes
|
||||
in the returned graph.
|
||||
|
||||
If a list of sets, the list must form a valid partition of
|
||||
the nodes of the graph. That is, each node must be in exactly
|
||||
one block of the partition.
|
||||
|
||||
edge_relation : Boolean function with two arguments
|
||||
This function must represent an edge relation on the *blocks* of
|
||||
`G` in the partition induced by `node_relation`. It must
|
||||
take two arguments, *B* and *C*, each one a set of nodes, and
|
||||
return True exactly when there should be an edge joining
|
||||
block *B* to block *C* in the returned graph.
|
||||
|
||||
If `edge_relation` is not specified, it is assumed to be the
|
||||
following relation. Block *B* is related to block *C* if and
|
||||
only if some node in *B* is adjacent to some node in *C*,
|
||||
according to the edge set of `G`.
|
||||
|
||||
edge_data : function
|
||||
This function takes two arguments, *B* and *C*, each one a set
|
||||
of nodes, and must return a dictionary representing the edge
|
||||
data attributes to set on the edge joining *B* and *C*, should
|
||||
there be an edge joining *B* and *C* in the quotient graph (if
|
||||
no such edge occurs in the quotient graph as determined by
|
||||
`edge_relation`, then the output of this function is ignored).
|
||||
|
||||
If the quotient graph would be a multigraph, this function is
|
||||
not applied, since the edge data from each edge in the graph
|
||||
`G` appears in the edges of the quotient graph.
|
||||
|
||||
node_data : function
|
||||
This function takes one argument, *B*, a set of nodes in `G`,
|
||||
and must return a dictionary representing the node data
|
||||
attributes to set on the node representing *B* in the quotient graph.
|
||||
If None, the following node attributes will be set:
|
||||
|
||||
* 'graph', the subgraph of the graph `G` that this block
|
||||
represents,
|
||||
* 'nnodes', the number of nodes in this block,
|
||||
* 'nedges', the number of edges within this block,
|
||||
* 'density', the density of the subgraph of `G` that this
|
||||
block represents.
|
||||
|
||||
relabel : bool
|
||||
If True, relabel the nodes of the quotient graph to be
|
||||
nonnegative integers. Otherwise, the nodes are identified with
|
||||
:class:`frozenset` instances representing the blocks given in
|
||||
`partition`.
|
||||
|
||||
create_using : NetworkX graph constructor, optional (default=nx.Graph)
|
||||
Graph type to create. If graph instance, then cleared before populated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
NetworkX graph
|
||||
The quotient graph of `G` under the equivalence relation
|
||||
specified by `partition`. If the partition were given as a
|
||||
list of :class:`set` instances and `relabel` is False,
|
||||
each node will be a :class:`frozenset` corresponding to the same
|
||||
:class:`set`.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXException
|
||||
If the given partition is not a valid partition of the nodes of
|
||||
`G`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
The quotient graph of the complete bipartite graph under the "same
|
||||
neighbors" equivalence relation is `K_2`. Under this relation, two nodes
|
||||
are equivalent if they are not adjacent but have the same neighbor set::
|
||||
|
||||
>>> G = nx.complete_bipartite_graph(2, 3)
|
||||
>>> same_neighbors = lambda u, v: (
|
||||
... u not in G[v] and v not in G[u] and G[u] == G[v]
|
||||
... )
|
||||
>>> Q = nx.quotient_graph(G, same_neighbors)
|
||||
>>> K2 = nx.complete_graph(2)
|
||||
>>> nx.is_isomorphic(Q, K2)
|
||||
True
|
||||
|
||||
The quotient graph of a directed graph under the "same strongly connected
|
||||
component" equivalence relation is the condensation of the graph (see
|
||||
:func:`condensation`). This example comes from the Wikipedia article
|
||||
*`Strongly connected component`_*::
|
||||
|
||||
>>> G = nx.DiGraph()
|
||||
>>> edges = [
|
||||
... "ab",
|
||||
... "be",
|
||||
... "bf",
|
||||
... "bc",
|
||||
... "cg",
|
||||
... "cd",
|
||||
... "dc",
|
||||
... "dh",
|
||||
... "ea",
|
||||
... "ef",
|
||||
... "fg",
|
||||
... "gf",
|
||||
... "hd",
|
||||
... "hf",
|
||||
... ]
|
||||
>>> G.add_edges_from(tuple(x) for x in edges)
|
||||
>>> components = list(nx.strongly_connected_components(G))
|
||||
>>> sorted(sorted(component) for component in components)
|
||||
[['a', 'b', 'e'], ['c', 'd', 'h'], ['f', 'g']]
|
||||
>>>
|
||||
>>> C = nx.condensation(G, components)
|
||||
>>> component_of = C.graph["mapping"]
|
||||
>>> same_component = lambda u, v: component_of[u] == component_of[v]
|
||||
>>> Q = nx.quotient_graph(G, same_component)
|
||||
>>> nx.is_isomorphic(C, Q)
|
||||
True
|
||||
|
||||
Node identification can be represented as the quotient of a graph under the
|
||||
equivalence relation that places the two nodes in one block and each other
|
||||
node in its own singleton block::
|
||||
|
||||
>>> K24 = nx.complete_bipartite_graph(2, 4)
|
||||
>>> K34 = nx.complete_bipartite_graph(3, 4)
|
||||
>>> C = nx.contracted_nodes(K34, 1, 2)
|
||||
>>> nodes = {1, 2}
|
||||
>>> is_contracted = lambda u, v: u in nodes and v in nodes
|
||||
>>> Q = nx.quotient_graph(K34, is_contracted)
|
||||
>>> nx.is_isomorphic(Q, C)
|
||||
True
|
||||
>>> nx.is_isomorphic(Q, K24)
|
||||
True
|
||||
|
||||
The blockmodeling technique described in [1]_ can be implemented as a
|
||||
quotient graph::
|
||||
|
||||
>>> G = nx.path_graph(6)
|
||||
>>> partition = [{0, 1}, {2, 3}, {4, 5}]
|
||||
>>> M = nx.quotient_graph(G, partition, relabel=True)
|
||||
>>> list(M.edges())
|
||||
[(0, 1), (1, 2)]
|
||||
|
||||
.. _Strongly connected component: https://en.wikipedia.org/wiki/Strongly_connected_component
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Patrick Doreian, Vladimir Batagelj, and Anuska Ferligoj.
|
||||
*Generalized Blockmodeling*.
|
||||
Cambridge University Press, 2004.
|
||||
|
||||
"""
|
||||
# If the user provided an equivalence relation as a function compute
|
||||
# the blocks of the partition on the nodes of G induced by the
|
||||
# equivalence relation.
|
||||
if callable(partition):
|
||||
# equivalence_classes always return partition of whole G.
|
||||
partition = equivalence_classes(G, partition)
|
||||
return _quotient_graph(
|
||||
G, partition, edge_relation, node_data, edge_data, relabel, create_using
|
||||
)
|
||||
|
||||
# If the user provided partition as a collection of sets. Then we
|
||||
# need to check if partition covers all of G nodes. If the answer
|
||||
# is 'No' then we need to prepare suitable subgraph view.
|
||||
partition_nodes = set().union(*partition)
|
||||
if len(partition_nodes) != len(G):
|
||||
G = G.subgraph(partition_nodes)
|
||||
return _quotient_graph(
|
||||
G, partition, edge_relation, node_data, edge_data, relabel, create_using
|
||||
)
|
||||
|
||||
|
||||
def _quotient_graph(
|
||||
G,
|
||||
partition,
|
||||
edge_relation=None,
|
||||
node_data=None,
|
||||
edge_data=None,
|
||||
relabel=False,
|
||||
create_using=None,
|
||||
):
|
||||
# Each node in the graph must be in exactly one block.
|
||||
if any(sum(1 for b in partition if v in b) != 1 for v in G):
|
||||
raise NetworkXException("each node must be in exactly one block")
|
||||
if create_using is None:
|
||||
H = G.__class__()
|
||||
else:
|
||||
H = nx.empty_graph(0, create_using)
|
||||
# By default set some basic information about the subgraph that each block
|
||||
# represents on the nodes in the quotient graph.
|
||||
if node_data is None:
|
||||
|
||||
def node_data(b):
|
||||
S = G.subgraph(b)
|
||||
return dict(
|
||||
graph=S, nnodes=len(S), nedges=S.number_of_edges(), density=density(S)
|
||||
)
|
||||
|
||||
# Each block of the partition becomes a node in the quotient graph.
|
||||
partition = [frozenset(b) for b in partition]
|
||||
H.add_nodes_from((b, node_data(b)) for b in partition)
|
||||
# By default, the edge relation is the relation defined as follows. B is
|
||||
# adjacent to C if a node in B is adjacent to a node in C, according to the
|
||||
# edge set of G.
|
||||
#
|
||||
# This is not a particularly efficient implementation of this relation:
|
||||
# there are O(n^2) pairs to check and each check may require O(log n) time
|
||||
# (to check set membership). This can certainly be parallelized.
|
||||
if edge_relation is None:
|
||||
|
||||
def edge_relation(b, c):
|
||||
return any(v in G[u] for u, v in product(b, c))
|
||||
|
||||
# By default, sum the weights of the edges joining pairs of nodes across
|
||||
# blocks to get the weight of the edge joining those two blocks.
|
||||
if edge_data is None:
|
||||
|
||||
def edge_data(b, c):
|
||||
edgedata = (
|
||||
d
|
||||
for u, v, d in G.edges(b | c, data=True)
|
||||
if (u in b and v in c) or (u in c and v in b)
|
||||
)
|
||||
return {"weight": sum(d.get("weight", 1) for d in edgedata)}
|
||||
|
||||
block_pairs = permutations(H, 2) if H.is_directed() else combinations(H, 2)
|
||||
# In a multigraph, add one edge in the quotient graph for each edge
|
||||
# in the original graph.
|
||||
if H.is_multigraph():
|
||||
edges = chaini(
|
||||
(
|
||||
(b, c, G.get_edge_data(u, v, default={}))
|
||||
for u, v in product(b, c)
|
||||
if v in G[u]
|
||||
)
|
||||
for b, c in block_pairs
|
||||
if edge_relation(b, c)
|
||||
)
|
||||
# In a simple graph, apply the edge data function to each pair of
|
||||
# blocks to determine the edge data attributes to apply to each edge
|
||||
# in the quotient graph.
|
||||
else:
|
||||
edges = (
|
||||
(b, c, edge_data(b, c)) for (b, c) in block_pairs if edge_relation(b, c)
|
||||
)
|
||||
H.add_edges_from(edges)
|
||||
# If requested by the user, relabel the nodes to be integers,
|
||||
# numbered in increasing order from zero in the same order as the
|
||||
# iteration order of `partition`.
|
||||
if relabel:
|
||||
# Can't use nx.convert_node_labels_to_integers() here since we
|
||||
# want the order of iteration to be the same for backward
|
||||
# compatibility with the nx.blockmodel() function.
|
||||
labels = {b: i for i, b in enumerate(partition)}
|
||||
H = nx.relabel_nodes(H, labels)
|
||||
return H
|
||||
|
||||
|
||||
def contracted_nodes(G, u, v, self_loops=True, copy=True):
|
||||
"""Returns the graph that results from contracting `u` and `v`.
|
||||
|
||||
Node contraction identifies the two nodes as a single node incident to any
|
||||
edge that was incident to the original two nodes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
The graph whose nodes will be contracted.
|
||||
|
||||
u, v : nodes
|
||||
Must be nodes in `G`.
|
||||
|
||||
self_loops : Boolean
|
||||
If this is True, any edges joining `u` and `v` in `G` become
|
||||
self-loops on the new node in the returned graph.
|
||||
|
||||
copy : Boolean
|
||||
If this is True (default True), make a copy of
|
||||
`G` and return that instead of directly changing `G`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Networkx graph
|
||||
If Copy is True:
|
||||
A new graph object of the same type as `G` (leaving `G` unmodified)
|
||||
with `u` and `v` identified in a single node. The right node `v`
|
||||
will be merged into the node `u`, so only `u` will appear in the
|
||||
returned graph.
|
||||
if Copy is False:
|
||||
Modifies `G` with `u` and `v` identified in a single node.
|
||||
The right node `v` will be merged into the node `u`, so
|
||||
only `u` will appear in the returned graph.
|
||||
|
||||
Notes
|
||||
-----
|
||||
For multigraphs, the edge keys for the realigned edges may
|
||||
not be the same as the edge keys for the old edges. This is
|
||||
natural because edge keys are unique only within each pair of nodes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Contracting two nonadjacent nodes of the cycle graph on four nodes `C_4`
|
||||
yields the path graph (ignoring parallel edges)::
|
||||
|
||||
>>> G = nx.cycle_graph(4)
|
||||
>>> M = nx.contracted_nodes(G, 1, 3)
|
||||
>>> P3 = nx.path_graph(3)
|
||||
>>> nx.is_isomorphic(M, P3)
|
||||
True
|
||||
|
||||
>>> G = nx.MultiGraph(P3)
|
||||
>>> M = nx.contracted_nodes(G, 0, 2)
|
||||
>>> M.edges
|
||||
MultiEdgeView([(0, 1, 0), (0, 1, 1)])
|
||||
|
||||
>>> G = nx.Graph([(1, 2), (2, 2)])
|
||||
>>> H = nx.contracted_nodes(G, 1, 2, self_loops=False)
|
||||
>>> list(H.nodes())
|
||||
[1]
|
||||
>>> list(H.edges())
|
||||
[(1, 1)]
|
||||
|
||||
See also
|
||||
--------
|
||||
contracted_edge
|
||||
quotient_graph
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function is also available as `identified_nodes`.
|
||||
"""
|
||||
# Copying has significant overhead and can be disabled if needed
|
||||
if copy:
|
||||
H = G.copy()
|
||||
else:
|
||||
H = G
|
||||
|
||||
# edge code uses G.edges(v) instead of G.adj[v] to handle multiedges
|
||||
if H.is_directed():
|
||||
in_edges = (
|
||||
(w if w != v else u, u, d)
|
||||
for w, x, d in G.in_edges(v, data=True)
|
||||
if self_loops or w != u
|
||||
)
|
||||
out_edges = (
|
||||
(u, w if w != v else u, d)
|
||||
for x, w, d in G.out_edges(v, data=True)
|
||||
if self_loops or w != u
|
||||
)
|
||||
new_edges = chain(in_edges, out_edges)
|
||||
else:
|
||||
new_edges = (
|
||||
(u, w if w != v else u, d)
|
||||
for x, w, d in G.edges(v, data=True)
|
||||
if self_loops or w != u
|
||||
)
|
||||
|
||||
# If the H=G, the generators change as H changes
|
||||
# This makes the new_edges independent of H
|
||||
if not copy:
|
||||
new_edges = list(new_edges)
|
||||
|
||||
v_data = H.nodes[v]
|
||||
H.remove_node(v)
|
||||
H.add_edges_from(new_edges)
|
||||
|
||||
if "contraction" in H.nodes[u]:
|
||||
H.nodes[u]["contraction"][v] = v_data
|
||||
else:
|
||||
H.nodes[u]["contraction"] = {v: v_data}
|
||||
return H
|
||||
|
||||
|
||||
identified_nodes = contracted_nodes
|
||||
|
||||
|
||||
def contracted_edge(G, edge, self_loops=True):
|
||||
"""Returns the graph that results from contracting the specified edge.
|
||||
|
||||
Edge contraction identifies the two endpoints of the edge as a single node
|
||||
incident to any edge that was incident to the original two nodes. A graph
|
||||
that results from edge contraction is called a *minor* of the original
|
||||
graph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
The graph whose edge will be contracted.
|
||||
|
||||
edge : tuple
|
||||
Must be a pair of nodes in `G`.
|
||||
|
||||
self_loops : Boolean
|
||||
If this is True, any edges (including `edge`) joining the
|
||||
endpoints of `edge` in `G` become self-loops on the new node in the
|
||||
returned graph.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Networkx graph
|
||||
A new graph object of the same type as `G` (leaving `G` unmodified)
|
||||
with endpoints of `edge` identified in a single node. The right node
|
||||
of `edge` will be merged into the left one, so only the left one will
|
||||
appear in the returned graph.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If `edge` is not an edge in `G`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Attempting to contract two nonadjacent nodes yields an error::
|
||||
|
||||
>>> G = nx.cycle_graph(4)
|
||||
>>> nx.contracted_edge(G, (1, 3))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Edge (1, 3) does not exist in graph G; cannot contract it
|
||||
|
||||
Contracting two adjacent nodes in the cycle graph on *n* nodes yields the
|
||||
cycle graph on *n - 1* nodes::
|
||||
|
||||
>>> C5 = nx.cycle_graph(5)
|
||||
>>> C4 = nx.cycle_graph(4)
|
||||
>>> M = nx.contracted_edge(C5, (0, 1), self_loops=False)
|
||||
>>> nx.is_isomorphic(M, C4)
|
||||
True
|
||||
|
||||
See also
|
||||
--------
|
||||
contracted_nodes
|
||||
quotient_graph
|
||||
|
||||
"""
|
||||
if not G.has_edge(*edge):
|
||||
raise ValueError(f"Edge {edge} does not exist in graph G; cannot contract it")
|
||||
return contracted_nodes(G, *edge, self_loops=self_loops)
|
Loading…
Add table
Add a link
Reference in a new issue