using for loop to install conda package
This commit is contained in:
24
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/__init__.py
vendored
Normal file
24
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/__init__.py
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
"""Approximations of graph properties and Heuristic methods for optimization.
|
||||
|
||||
The functions in this class are not imported into the top-level ``networkx``
|
||||
namespace so the easiest way to use them is with::
|
||||
|
||||
>>> from networkx.algorithms import approximation
|
||||
|
||||
Another option is to import the specific function with
|
||||
``from networkx.algorithms.approximation import function_name``.
|
||||
|
||||
"""
|
||||
from networkx.algorithms.approximation.clustering_coefficient import *
|
||||
from networkx.algorithms.approximation.clique import *
|
||||
from networkx.algorithms.approximation.connectivity import *
|
||||
from networkx.algorithms.approximation.distance_measures import *
|
||||
from networkx.algorithms.approximation.dominating_set import *
|
||||
from networkx.algorithms.approximation.kcomponents import *
|
||||
from networkx.algorithms.approximation.matching import *
|
||||
from networkx.algorithms.approximation.ramsey import *
|
||||
from networkx.algorithms.approximation.steinertree import *
|
||||
from networkx.algorithms.approximation.traveling_salesman import *
|
||||
from networkx.algorithms.approximation.treewidth import *
|
||||
from networkx.algorithms.approximation.vertex_cover import *
|
||||
from networkx.algorithms.approximation.maxcut import *
|
||||
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/__pycache__/clique.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/__pycache__/clique.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/__pycache__/maxcut.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/__pycache__/maxcut.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/__pycache__/ramsey.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/__pycache__/ramsey.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
233
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/clique.py
vendored
Normal file
233
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/clique.py
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
"""Functions for computing large cliques and maximum independent sets."""
|
||||
import networkx as nx
|
||||
from networkx.algorithms.approximation import ramsey
|
||||
from networkx.utils import not_implemented_for
|
||||
|
||||
__all__ = [
|
||||
"clique_removal",
|
||||
"max_clique",
|
||||
"large_clique_size",
|
||||
"maximum_independent_set",
|
||||
]
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
@not_implemented_for("multigraph")
|
||||
def maximum_independent_set(G):
|
||||
"""Returns an approximate maximum independent set.
|
||||
|
||||
Independent set or stable set is a set of vertices in a graph, no two of
|
||||
which are adjacent. That is, it is a set I of vertices such that for every
|
||||
two vertices in I, there is no edge connecting the two. Equivalently, each
|
||||
edge in the graph has at most one endpoint in I. The size of an independent
|
||||
set is the number of vertices it contains [1]_.
|
||||
|
||||
A maximum independent set is a largest independent set for a given graph G
|
||||
and its size is denoted $\\alpha(G)$. The problem of finding such a set is called
|
||||
the maximum independent set problem and is an NP-hard optimization problem.
|
||||
As such, it is unlikely that there exists an efficient algorithm for finding
|
||||
a maximum independent set of a graph.
|
||||
|
||||
The Independent Set algorithm is based on [2]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
Undirected graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
iset : Set
|
||||
The apx-maximum independent set
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
If the graph is directed or is a multigraph.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Finds the $O(|V|/(log|V|)^2)$ apx of independent set in the worst case.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] `Wikipedia: Independent set
|
||||
<https://en.wikipedia.org/wiki/Independent_set_(graph_theory)>`_
|
||||
.. [2] Boppana, R., & Halldórsson, M. M. (1992).
|
||||
Approximating maximum independent sets by excluding subgraphs.
|
||||
BIT Numerical Mathematics, 32(2), 180–196. Springer.
|
||||
"""
|
||||
iset, _ = clique_removal(G)
|
||||
return iset
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
@not_implemented_for("multigraph")
|
||||
def max_clique(G):
|
||||
r"""Find the Maximum Clique
|
||||
|
||||
Finds the $O(|V|/(log|V|)^2)$ apx of maximum clique/independent set
|
||||
in the worst case.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
Undirected graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
clique : set
|
||||
The apx-maximum clique of the graph
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
If the graph is directed or is a multigraph.
|
||||
|
||||
Notes
|
||||
-----
|
||||
A clique in an undirected graph G = (V, E) is a subset of the vertex set
|
||||
`C \subseteq V` such that for every two vertices in C there exists an edge
|
||||
connecting the two. This is equivalent to saying that the subgraph
|
||||
induced by C is complete (in some cases, the term clique may also refer
|
||||
to the subgraph).
|
||||
|
||||
A maximum clique is a clique of the largest possible size in a given graph.
|
||||
The clique number `\omega(G)` of a graph G is the number of
|
||||
vertices in a maximum clique in G. The intersection number of
|
||||
G is the smallest number of cliques that together cover all edges of G.
|
||||
|
||||
https://en.wikipedia.org/wiki/Maximum_clique
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Boppana, R., & Halldórsson, M. M. (1992).
|
||||
Approximating maximum independent sets by excluding subgraphs.
|
||||
BIT Numerical Mathematics, 32(2), 180–196. Springer.
|
||||
doi:10.1007/BF01994876
|
||||
"""
|
||||
if G is None:
|
||||
raise ValueError("Expected NetworkX graph!")
|
||||
|
||||
# finding the maximum clique in a graph is equivalent to finding
|
||||
# the independent set in the complementary graph
|
||||
cgraph = nx.complement(G)
|
||||
iset, _ = clique_removal(cgraph)
|
||||
return iset
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
@not_implemented_for("multigraph")
|
||||
def clique_removal(G):
|
||||
r"""Repeatedly remove cliques from the graph.
|
||||
|
||||
Results in a $O(|V|/(\log |V|)^2)$ approximation of maximum clique
|
||||
and independent set. Returns the largest independent set found, along
|
||||
with found maximal cliques.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
Undirected graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
max_ind_cliques : (set, list) tuple
|
||||
2-tuple of Maximal Independent Set and list of maximal cliques (sets).
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
If the graph is directed or is a multigraph.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Boppana, R., & Halldórsson, M. M. (1992).
|
||||
Approximating maximum independent sets by excluding subgraphs.
|
||||
BIT Numerical Mathematics, 32(2), 180–196. Springer.
|
||||
"""
|
||||
graph = G.copy()
|
||||
c_i, i_i = ramsey.ramsey_R2(graph)
|
||||
cliques = [c_i]
|
||||
isets = [i_i]
|
||||
while graph:
|
||||
graph.remove_nodes_from(c_i)
|
||||
c_i, i_i = ramsey.ramsey_R2(graph)
|
||||
if c_i:
|
||||
cliques.append(c_i)
|
||||
if i_i:
|
||||
isets.append(i_i)
|
||||
# Determine the largest independent set as measured by cardinality.
|
||||
maxiset = max(isets, key=len)
|
||||
return maxiset, cliques
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
@not_implemented_for("multigraph")
|
||||
def large_clique_size(G):
|
||||
"""Find the size of a large clique in a graph.
|
||||
|
||||
A *clique* is a subset of nodes in which each pair of nodes is
|
||||
adjacent. This function is a heuristic for finding the size of a
|
||||
large clique in the graph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
k: integer
|
||||
The size of a large clique in the graph.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
If the graph is directed or is a multigraph.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This implementation is from [1]_. Its worst case time complexity is
|
||||
:math:`O(n d^2)`, where *n* is the number of nodes in the graph and
|
||||
*d* is the maximum degree.
|
||||
|
||||
This function is a heuristic, which means it may work well in
|
||||
practice, but there is no rigorous mathematical guarantee on the
|
||||
ratio between the returned number and the actual largest clique size
|
||||
in the graph.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Pattabiraman, Bharath, et al.
|
||||
"Fast Algorithms for the Maximum Clique Problem on Massive Graphs
|
||||
with Applications to Overlapping Community Detection."
|
||||
*Internet Mathematics* 11.4-5 (2015): 421--448.
|
||||
<https://doi.org/10.1080/15427951.2014.986778>
|
||||
|
||||
See also
|
||||
--------
|
||||
|
||||
:func:`networkx.algorithms.approximation.clique.max_clique`
|
||||
A function that returns an approximate maximum clique with a
|
||||
guarantee on the approximation ratio.
|
||||
|
||||
:mod:`networkx.algorithms.clique`
|
||||
Functions for finding the exact maximum clique in a graph.
|
||||
|
||||
"""
|
||||
degrees = G.degree
|
||||
|
||||
def _clique_heuristic(G, U, size, best_size):
|
||||
if not U:
|
||||
return max(best_size, size)
|
||||
u = max(U, key=degrees)
|
||||
U.remove(u)
|
||||
N_prime = {v for v in G[u] if degrees[v] >= best_size}
|
||||
return _clique_heuristic(G, U & N_prime, size + 1, best_size)
|
||||
|
||||
best_size = 0
|
||||
nodes = (u for u in G if degrees[u] >= best_size)
|
||||
for u in nodes:
|
||||
neighbors = {v for v in G[u] if degrees[v] >= best_size}
|
||||
best_size = _clique_heuristic(G, neighbors, 1, best_size)
|
||||
return best_size
|
||||
64
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/clustering_coefficient.py
vendored
Normal file
64
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/clustering_coefficient.py
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
from networkx.utils import not_implemented_for, py_random_state
|
||||
|
||||
__all__ = ["average_clustering"]
|
||||
|
||||
|
||||
@py_random_state(2)
|
||||
@not_implemented_for("directed")
|
||||
def average_clustering(G, trials=1000, seed=None):
|
||||
r"""Estimates the average clustering coefficient of G.
|
||||
|
||||
The local clustering of each node in `G` is the fraction of triangles
|
||||
that actually exist over all possible triangles in its neighborhood.
|
||||
The average clustering coefficient of a graph `G` is the mean of
|
||||
local clusterings.
|
||||
|
||||
This function finds an approximate average clustering coefficient
|
||||
for G by repeating `n` times (defined in `trials`) the following
|
||||
experiment: choose a node at random, choose two of its neighbors
|
||||
at random, and check if they are connected. The approximate
|
||||
coefficient is the fraction of triangles found over the number
|
||||
of trials [1]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
trials : integer
|
||||
Number of trials to perform (default 1000).
|
||||
|
||||
seed : integer, random_state, or None (default)
|
||||
Indicator of random number generation state.
|
||||
See :ref:`Randomness<randomness>`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
c : float
|
||||
Approximated average clustering coefficient.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from networkx.algorithms import approximation
|
||||
>>> G = nx.erdos_renyi_graph(10, 0.2, seed=10)
|
||||
>>> approximation.average_clustering(G, trials=1000, seed=10)
|
||||
0.214
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Schank, Thomas, and Dorothea Wagner. Approximating clustering
|
||||
coefficient and transitivity. Universität Karlsruhe, Fakultät für
|
||||
Informatik, 2004.
|
||||
https://doi.org/10.5445/IR/1000001239
|
||||
|
||||
"""
|
||||
n = len(G)
|
||||
triangles = 0
|
||||
nodes = list(G)
|
||||
for i in [int(seed.random() * n) for i in range(trials)]:
|
||||
nbrs = list(G[nodes[i]])
|
||||
if len(nbrs) < 2:
|
||||
continue
|
||||
u, v = seed.sample(nbrs, 2)
|
||||
if u in G[v]:
|
||||
triangles += 1
|
||||
return triangles / trials
|
||||
415
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/connectivity.py
vendored
Normal file
415
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/connectivity.py
vendored
Normal file
@@ -0,0 +1,415 @@
|
||||
""" Fast approximation for node connectivity
|
||||
"""
|
||||
import itertools
|
||||
from operator import itemgetter
|
||||
|
||||
import networkx as nx
|
||||
|
||||
__all__ = [
|
||||
"local_node_connectivity",
|
||||
"node_connectivity",
|
||||
"all_pairs_node_connectivity",
|
||||
]
|
||||
|
||||
|
||||
def local_node_connectivity(G, source, target, cutoff=None):
|
||||
"""Compute node connectivity between source and target.
|
||||
|
||||
Pairwise or local node connectivity between two distinct and nonadjacent
|
||||
nodes is the minimum number of nodes that must be removed (minimum
|
||||
separating cutset) to disconnect them. By Menger's theorem, this is equal
|
||||
to the number of node independent paths (paths that share no nodes other
|
||||
than source and target). Which is what we compute in this function.
|
||||
|
||||
This algorithm is a fast approximation that gives an strict lower
|
||||
bound on the actual number of node independent paths between two nodes [1]_.
|
||||
It works for both directed and undirected graphs.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
G : NetworkX graph
|
||||
|
||||
source : node
|
||||
Starting node for node connectivity
|
||||
|
||||
target : node
|
||||
Ending node for node connectivity
|
||||
|
||||
cutoff : integer
|
||||
Maximum node connectivity to consider. If None, the minimum degree
|
||||
of source or target is used as a cutoff. Default value None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
k: integer
|
||||
pairwise node connectivity
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> # Platonic octahedral graph has node connectivity 4
|
||||
>>> # for each non adjacent node pair
|
||||
>>> from networkx.algorithms import approximation as approx
|
||||
>>> G = nx.octahedral_graph()
|
||||
>>> approx.local_node_connectivity(G, 0, 5)
|
||||
4
|
||||
|
||||
Notes
|
||||
-----
|
||||
This algorithm [1]_ finds node independents paths between two nodes by
|
||||
computing their shortest path using BFS, marking the nodes of the path
|
||||
found as 'used' and then searching other shortest paths excluding the
|
||||
nodes marked as used until no more paths exist. It is not exact because
|
||||
a shortest path could use nodes that, if the path were longer, may belong
|
||||
to two different node independent paths. Thus it only guarantees an
|
||||
strict lower bound on node connectivity.
|
||||
|
||||
Note that the authors propose a further refinement, losing accuracy and
|
||||
gaining speed, which is not implemented yet.
|
||||
|
||||
See also
|
||||
--------
|
||||
all_pairs_node_connectivity
|
||||
node_connectivity
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for
|
||||
Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035
|
||||
http://eclectic.ss.uci.edu/~drwhite/working.pdf
|
||||
|
||||
"""
|
||||
if target == source:
|
||||
raise nx.NetworkXError("source and target have to be different nodes.")
|
||||
|
||||
# Maximum possible node independent paths
|
||||
if G.is_directed():
|
||||
possible = min(G.out_degree(source), G.in_degree(target))
|
||||
else:
|
||||
possible = min(G.degree(source), G.degree(target))
|
||||
|
||||
K = 0
|
||||
if not possible:
|
||||
return K
|
||||
|
||||
if cutoff is None:
|
||||
cutoff = float("inf")
|
||||
|
||||
exclude = set()
|
||||
for i in range(min(possible, cutoff)):
|
||||
try:
|
||||
path = _bidirectional_shortest_path(G, source, target, exclude)
|
||||
exclude.update(set(path))
|
||||
K += 1
|
||||
except nx.NetworkXNoPath:
|
||||
break
|
||||
|
||||
return K
|
||||
|
||||
|
||||
def node_connectivity(G, s=None, t=None):
|
||||
r"""Returns an approximation for node connectivity for a graph or digraph G.
|
||||
|
||||
Node connectivity is equal to the minimum number of nodes that
|
||||
must be removed to disconnect G or render it trivial. By Menger's theorem,
|
||||
this is equal to the number of node independent paths (paths that
|
||||
share no nodes other than source and target).
|
||||
|
||||
If source and target nodes are provided, this function returns the
|
||||
local node connectivity: the minimum number of nodes that must be
|
||||
removed to break all paths from source to target in G.
|
||||
|
||||
This algorithm is based on a fast approximation that gives an strict lower
|
||||
bound on the actual number of node independent paths between two nodes [1]_.
|
||||
It works for both directed and undirected graphs.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
Undirected graph
|
||||
|
||||
s : node
|
||||
Source node. Optional. Default value: None.
|
||||
|
||||
t : node
|
||||
Target node. Optional. Default value: None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
K : integer
|
||||
Node connectivity of G, or local node connectivity if source
|
||||
and target are provided.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> # Platonic octahedral graph is 4-node-connected
|
||||
>>> from networkx.algorithms import approximation as approx
|
||||
>>> G = nx.octahedral_graph()
|
||||
>>> approx.node_connectivity(G)
|
||||
4
|
||||
|
||||
Notes
|
||||
-----
|
||||
This algorithm [1]_ finds node independents paths between two nodes by
|
||||
computing their shortest path using BFS, marking the nodes of the path
|
||||
found as 'used' and then searching other shortest paths excluding the
|
||||
nodes marked as used until no more paths exist. It is not exact because
|
||||
a shortest path could use nodes that, if the path were longer, may belong
|
||||
to two different node independent paths. Thus it only guarantees an
|
||||
strict lower bound on node connectivity.
|
||||
|
||||
See also
|
||||
--------
|
||||
all_pairs_node_connectivity
|
||||
local_node_connectivity
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for
|
||||
Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035
|
||||
http://eclectic.ss.uci.edu/~drwhite/working.pdf
|
||||
|
||||
"""
|
||||
if (s is not None and t is None) or (s is None and t is not None):
|
||||
raise nx.NetworkXError("Both source and target must be specified.")
|
||||
|
||||
# Local node connectivity
|
||||
if s is not None and t is not None:
|
||||
if s not in G:
|
||||
raise nx.NetworkXError(f"node {s} not in graph")
|
||||
if t not in G:
|
||||
raise nx.NetworkXError(f"node {t} not in graph")
|
||||
return local_node_connectivity(G, s, t)
|
||||
|
||||
# Global node connectivity
|
||||
if G.is_directed():
|
||||
connected_func = nx.is_weakly_connected
|
||||
iter_func = itertools.permutations
|
||||
|
||||
def neighbors(v):
|
||||
return itertools.chain(G.predecessors(v), G.successors(v))
|
||||
|
||||
else:
|
||||
connected_func = nx.is_connected
|
||||
iter_func = itertools.combinations
|
||||
neighbors = G.neighbors
|
||||
|
||||
if not connected_func(G):
|
||||
return 0
|
||||
|
||||
# Choose a node with minimum degree
|
||||
v, minimum_degree = min(G.degree(), key=itemgetter(1))
|
||||
# Node connectivity is bounded by minimum degree
|
||||
K = minimum_degree
|
||||
# compute local node connectivity with all non-neighbors nodes
|
||||
# and store the minimum
|
||||
for w in set(G) - set(neighbors(v)) - {v}:
|
||||
K = min(K, local_node_connectivity(G, v, w, cutoff=K))
|
||||
# Same for non adjacent pairs of neighbors of v
|
||||
for x, y in iter_func(neighbors(v), 2):
|
||||
if y not in G[x] and x != y:
|
||||
K = min(K, local_node_connectivity(G, x, y, cutoff=K))
|
||||
return K
|
||||
|
||||
|
||||
def all_pairs_node_connectivity(G, nbunch=None, cutoff=None):
|
||||
"""Compute node connectivity between all pairs of nodes.
|
||||
|
||||
Pairwise or local node connectivity between two distinct and nonadjacent
|
||||
nodes is the minimum number of nodes that must be removed (minimum
|
||||
separating cutset) to disconnect them. By Menger's theorem, this is equal
|
||||
to the number of node independent paths (paths that share no nodes other
|
||||
than source and target). Which is what we compute in this function.
|
||||
|
||||
This algorithm is a fast approximation that gives an strict lower
|
||||
bound on the actual number of node independent paths between two nodes [1]_.
|
||||
It works for both directed and undirected graphs.
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
nbunch: container
|
||||
Container of nodes. If provided node connectivity will be computed
|
||||
only over pairs of nodes in nbunch.
|
||||
|
||||
cutoff : integer
|
||||
Maximum node connectivity to consider. If None, the minimum degree
|
||||
of source or target is used as a cutoff in each pair of nodes.
|
||||
Default value None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
K : dictionary
|
||||
Dictionary, keyed by source and target, of pairwise node connectivity
|
||||
|
||||
Examples
|
||||
--------
|
||||
A 3 node cycle with one extra node attached has connectivity 2 between all
|
||||
nodes in the cycle and connectivity 1 between the extra node and the rest:
|
||||
|
||||
>>> G = nx.cycle_graph(3)
|
||||
>>> G.add_edge(2, 3)
|
||||
>>> import pprint # for nice dictionary formatting
|
||||
>>> pprint.pprint(nx.all_pairs_node_connectivity(G))
|
||||
{0: {1: 2, 2: 2, 3: 1},
|
||||
1: {0: 2, 2: 2, 3: 1},
|
||||
2: {0: 2, 1: 2, 3: 1},
|
||||
3: {0: 1, 1: 1, 2: 1}}
|
||||
|
||||
See Also
|
||||
--------
|
||||
local_node_connectivity
|
||||
node_connectivity
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for
|
||||
Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035
|
||||
http://eclectic.ss.uci.edu/~drwhite/working.pdf
|
||||
"""
|
||||
if nbunch is None:
|
||||
nbunch = G
|
||||
else:
|
||||
nbunch = set(nbunch)
|
||||
|
||||
directed = G.is_directed()
|
||||
if directed:
|
||||
iter_func = itertools.permutations
|
||||
else:
|
||||
iter_func = itertools.combinations
|
||||
|
||||
all_pairs = {n: {} for n in nbunch}
|
||||
|
||||
for u, v in iter_func(nbunch, 2):
|
||||
k = local_node_connectivity(G, u, v, cutoff=cutoff)
|
||||
all_pairs[u][v] = k
|
||||
if not directed:
|
||||
all_pairs[v][u] = k
|
||||
|
||||
return all_pairs
|
||||
|
||||
|
||||
def _bidirectional_shortest_path(G, source, target, exclude):
|
||||
"""Returns shortest path between source and target ignoring nodes in the
|
||||
container 'exclude'.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
G : NetworkX graph
|
||||
|
||||
source : node
|
||||
Starting node for path
|
||||
|
||||
target : node
|
||||
Ending node for path
|
||||
|
||||
exclude: container
|
||||
Container for nodes to exclude from the search for shortest paths
|
||||
|
||||
Returns
|
||||
-------
|
||||
path: list
|
||||
Shortest path between source and target ignoring nodes in 'exclude'
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNoPath
|
||||
If there is no path or if nodes are adjacent and have only one path
|
||||
between them
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function and its helper are originally from
|
||||
networkx.algorithms.shortest_paths.unweighted and are modified to
|
||||
accept the extra parameter 'exclude', which is a container for nodes
|
||||
already used in other paths that should be ignored.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for
|
||||
Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035
|
||||
http://eclectic.ss.uci.edu/~drwhite/working.pdf
|
||||
|
||||
"""
|
||||
# call helper to do the real work
|
||||
results = _bidirectional_pred_succ(G, source, target, exclude)
|
||||
pred, succ, w = results
|
||||
|
||||
# build path from pred+w+succ
|
||||
path = []
|
||||
# from source to w
|
||||
while w is not None:
|
||||
path.append(w)
|
||||
w = pred[w]
|
||||
path.reverse()
|
||||
# from w to target
|
||||
w = succ[path[-1]]
|
||||
while w is not None:
|
||||
path.append(w)
|
||||
w = succ[w]
|
||||
|
||||
return path
|
||||
|
||||
|
||||
def _bidirectional_pred_succ(G, source, target, exclude):
|
||||
# does BFS from both source and target and meets in the middle
|
||||
# excludes nodes in the container "exclude" from the search
|
||||
if source is None or target is None:
|
||||
raise nx.NetworkXException(
|
||||
"Bidirectional shortest path called without source or target"
|
||||
)
|
||||
if target == source:
|
||||
return ({target: None}, {source: None}, source)
|
||||
|
||||
# handle either directed or undirected
|
||||
if G.is_directed():
|
||||
Gpred = G.predecessors
|
||||
Gsucc = G.successors
|
||||
else:
|
||||
Gpred = G.neighbors
|
||||
Gsucc = G.neighbors
|
||||
|
||||
# predecesssor and successors in search
|
||||
pred = {source: None}
|
||||
succ = {target: None}
|
||||
|
||||
# initialize fringes, start with forward
|
||||
forward_fringe = [source]
|
||||
reverse_fringe = [target]
|
||||
|
||||
level = 0
|
||||
|
||||
while forward_fringe and reverse_fringe:
|
||||
# Make sure that we iterate one step forward and one step backwards
|
||||
# thus source and target will only trigger "found path" when they are
|
||||
# adjacent and then they can be safely included in the container 'exclude'
|
||||
level += 1
|
||||
if level % 2 != 0:
|
||||
this_level = forward_fringe
|
||||
forward_fringe = []
|
||||
for v in this_level:
|
||||
for w in Gsucc(v):
|
||||
if w in exclude:
|
||||
continue
|
||||
if w not in pred:
|
||||
forward_fringe.append(w)
|
||||
pred[w] = v
|
||||
if w in succ:
|
||||
return pred, succ, w # found path
|
||||
else:
|
||||
this_level = reverse_fringe
|
||||
reverse_fringe = []
|
||||
for v in this_level:
|
||||
for w in Gpred(v):
|
||||
if w in exclude:
|
||||
continue
|
||||
if w not in succ:
|
||||
succ[w] = v
|
||||
reverse_fringe.append(w)
|
||||
if w in pred:
|
||||
return pred, succ, w # found path
|
||||
|
||||
raise nx.NetworkXNoPath(f"No path between {source} and {target}.")
|
||||
140
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/distance_measures.py
vendored
Normal file
140
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/distance_measures.py
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
"""Distance measures approximated metrics."""
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils.decorators import py_random_state
|
||||
|
||||
__all__ = ["diameter"]
|
||||
|
||||
|
||||
@py_random_state(1)
|
||||
def diameter(G, seed=None):
|
||||
"""Returns a lower bound on the diameter of the graph G.
|
||||
|
||||
The function computes a lower bound on the diameter (i.e., the maximum eccentricity)
|
||||
of a directed or undirected graph G. The procedure used varies depending on the graph
|
||||
being directed or not.
|
||||
|
||||
If G is an `undirected` graph, then the function uses the `2-sweep` algorithm [1]_.
|
||||
The main idea is to pick the farthest node from a random node and return its eccentricity.
|
||||
|
||||
Otherwise, if G is a `directed` graph, the function uses the `2-dSweep` algorithm [2]_,
|
||||
The procedure starts by selecting a random source node $s$ from which it performs a
|
||||
forward and a backward BFS. Let $a_1$ and $a_2$ be the farthest nodes in the forward and
|
||||
backward cases, respectively. Then, it computes the backward eccentricity of $a_1$ using
|
||||
a backward BFS and the forward eccentricity of $a_2$ using a forward BFS.
|
||||
Finally, it returns the best lower bound between the two.
|
||||
|
||||
In both cases, the time complexity is linear with respect to the size of G.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
seed : integer, random_state, or None (default)
|
||||
Indicator of random number generation state.
|
||||
See :ref:`Randomness<randomness>`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
d : integer
|
||||
Lower Bound on the Diameter of G
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the graph is empty or
|
||||
If the graph is undirected and not connected or
|
||||
If the graph is directed and not strongly connected.
|
||||
|
||||
See Also
|
||||
--------
|
||||
networkx.algorithms.distance_measures.diameter
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Magnien, Clémence, Matthieu Latapy, and Michel Habib.
|
||||
*Fast computation of empirically tight bounds for the diameter of massive graphs.*
|
||||
Journal of Experimental Algorithmics (JEA), 2009.
|
||||
https://arxiv.org/pdf/0904.2728.pdf
|
||||
.. [2] Crescenzi, Pierluigi, Roberto Grossi, Leonardo Lanzi, and Andrea Marino.
|
||||
*On computing the diameter of real-world directed (weighted) graphs.*
|
||||
International Symposium on Experimental Algorithms. Springer, Berlin, Heidelberg, 2012.
|
||||
https://courses.cs.ut.ee/MTAT.03.238/2014_fall/uploads/Main/diameter.pdf
|
||||
"""
|
||||
# if G is empty
|
||||
if not G:
|
||||
raise nx.NetworkXError("Expected non-empty NetworkX graph!")
|
||||
# if there's only a node
|
||||
if G.number_of_nodes() == 1:
|
||||
return 0
|
||||
# if G is directed
|
||||
if G.is_directed():
|
||||
return _two_sweep_directed(G, seed)
|
||||
# else if G is undirected
|
||||
return _two_sweep_undirected(G, seed)
|
||||
|
||||
|
||||
def _two_sweep_undirected(G, seed):
|
||||
"""Helper function for finding a lower bound on the diameter
|
||||
for undirected Graphs.
|
||||
|
||||
The idea is to pick the farthest node from a random node
|
||||
and return its eccentricity.
|
||||
|
||||
``G`` is a NetworkX undirected graph.
|
||||
|
||||
.. note::
|
||||
|
||||
``seed`` is a random.Random or numpy.random.RandomState instance
|
||||
"""
|
||||
# select a random source node
|
||||
source = seed.choice(list(G))
|
||||
# get the distances to the other nodes
|
||||
distances = nx.shortest_path_length(G, source)
|
||||
# if some nodes have not been visited, then the graph is not connected
|
||||
if len(distances) != len(G):
|
||||
raise nx.NetworkXError("Graph not connected.")
|
||||
# take a node that is (one of) the farthest nodes from the source
|
||||
*_, node = distances
|
||||
# return the eccentricity of the node
|
||||
return nx.eccentricity(G, node)
|
||||
|
||||
|
||||
def _two_sweep_directed(G, seed):
|
||||
"""Helper function for finding a lower bound on the diameter
|
||||
for directed Graphs.
|
||||
|
||||
It implements 2-dSweep, the directed version of the 2-sweep algorithm.
|
||||
The algorithm follows the following steps.
|
||||
1. Select a source node $s$ at random.
|
||||
2. Perform a forward BFS from $s$ to select a node $a_1$ at the maximum
|
||||
distance from the source, and compute $LB_1$, the backward eccentricity of $a_1$.
|
||||
3. Perform a backward BFS from $s$ to select a node $a_2$ at the maximum
|
||||
distance from the source, and compute $LB_2$, the forward eccentricity of $a_2$.
|
||||
4. Return the maximum between $LB_1$ and $LB_2$.
|
||||
|
||||
``G`` is a NetworkX directed graph.
|
||||
|
||||
.. note::
|
||||
|
||||
``seed`` is a random.Random or numpy.random.RandomState instance
|
||||
"""
|
||||
# get a new digraph G' with the edges reversed in the opposite direction
|
||||
G_reversed = G.reverse()
|
||||
# select a random source node
|
||||
source = seed.choice(list(G))
|
||||
# compute forward distances from source
|
||||
forward_distances = nx.shortest_path_length(G, source)
|
||||
# compute backward distances from source
|
||||
backward_distances = nx.shortest_path_length(G_reversed, source)
|
||||
# if either the source can't reach every node or not every node
|
||||
# can reach the source, then the graph is not strongly connected
|
||||
n = len(G)
|
||||
if len(forward_distances) != n or len(backward_distances) != n:
|
||||
raise nx.NetworkXError("DiGraph not strongly connected.")
|
||||
# take a node a_1 at the maximum distance from the source in G
|
||||
*_, a_1 = forward_distances
|
||||
# take a node a_2 at the maximum distance from the source in G_reversed
|
||||
*_, a_2 = backward_distances
|
||||
# return the max between the backward eccentricity of a_1 and the forward eccentricity of a_2
|
||||
return max(nx.eccentricity(G_reversed, a_1), nx.eccentricity(G, a_2))
|
||||
123
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/dominating_set.py
vendored
Normal file
123
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/dominating_set.py
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
"""Functions for finding node and edge dominating sets.
|
||||
|
||||
A `dominating set`_ for an undirected graph *G* with vertex set *V*
|
||||
and edge set *E* is a subset *D* of *V* such that every vertex not in
|
||||
*D* is adjacent to at least one member of *D*. An `edge dominating set`_
|
||||
is a subset *F* of *E* such that every edge not in *F* is
|
||||
incident to an endpoint of at least one edge in *F*.
|
||||
|
||||
.. _dominating set: https://en.wikipedia.org/wiki/Dominating_set
|
||||
.. _edge dominating set: https://en.wikipedia.org/wiki/Edge_dominating_set
|
||||
|
||||
"""
|
||||
|
||||
from ...utils import not_implemented_for
|
||||
from ..matching import maximal_matching
|
||||
|
||||
__all__ = ["min_weighted_dominating_set", "min_edge_dominating_set"]
|
||||
|
||||
|
||||
# TODO Why doesn't this algorithm work for directed graphs?
|
||||
@not_implemented_for("directed")
|
||||
def min_weighted_dominating_set(G, weight=None):
|
||||
r"""Returns a dominating set that approximates the minimum weight node
|
||||
dominating set.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
Undirected graph.
|
||||
|
||||
weight : string
|
||||
The node attribute storing the weight of an node. If provided,
|
||||
the node attribute with this key must be a number for each
|
||||
node. If not provided, each node is assumed to have weight one.
|
||||
|
||||
Returns
|
||||
-------
|
||||
min_weight_dominating_set : set
|
||||
A set of nodes, the sum of whose weights is no more than `(\log
|
||||
w(V)) w(V^*)`, where `w(V)` denotes the sum of the weights of
|
||||
each node in the graph and `w(V^*)` denotes the sum of the
|
||||
weights of each node in the minimum weight dominating set.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This algorithm computes an approximate minimum weighted dominating
|
||||
set for the graph `G`. The returned solution has weight `(\log
|
||||
w(V)) w(V^*)`, where `w(V)` denotes the sum of the weights of each
|
||||
node in the graph and `w(V^*)` denotes the sum of the weights of
|
||||
each node in the minimum weight dominating set for the graph.
|
||||
|
||||
This implementation of the algorithm runs in $O(m)$ time, where $m$
|
||||
is the number of edges in the graph.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Vazirani, Vijay V.
|
||||
*Approximation Algorithms*.
|
||||
Springer Science & Business Media, 2001.
|
||||
|
||||
"""
|
||||
# The unique dominating set for the null graph is the empty set.
|
||||
if len(G) == 0:
|
||||
return set()
|
||||
|
||||
# This is the dominating set that will eventually be returned.
|
||||
dom_set = set()
|
||||
|
||||
def _cost(node_and_neighborhood):
|
||||
"""Returns the cost-effectiveness of greedily choosing the given
|
||||
node.
|
||||
|
||||
`node_and_neighborhood` is a two-tuple comprising a node and its
|
||||
closed neighborhood.
|
||||
|
||||
"""
|
||||
v, neighborhood = node_and_neighborhood
|
||||
return G.nodes[v].get(weight, 1) / len(neighborhood - dom_set)
|
||||
|
||||
# This is a set of all vertices not already covered by the
|
||||
# dominating set.
|
||||
vertices = set(G)
|
||||
# This is a dictionary mapping each node to the closed neighborhood
|
||||
# of that node.
|
||||
neighborhoods = {v: {v} | set(G[v]) for v in G}
|
||||
|
||||
# Continue until all vertices are adjacent to some node in the
|
||||
# dominating set.
|
||||
while vertices:
|
||||
# Find the most cost-effective node to add, along with its
|
||||
# closed neighborhood.
|
||||
dom_node, min_set = min(neighborhoods.items(), key=_cost)
|
||||
# Add the node to the dominating set and reduce the remaining
|
||||
# set of nodes to cover.
|
||||
dom_set.add(dom_node)
|
||||
del neighborhoods[dom_node]
|
||||
vertices -= min_set
|
||||
|
||||
return dom_set
|
||||
|
||||
|
||||
def min_edge_dominating_set(G):
|
||||
r"""Returns minimum cardinality edge dominating set.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
Undirected graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
min_edge_dominating_set : set
|
||||
Returns a set of dominating edges whose size is no more than 2 * OPT.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The algorithm computes an approximate solution to the edge dominating set
|
||||
problem. The result is no more than 2 * OPT in terms of size of the set.
|
||||
Runtime of the algorithm is $O(|E|)$.
|
||||
"""
|
||||
if not G:
|
||||
raise ValueError("Expected non-empty NetworkX graph!")
|
||||
return maximal_matching(G)
|
||||
368
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/kcomponents.py
vendored
Normal file
368
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/kcomponents.py
vendored
Normal file
@@ -0,0 +1,368 @@
|
||||
""" Fast approximation for k-component structure
|
||||
"""
|
||||
import itertools
|
||||
from collections import defaultdict
|
||||
from collections.abc import Mapping
|
||||
from functools import cached_property
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.approximation import local_node_connectivity
|
||||
from networkx.exception import NetworkXError
|
||||
from networkx.utils import not_implemented_for
|
||||
|
||||
__all__ = ["k_components"]
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
def k_components(G, min_density=0.95):
|
||||
r"""Returns the approximate k-component structure of a graph G.
|
||||
|
||||
A `k`-component is a maximal subgraph of a graph G that has, at least,
|
||||
node connectivity `k`: we need to remove at least `k` nodes to break it
|
||||
into more components. `k`-components have an inherent hierarchical
|
||||
structure because they are nested in terms of connectivity: a connected
|
||||
graph can contain several 2-components, each of which can contain
|
||||
one or more 3-components, and so forth.
|
||||
|
||||
This implementation is based on the fast heuristics to approximate
|
||||
the `k`-component structure of a graph [1]_. Which, in turn, it is based on
|
||||
a fast approximation algorithm for finding good lower bounds of the number
|
||||
of node independent paths between two nodes [2]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
Undirected graph
|
||||
|
||||
min_density : Float
|
||||
Density relaxation threshold. Default value 0.95
|
||||
|
||||
Returns
|
||||
-------
|
||||
k_components : dict
|
||||
Dictionary with connectivity level `k` as key and a list of
|
||||
sets of nodes that form a k-component of level `k` as values.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
If G is directed.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> # Petersen graph has 10 nodes and it is triconnected, thus all
|
||||
>>> # nodes are in a single component on all three connectivity levels
|
||||
>>> from networkx.algorithms import approximation as apxa
|
||||
>>> G = nx.petersen_graph()
|
||||
>>> k_components = apxa.k_components(G)
|
||||
|
||||
Notes
|
||||
-----
|
||||
The logic of the approximation algorithm for computing the `k`-component
|
||||
structure [1]_ is based on repeatedly applying simple and fast algorithms
|
||||
for `k`-cores and biconnected components in order to narrow down the
|
||||
number of pairs of nodes over which we have to compute White and Newman's
|
||||
approximation algorithm for finding node independent paths [2]_. More
|
||||
formally, this algorithm is based on Whitney's theorem, which states
|
||||
an inclusion relation among node connectivity, edge connectivity, and
|
||||
minimum degree for any graph G. This theorem implies that every
|
||||
`k`-component is nested inside a `k`-edge-component, which in turn,
|
||||
is contained in a `k`-core. Thus, this algorithm computes node independent
|
||||
paths among pairs of nodes in each biconnected part of each `k`-core,
|
||||
and repeats this procedure for each `k` from 3 to the maximal core number
|
||||
of a node in the input graph.
|
||||
|
||||
Because, in practice, many nodes of the core of level `k` inside a
|
||||
bicomponent actually are part of a component of level k, the auxiliary
|
||||
graph needed for the algorithm is likely to be very dense. Thus, we use
|
||||
a complement graph data structure (see `AntiGraph`) to save memory.
|
||||
AntiGraph only stores information of the edges that are *not* present
|
||||
in the actual auxiliary graph. When applying algorithms to this
|
||||
complement graph data structure, it behaves as if it were the dense
|
||||
version.
|
||||
|
||||
See also
|
||||
--------
|
||||
k_components
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Torrents, J. and F. Ferraro (2015) Structural Cohesion:
|
||||
Visualization and Heuristics for Fast Computation.
|
||||
https://arxiv.org/pdf/1503.04476v1
|
||||
|
||||
.. [2] White, Douglas R., and Mark Newman (2001) A Fast Algorithm for
|
||||
Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035
|
||||
https://www.santafe.edu/research/results/working-papers/fast-approximation-algorithms-for-finding-node-ind
|
||||
|
||||
.. [3] Moody, J. and D. White (2003). Social cohesion and embeddedness:
|
||||
A hierarchical conception of social groups.
|
||||
American Sociological Review 68(1), 103--28.
|
||||
https://doi.org/10.2307/3088904
|
||||
|
||||
"""
|
||||
# Dictionary with connectivity level (k) as keys and a list of
|
||||
# sets of nodes that form a k-component as values
|
||||
k_components = defaultdict(list)
|
||||
# make a few functions local for speed
|
||||
node_connectivity = local_node_connectivity
|
||||
k_core = nx.k_core
|
||||
core_number = nx.core_number
|
||||
biconnected_components = nx.biconnected_components
|
||||
combinations = itertools.combinations
|
||||
# Exact solution for k = {1,2}
|
||||
# There is a linear time algorithm for triconnectivity, if we had an
|
||||
# implementation available we could start from k = 4.
|
||||
for component in nx.connected_components(G):
|
||||
# isolated nodes have connectivity 0
|
||||
comp = set(component)
|
||||
if len(comp) > 1:
|
||||
k_components[1].append(comp)
|
||||
for bicomponent in nx.biconnected_components(G):
|
||||
# avoid considering dyads as bicomponents
|
||||
bicomp = set(bicomponent)
|
||||
if len(bicomp) > 2:
|
||||
k_components[2].append(bicomp)
|
||||
# There is no k-component of k > maximum core number
|
||||
# \kappa(G) <= \lambda(G) <= \delta(G)
|
||||
g_cnumber = core_number(G)
|
||||
max_core = max(g_cnumber.values())
|
||||
for k in range(3, max_core + 1):
|
||||
C = k_core(G, k, core_number=g_cnumber)
|
||||
for nodes in biconnected_components(C):
|
||||
# Build a subgraph SG induced by the nodes that are part of
|
||||
# each biconnected component of the k-core subgraph C.
|
||||
if len(nodes) < k:
|
||||
continue
|
||||
SG = G.subgraph(nodes)
|
||||
# Build auxiliary graph
|
||||
H = _AntiGraph()
|
||||
H.add_nodes_from(SG.nodes())
|
||||
for u, v in combinations(SG, 2):
|
||||
K = node_connectivity(SG, u, v, cutoff=k)
|
||||
if k > K:
|
||||
H.add_edge(u, v)
|
||||
for h_nodes in biconnected_components(H):
|
||||
if len(h_nodes) <= k:
|
||||
continue
|
||||
SH = H.subgraph(h_nodes)
|
||||
for Gc in _cliques_heuristic(SG, SH, k, min_density):
|
||||
for k_nodes in biconnected_components(Gc):
|
||||
Gk = nx.k_core(SG.subgraph(k_nodes), k)
|
||||
if len(Gk) <= k:
|
||||
continue
|
||||
k_components[k].append(set(Gk))
|
||||
return k_components
|
||||
|
||||
|
||||
def _cliques_heuristic(G, H, k, min_density):
|
||||
h_cnumber = nx.core_number(H)
|
||||
for i, c_value in enumerate(sorted(set(h_cnumber.values()), reverse=True)):
|
||||
cands = {n for n, c in h_cnumber.items() if c == c_value}
|
||||
# Skip checking for overlap for the highest core value
|
||||
if i == 0:
|
||||
overlap = False
|
||||
else:
|
||||
overlap = set.intersection(
|
||||
*[{x for x in H[n] if x not in cands} for n in cands]
|
||||
)
|
||||
if overlap and len(overlap) < k:
|
||||
SH = H.subgraph(cands | overlap)
|
||||
else:
|
||||
SH = H.subgraph(cands)
|
||||
sh_cnumber = nx.core_number(SH)
|
||||
SG = nx.k_core(G.subgraph(SH), k)
|
||||
while not (_same(sh_cnumber) and nx.density(SH) >= min_density):
|
||||
# This subgraph must be writable => .copy()
|
||||
SH = H.subgraph(SG).copy()
|
||||
if len(SH) <= k:
|
||||
break
|
||||
sh_cnumber = nx.core_number(SH)
|
||||
sh_deg = dict(SH.degree())
|
||||
min_deg = min(sh_deg.values())
|
||||
SH.remove_nodes_from(n for n, d in sh_deg.items() if d == min_deg)
|
||||
SG = nx.k_core(G.subgraph(SH), k)
|
||||
else:
|
||||
yield SG
|
||||
|
||||
|
||||
def _same(measure, tol=0):
|
||||
vals = set(measure.values())
|
||||
if (max(vals) - min(vals)) <= tol:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class _AntiGraph(nx.Graph):
|
||||
"""
|
||||
Class for complement graphs.
|
||||
|
||||
The main goal is to be able to work with big and dense graphs with
|
||||
a low memory footprint.
|
||||
|
||||
In this class you add the edges that *do not exist* in the dense graph,
|
||||
the report methods of the class return the neighbors, the edges and
|
||||
the degree as if it was the dense graph. Thus it's possible to use
|
||||
an instance of this class with some of NetworkX functions. In this
|
||||
case we only use k-core, connected_components, and biconnected_components.
|
||||
"""
|
||||
|
||||
all_edge_dict = {"weight": 1}
|
||||
|
||||
def single_edge_dict(self):
|
||||
return self.all_edge_dict
|
||||
|
||||
edge_attr_dict_factory = single_edge_dict # type: ignore[assignment]
|
||||
|
||||
def __getitem__(self, n):
|
||||
"""Returns a dict of neighbors of node n in the dense graph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
n : node
|
||||
A node in the graph.
|
||||
|
||||
Returns
|
||||
-------
|
||||
adj_dict : dictionary
|
||||
The adjacency dictionary for nodes connected to n.
|
||||
|
||||
"""
|
||||
all_edge_dict = self.all_edge_dict
|
||||
return {
|
||||
node: all_edge_dict for node in set(self._adj) - set(self._adj[n]) - {n}
|
||||
}
|
||||
|
||||
def neighbors(self, n):
|
||||
"""Returns an iterator over all neighbors of node n in the
|
||||
dense graph.
|
||||
"""
|
||||
try:
|
||||
return iter(set(self._adj) - set(self._adj[n]) - {n})
|
||||
except KeyError as err:
|
||||
raise NetworkXError(f"The node {n} is not in the graph.") from err
|
||||
|
||||
class AntiAtlasView(Mapping):
|
||||
"""An adjacency inner dict for AntiGraph"""
|
||||
|
||||
def __init__(self, graph, node):
|
||||
self._graph = graph
|
||||
self._atlas = graph._adj[node]
|
||||
self._node = node
|
||||
|
||||
def __len__(self):
|
||||
return len(self._graph) - len(self._atlas) - 1
|
||||
|
||||
def __iter__(self):
|
||||
return (n for n in self._graph if n not in self._atlas and n != self._node)
|
||||
|
||||
def __getitem__(self, nbr):
|
||||
nbrs = set(self._graph._adj) - set(self._atlas) - {self._node}
|
||||
if nbr in nbrs:
|
||||
return self._graph.all_edge_dict
|
||||
raise KeyError(nbr)
|
||||
|
||||
class AntiAdjacencyView(AntiAtlasView):
|
||||
"""An adjacency outer dict for AntiGraph"""
|
||||
|
||||
def __init__(self, graph):
|
||||
self._graph = graph
|
||||
self._atlas = graph._adj
|
||||
|
||||
def __len__(self):
|
||||
return len(self._atlas)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._graph)
|
||||
|
||||
def __getitem__(self, node):
|
||||
if node not in self._graph:
|
||||
raise KeyError(node)
|
||||
return self._graph.AntiAtlasView(self._graph, node)
|
||||
|
||||
@cached_property
|
||||
def adj(self):
|
||||
return self.AntiAdjacencyView(self)
|
||||
|
||||
def subgraph(self, nodes):
|
||||
"""This subgraph method returns a full AntiGraph. Not a View"""
|
||||
nodes = set(nodes)
|
||||
G = _AntiGraph()
|
||||
G.add_nodes_from(nodes)
|
||||
for n in G:
|
||||
Gnbrs = G.adjlist_inner_dict_factory()
|
||||
G._adj[n] = Gnbrs
|
||||
for nbr, d in self._adj[n].items():
|
||||
if nbr in G._adj:
|
||||
Gnbrs[nbr] = d
|
||||
G._adj[nbr][n] = d
|
||||
G.graph = self.graph
|
||||
return G
|
||||
|
||||
class AntiDegreeView(nx.reportviews.DegreeView):
|
||||
def __iter__(self):
|
||||
all_nodes = set(self._succ)
|
||||
for n in self._nodes:
|
||||
nbrs = all_nodes - set(self._succ[n]) - {n}
|
||||
yield (n, len(nbrs))
|
||||
|
||||
def __getitem__(self, n):
|
||||
nbrs = set(self._succ) - set(self._succ[n]) - {n}
|
||||
# AntiGraph is a ThinGraph so all edges have weight 1
|
||||
return len(nbrs) + (n in nbrs)
|
||||
|
||||
@cached_property
|
||||
def degree(self):
|
||||
"""Returns an iterator for (node, degree) and degree for single node.
|
||||
|
||||
The node degree is the number of edges adjacent to the node.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nbunch : iterable container, optional (default=all nodes)
|
||||
A container of nodes. The container will be iterated
|
||||
through once.
|
||||
|
||||
weight : string or None, optional (default=None)
|
||||
The edge attribute that holds the numerical value used
|
||||
as a weight. If None, then each edge has weight 1.
|
||||
The degree is the sum of the edge weights adjacent to the node.
|
||||
|
||||
Returns
|
||||
-------
|
||||
deg:
|
||||
Degree of the node, if a single node is passed as argument.
|
||||
nd_iter : an iterator
|
||||
The iterator returns two-tuples of (node, degree).
|
||||
|
||||
See Also
|
||||
--------
|
||||
degree
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> G.degree(0) # node 0 with degree 1
|
||||
1
|
||||
>>> list(G.degree([0, 1]))
|
||||
[(0, 1), (1, 2)]
|
||||
|
||||
"""
|
||||
return self.AntiDegreeView(self)
|
||||
|
||||
def adjacency(self):
|
||||
"""Returns an iterator of (node, adjacency set) tuples for all nodes
|
||||
in the dense graph.
|
||||
|
||||
This is the fastest way to look at every edge.
|
||||
For directed graphs, only outgoing adjacencies are included.
|
||||
|
||||
Returns
|
||||
-------
|
||||
adj_iter : iterator
|
||||
An iterator of (node, adjacency set) for all nodes in
|
||||
the graph.
|
||||
|
||||
"""
|
||||
for n in self._adj:
|
||||
yield (n, set(self._adj) - set(self._adj[n]) - {n})
|
||||
42
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/matching.py
vendored
Normal file
42
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/matching.py
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
**************
|
||||
Graph Matching
|
||||
**************
|
||||
|
||||
Given a graph G = (V,E), a matching M in G is a set of pairwise non-adjacent
|
||||
edges; that is, no two edges share a common vertex.
|
||||
|
||||
`Wikipedia: Matching <https://en.wikipedia.org/wiki/Matching_(graph_theory)>`_
|
||||
"""
|
||||
import networkx as nx
|
||||
|
||||
__all__ = ["min_maximal_matching"]
|
||||
|
||||
|
||||
def min_maximal_matching(G):
|
||||
r"""Returns the minimum maximal matching of G. That is, out of all maximal
|
||||
matchings of the graph G, the smallest is returned.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
Undirected graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
min_maximal_matching : set
|
||||
Returns a set of edges such that no two edges share a common endpoint
|
||||
and every edge not in the set shares some common endpoint in the set.
|
||||
Cardinality will be 2*OPT in the worst case.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The algorithm computes an approximate solution fo the minimum maximal
|
||||
cardinality matching problem. The solution is no more than 2 * OPT in size.
|
||||
Runtime is $O(|E|)$.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Vazirani, Vijay Approximation Algorithms (2001)
|
||||
"""
|
||||
return nx.maximal_matching(G)
|
||||
111
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/maxcut.py
vendored
Normal file
111
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/maxcut.py
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
import networkx as nx
|
||||
from networkx.utils.decorators import not_implemented_for, py_random_state
|
||||
|
||||
__all__ = ["randomized_partitioning", "one_exchange"]
|
||||
|
||||
|
||||
@not_implemented_for("directed", "multigraph")
|
||||
@py_random_state(1)
|
||||
def randomized_partitioning(G, seed=None, p=0.5, weight=None):
|
||||
"""Compute a random partitioning of the graph nodes and its cut value.
|
||||
|
||||
A partitioning is calculated by observing each node
|
||||
and deciding to add it to the partition with probability `p`,
|
||||
returning a random cut and its corresponding value (the
|
||||
sum of weights of edges connecting different partitions).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
seed : integer, random_state, or None (default)
|
||||
Indicator of random number generation state.
|
||||
See :ref:`Randomness<randomness>`.
|
||||
|
||||
p : scalar
|
||||
Probability for each node to be part of the first partition.
|
||||
Should be in [0,1]
|
||||
|
||||
weight : object
|
||||
Edge attribute key to use as weight. If not specified, edges
|
||||
have weight one.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cut_size : scalar
|
||||
Value of the minimum cut.
|
||||
|
||||
partition : pair of node sets
|
||||
A partitioning of the nodes that defines a minimum cut.
|
||||
"""
|
||||
cut = {node for node in G.nodes() if seed.random() < p}
|
||||
cut_size = nx.algorithms.cut_size(G, cut, weight=weight)
|
||||
partition = (cut, G.nodes - cut)
|
||||
return cut_size, partition
|
||||
|
||||
|
||||
def _swap_node_partition(cut, node):
|
||||
return cut - {node} if node in cut else cut.union({node})
|
||||
|
||||
|
||||
@not_implemented_for("directed", "multigraph")
|
||||
@py_random_state(2)
|
||||
def one_exchange(G, initial_cut=None, seed=None, weight=None):
|
||||
"""Compute a partitioning of the graphs nodes and the corresponding cut value.
|
||||
|
||||
Use a greedy one exchange strategy to find a locally maximal cut
|
||||
and its value, it works by finding the best node (one that gives
|
||||
the highest gain to the cut value) to add to the current cut
|
||||
and repeats this process until no improvement can be made.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : networkx Graph
|
||||
Graph to find a maximum cut for.
|
||||
|
||||
initial_cut : set
|
||||
Cut to use as a starting point. If not supplied the algorithm
|
||||
starts with an empty cut.
|
||||
|
||||
seed : integer, random_state, or None (default)
|
||||
Indicator of random number generation state.
|
||||
See :ref:`Randomness<randomness>`.
|
||||
|
||||
weight : object
|
||||
Edge attribute key to use as weight. If not specified, edges
|
||||
have weight one.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cut_value : scalar
|
||||
Value of the maximum cut.
|
||||
|
||||
partition : pair of node sets
|
||||
A partitioning of the nodes that defines a maximum cut.
|
||||
"""
|
||||
if initial_cut is None:
|
||||
initial_cut = set()
|
||||
cut = set(initial_cut)
|
||||
current_cut_size = nx.algorithms.cut_size(G, cut, weight=weight)
|
||||
while True:
|
||||
nodes = list(G.nodes())
|
||||
# Shuffling the nodes ensures random tie-breaks in the following call to max
|
||||
seed.shuffle(nodes)
|
||||
best_node_to_swap = max(
|
||||
nodes,
|
||||
key=lambda v: nx.algorithms.cut_size(
|
||||
G, _swap_node_partition(cut, v), weight=weight
|
||||
),
|
||||
default=None,
|
||||
)
|
||||
potential_cut = _swap_node_partition(cut, best_node_to_swap)
|
||||
potential_cut_size = nx.algorithms.cut_size(G, potential_cut, weight=weight)
|
||||
|
||||
if potential_cut_size > current_cut_size:
|
||||
cut = potential_cut
|
||||
current_cut_size = potential_cut_size
|
||||
else:
|
||||
break
|
||||
|
||||
partition = (cut, G.nodes - cut)
|
||||
return current_cut_size, partition
|
||||
51
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/ramsey.py
vendored
Normal file
51
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/ramsey.py
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
Ramsey numbers.
|
||||
"""
|
||||
import networkx as nx
|
||||
from networkx.utils import not_implemented_for
|
||||
|
||||
from ...utils import arbitrary_element
|
||||
|
||||
__all__ = ["ramsey_R2"]
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
@not_implemented_for("multigraph")
|
||||
def ramsey_R2(G):
|
||||
r"""Compute the largest clique and largest independent set in `G`.
|
||||
|
||||
This can be used to estimate bounds for the 2-color
|
||||
Ramsey number `R(2;s,t)` for `G`.
|
||||
|
||||
This is a recursive implementation which could run into trouble
|
||||
for large recursions. Note that self-loop edges are ignored.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
Undirected graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
max_pair : (set, set) tuple
|
||||
Maximum clique, Maximum independent set.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
If the graph is directed or is a multigraph.
|
||||
"""
|
||||
if not G:
|
||||
return set(), set()
|
||||
|
||||
node = arbitrary_element(G)
|
||||
nbrs = (nbr for nbr in nx.all_neighbors(G, node) if nbr != node)
|
||||
nnbrs = nx.non_neighbors(G, node)
|
||||
c_1, i_1 = ramsey_R2(G.subgraph(nbrs).copy())
|
||||
c_2, i_2 = ramsey_R2(G.subgraph(nnbrs).copy())
|
||||
|
||||
c_1.add(node)
|
||||
i_2.add(node)
|
||||
# Choose the larger of the two cliques and the larger of the two
|
||||
# independent sets, according to cardinality.
|
||||
return max(c_1, c_2, key=len), max(i_1, i_2, key=len)
|
||||
218
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/steinertree.py
vendored
Normal file
218
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/steinertree.py
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
from itertools import chain
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import not_implemented_for, pairwise
|
||||
|
||||
__all__ = ["metric_closure", "steiner_tree"]
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
def metric_closure(G, weight="weight"):
|
||||
"""Return the metric closure of a graph.
|
||||
|
||||
The metric closure of a graph *G* is the complete graph in which each edge
|
||||
is weighted by the shortest path distance between the nodes in *G* .
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
NetworkX graph
|
||||
Metric closure of the graph `G`.
|
||||
|
||||
"""
|
||||
M = nx.Graph()
|
||||
|
||||
Gnodes = set(G)
|
||||
|
||||
# check for connected graph while processing first node
|
||||
all_paths_iter = nx.all_pairs_dijkstra(G, weight=weight)
|
||||
u, (distance, path) = next(all_paths_iter)
|
||||
if Gnodes - set(distance):
|
||||
msg = "G is not a connected graph. metric_closure is not defined."
|
||||
raise nx.NetworkXError(msg)
|
||||
Gnodes.remove(u)
|
||||
for v in Gnodes:
|
||||
M.add_edge(u, v, distance=distance[v], path=path[v])
|
||||
|
||||
# first node done -- now process the rest
|
||||
for u, (distance, path) in all_paths_iter:
|
||||
Gnodes.remove(u)
|
||||
for v in Gnodes:
|
||||
M.add_edge(u, v, distance=distance[v], path=path[v])
|
||||
|
||||
return M
|
||||
|
||||
|
||||
def _mehlhorn_steiner_tree(G, terminal_nodes, weight):
|
||||
paths = nx.multi_source_dijkstra_path(G, terminal_nodes)
|
||||
|
||||
d_1 = {}
|
||||
s = {}
|
||||
for v in G.nodes():
|
||||
s[v] = paths[v][0]
|
||||
d_1[(v, s[v])] = len(paths[v]) - 1
|
||||
|
||||
# G1-G4 names match those from the Mehlhorn 1988 paper.
|
||||
G_1_prime = nx.Graph()
|
||||
for u, v, data in G.edges(data=True):
|
||||
su, sv = s[u], s[v]
|
||||
weight_here = d_1[(u, su)] + data.get(weight, 1) + d_1[(v, sv)]
|
||||
if not G_1_prime.has_edge(su, sv):
|
||||
G_1_prime.add_edge(su, sv, weight=weight_here)
|
||||
else:
|
||||
new_weight = min(weight_here, G_1_prime[su][sv][weight])
|
||||
G_1_prime.add_edge(su, sv, weight=new_weight)
|
||||
|
||||
G_2 = nx.minimum_spanning_edges(G_1_prime, data=True)
|
||||
|
||||
G_3 = nx.Graph()
|
||||
for u, v, d in G_2:
|
||||
path = nx.shortest_path(G, u, v, weight)
|
||||
for n1, n2 in pairwise(path):
|
||||
G_3.add_edge(n1, n2)
|
||||
|
||||
G_3_mst = list(nx.minimum_spanning_edges(G_3, data=False))
|
||||
if G.is_multigraph():
|
||||
G_3_mst = (
|
||||
(u, v, min(G[u][v], key=lambda k: G[u][v][k][weight])) for u, v in G_3_mst
|
||||
)
|
||||
G_4 = G.edge_subgraph(G_3_mst).copy()
|
||||
_remove_nonterminal_leaves(G_4, terminal_nodes)
|
||||
return G_4.edges()
|
||||
|
||||
|
||||
def _kou_steiner_tree(G, terminal_nodes, weight):
|
||||
# H is the subgraph induced by terminal_nodes in the metric closure M of G.
|
||||
M = metric_closure(G, weight=weight)
|
||||
H = M.subgraph(terminal_nodes)
|
||||
|
||||
# Use the 'distance' attribute of each edge provided by M.
|
||||
mst_edges = nx.minimum_spanning_edges(H, weight="distance", data=True)
|
||||
|
||||
# Create an iterator over each edge in each shortest path; repeats are okay
|
||||
mst_all_edges = chain.from_iterable(pairwise(d["path"]) for u, v, d in mst_edges)
|
||||
if G.is_multigraph():
|
||||
mst_all_edges = (
|
||||
(u, v, min(G[u][v], key=lambda k: G[u][v][k][weight]))
|
||||
for u, v in mst_all_edges
|
||||
)
|
||||
|
||||
# Find the MST again, over this new set of edges
|
||||
G_S = G.edge_subgraph(mst_all_edges)
|
||||
T_S = nx.minimum_spanning_edges(G_S, weight="weight", data=False)
|
||||
|
||||
# Leaf nodes that are not terminal might still remain; remove them here
|
||||
T_H = G.edge_subgraph(T_S).copy()
|
||||
_remove_nonterminal_leaves(T_H, terminal_nodes)
|
||||
|
||||
return T_H.edges()
|
||||
|
||||
|
||||
def _remove_nonterminal_leaves(G, terminals):
|
||||
terminals_set = set(terminals)
|
||||
for n in list(G.nodes):
|
||||
if n not in terminals_set and G.degree(n) == 1:
|
||||
G.remove_node(n)
|
||||
|
||||
|
||||
ALGORITHMS = {
|
||||
"kou": _kou_steiner_tree,
|
||||
"mehlhorn": _mehlhorn_steiner_tree,
|
||||
}
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
def steiner_tree(G, terminal_nodes, weight="weight", method=None):
|
||||
r"""Return an approximation to the minimum Steiner tree of a graph.
|
||||
|
||||
The minimum Steiner tree of `G` w.r.t a set of `terminal_nodes` (also *S*)
|
||||
is a tree within `G` that spans those nodes and has minimum size (sum of
|
||||
edge weights) among all such trees.
|
||||
|
||||
The approximation algorithm is specified with the `method` keyword
|
||||
argument. All three available algorithms produce a tree whose weight is
|
||||
within a ``(2 - (2 / l))`` factor of the weight of the optimal Steiner tree,
|
||||
where ``l`` is the minimum number of leaf nodes across all possible Steiner
|
||||
trees.
|
||||
|
||||
* ``"kou"`` [2]_ (runtime $O(|S| |V|^2)$) computes the minimum spanning tree of
|
||||
the subgraph of the metric closure of *G* induced by the terminal nodes,
|
||||
where the metric closure of *G* is the complete graph in which each edge is
|
||||
weighted by the shortest path distance between the nodes in *G*.
|
||||
|
||||
* ``"mehlhorn"`` [3]_ (runtime $O(|E|+|V|\log|V|)$) modifies Kou et al.'s
|
||||
algorithm, beginning by finding the closest terminal node for each
|
||||
non-terminal. This data is used to create a complete graph containing only
|
||||
the terminal nodes, in which edge is weighted with the shortest path
|
||||
distance between them. The algorithm then proceeds in the same way as Kou
|
||||
et al..
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
terminal_nodes : list
|
||||
A list of terminal nodes for which minimum steiner tree is
|
||||
to be found.
|
||||
|
||||
weight : string (default = 'weight')
|
||||
Use the edge attribute specified by this string as the edge weight.
|
||||
Any edge attribute not present defaults to 1.
|
||||
|
||||
method : string, optional (default = 'kou')
|
||||
The algorithm to use to approximate the Steiner tree.
|
||||
Supported options: 'kou', 'mehlhorn'.
|
||||
Other inputs produce a ValueError.
|
||||
|
||||
Returns
|
||||
-------
|
||||
NetworkX graph
|
||||
Approximation to the minimum steiner tree of `G` induced by
|
||||
`terminal_nodes` .
|
||||
|
||||
Notes
|
||||
-----
|
||||
For multigraphs, the edge between two nodes with minimum weight is the
|
||||
edge put into the Steiner tree.
|
||||
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Steiner_tree_problem on Wikipedia.
|
||||
https://en.wikipedia.org/wiki/Steiner_tree_problem
|
||||
.. [2] Kou, L., G. Markowsky, and L. Berman. 1981.
|
||||
‘A Fast Algorithm for Steiner Trees’.
|
||||
Acta Informatica 15 (2): 141–45.
|
||||
https://doi.org/10.1007/BF00288961.
|
||||
.. [3] Mehlhorn, Kurt. 1988.
|
||||
‘A Faster Approximation Algorithm for the Steiner Problem in Graphs’.
|
||||
Information Processing Letters 27 (3): 125–28.
|
||||
https://doi.org/10.1016/0020-0190(88)90066-X.
|
||||
"""
|
||||
if method is None:
|
||||
import warnings
|
||||
|
||||
msg = (
|
||||
"steiner_tree will change default method from 'kou' to 'mehlhorn'"
|
||||
"in version 3.2.\nSet the `method` kwarg to remove this warning."
|
||||
)
|
||||
warnings.warn(msg, FutureWarning, stacklevel=4)
|
||||
method = "kou"
|
||||
|
||||
try:
|
||||
algo = ALGORITHMS[method]
|
||||
except KeyError as e:
|
||||
msg = f"{method} is not a valid choice for an algorithm."
|
||||
raise ValueError(msg) from e
|
||||
|
||||
edges = algo(G, terminal_nodes, weight)
|
||||
# For multigraph we should add the minimal weight edge keys
|
||||
if G.is_multigraph():
|
||||
edges = (
|
||||
(u, v, min(G[u][v], key=lambda k: G[u][v][k][weight])) for u, v in edges
|
||||
)
|
||||
T = G.edge_subgraph(edges)
|
||||
return T
|
||||
0
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/__init__.py
vendored
Normal file
0
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/__init__.py
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,41 @@
|
||||
import networkx as nx
|
||||
from networkx.algorithms.approximation import average_clustering
|
||||
|
||||
# This approximation has to be exact in regular graphs
|
||||
# with no triangles or with all possible triangles.
|
||||
|
||||
|
||||
def test_petersen():
|
||||
# Actual coefficient is 0
|
||||
G = nx.petersen_graph()
|
||||
assert average_clustering(G, trials=len(G) // 2) == nx.average_clustering(G)
|
||||
|
||||
|
||||
def test_petersen_seed():
|
||||
# Actual coefficient is 0
|
||||
G = nx.petersen_graph()
|
||||
assert average_clustering(G, trials=len(G) // 2, seed=1) == nx.average_clustering(G)
|
||||
|
||||
|
||||
def test_tetrahedral():
|
||||
# Actual coefficient is 1
|
||||
G = nx.tetrahedral_graph()
|
||||
assert average_clustering(G, trials=len(G) // 2) == nx.average_clustering(G)
|
||||
|
||||
|
||||
def test_dodecahedral():
|
||||
# Actual coefficient is 0
|
||||
G = nx.dodecahedral_graph()
|
||||
assert average_clustering(G, trials=len(G) // 2) == nx.average_clustering(G)
|
||||
|
||||
|
||||
def test_empty():
|
||||
G = nx.empty_graph(5)
|
||||
assert average_clustering(G, trials=len(G) // 2) == 0
|
||||
|
||||
|
||||
def test_complete():
|
||||
G = nx.complete_graph(5)
|
||||
assert average_clustering(G, trials=len(G) // 2) == 1
|
||||
G = nx.complete_graph(7)
|
||||
assert average_clustering(G, trials=len(G) // 2) == 1
|
||||
113
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_clique.py
vendored
Normal file
113
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_clique.py
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
"""Unit tests for the :mod:`networkx.algorithms.approximation.clique` module."""
|
||||
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.approximation import (
|
||||
clique_removal,
|
||||
large_clique_size,
|
||||
max_clique,
|
||||
maximum_independent_set,
|
||||
)
|
||||
|
||||
|
||||
def is_independent_set(G, nodes):
|
||||
"""Returns True if and only if `nodes` is a clique in `G`.
|
||||
|
||||
`G` is a NetworkX graph. `nodes` is an iterable of nodes in
|
||||
`G`.
|
||||
|
||||
"""
|
||||
return G.subgraph(nodes).number_of_edges() == 0
|
||||
|
||||
|
||||
def is_clique(G, nodes):
|
||||
"""Returns True if and only if `nodes` is an independent set
|
||||
in `G`.
|
||||
|
||||
`G` is an undirected simple graph. `nodes` is an iterable of
|
||||
nodes in `G`.
|
||||
|
||||
"""
|
||||
H = G.subgraph(nodes)
|
||||
n = len(H)
|
||||
return H.number_of_edges() == n * (n - 1) // 2
|
||||
|
||||
|
||||
class TestCliqueRemoval:
|
||||
"""Unit tests for the
|
||||
:func:`~networkx.algorithms.approximation.clique_removal` function.
|
||||
|
||||
"""
|
||||
|
||||
def test_trivial_graph(self):
|
||||
G = nx.trivial_graph()
|
||||
independent_set, cliques = clique_removal(G)
|
||||
assert is_independent_set(G, independent_set)
|
||||
assert all(is_clique(G, clique) for clique in cliques)
|
||||
# In fact, we should only have 1-cliques, that is, singleton nodes.
|
||||
assert all(len(clique) == 1 for clique in cliques)
|
||||
|
||||
def test_complete_graph(self):
|
||||
G = nx.complete_graph(10)
|
||||
independent_set, cliques = clique_removal(G)
|
||||
assert is_independent_set(G, independent_set)
|
||||
assert all(is_clique(G, clique) for clique in cliques)
|
||||
|
||||
def test_barbell_graph(self):
|
||||
G = nx.barbell_graph(10, 5)
|
||||
independent_set, cliques = clique_removal(G)
|
||||
assert is_independent_set(G, independent_set)
|
||||
assert all(is_clique(G, clique) for clique in cliques)
|
||||
|
||||
|
||||
class TestMaxClique:
|
||||
"""Unit tests for the :func:`networkx.algorithms.approximation.max_clique`
|
||||
function.
|
||||
|
||||
"""
|
||||
|
||||
def test_null_graph(self):
|
||||
G = nx.null_graph()
|
||||
assert len(max_clique(G)) == 0
|
||||
|
||||
def test_complete_graph(self):
|
||||
graph = nx.complete_graph(30)
|
||||
# this should return the entire graph
|
||||
mc = max_clique(graph)
|
||||
assert 30 == len(mc)
|
||||
|
||||
def test_maximal_by_cardinality(self):
|
||||
"""Tests that the maximal clique is computed according to maximum
|
||||
cardinality of the sets.
|
||||
|
||||
For more information, see pull request #1531.
|
||||
|
||||
"""
|
||||
G = nx.complete_graph(5)
|
||||
G.add_edge(4, 5)
|
||||
clique = max_clique(G)
|
||||
assert len(clique) > 1
|
||||
|
||||
G = nx.lollipop_graph(30, 2)
|
||||
clique = max_clique(G)
|
||||
assert len(clique) > 2
|
||||
|
||||
|
||||
def test_large_clique_size():
|
||||
G = nx.complete_graph(9)
|
||||
nx.add_cycle(G, [9, 10, 11])
|
||||
G.add_edge(8, 9)
|
||||
G.add_edge(1, 12)
|
||||
G.add_node(13)
|
||||
|
||||
assert large_clique_size(G) == 9
|
||||
G.remove_node(5)
|
||||
assert large_clique_size(G) == 8
|
||||
G.remove_edge(2, 3)
|
||||
assert large_clique_size(G) == 7
|
||||
|
||||
|
||||
def test_independent_set():
|
||||
# smoke test
|
||||
G = nx.Graph()
|
||||
assert len(maximum_independent_set(G)) == 0
|
||||
199
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_connectivity.py
vendored
Normal file
199
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_connectivity.py
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms import approximation as approx
|
||||
|
||||
|
||||
def test_global_node_connectivity():
|
||||
# Figure 1 chapter on Connectivity
|
||||
G = nx.Graph()
|
||||
G.add_edges_from(
|
||||
[
|
||||
(1, 2),
|
||||
(1, 3),
|
||||
(1, 4),
|
||||
(1, 5),
|
||||
(2, 3),
|
||||
(2, 6),
|
||||
(3, 4),
|
||||
(3, 6),
|
||||
(4, 6),
|
||||
(4, 7),
|
||||
(5, 7),
|
||||
(6, 8),
|
||||
(6, 9),
|
||||
(7, 8),
|
||||
(7, 10),
|
||||
(8, 11),
|
||||
(9, 10),
|
||||
(9, 11),
|
||||
(10, 11),
|
||||
]
|
||||
)
|
||||
assert 2 == approx.local_node_connectivity(G, 1, 11)
|
||||
assert 2 == approx.node_connectivity(G)
|
||||
assert 2 == approx.node_connectivity(G, 1, 11)
|
||||
|
||||
|
||||
def test_white_harary1():
|
||||
# Figure 1b white and harary (2001)
|
||||
# A graph with high adhesion (edge connectivity) and low cohesion
|
||||
# (node connectivity)
|
||||
G = nx.disjoint_union(nx.complete_graph(4), nx.complete_graph(4))
|
||||
G.remove_node(7)
|
||||
for i in range(4, 7):
|
||||
G.add_edge(0, i)
|
||||
G = nx.disjoint_union(G, nx.complete_graph(4))
|
||||
G.remove_node(G.order() - 1)
|
||||
for i in range(7, 10):
|
||||
G.add_edge(0, i)
|
||||
assert 1 == approx.node_connectivity(G)
|
||||
|
||||
|
||||
def test_complete_graphs():
|
||||
for n in range(5, 25, 5):
|
||||
G = nx.complete_graph(n)
|
||||
assert n - 1 == approx.node_connectivity(G)
|
||||
assert n - 1 == approx.node_connectivity(G, 0, 3)
|
||||
|
||||
|
||||
def test_empty_graphs():
|
||||
for k in range(5, 25, 5):
|
||||
G = nx.empty_graph(k)
|
||||
assert 0 == approx.node_connectivity(G)
|
||||
assert 0 == approx.node_connectivity(G, 0, 3)
|
||||
|
||||
|
||||
def test_petersen():
|
||||
G = nx.petersen_graph()
|
||||
assert 3 == approx.node_connectivity(G)
|
||||
assert 3 == approx.node_connectivity(G, 0, 5)
|
||||
|
||||
|
||||
# Approximation fails with tutte graph
|
||||
# def test_tutte():
|
||||
# G = nx.tutte_graph()
|
||||
# assert_equal(3, approx.node_connectivity(G))
|
||||
|
||||
|
||||
def test_dodecahedral():
|
||||
G = nx.dodecahedral_graph()
|
||||
assert 3 == approx.node_connectivity(G)
|
||||
assert 3 == approx.node_connectivity(G, 0, 5)
|
||||
|
||||
|
||||
def test_octahedral():
|
||||
G = nx.octahedral_graph()
|
||||
assert 4 == approx.node_connectivity(G)
|
||||
assert 4 == approx.node_connectivity(G, 0, 5)
|
||||
|
||||
|
||||
# Approximation can fail with icosahedral graph depending
|
||||
# on iteration order.
|
||||
# def test_icosahedral():
|
||||
# G=nx.icosahedral_graph()
|
||||
# assert_equal(5, approx.node_connectivity(G))
|
||||
# assert_equal(5, approx.node_connectivity(G, 0, 5))
|
||||
|
||||
|
||||
def test_only_source():
|
||||
G = nx.complete_graph(5)
|
||||
pytest.raises(nx.NetworkXError, approx.node_connectivity, G, s=0)
|
||||
|
||||
|
||||
def test_only_target():
|
||||
G = nx.complete_graph(5)
|
||||
pytest.raises(nx.NetworkXError, approx.node_connectivity, G, t=0)
|
||||
|
||||
|
||||
def test_missing_source():
|
||||
G = nx.path_graph(4)
|
||||
pytest.raises(nx.NetworkXError, approx.node_connectivity, G, 10, 1)
|
||||
|
||||
|
||||
def test_missing_target():
|
||||
G = nx.path_graph(4)
|
||||
pytest.raises(nx.NetworkXError, approx.node_connectivity, G, 1, 10)
|
||||
|
||||
|
||||
def test_source_equals_target():
|
||||
G = nx.complete_graph(5)
|
||||
pytest.raises(nx.NetworkXError, approx.local_node_connectivity, G, 0, 0)
|
||||
|
||||
|
||||
def test_directed_node_connectivity():
|
||||
G = nx.cycle_graph(10, create_using=nx.DiGraph()) # only one direction
|
||||
D = nx.cycle_graph(10).to_directed() # 2 reciprocal edges
|
||||
assert 1 == approx.node_connectivity(G)
|
||||
assert 1 == approx.node_connectivity(G, 1, 4)
|
||||
assert 2 == approx.node_connectivity(D)
|
||||
assert 2 == approx.node_connectivity(D, 1, 4)
|
||||
|
||||
|
||||
class TestAllPairsNodeConnectivityApprox:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.path = nx.path_graph(7)
|
||||
cls.directed_path = nx.path_graph(7, create_using=nx.DiGraph())
|
||||
cls.cycle = nx.cycle_graph(7)
|
||||
cls.directed_cycle = nx.cycle_graph(7, create_using=nx.DiGraph())
|
||||
cls.gnp = nx.gnp_random_graph(30, 0.1)
|
||||
cls.directed_gnp = nx.gnp_random_graph(30, 0.1, directed=True)
|
||||
cls.K20 = nx.complete_graph(20)
|
||||
cls.K10 = nx.complete_graph(10)
|
||||
cls.K5 = nx.complete_graph(5)
|
||||
cls.G_list = [
|
||||
cls.path,
|
||||
cls.directed_path,
|
||||
cls.cycle,
|
||||
cls.directed_cycle,
|
||||
cls.gnp,
|
||||
cls.directed_gnp,
|
||||
cls.K10,
|
||||
cls.K5,
|
||||
cls.K20,
|
||||
]
|
||||
|
||||
def test_cycles(self):
|
||||
K_undir = approx.all_pairs_node_connectivity(self.cycle)
|
||||
for source in K_undir:
|
||||
for target, k in K_undir[source].items():
|
||||
assert k == 2
|
||||
K_dir = approx.all_pairs_node_connectivity(self.directed_cycle)
|
||||
for source in K_dir:
|
||||
for target, k in K_dir[source].items():
|
||||
assert k == 1
|
||||
|
||||
def test_complete(self):
|
||||
for G in [self.K10, self.K5, self.K20]:
|
||||
K = approx.all_pairs_node_connectivity(G)
|
||||
for source in K:
|
||||
for target, k in K[source].items():
|
||||
assert k == len(G) - 1
|
||||
|
||||
def test_paths(self):
|
||||
K_undir = approx.all_pairs_node_connectivity(self.path)
|
||||
for source in K_undir:
|
||||
for target, k in K_undir[source].items():
|
||||
assert k == 1
|
||||
K_dir = approx.all_pairs_node_connectivity(self.directed_path)
|
||||
for source in K_dir:
|
||||
for target, k in K_dir[source].items():
|
||||
if source < target:
|
||||
assert k == 1
|
||||
else:
|
||||
assert k == 0
|
||||
|
||||
def test_cutoff(self):
|
||||
for G in [self.K10, self.K5, self.K20]:
|
||||
for mp in [2, 3, 4]:
|
||||
paths = approx.all_pairs_node_connectivity(G, cutoff=mp)
|
||||
for source in paths:
|
||||
for target, K in paths[source].items():
|
||||
assert K == mp
|
||||
|
||||
def test_all_pairs_connectivity_nbunch(self):
|
||||
G = nx.complete_graph(5)
|
||||
nbunch = [0, 2, 3]
|
||||
C = approx.all_pairs_node_connectivity(G, nbunch=nbunch)
|
||||
assert len(C) == len(nbunch)
|
||||
@@ -0,0 +1,60 @@
|
||||
"""Unit tests for the :mod:`networkx.algorithms.approximation.distance_measures` module.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.approximation import diameter
|
||||
|
||||
|
||||
class TestDiameter:
|
||||
"""Unit tests for the approximate diameter function
|
||||
:func:`~networkx.algorithms.approximation.distance_measures.diameter`.
|
||||
"""
|
||||
|
||||
def test_null_graph(self):
|
||||
"""Test empty graph."""
|
||||
G = nx.null_graph()
|
||||
with pytest.raises(
|
||||
nx.NetworkXError, match="Expected non-empty NetworkX graph!"
|
||||
):
|
||||
diameter(G)
|
||||
|
||||
def test_undirected_non_connected(self):
|
||||
"""Test an undirected disconnected graph."""
|
||||
graph = nx.path_graph(10)
|
||||
graph.remove_edge(3, 4)
|
||||
with pytest.raises(nx.NetworkXError, match="Graph not connected."):
|
||||
diameter(graph)
|
||||
|
||||
def test_directed_non_strongly_connected(self):
|
||||
"""Test a directed non strongly connected graph."""
|
||||
graph = nx.path_graph(10, create_using=nx.DiGraph())
|
||||
with pytest.raises(nx.NetworkXError, match="DiGraph not strongly connected."):
|
||||
diameter(graph)
|
||||
|
||||
def test_complete_undirected_graph(self):
|
||||
"""Test a complete undirected graph."""
|
||||
graph = nx.complete_graph(10)
|
||||
assert diameter(graph) == 1
|
||||
|
||||
def test_complete_directed_graph(self):
|
||||
"""Test a complete directed graph."""
|
||||
graph = nx.complete_graph(10, create_using=nx.DiGraph())
|
||||
assert diameter(graph) == 1
|
||||
|
||||
def test_undirected_path_graph(self):
|
||||
"""Test an undirected path graph with 10 nodes."""
|
||||
graph = nx.path_graph(10)
|
||||
assert diameter(graph) == 9
|
||||
|
||||
def test_directed_path_graph(self):
|
||||
"""Test a directed path graph with 10 nodes."""
|
||||
graph = nx.path_graph(10).to_directed()
|
||||
assert diameter(graph) == 9
|
||||
|
||||
def test_single_node(self):
|
||||
"""Test a graph which contains just a node."""
|
||||
graph = nx.Graph()
|
||||
graph.add_node(1)
|
||||
assert diameter(graph) == 0
|
||||
78
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_dominating_set.py
vendored
Normal file
78
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_dominating_set.py
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.approximation import (
|
||||
min_edge_dominating_set,
|
||||
min_weighted_dominating_set,
|
||||
)
|
||||
|
||||
|
||||
class TestMinWeightDominatingSet:
|
||||
def test_min_weighted_dominating_set(self):
|
||||
graph = nx.Graph()
|
||||
graph.add_edge(1, 2)
|
||||
graph.add_edge(1, 5)
|
||||
graph.add_edge(2, 3)
|
||||
graph.add_edge(2, 5)
|
||||
graph.add_edge(3, 4)
|
||||
graph.add_edge(3, 6)
|
||||
graph.add_edge(5, 6)
|
||||
|
||||
vertices = {1, 2, 3, 4, 5, 6}
|
||||
# due to ties, this might be hard to test tight bounds
|
||||
dom_set = min_weighted_dominating_set(graph)
|
||||
for vertex in vertices - dom_set:
|
||||
neighbors = set(graph.neighbors(vertex))
|
||||
assert len(neighbors & dom_set) > 0, "Non dominating set found!"
|
||||
|
||||
def test_star_graph(self):
|
||||
"""Tests that an approximate dominating set for the star graph,
|
||||
even when the center node does not have the smallest integer
|
||||
label, gives just the center node.
|
||||
|
||||
For more information, see #1527.
|
||||
|
||||
"""
|
||||
# Create a star graph in which the center node has the highest
|
||||
# label instead of the lowest.
|
||||
G = nx.star_graph(10)
|
||||
G = nx.relabel_nodes(G, {0: 9, 9: 0})
|
||||
assert min_weighted_dominating_set(G) == {9}
|
||||
|
||||
def test_null_graph(self):
|
||||
"""Tests that the unique dominating set for the null graph is an empty set"""
|
||||
G = nx.Graph()
|
||||
assert min_weighted_dominating_set(G) == set()
|
||||
|
||||
def test_min_edge_dominating_set(self):
|
||||
graph = nx.path_graph(5)
|
||||
dom_set = min_edge_dominating_set(graph)
|
||||
|
||||
# this is a crappy way to test, but good enough for now.
|
||||
for edge in graph.edges():
|
||||
if edge in dom_set:
|
||||
continue
|
||||
else:
|
||||
u, v = edge
|
||||
found = False
|
||||
for dom_edge in dom_set:
|
||||
found |= u == dom_edge[0] or u == dom_edge[1]
|
||||
assert found, "Non adjacent edge found!"
|
||||
|
||||
graph = nx.complete_graph(10)
|
||||
dom_set = min_edge_dominating_set(graph)
|
||||
|
||||
# this is a crappy way to test, but good enough for now.
|
||||
for edge in graph.edges():
|
||||
if edge in dom_set:
|
||||
continue
|
||||
else:
|
||||
u, v = edge
|
||||
found = False
|
||||
for dom_edge in dom_set:
|
||||
found |= u == dom_edge[0] or u == dom_edge[1]
|
||||
assert found, "Non adjacent edge found!"
|
||||
|
||||
graph = nx.Graph() # empty Networkx graph
|
||||
with pytest.raises(ValueError, match="Expected non-empty NetworkX graph!"):
|
||||
min_edge_dominating_set(graph)
|
||||
301
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_kcomponents.py
vendored
Normal file
301
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_kcomponents.py
vendored
Normal file
@@ -0,0 +1,301 @@
|
||||
# Test for approximation to k-components algorithm
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.approximation import k_components
|
||||
from networkx.algorithms.approximation.kcomponents import _AntiGraph, _same
|
||||
|
||||
|
||||
def build_k_number_dict(k_components):
|
||||
k_num = {}
|
||||
for k, comps in sorted(k_components.items()):
|
||||
for comp in comps:
|
||||
for node in comp:
|
||||
k_num[node] = k
|
||||
return k_num
|
||||
|
||||
|
||||
##
|
||||
# Some nice synthetic graphs
|
||||
##
|
||||
|
||||
|
||||
def graph_example_1():
|
||||
G = nx.convert_node_labels_to_integers(
|
||||
nx.grid_graph([5, 5]), label_attribute="labels"
|
||||
)
|
||||
rlabels = nx.get_node_attributes(G, "labels")
|
||||
labels = {v: k for k, v in rlabels.items()}
|
||||
|
||||
for nodes in [
|
||||
(labels[(0, 0)], labels[(1, 0)]),
|
||||
(labels[(0, 4)], labels[(1, 4)]),
|
||||
(labels[(3, 0)], labels[(4, 0)]),
|
||||
(labels[(3, 4)], labels[(4, 4)]),
|
||||
]:
|
||||
new_node = G.order() + 1
|
||||
# Petersen graph is triconnected
|
||||
P = nx.petersen_graph()
|
||||
G = nx.disjoint_union(G, P)
|
||||
# Add two edges between the grid and P
|
||||
G.add_edge(new_node + 1, nodes[0])
|
||||
G.add_edge(new_node, nodes[1])
|
||||
# K5 is 4-connected
|
||||
K = nx.complete_graph(5)
|
||||
G = nx.disjoint_union(G, K)
|
||||
# Add three edges between P and K5
|
||||
G.add_edge(new_node + 2, new_node + 11)
|
||||
G.add_edge(new_node + 3, new_node + 12)
|
||||
G.add_edge(new_node + 4, new_node + 13)
|
||||
# Add another K5 sharing a node
|
||||
G = nx.disjoint_union(G, K)
|
||||
nbrs = G[new_node + 10]
|
||||
G.remove_node(new_node + 10)
|
||||
for nbr in nbrs:
|
||||
G.add_edge(new_node + 17, nbr)
|
||||
G.add_edge(new_node + 16, new_node + 5)
|
||||
return G
|
||||
|
||||
|
||||
def torrents_and_ferraro_graph():
|
||||
G = nx.convert_node_labels_to_integers(
|
||||
nx.grid_graph([5, 5]), label_attribute="labels"
|
||||
)
|
||||
rlabels = nx.get_node_attributes(G, "labels")
|
||||
labels = {v: k for k, v in rlabels.items()}
|
||||
|
||||
for nodes in [(labels[(0, 4)], labels[(1, 4)]), (labels[(3, 4)], labels[(4, 4)])]:
|
||||
new_node = G.order() + 1
|
||||
# Petersen graph is triconnected
|
||||
P = nx.petersen_graph()
|
||||
G = nx.disjoint_union(G, P)
|
||||
# Add two edges between the grid and P
|
||||
G.add_edge(new_node + 1, nodes[0])
|
||||
G.add_edge(new_node, nodes[1])
|
||||
# K5 is 4-connected
|
||||
K = nx.complete_graph(5)
|
||||
G = nx.disjoint_union(G, K)
|
||||
# Add three edges between P and K5
|
||||
G.add_edge(new_node + 2, new_node + 11)
|
||||
G.add_edge(new_node + 3, new_node + 12)
|
||||
G.add_edge(new_node + 4, new_node + 13)
|
||||
# Add another K5 sharing a node
|
||||
G = nx.disjoint_union(G, K)
|
||||
nbrs = G[new_node + 10]
|
||||
G.remove_node(new_node + 10)
|
||||
for nbr in nbrs:
|
||||
G.add_edge(new_node + 17, nbr)
|
||||
# Commenting this makes the graph not biconnected !!
|
||||
# This stupid mistake make one reviewer very angry :P
|
||||
G.add_edge(new_node + 16, new_node + 8)
|
||||
|
||||
for nodes in [(labels[(0, 0)], labels[(1, 0)]), (labels[(3, 0)], labels[(4, 0)])]:
|
||||
new_node = G.order() + 1
|
||||
# Petersen graph is triconnected
|
||||
P = nx.petersen_graph()
|
||||
G = nx.disjoint_union(G, P)
|
||||
# Add two edges between the grid and P
|
||||
G.add_edge(new_node + 1, nodes[0])
|
||||
G.add_edge(new_node, nodes[1])
|
||||
# K5 is 4-connected
|
||||
K = nx.complete_graph(5)
|
||||
G = nx.disjoint_union(G, K)
|
||||
# Add three edges between P and K5
|
||||
G.add_edge(new_node + 2, new_node + 11)
|
||||
G.add_edge(new_node + 3, new_node + 12)
|
||||
G.add_edge(new_node + 4, new_node + 13)
|
||||
# Add another K5 sharing two nodes
|
||||
G = nx.disjoint_union(G, K)
|
||||
nbrs = G[new_node + 10]
|
||||
G.remove_node(new_node + 10)
|
||||
for nbr in nbrs:
|
||||
G.add_edge(new_node + 17, nbr)
|
||||
nbrs2 = G[new_node + 9]
|
||||
G.remove_node(new_node + 9)
|
||||
for nbr in nbrs2:
|
||||
G.add_edge(new_node + 18, nbr)
|
||||
return G
|
||||
|
||||
|
||||
# Helper function
|
||||
|
||||
|
||||
def _check_connectivity(G):
|
||||
result = k_components(G)
|
||||
for k, components in result.items():
|
||||
if k < 3:
|
||||
continue
|
||||
for component in components:
|
||||
C = G.subgraph(component)
|
||||
K = nx.node_connectivity(C)
|
||||
assert K >= k
|
||||
|
||||
|
||||
def test_torrents_and_ferraro_graph():
|
||||
G = torrents_and_ferraro_graph()
|
||||
_check_connectivity(G)
|
||||
|
||||
|
||||
def test_example_1():
|
||||
G = graph_example_1()
|
||||
_check_connectivity(G)
|
||||
|
||||
|
||||
def test_karate_0():
|
||||
G = nx.karate_club_graph()
|
||||
_check_connectivity(G)
|
||||
|
||||
|
||||
def test_karate_1():
|
||||
karate_k_num = {
|
||||
0: 4,
|
||||
1: 4,
|
||||
2: 4,
|
||||
3: 4,
|
||||
4: 3,
|
||||
5: 3,
|
||||
6: 3,
|
||||
7: 4,
|
||||
8: 4,
|
||||
9: 2,
|
||||
10: 3,
|
||||
11: 1,
|
||||
12: 2,
|
||||
13: 4,
|
||||
14: 2,
|
||||
15: 2,
|
||||
16: 2,
|
||||
17: 2,
|
||||
18: 2,
|
||||
19: 3,
|
||||
20: 2,
|
||||
21: 2,
|
||||
22: 2,
|
||||
23: 3,
|
||||
24: 3,
|
||||
25: 3,
|
||||
26: 2,
|
||||
27: 3,
|
||||
28: 3,
|
||||
29: 3,
|
||||
30: 4,
|
||||
31: 3,
|
||||
32: 4,
|
||||
33: 4,
|
||||
}
|
||||
approx_karate_k_num = karate_k_num.copy()
|
||||
approx_karate_k_num[24] = 2
|
||||
approx_karate_k_num[25] = 2
|
||||
G = nx.karate_club_graph()
|
||||
k_comps = k_components(G)
|
||||
k_num = build_k_number_dict(k_comps)
|
||||
assert k_num in (karate_k_num, approx_karate_k_num)
|
||||
|
||||
|
||||
def test_example_1_detail_3_and_4():
|
||||
G = graph_example_1()
|
||||
result = k_components(G)
|
||||
# In this example graph there are 8 3-components, 4 with 15 nodes
|
||||
# and 4 with 5 nodes.
|
||||
assert len(result[3]) == 8
|
||||
assert len([c for c in result[3] if len(c) == 15]) == 4
|
||||
assert len([c for c in result[3] if len(c) == 5]) == 4
|
||||
# There are also 8 4-components all with 5 nodes.
|
||||
assert len(result[4]) == 8
|
||||
assert all(len(c) == 5 for c in result[4])
|
||||
# Finally check that the k-components detected have actually node
|
||||
# connectivity >= k.
|
||||
for k, components in result.items():
|
||||
if k < 3:
|
||||
continue
|
||||
for component in components:
|
||||
K = nx.node_connectivity(G.subgraph(component))
|
||||
assert K >= k
|
||||
|
||||
|
||||
def test_directed():
|
||||
with pytest.raises(nx.NetworkXNotImplemented):
|
||||
G = nx.gnp_random_graph(10, 0.4, directed=True)
|
||||
kc = k_components(G)
|
||||
|
||||
|
||||
def test_same():
|
||||
equal = {"A": 2, "B": 2, "C": 2}
|
||||
slightly_different = {"A": 2, "B": 1, "C": 2}
|
||||
different = {"A": 2, "B": 8, "C": 18}
|
||||
assert _same(equal)
|
||||
assert not _same(slightly_different)
|
||||
assert _same(slightly_different, tol=1)
|
||||
assert not _same(different)
|
||||
assert not _same(different, tol=4)
|
||||
|
||||
|
||||
class TestAntiGraph:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.Gnp = nx.gnp_random_graph(20, 0.8)
|
||||
cls.Anp = _AntiGraph(nx.complement(cls.Gnp))
|
||||
cls.Gd = nx.davis_southern_women_graph()
|
||||
cls.Ad = _AntiGraph(nx.complement(cls.Gd))
|
||||
cls.Gk = nx.karate_club_graph()
|
||||
cls.Ak = _AntiGraph(nx.complement(cls.Gk))
|
||||
cls.GA = [(cls.Gnp, cls.Anp), (cls.Gd, cls.Ad), (cls.Gk, cls.Ak)]
|
||||
|
||||
def test_size(self):
|
||||
for G, A in self.GA:
|
||||
n = G.order()
|
||||
s = len(list(G.edges())) + len(list(A.edges()))
|
||||
assert s == (n * (n - 1)) / 2
|
||||
|
||||
def test_degree(self):
|
||||
for G, A in self.GA:
|
||||
assert sorted(G.degree()) == sorted(A.degree())
|
||||
|
||||
def test_core_number(self):
|
||||
for G, A in self.GA:
|
||||
assert nx.core_number(G) == nx.core_number(A)
|
||||
|
||||
def test_connected_components(self):
|
||||
for G, A in self.GA:
|
||||
gc = [set(c) for c in nx.connected_components(G)]
|
||||
ac = [set(c) for c in nx.connected_components(A)]
|
||||
for comp in ac:
|
||||
assert comp in gc
|
||||
|
||||
def test_adj(self):
|
||||
for G, A in self.GA:
|
||||
for n, nbrs in G.adj.items():
|
||||
a_adj = sorted((n, sorted(ad)) for n, ad in A.adj.items())
|
||||
g_adj = sorted((n, sorted(ad)) for n, ad in G.adj.items())
|
||||
assert a_adj == g_adj
|
||||
|
||||
def test_adjacency(self):
|
||||
for G, A in self.GA:
|
||||
a_adj = list(A.adjacency())
|
||||
for n, nbrs in G.adjacency():
|
||||
assert (n, set(nbrs)) in a_adj
|
||||
|
||||
def test_neighbors(self):
|
||||
for G, A in self.GA:
|
||||
node = list(G.nodes())[0]
|
||||
assert set(G.neighbors(node)) == set(A.neighbors(node))
|
||||
|
||||
def test_node_not_in_graph(self):
|
||||
for G, A in self.GA:
|
||||
node = "non_existent_node"
|
||||
pytest.raises(nx.NetworkXError, A.neighbors, node)
|
||||
pytest.raises(nx.NetworkXError, G.neighbors, node)
|
||||
|
||||
def test_degree_thingraph(self):
|
||||
for G, A in self.GA:
|
||||
node = list(G.nodes())[0]
|
||||
nodes = list(G.nodes())[1:4]
|
||||
assert G.degree(node) == A.degree(node)
|
||||
assert sum(d for n, d in G.degree()) == sum(d for n, d in A.degree())
|
||||
# AntiGraph is a ThinGraph, so all the weights are 1
|
||||
assert sum(d for n, d in A.degree()) == sum(
|
||||
d for n, d in A.degree(weight="weight")
|
||||
)
|
||||
assert sum(d for n, d in G.degree(nodes)) == sum(
|
||||
d for n, d in A.degree(nodes)
|
||||
)
|
||||
8
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_matching.py
vendored
Normal file
8
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_matching.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import networkx as nx
|
||||
import networkx.algorithms.approximation as a
|
||||
|
||||
|
||||
def test_min_maximal_matching():
|
||||
# smoke test
|
||||
G = nx.Graph()
|
||||
assert len(a.min_maximal_matching(G)) == 0
|
||||
82
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_maxcut.py
vendored
Normal file
82
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_maxcut.py
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
import random
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.approximation import maxcut
|
||||
|
||||
|
||||
def _is_valid_cut(G, set1, set2):
|
||||
union = set1.union(set2)
|
||||
assert union == set(G.nodes)
|
||||
assert len(set1) + len(set2) == G.number_of_nodes()
|
||||
|
||||
|
||||
def _cut_is_locally_optimal(G, cut_size, set1):
|
||||
# test if cut can be locally improved
|
||||
for i, node in enumerate(set1):
|
||||
cut_size_without_node = nx.algorithms.cut_size(
|
||||
G, set1 - {node}, weight="weight"
|
||||
)
|
||||
assert cut_size_without_node <= cut_size
|
||||
|
||||
|
||||
def test_random_partitioning():
|
||||
G = nx.complete_graph(5)
|
||||
_, (set1, set2) = maxcut.randomized_partitioning(G, seed=5)
|
||||
_is_valid_cut(G, set1, set2)
|
||||
|
||||
|
||||
def test_random_partitioning_all_to_one():
|
||||
G = nx.complete_graph(5)
|
||||
_, (set1, set2) = maxcut.randomized_partitioning(G, p=1)
|
||||
_is_valid_cut(G, set1, set2)
|
||||
assert len(set1) == G.number_of_nodes()
|
||||
assert len(set2) == 0
|
||||
|
||||
|
||||
def test_one_exchange_basic():
|
||||
G = nx.complete_graph(5)
|
||||
random.seed(5)
|
||||
for u, v, w in G.edges(data=True):
|
||||
w["weight"] = random.randrange(-100, 100, 1) / 10
|
||||
|
||||
initial_cut = set(random.sample(sorted(G.nodes()), k=5))
|
||||
cut_size, (set1, set2) = maxcut.one_exchange(
|
||||
G, initial_cut, weight="weight", seed=5
|
||||
)
|
||||
|
||||
_is_valid_cut(G, set1, set2)
|
||||
_cut_is_locally_optimal(G, cut_size, set1)
|
||||
|
||||
|
||||
def test_one_exchange_optimal():
|
||||
# Greedy one exchange should find the optimal solution for this graph (14)
|
||||
G = nx.Graph()
|
||||
G.add_edge(1, 2, weight=3)
|
||||
G.add_edge(1, 3, weight=3)
|
||||
G.add_edge(1, 4, weight=3)
|
||||
G.add_edge(1, 5, weight=3)
|
||||
G.add_edge(2, 3, weight=5)
|
||||
|
||||
cut_size, (set1, set2) = maxcut.one_exchange(G, weight="weight", seed=5)
|
||||
|
||||
_is_valid_cut(G, set1, set2)
|
||||
_cut_is_locally_optimal(G, cut_size, set1)
|
||||
# check global optimality
|
||||
assert cut_size == 14
|
||||
|
||||
|
||||
def test_negative_weights():
|
||||
G = nx.complete_graph(5)
|
||||
random.seed(5)
|
||||
for u, v, w in G.edges(data=True):
|
||||
w["weight"] = -1 * random.random()
|
||||
|
||||
initial_cut = set(random.sample(sorted(G.nodes()), k=5))
|
||||
cut_size, (set1, set2) = maxcut.one_exchange(G, initial_cut, weight="weight")
|
||||
|
||||
# make sure it is a valid cut
|
||||
_is_valid_cut(G, set1, set2)
|
||||
# check local optimality
|
||||
_cut_is_locally_optimal(G, cut_size, set1)
|
||||
# test that all nodes are in the same partition
|
||||
assert len(set1) == len(G.nodes) or len(set2) == len(G.nodes)
|
||||
31
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_ramsey.py
vendored
Normal file
31
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_ramsey.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import networkx as nx
|
||||
import networkx.algorithms.approximation as apxa
|
||||
|
||||
|
||||
def test_ramsey():
|
||||
# this should only find the complete graph
|
||||
graph = nx.complete_graph(10)
|
||||
c, i = apxa.ramsey_R2(graph)
|
||||
cdens = nx.density(graph.subgraph(c))
|
||||
assert cdens == 1.0, "clique not correctly found by ramsey!"
|
||||
idens = nx.density(graph.subgraph(i))
|
||||
assert idens == 0.0, "i-set not correctly found by ramsey!"
|
||||
|
||||
# this trivial graph has no cliques. should just find i-sets
|
||||
graph = nx.trivial_graph()
|
||||
c, i = apxa.ramsey_R2(graph)
|
||||
assert c == {0}, "clique not correctly found by ramsey!"
|
||||
assert i == {0}, "i-set not correctly found by ramsey!"
|
||||
|
||||
graph = nx.barbell_graph(10, 5, nx.Graph())
|
||||
c, i = apxa.ramsey_R2(graph)
|
||||
cdens = nx.density(graph.subgraph(c))
|
||||
assert cdens == 1.0, "clique not correctly found by ramsey!"
|
||||
idens = nx.density(graph.subgraph(i))
|
||||
assert idens == 0.0, "i-set not correctly found by ramsey!"
|
||||
|
||||
# add self-loops and test again
|
||||
graph.add_edges_from([(n, n) for n in range(0, len(graph), 2)])
|
||||
cc, ii = apxa.ramsey_R2(graph)
|
||||
assert cc == c
|
||||
assert ii == i
|
||||
191
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_steinertree.py
vendored
Normal file
191
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_steinertree.py
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.approximation.steinertree import metric_closure, steiner_tree
|
||||
from networkx.utils import edges_equal
|
||||
|
||||
|
||||
class TestSteinerTree:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
G1 = nx.Graph()
|
||||
G1.add_edge(1, 2, weight=10)
|
||||
G1.add_edge(2, 3, weight=10)
|
||||
G1.add_edge(3, 4, weight=10)
|
||||
G1.add_edge(4, 5, weight=10)
|
||||
G1.add_edge(5, 6, weight=10)
|
||||
G1.add_edge(2, 7, weight=1)
|
||||
G1.add_edge(7, 5, weight=1)
|
||||
|
||||
G2 = nx.Graph()
|
||||
G2.add_edge(0, 5, weight=6)
|
||||
G2.add_edge(1, 2, weight=2)
|
||||
G2.add_edge(1, 5, weight=3)
|
||||
G2.add_edge(2, 4, weight=4)
|
||||
G2.add_edge(3, 5, weight=5)
|
||||
G2.add_edge(4, 5, weight=1)
|
||||
|
||||
G3 = nx.Graph()
|
||||
G3.add_edge(1, 2, weight=8)
|
||||
G3.add_edge(1, 9, weight=3)
|
||||
G3.add_edge(1, 8, weight=6)
|
||||
G3.add_edge(1, 10, weight=2)
|
||||
G3.add_edge(1, 14, weight=3)
|
||||
G3.add_edge(2, 3, weight=6)
|
||||
G3.add_edge(3, 4, weight=3)
|
||||
G3.add_edge(3, 10, weight=2)
|
||||
G3.add_edge(3, 11, weight=1)
|
||||
G3.add_edge(4, 5, weight=1)
|
||||
G3.add_edge(4, 11, weight=1)
|
||||
G3.add_edge(5, 6, weight=4)
|
||||
G3.add_edge(5, 11, weight=2)
|
||||
G3.add_edge(5, 12, weight=1)
|
||||
G3.add_edge(5, 13, weight=3)
|
||||
G3.add_edge(6, 7, weight=2)
|
||||
G3.add_edge(6, 12, weight=3)
|
||||
G3.add_edge(6, 13, weight=1)
|
||||
G3.add_edge(7, 8, weight=3)
|
||||
G3.add_edge(7, 9, weight=3)
|
||||
G3.add_edge(7, 11, weight=5)
|
||||
G3.add_edge(7, 13, weight=2)
|
||||
G3.add_edge(7, 14, weight=4)
|
||||
G3.add_edge(8, 9, weight=2)
|
||||
G3.add_edge(9, 14, weight=1)
|
||||
G3.add_edge(10, 11, weight=2)
|
||||
G3.add_edge(10, 14, weight=1)
|
||||
G3.add_edge(11, 12, weight=1)
|
||||
G3.add_edge(11, 14, weight=7)
|
||||
G3.add_edge(12, 14, weight=3)
|
||||
G3.add_edge(12, 15, weight=1)
|
||||
G3.add_edge(13, 14, weight=4)
|
||||
G3.add_edge(13, 15, weight=1)
|
||||
G3.add_edge(14, 15, weight=2)
|
||||
|
||||
cls.G1 = G1
|
||||
cls.G2 = G2
|
||||
cls.G3 = G3
|
||||
cls.G1_term_nodes = [1, 2, 3, 4, 5]
|
||||
cls.G2_term_nodes = [0, 2, 3]
|
||||
cls.G3_term_nodes = [1, 3, 5, 6, 8, 10, 11, 12, 13]
|
||||
|
||||
cls.methods = ["kou", "mehlhorn"]
|
||||
|
||||
def test_connected_metric_closure(self):
|
||||
G = self.G1.copy()
|
||||
G.add_node(100)
|
||||
pytest.raises(nx.NetworkXError, metric_closure, G)
|
||||
|
||||
def test_metric_closure(self):
|
||||
M = metric_closure(self.G1)
|
||||
mc = [
|
||||
(1, 2, {"distance": 10, "path": [1, 2]}),
|
||||
(1, 3, {"distance": 20, "path": [1, 2, 3]}),
|
||||
(1, 4, {"distance": 22, "path": [1, 2, 7, 5, 4]}),
|
||||
(1, 5, {"distance": 12, "path": [1, 2, 7, 5]}),
|
||||
(1, 6, {"distance": 22, "path": [1, 2, 7, 5, 6]}),
|
||||
(1, 7, {"distance": 11, "path": [1, 2, 7]}),
|
||||
(2, 3, {"distance": 10, "path": [2, 3]}),
|
||||
(2, 4, {"distance": 12, "path": [2, 7, 5, 4]}),
|
||||
(2, 5, {"distance": 2, "path": [2, 7, 5]}),
|
||||
(2, 6, {"distance": 12, "path": [2, 7, 5, 6]}),
|
||||
(2, 7, {"distance": 1, "path": [2, 7]}),
|
||||
(3, 4, {"distance": 10, "path": [3, 4]}),
|
||||
(3, 5, {"distance": 12, "path": [3, 2, 7, 5]}),
|
||||
(3, 6, {"distance": 22, "path": [3, 2, 7, 5, 6]}),
|
||||
(3, 7, {"distance": 11, "path": [3, 2, 7]}),
|
||||
(4, 5, {"distance": 10, "path": [4, 5]}),
|
||||
(4, 6, {"distance": 20, "path": [4, 5, 6]}),
|
||||
(4, 7, {"distance": 11, "path": [4, 5, 7]}),
|
||||
(5, 6, {"distance": 10, "path": [5, 6]}),
|
||||
(5, 7, {"distance": 1, "path": [5, 7]}),
|
||||
(6, 7, {"distance": 11, "path": [6, 5, 7]}),
|
||||
]
|
||||
assert edges_equal(list(M.edges(data=True)), mc)
|
||||
|
||||
def test_steiner_tree(self):
|
||||
valid_steiner_trees = [
|
||||
[
|
||||
[
|
||||
(1, 2, {"weight": 10}),
|
||||
(2, 3, {"weight": 10}),
|
||||
(2, 7, {"weight": 1}),
|
||||
(3, 4, {"weight": 10}),
|
||||
(5, 7, {"weight": 1}),
|
||||
],
|
||||
[
|
||||
(1, 2, {"weight": 10}),
|
||||
(2, 7, {"weight": 1}),
|
||||
(3, 4, {"weight": 10}),
|
||||
(4, 5, {"weight": 10}),
|
||||
(5, 7, {"weight": 1}),
|
||||
],
|
||||
[
|
||||
(1, 2, {"weight": 10}),
|
||||
(2, 3, {"weight": 10}),
|
||||
(2, 7, {"weight": 1}),
|
||||
(4, 5, {"weight": 10}),
|
||||
(5, 7, {"weight": 1}),
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
(0, 5, {"weight": 6}),
|
||||
(1, 2, {"weight": 2}),
|
||||
(1, 5, {"weight": 3}),
|
||||
(3, 5, {"weight": 5}),
|
||||
],
|
||||
[
|
||||
(0, 5, {"weight": 6}),
|
||||
(4, 2, {"weight": 4}),
|
||||
(4, 5, {"weight": 1}),
|
||||
(3, 5, {"weight": 5}),
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
(1, 10, {"weight": 2}),
|
||||
(3, 10, {"weight": 2}),
|
||||
(3, 11, {"weight": 1}),
|
||||
(5, 12, {"weight": 1}),
|
||||
(6, 13, {"weight": 1}),
|
||||
(8, 9, {"weight": 2}),
|
||||
(9, 14, {"weight": 1}),
|
||||
(10, 14, {"weight": 1}),
|
||||
(11, 12, {"weight": 1}),
|
||||
(12, 15, {"weight": 1}),
|
||||
(13, 15, {"weight": 1}),
|
||||
]
|
||||
],
|
||||
]
|
||||
for method in self.methods:
|
||||
for G, term_nodes, valid_trees in zip(
|
||||
[self.G1, self.G2, self.G3],
|
||||
[self.G1_term_nodes, self.G2_term_nodes, self.G3_term_nodes],
|
||||
valid_steiner_trees,
|
||||
):
|
||||
S = steiner_tree(G, term_nodes, method=method)
|
||||
assert any(
|
||||
edges_equal(list(S.edges(data=True)), valid_tree)
|
||||
for valid_tree in valid_trees
|
||||
)
|
||||
|
||||
def test_multigraph_steiner_tree(self):
|
||||
G = nx.MultiGraph()
|
||||
G.add_edges_from(
|
||||
[
|
||||
(1, 2, 0, {"weight": 1}),
|
||||
(2, 3, 0, {"weight": 999}),
|
||||
(2, 3, 1, {"weight": 1}),
|
||||
(3, 4, 0, {"weight": 1}),
|
||||
(3, 5, 0, {"weight": 1}),
|
||||
]
|
||||
)
|
||||
terminal_nodes = [2, 4, 5]
|
||||
expected_edges = [
|
||||
(2, 3, 1, {"weight": 1}), # edge with key 1 has lower weight
|
||||
(3, 4, 0, {"weight": 1}),
|
||||
(3, 5, 0, {"weight": 1}),
|
||||
]
|
||||
for method in self.methods:
|
||||
S = steiner_tree(G, terminal_nodes, method=method)
|
||||
assert edges_equal(S.edges(data=True, keys=True), expected_edges)
|
||||
963
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_traveling_salesman.py
vendored
Normal file
963
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_traveling_salesman.py
vendored
Normal file
@@ -0,0 +1,963 @@
|
||||
"""Unit tests for the traveling_salesman module."""
|
||||
import random
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
import networkx.algorithms.approximation as nx_app
|
||||
|
||||
pairwise = nx.utils.pairwise
|
||||
|
||||
|
||||
def test_christofides_hamiltonian():
|
||||
random.seed(42)
|
||||
G = nx.complete_graph(20)
|
||||
for u, v in G.edges():
|
||||
G[u][v]["weight"] = random.randint(0, 10)
|
||||
|
||||
H = nx.Graph()
|
||||
H.add_edges_from(pairwise(nx_app.christofides(G)))
|
||||
H.remove_edges_from(nx.find_cycle(H))
|
||||
assert len(H.edges) == 0
|
||||
|
||||
tree = nx.minimum_spanning_tree(G, weight="weight")
|
||||
H = nx.Graph()
|
||||
H.add_edges_from(pairwise(nx_app.christofides(G, tree)))
|
||||
H.remove_edges_from(nx.find_cycle(H))
|
||||
assert len(H.edges) == 0
|
||||
|
||||
|
||||
def test_christofides_incomplete_graph():
|
||||
G = nx.complete_graph(10)
|
||||
G.remove_edge(0, 1)
|
||||
pytest.raises(nx.NetworkXError, nx_app.christofides, G)
|
||||
|
||||
|
||||
def test_christofides_ignore_selfloops():
|
||||
G = nx.complete_graph(5)
|
||||
G.add_edge(3, 3)
|
||||
cycle = nx_app.christofides(G)
|
||||
assert len(cycle) - 1 == len(G) == len(set(cycle))
|
||||
|
||||
|
||||
# set up graphs for other tests
|
||||
class TestBase:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.DG = nx.DiGraph()
|
||||
cls.DG.add_weighted_edges_from(
|
||||
{
|
||||
("A", "B", 3),
|
||||
("A", "C", 17),
|
||||
("A", "D", 14),
|
||||
("B", "A", 3),
|
||||
("B", "C", 12),
|
||||
("B", "D", 16),
|
||||
("C", "A", 13),
|
||||
("C", "B", 12),
|
||||
("C", "D", 4),
|
||||
("D", "A", 14),
|
||||
("D", "B", 15),
|
||||
("D", "C", 2),
|
||||
}
|
||||
)
|
||||
cls.DG_cycle = ["D", "C", "B", "A", "D"]
|
||||
cls.DG_cost = 31.0
|
||||
|
||||
cls.DG2 = nx.DiGraph()
|
||||
cls.DG2.add_weighted_edges_from(
|
||||
{
|
||||
("A", "B", 3),
|
||||
("A", "C", 17),
|
||||
("A", "D", 14),
|
||||
("B", "A", 30),
|
||||
("B", "C", 2),
|
||||
("B", "D", 16),
|
||||
("C", "A", 33),
|
||||
("C", "B", 32),
|
||||
("C", "D", 34),
|
||||
("D", "A", 14),
|
||||
("D", "B", 15),
|
||||
("D", "C", 2),
|
||||
}
|
||||
)
|
||||
cls.DG2_cycle = ["D", "A", "B", "C", "D"]
|
||||
cls.DG2_cost = 53.0
|
||||
|
||||
cls.unweightedUG = nx.complete_graph(5, nx.Graph())
|
||||
cls.unweightedDG = nx.complete_graph(5, nx.DiGraph())
|
||||
|
||||
cls.incompleteUG = nx.Graph()
|
||||
cls.incompleteUG.add_weighted_edges_from({(0, 1, 1), (1, 2, 3)})
|
||||
cls.incompleteDG = nx.DiGraph()
|
||||
cls.incompleteDG.add_weighted_edges_from({(0, 1, 1), (1, 2, 3)})
|
||||
|
||||
cls.UG = nx.Graph()
|
||||
cls.UG.add_weighted_edges_from(
|
||||
{
|
||||
("A", "B", 3),
|
||||
("A", "C", 17),
|
||||
("A", "D", 14),
|
||||
("B", "C", 12),
|
||||
("B", "D", 16),
|
||||
("C", "D", 4),
|
||||
}
|
||||
)
|
||||
cls.UG_cycle = ["D", "C", "B", "A", "D"]
|
||||
cls.UG_cost = 33.0
|
||||
|
||||
cls.UG2 = nx.Graph()
|
||||
cls.UG2.add_weighted_edges_from(
|
||||
{
|
||||
("A", "B", 1),
|
||||
("A", "C", 15),
|
||||
("A", "D", 5),
|
||||
("B", "C", 16),
|
||||
("B", "D", 8),
|
||||
("C", "D", 3),
|
||||
}
|
||||
)
|
||||
cls.UG2_cycle = ["D", "C", "B", "A", "D"]
|
||||
cls.UG2_cost = 25.0
|
||||
|
||||
|
||||
def validate_solution(soln, cost, exp_soln, exp_cost):
|
||||
assert soln == exp_soln
|
||||
assert cost == exp_cost
|
||||
|
||||
|
||||
def validate_symmetric_solution(soln, cost, exp_soln, exp_cost):
|
||||
assert soln == exp_soln or soln == exp_soln[::-1]
|
||||
assert cost == exp_cost
|
||||
|
||||
|
||||
class TestGreedyTSP(TestBase):
|
||||
def test_greedy(self):
|
||||
cycle = nx_app.greedy_tsp(self.DG, source="D")
|
||||
cost = sum(self.DG[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_solution(cycle, cost, ["D", "C", "B", "A", "D"], 31.0)
|
||||
|
||||
cycle = nx_app.greedy_tsp(self.DG2, source="D")
|
||||
cost = sum(self.DG2[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_solution(cycle, cost, ["D", "C", "B", "A", "D"], 78.0)
|
||||
|
||||
cycle = nx_app.greedy_tsp(self.UG, source="D")
|
||||
cost = sum(self.UG[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_solution(cycle, cost, ["D", "C", "B", "A", "D"], 33.0)
|
||||
|
||||
cycle = nx_app.greedy_tsp(self.UG2, source="D")
|
||||
cost = sum(self.UG2[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_solution(cycle, cost, ["D", "C", "A", "B", "D"], 27.0)
|
||||
|
||||
def test_not_complete_graph(self):
|
||||
pytest.raises(nx.NetworkXError, nx_app.greedy_tsp, self.incompleteUG)
|
||||
pytest.raises(nx.NetworkXError, nx_app.greedy_tsp, self.incompleteDG)
|
||||
|
||||
def test_not_weighted_graph(self):
|
||||
nx_app.greedy_tsp(self.unweightedUG)
|
||||
nx_app.greedy_tsp(self.unweightedDG)
|
||||
|
||||
def test_two_nodes(self):
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from({(1, 2, 1)})
|
||||
cycle = nx_app.greedy_tsp(G)
|
||||
cost = sum(G[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_solution(cycle, cost, [1, 2, 1], 2)
|
||||
|
||||
def test_ignore_selfloops(self):
|
||||
G = nx.complete_graph(5)
|
||||
G.add_edge(3, 3)
|
||||
cycle = nx_app.greedy_tsp(G)
|
||||
assert len(cycle) - 1 == len(G) == len(set(cycle))
|
||||
|
||||
|
||||
class TestSimulatedAnnealingTSP(TestBase):
|
||||
tsp = staticmethod(nx_app.simulated_annealing_tsp)
|
||||
|
||||
def test_simulated_annealing_directed(self):
|
||||
cycle = self.tsp(self.DG, "greedy", source="D", seed=42)
|
||||
cost = sum(self.DG[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_solution(cycle, cost, self.DG_cycle, self.DG_cost)
|
||||
|
||||
initial_sol = ["D", "B", "A", "C", "D"]
|
||||
cycle = self.tsp(self.DG, initial_sol, source="D", seed=42)
|
||||
cost = sum(self.DG[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_solution(cycle, cost, self.DG_cycle, self.DG_cost)
|
||||
|
||||
initial_sol = ["D", "A", "C", "B", "D"]
|
||||
cycle = self.tsp(self.DG, initial_sol, move="1-0", source="D", seed=42)
|
||||
cost = sum(self.DG[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_solution(cycle, cost, self.DG_cycle, self.DG_cost)
|
||||
|
||||
cycle = self.tsp(self.DG2, "greedy", source="D", seed=42)
|
||||
cost = sum(self.DG2[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_solution(cycle, cost, self.DG2_cycle, self.DG2_cost)
|
||||
|
||||
cycle = self.tsp(self.DG2, "greedy", move="1-0", source="D", seed=42)
|
||||
cost = sum(self.DG2[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_solution(cycle, cost, self.DG2_cycle, self.DG2_cost)
|
||||
|
||||
def test_simulated_annealing_undirected(self):
|
||||
cycle = self.tsp(self.UG, "greedy", source="D", seed=42)
|
||||
cost = sum(self.UG[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_solution(cycle, cost, self.UG_cycle, self.UG_cost)
|
||||
|
||||
cycle = self.tsp(self.UG2, "greedy", source="D", seed=42)
|
||||
cost = sum(self.UG2[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_symmetric_solution(cycle, cost, self.UG2_cycle, self.UG2_cost)
|
||||
|
||||
cycle = self.tsp(self.UG2, "greedy", move="1-0", source="D", seed=42)
|
||||
cost = sum(self.UG2[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_symmetric_solution(cycle, cost, self.UG2_cycle, self.UG2_cost)
|
||||
|
||||
def test_error_on_input_order_mistake(self):
|
||||
# see issue #4846 https://github.com/networkx/networkx/issues/4846
|
||||
pytest.raises(TypeError, self.tsp, self.UG, weight="weight")
|
||||
pytest.raises(nx.NetworkXError, self.tsp, self.UG, "weight")
|
||||
|
||||
def test_not_complete_graph(self):
|
||||
pytest.raises(nx.NetworkXError, self.tsp, self.incompleteUG, "greedy", source=0)
|
||||
pytest.raises(nx.NetworkXError, self.tsp, self.incompleteDG, "greedy", source=0)
|
||||
|
||||
def test_ignore_selfloops(self):
|
||||
G = nx.complete_graph(5)
|
||||
G.add_edge(3, 3)
|
||||
cycle = self.tsp(G, "greedy")
|
||||
assert len(cycle) - 1 == len(G) == len(set(cycle))
|
||||
|
||||
def test_not_weighted_graph(self):
|
||||
self.tsp(self.unweightedUG, "greedy")
|
||||
self.tsp(self.unweightedDG, "greedy")
|
||||
|
||||
def test_two_nodes(self):
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from({(1, 2, 1)})
|
||||
|
||||
cycle = self.tsp(G, "greedy", source=1, seed=42)
|
||||
cost = sum(G[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_solution(cycle, cost, [1, 2, 1], 2)
|
||||
|
||||
cycle = self.tsp(G, [1, 2, 1], source=1, seed=42)
|
||||
cost = sum(G[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
validate_solution(cycle, cost, [1, 2, 1], 2)
|
||||
|
||||
def test_failure_of_costs_too_high_when_iterations_low(self):
|
||||
# Simulated Annealing Version:
|
||||
# set number of moves low and alpha high
|
||||
cycle = self.tsp(
|
||||
self.DG2, "greedy", source="D", move="1-0", alpha=1, N_inner=1, seed=42
|
||||
)
|
||||
cost = sum(self.DG2[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
print(cycle, cost)
|
||||
assert cost > self.DG2_cost
|
||||
|
||||
# Try with an incorrect initial guess
|
||||
initial_sol = ["D", "A", "B", "C", "D"]
|
||||
cycle = self.tsp(
|
||||
self.DG,
|
||||
initial_sol,
|
||||
source="D",
|
||||
move="1-0",
|
||||
alpha=0.1,
|
||||
N_inner=1,
|
||||
max_iterations=1,
|
||||
seed=42,
|
||||
)
|
||||
cost = sum(self.DG[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
print(cycle, cost)
|
||||
assert cost > self.DG_cost
|
||||
|
||||
|
||||
class TestThresholdAcceptingTSP(TestSimulatedAnnealingTSP):
|
||||
tsp = staticmethod(nx_app.threshold_accepting_tsp)
|
||||
|
||||
def test_failure_of_costs_too_high_when_iterations_low(self):
|
||||
# Threshold Version:
|
||||
# set number of moves low and number of iterations low
|
||||
cycle = self.tsp(
|
||||
self.DG2,
|
||||
"greedy",
|
||||
source="D",
|
||||
move="1-0",
|
||||
N_inner=1,
|
||||
max_iterations=1,
|
||||
seed=4,
|
||||
)
|
||||
cost = sum(self.DG2[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
assert cost > self.DG2_cost
|
||||
|
||||
# set threshold too low
|
||||
initial_sol = ["D", "A", "B", "C", "D"]
|
||||
cycle = self.tsp(
|
||||
self.DG, initial_sol, source="D", move="1-0", threshold=-3, seed=42
|
||||
)
|
||||
cost = sum(self.DG[n][nbr]["weight"] for n, nbr in pairwise(cycle))
|
||||
assert cost > self.DG_cost
|
||||
|
||||
|
||||
# Tests for function traveling_salesman_problem
|
||||
def test_TSP_method():
|
||||
G = nx.cycle_graph(9)
|
||||
G[4][5]["weight"] = 10
|
||||
|
||||
def my_tsp_method(G, weight):
|
||||
return nx_app.simulated_annealing_tsp(G, "greedy", weight, source=4, seed=1)
|
||||
|
||||
path = nx_app.traveling_salesman_problem(G, method=my_tsp_method, cycle=False)
|
||||
print(path)
|
||||
assert path == [4, 3, 2, 1, 0, 8, 7, 6, 5]
|
||||
|
||||
|
||||
def test_TSP_unweighted():
|
||||
G = nx.cycle_graph(9)
|
||||
path = nx_app.traveling_salesman_problem(G, nodes=[3, 6], cycle=False)
|
||||
assert path in ([3, 4, 5, 6], [6, 5, 4, 3])
|
||||
|
||||
cycle = nx_app.traveling_salesman_problem(G, nodes=[3, 6])
|
||||
assert cycle in ([3, 4, 5, 6, 5, 4, 3], [6, 5, 4, 3, 4, 5, 6])
|
||||
|
||||
|
||||
def test_TSP_weighted():
|
||||
G = nx.cycle_graph(9)
|
||||
G[0][1]["weight"] = 2
|
||||
G[1][2]["weight"] = 2
|
||||
G[2][3]["weight"] = 2
|
||||
G[3][4]["weight"] = 4
|
||||
G[4][5]["weight"] = 5
|
||||
G[5][6]["weight"] = 4
|
||||
G[6][7]["weight"] = 2
|
||||
G[7][8]["weight"] = 2
|
||||
G[8][0]["weight"] = 2
|
||||
tsp = nx_app.traveling_salesman_problem
|
||||
|
||||
# path between 3 and 6
|
||||
expected_paths = ([3, 2, 1, 0, 8, 7, 6], [6, 7, 8, 0, 1, 2, 3])
|
||||
# cycle between 3 and 6
|
||||
expected_cycles = (
|
||||
[3, 2, 1, 0, 8, 7, 6, 7, 8, 0, 1, 2, 3],
|
||||
[6, 7, 8, 0, 1, 2, 3, 2, 1, 0, 8, 7, 6],
|
||||
)
|
||||
# path through all nodes
|
||||
expected_tourpaths = ([5, 6, 7, 8, 0, 1, 2, 3, 4], [4, 3, 2, 1, 0, 8, 7, 6, 5])
|
||||
|
||||
# Check default method
|
||||
cycle = tsp(G, nodes=[3, 6], weight="weight")
|
||||
assert cycle in expected_cycles
|
||||
|
||||
path = tsp(G, nodes=[3, 6], weight="weight", cycle=False)
|
||||
assert path in expected_paths
|
||||
|
||||
tourpath = tsp(G, weight="weight", cycle=False)
|
||||
assert tourpath in expected_tourpaths
|
||||
|
||||
# Check all methods
|
||||
methods = [
|
||||
nx_app.christofides,
|
||||
nx_app.greedy_tsp,
|
||||
lambda G, wt: nx_app.simulated_annealing_tsp(G, "greedy", weight=wt),
|
||||
lambda G, wt: nx_app.threshold_accepting_tsp(G, "greedy", weight=wt),
|
||||
]
|
||||
for method in methods:
|
||||
cycle = tsp(G, nodes=[3, 6], weight="weight", method=method)
|
||||
assert cycle in expected_cycles
|
||||
|
||||
path = tsp(G, nodes=[3, 6], weight="weight", method=method, cycle=False)
|
||||
assert path in expected_paths
|
||||
|
||||
tourpath = tsp(G, weight="weight", method=method, cycle=False)
|
||||
assert tourpath in expected_tourpaths
|
||||
|
||||
|
||||
def test_TSP_incomplete_graph_short_path():
|
||||
G = nx.cycle_graph(9)
|
||||
G.add_edges_from([(4, 9), (9, 10), (10, 11), (11, 0)])
|
||||
G[4][5]["weight"] = 5
|
||||
|
||||
cycle = nx_app.traveling_salesman_problem(G)
|
||||
print(cycle)
|
||||
assert len(cycle) == 17 and len(set(cycle)) == 12
|
||||
|
||||
# make sure that cutting one edge out of complete graph formulation
|
||||
# cuts out many edges out of the path of the TSP
|
||||
path = nx_app.traveling_salesman_problem(G, cycle=False)
|
||||
print(path)
|
||||
assert len(path) == 13 and len(set(path)) == 12
|
||||
|
||||
|
||||
def test_held_karp_ascent():
|
||||
"""
|
||||
Test the Held-Karp relaxation with the ascent method
|
||||
"""
|
||||
import networkx.algorithms.approximation.traveling_salesman as tsp
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
pytest.importorskip("scipy")
|
||||
|
||||
# Adjacency matrix from page 1153 of the 1970 Held and Karp paper
|
||||
# which have been edited to be directional, but also symmetric
|
||||
G_array = np.array(
|
||||
[
|
||||
[0, 97, 60, 73, 17, 52],
|
||||
[97, 0, 41, 52, 90, 30],
|
||||
[60, 41, 0, 21, 35, 41],
|
||||
[73, 52, 21, 0, 95, 46],
|
||||
[17, 90, 35, 95, 0, 81],
|
||||
[52, 30, 41, 46, 81, 0],
|
||||
]
|
||||
)
|
||||
|
||||
solution_edges = [(1, 3), (2, 4), (3, 2), (4, 0), (5, 1), (0, 5)]
|
||||
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.DiGraph)
|
||||
opt_hk, z_star = tsp.held_karp_ascent(G)
|
||||
|
||||
# Check that the optimal weights are the same
|
||||
assert round(opt_hk, 2) == 207.00
|
||||
# Check that the z_stars are the same
|
||||
solution = nx.DiGraph()
|
||||
solution.add_edges_from(solution_edges)
|
||||
assert nx.utils.edges_equal(z_star.edges, solution.edges)
|
||||
|
||||
|
||||
def test_ascent_fractional_solution():
|
||||
"""
|
||||
Test the ascent method using a modified version of Figure 2 on page 1140
|
||||
in 'The Traveling Salesman Problem and Minimum Spanning Trees' by Held and
|
||||
Karp
|
||||
"""
|
||||
import networkx.algorithms.approximation.traveling_salesman as tsp
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
pytest.importorskip("scipy")
|
||||
|
||||
# This version of Figure 2 has all of the edge weights multiplied by 100
|
||||
# and is a complete directed graph with infinite edge weights for the
|
||||
# edges not listed in the original graph
|
||||
G_array = np.array(
|
||||
[
|
||||
[0, 100, 100, 100000, 100000, 1],
|
||||
[100, 0, 100, 100000, 1, 100000],
|
||||
[100, 100, 0, 1, 100000, 100000],
|
||||
[100000, 100000, 1, 0, 100, 100],
|
||||
[100000, 1, 100000, 100, 0, 100],
|
||||
[1, 100000, 100000, 100, 100, 0],
|
||||
]
|
||||
)
|
||||
|
||||
solution_z_star = {
|
||||
(0, 1): 5 / 12,
|
||||
(0, 2): 5 / 12,
|
||||
(0, 5): 5 / 6,
|
||||
(1, 0): 5 / 12,
|
||||
(1, 2): 1 / 3,
|
||||
(1, 4): 5 / 6,
|
||||
(2, 0): 5 / 12,
|
||||
(2, 1): 1 / 3,
|
||||
(2, 3): 5 / 6,
|
||||
(3, 2): 5 / 6,
|
||||
(3, 4): 1 / 3,
|
||||
(3, 5): 1 / 2,
|
||||
(4, 1): 5 / 6,
|
||||
(4, 3): 1 / 3,
|
||||
(4, 5): 1 / 2,
|
||||
(5, 0): 5 / 6,
|
||||
(5, 3): 1 / 2,
|
||||
(5, 4): 1 / 2,
|
||||
}
|
||||
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.DiGraph)
|
||||
opt_hk, z_star = tsp.held_karp_ascent(G)
|
||||
|
||||
# Check that the optimal weights are the same
|
||||
assert round(opt_hk, 2) == 303.00
|
||||
# Check that the z_stars are the same
|
||||
assert {key: round(z_star[key], 4) for key in z_star} == {
|
||||
key: round(solution_z_star[key], 4) for key in solution_z_star
|
||||
}
|
||||
|
||||
|
||||
def test_ascent_method_asymmetric():
|
||||
"""
|
||||
Tests the ascent method using a truly asymmetric graph for which the
|
||||
solution has been brute forced
|
||||
"""
|
||||
import networkx.algorithms.approximation.traveling_salesman as tsp
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
pytest.importorskip("scipy")
|
||||
|
||||
G_array = np.array(
|
||||
[
|
||||
[0, 26, 63, 59, 69, 31, 41],
|
||||
[62, 0, 91, 53, 75, 87, 47],
|
||||
[47, 82, 0, 90, 15, 9, 18],
|
||||
[68, 19, 5, 0, 58, 34, 93],
|
||||
[11, 58, 53, 55, 0, 61, 79],
|
||||
[88, 75, 13, 76, 98, 0, 40],
|
||||
[41, 61, 55, 88, 46, 45, 0],
|
||||
]
|
||||
)
|
||||
|
||||
solution_edges = [(0, 1), (1, 3), (3, 2), (2, 5), (5, 6), (4, 0), (6, 4)]
|
||||
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.DiGraph)
|
||||
opt_hk, z_star = tsp.held_karp_ascent(G)
|
||||
|
||||
# Check that the optimal weights are the same
|
||||
assert round(opt_hk, 2) == 190.00
|
||||
# Check that the z_stars match.
|
||||
solution = nx.DiGraph()
|
||||
solution.add_edges_from(solution_edges)
|
||||
assert nx.utils.edges_equal(z_star.edges, solution.edges)
|
||||
|
||||
|
||||
def test_ascent_method_asymmetric_2():
|
||||
"""
|
||||
Tests the ascent method using a truly asymmetric graph for which the
|
||||
solution has been brute forced
|
||||
"""
|
||||
import networkx.algorithms.approximation.traveling_salesman as tsp
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
pytest.importorskip("scipy")
|
||||
|
||||
G_array = np.array(
|
||||
[
|
||||
[0, 45, 39, 92, 29, 31],
|
||||
[72, 0, 4, 12, 21, 60],
|
||||
[81, 6, 0, 98, 70, 53],
|
||||
[49, 71, 59, 0, 98, 94],
|
||||
[74, 95, 24, 43, 0, 47],
|
||||
[56, 43, 3, 65, 22, 0],
|
||||
]
|
||||
)
|
||||
|
||||
solution_edges = [(0, 5), (5, 4), (1, 3), (3, 0), (2, 1), (4, 2)]
|
||||
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.DiGraph)
|
||||
opt_hk, z_star = tsp.held_karp_ascent(G)
|
||||
|
||||
# Check that the optimal weights are the same
|
||||
assert round(opt_hk, 2) == 144.00
|
||||
# Check that the z_stars match.
|
||||
solution = nx.DiGraph()
|
||||
solution.add_edges_from(solution_edges)
|
||||
assert nx.utils.edges_equal(z_star.edges, solution.edges)
|
||||
|
||||
|
||||
def test_held_karp_ascent_asymmetric_3():
|
||||
"""
|
||||
Tests the ascent method using a truly asymmetric graph with a fractional
|
||||
solution for which the solution has been brute forced.
|
||||
|
||||
In this graph their are two different optimal, integral solutions (which
|
||||
are also the overall atsp solutions) to the Held Karp relaxation. However,
|
||||
this particular graph has two different tours of optimal value and the
|
||||
possible solutions in the held_karp_ascent function are not stored in an
|
||||
ordered data structure.
|
||||
"""
|
||||
import networkx.algorithms.approximation.traveling_salesman as tsp
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
pytest.importorskip("scipy")
|
||||
|
||||
G_array = np.array(
|
||||
[
|
||||
[0, 1, 5, 2, 7, 4],
|
||||
[7, 0, 7, 7, 1, 4],
|
||||
[4, 7, 0, 9, 2, 1],
|
||||
[7, 2, 7, 0, 4, 4],
|
||||
[5, 5, 4, 4, 0, 3],
|
||||
[3, 9, 1, 3, 4, 0],
|
||||
]
|
||||
)
|
||||
|
||||
solution1_edges = [(0, 3), (1, 4), (2, 5), (3, 1), (4, 2), (5, 0)]
|
||||
|
||||
solution2_edges = [(0, 3), (3, 1), (1, 4), (4, 5), (2, 0), (5, 2)]
|
||||
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.DiGraph)
|
||||
opt_hk, z_star = tsp.held_karp_ascent(G)
|
||||
|
||||
assert round(opt_hk, 2) == 13.00
|
||||
# Check that the z_stars are the same
|
||||
solution1 = nx.DiGraph()
|
||||
solution1.add_edges_from(solution1_edges)
|
||||
solution2 = nx.DiGraph()
|
||||
solution2.add_edges_from(solution2_edges)
|
||||
assert nx.utils.edges_equal(z_star.edges, solution1.edges) or nx.utils.edges_equal(
|
||||
z_star.edges, solution2.edges
|
||||
)
|
||||
|
||||
|
||||
def test_held_karp_ascent_fractional_asymmetric():
|
||||
"""
|
||||
Tests the ascent method using a truly asymmetric graph with a fractional
|
||||
solution for which the solution has been brute forced
|
||||
"""
|
||||
import networkx.algorithms.approximation.traveling_salesman as tsp
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
pytest.importorskip("scipy")
|
||||
|
||||
G_array = np.array(
|
||||
[
|
||||
[0, 100, 150, 100000, 100000, 1],
|
||||
[150, 0, 100, 100000, 1, 100000],
|
||||
[100, 150, 0, 1, 100000, 100000],
|
||||
[100000, 100000, 1, 0, 150, 100],
|
||||
[100000, 2, 100000, 100, 0, 150],
|
||||
[2, 100000, 100000, 150, 100, 0],
|
||||
]
|
||||
)
|
||||
|
||||
solution_z_star = {
|
||||
(0, 1): 5 / 12,
|
||||
(0, 2): 5 / 12,
|
||||
(0, 5): 5 / 6,
|
||||
(1, 0): 5 / 12,
|
||||
(1, 2): 5 / 12,
|
||||
(1, 4): 5 / 6,
|
||||
(2, 0): 5 / 12,
|
||||
(2, 1): 5 / 12,
|
||||
(2, 3): 5 / 6,
|
||||
(3, 2): 5 / 6,
|
||||
(3, 4): 5 / 12,
|
||||
(3, 5): 5 / 12,
|
||||
(4, 1): 5 / 6,
|
||||
(4, 3): 5 / 12,
|
||||
(4, 5): 5 / 12,
|
||||
(5, 0): 5 / 6,
|
||||
(5, 3): 5 / 12,
|
||||
(5, 4): 5 / 12,
|
||||
}
|
||||
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.DiGraph)
|
||||
opt_hk, z_star = tsp.held_karp_ascent(G)
|
||||
|
||||
# Check that the optimal weights are the same
|
||||
assert round(opt_hk, 2) == 304.00
|
||||
# Check that the z_stars are the same
|
||||
assert {key: round(z_star[key], 4) for key in z_star} == {
|
||||
key: round(solution_z_star[key], 4) for key in solution_z_star
|
||||
}
|
||||
|
||||
|
||||
def test_spanning_tree_distribution():
|
||||
"""
|
||||
Test that we can create an exponential distribution of spanning trees such
|
||||
that the probability of each tree is proportional to the product of edge
|
||||
weights.
|
||||
|
||||
Results of this test have been confirmed with hypothesis testing from the
|
||||
created distribution.
|
||||
|
||||
This test uses the symmetric, fractional Held Karp solution.
|
||||
"""
|
||||
import networkx.algorithms.approximation.traveling_salesman as tsp
|
||||
|
||||
pytest.importorskip("numpy")
|
||||
pytest.importorskip("scipy")
|
||||
|
||||
z_star = {
|
||||
(0, 1): 5 / 12,
|
||||
(0, 2): 5 / 12,
|
||||
(0, 5): 5 / 6,
|
||||
(1, 0): 5 / 12,
|
||||
(1, 2): 1 / 3,
|
||||
(1, 4): 5 / 6,
|
||||
(2, 0): 5 / 12,
|
||||
(2, 1): 1 / 3,
|
||||
(2, 3): 5 / 6,
|
||||
(3, 2): 5 / 6,
|
||||
(3, 4): 1 / 3,
|
||||
(3, 5): 1 / 2,
|
||||
(4, 1): 5 / 6,
|
||||
(4, 3): 1 / 3,
|
||||
(4, 5): 1 / 2,
|
||||
(5, 0): 5 / 6,
|
||||
(5, 3): 1 / 2,
|
||||
(5, 4): 1 / 2,
|
||||
}
|
||||
|
||||
solution_gamma = {
|
||||
(0, 1): -0.6383,
|
||||
(0, 2): -0.6827,
|
||||
(0, 5): 0,
|
||||
(1, 2): -1.0781,
|
||||
(1, 4): 0,
|
||||
(2, 3): 0,
|
||||
(5, 3): -0.2820,
|
||||
(5, 4): -0.3327,
|
||||
(4, 3): -0.9927,
|
||||
}
|
||||
|
||||
# The undirected support of z_star
|
||||
G = nx.MultiGraph()
|
||||
for u, v in z_star:
|
||||
if (u, v) in G.edges or (v, u) in G.edges:
|
||||
continue
|
||||
G.add_edge(u, v)
|
||||
|
||||
gamma = tsp.spanning_tree_distribution(G, z_star)
|
||||
|
||||
assert {key: round(gamma[key], 4) for key in gamma} == solution_gamma
|
||||
|
||||
|
||||
def test_asadpour_tsp():
|
||||
"""
|
||||
Test the complete asadpour tsp algorithm with the fractional, symmetric
|
||||
Held Karp solution. This test also uses an incomplete graph as input.
|
||||
"""
|
||||
# This version of Figure 2 has all of the edge weights multiplied by 100
|
||||
# and the 0 weight edges have a weight of 1.
|
||||
pytest.importorskip("numpy")
|
||||
pytest.importorskip("scipy")
|
||||
|
||||
edge_list = [
|
||||
(0, 1, 100),
|
||||
(0, 2, 100),
|
||||
(0, 5, 1),
|
||||
(1, 2, 100),
|
||||
(1, 4, 1),
|
||||
(2, 3, 1),
|
||||
(3, 4, 100),
|
||||
(3, 5, 100),
|
||||
(4, 5, 100),
|
||||
(1, 0, 100),
|
||||
(2, 0, 100),
|
||||
(5, 0, 1),
|
||||
(2, 1, 100),
|
||||
(4, 1, 1),
|
||||
(3, 2, 1),
|
||||
(4, 3, 100),
|
||||
(5, 3, 100),
|
||||
(5, 4, 100),
|
||||
]
|
||||
|
||||
G = nx.DiGraph()
|
||||
G.add_weighted_edges_from(edge_list)
|
||||
|
||||
def fixed_asadpour(G, weight):
|
||||
return nx_app.asadpour_atsp(G, weight, 19)
|
||||
|
||||
tour = nx_app.traveling_salesman_problem(G, weight="weight", method=fixed_asadpour)
|
||||
|
||||
# Check that the returned list is a valid tour. Because this is an
|
||||
# incomplete graph, the conditions are not as strict. We need the tour to
|
||||
#
|
||||
# Start and end at the same node
|
||||
# Pass through every vertex at least once
|
||||
# Have a total cost at most ln(6) / ln(ln(6)) = 3.0723 times the optimal
|
||||
#
|
||||
# For the second condition it is possible to have the tour pass through the
|
||||
# same vertex more then. Imagine that the tour on the complete version takes
|
||||
# an edge not in the original graph. In the output this is substituted with
|
||||
# the shortest path between those vertices, allowing vertices to appear more
|
||||
# than once.
|
||||
#
|
||||
# However, we are using a fixed random number generator so we know what the
|
||||
# expected tour is.
|
||||
expected_tours = [[1, 4, 5, 0, 2, 3, 2, 1], [3, 2, 0, 1, 4, 5, 3]]
|
||||
|
||||
assert tour in expected_tours
|
||||
|
||||
|
||||
def test_asadpour_real_world():
|
||||
"""
|
||||
This test uses airline prices between the six largest cities in the US.
|
||||
|
||||
* New York City -> JFK
|
||||
* Los Angeles -> LAX
|
||||
* Chicago -> ORD
|
||||
* Houston -> IAH
|
||||
* Phoenix -> PHX
|
||||
* Philadelphia -> PHL
|
||||
|
||||
Flight prices from August 2021 using Delta or American airlines to get
|
||||
nonstop flight. The brute force solution found the optimal tour to cost $872
|
||||
|
||||
This test also uses the `source` keyword argument to ensure that the tour
|
||||
always starts at city 0.
|
||||
"""
|
||||
np = pytest.importorskip("numpy")
|
||||
pytest.importorskip("scipy")
|
||||
|
||||
G_array = np.array(
|
||||
[
|
||||
# JFK LAX ORD IAH PHX PHL
|
||||
[0, 243, 199, 208, 169, 183], # JFK
|
||||
[277, 0, 217, 123, 127, 252], # LAX
|
||||
[297, 197, 0, 197, 123, 177], # ORD
|
||||
[303, 169, 197, 0, 117, 117], # IAH
|
||||
[257, 127, 160, 117, 0, 319], # PHX
|
||||
[183, 332, 217, 117, 319, 0], # PHL
|
||||
]
|
||||
)
|
||||
|
||||
node_map = {0: "JFK", 1: "LAX", 2: "ORD", 3: "IAH", 4: "PHX", 5: "PHL"}
|
||||
|
||||
expected_tours = [
|
||||
["JFK", "LAX", "PHX", "ORD", "IAH", "PHL", "JFK"],
|
||||
["JFK", "ORD", "PHX", "LAX", "IAH", "PHL", "JFK"],
|
||||
]
|
||||
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.DiGraph)
|
||||
nx.relabel_nodes(G, node_map, copy=False)
|
||||
|
||||
def fixed_asadpour(G, weight):
|
||||
return nx_app.asadpour_atsp(G, weight, 37, source="JFK")
|
||||
|
||||
tour = nx_app.traveling_salesman_problem(G, weight="weight", method=fixed_asadpour)
|
||||
|
||||
assert tour in expected_tours
|
||||
|
||||
|
||||
def test_asadpour_real_world_path():
|
||||
"""
|
||||
This test uses airline prices between the six largest cities in the US. This
|
||||
time using a path, not a cycle.
|
||||
|
||||
* New York City -> JFK
|
||||
* Los Angeles -> LAX
|
||||
* Chicago -> ORD
|
||||
* Houston -> IAH
|
||||
* Phoenix -> PHX
|
||||
* Philadelphia -> PHL
|
||||
|
||||
Flight prices from August 2021 using Delta or American airlines to get
|
||||
nonstop flight. The brute force solution found the optimal tour to cost $872
|
||||
"""
|
||||
np = pytest.importorskip("numpy")
|
||||
pytest.importorskip("scipy")
|
||||
|
||||
G_array = np.array(
|
||||
[
|
||||
# JFK LAX ORD IAH PHX PHL
|
||||
[0, 243, 199, 208, 169, 183], # JFK
|
||||
[277, 0, 217, 123, 127, 252], # LAX
|
||||
[297, 197, 0, 197, 123, 177], # ORD
|
||||
[303, 169, 197, 0, 117, 117], # IAH
|
||||
[257, 127, 160, 117, 0, 319], # PHX
|
||||
[183, 332, 217, 117, 319, 0], # PHL
|
||||
]
|
||||
)
|
||||
|
||||
node_map = {0: "JFK", 1: "LAX", 2: "ORD", 3: "IAH", 4: "PHX", 5: "PHL"}
|
||||
|
||||
expected_paths = [
|
||||
["ORD", "PHX", "LAX", "IAH", "PHL", "JFK"],
|
||||
["JFK", "PHL", "IAH", "ORD", "PHX", "LAX"],
|
||||
]
|
||||
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.DiGraph)
|
||||
nx.relabel_nodes(G, node_map, copy=False)
|
||||
|
||||
def fixed_asadpour(G, weight):
|
||||
return nx_app.asadpour_atsp(G, weight, 56)
|
||||
|
||||
path = nx_app.traveling_salesman_problem(
|
||||
G, weight="weight", cycle=False, method=fixed_asadpour
|
||||
)
|
||||
|
||||
assert path in expected_paths
|
||||
|
||||
|
||||
def test_asadpour_disconnected_graph():
|
||||
"""
|
||||
Test that the proper exception is raised when asadpour_atsp is given an
|
||||
disconnected graph.
|
||||
"""
|
||||
|
||||
G = nx.complete_graph(4, create_using=nx.DiGraph)
|
||||
# have to set edge weights so that if the exception is not raised, the
|
||||
# function will complete and we will fail the test
|
||||
nx.set_edge_attributes(G, 1, "weight")
|
||||
G.add_node(5)
|
||||
|
||||
pytest.raises(nx.NetworkXError, nx_app.asadpour_atsp, G)
|
||||
|
||||
|
||||
def test_asadpour_incomplete_graph():
|
||||
"""
|
||||
Test that the proper exception is raised when asadpour_atsp is given an
|
||||
incomplete graph
|
||||
"""
|
||||
|
||||
G = nx.complete_graph(4, create_using=nx.DiGraph)
|
||||
# have to set edge weights so that if the exception is not raised, the
|
||||
# function will complete and we will fail the test
|
||||
nx.set_edge_attributes(G, 1, "weight")
|
||||
G.remove_edge(0, 1)
|
||||
|
||||
pytest.raises(nx.NetworkXError, nx_app.asadpour_atsp, G)
|
||||
|
||||
|
||||
def test_asadpour_empty_graph():
|
||||
"""
|
||||
Test the asadpour_atsp function with an empty graph
|
||||
"""
|
||||
G = nx.DiGraph()
|
||||
|
||||
pytest.raises(nx.NetworkXError, nx_app.asadpour_atsp, G)
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_asadpour_integral_held_karp():
|
||||
"""
|
||||
This test uses an integral held karp solution and the held karp function
|
||||
will return a graph rather than a dict, bypassing most of the asadpour
|
||||
algorithm.
|
||||
|
||||
At first glance, this test probably doesn't look like it ensures that we
|
||||
skip the rest of the asadpour algorithm, but it does. We are not fixing a
|
||||
see for the random number generator, so if we sample any spanning trees
|
||||
the approximation would be different basically every time this test is
|
||||
executed but it is not since held karp is deterministic and we do not
|
||||
reach the portion of the code with the dependence on random numbers.
|
||||
"""
|
||||
np = pytest.importorskip("numpy")
|
||||
|
||||
G_array = np.array(
|
||||
[
|
||||
[0, 26, 63, 59, 69, 31, 41],
|
||||
[62, 0, 91, 53, 75, 87, 47],
|
||||
[47, 82, 0, 90, 15, 9, 18],
|
||||
[68, 19, 5, 0, 58, 34, 93],
|
||||
[11, 58, 53, 55, 0, 61, 79],
|
||||
[88, 75, 13, 76, 98, 0, 40],
|
||||
[41, 61, 55, 88, 46, 45, 0],
|
||||
]
|
||||
)
|
||||
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.DiGraph)
|
||||
|
||||
for _ in range(2):
|
||||
tour = nx_app.traveling_salesman_problem(G, method=nx_app.asadpour_atsp)
|
||||
|
||||
assert [1, 3, 2, 5, 2, 6, 4, 0, 1] == tour
|
||||
|
||||
|
||||
def test_directed_tsp_impossible():
|
||||
"""
|
||||
Test the asadpour algorithm with a graph without a hamiltonian circuit
|
||||
"""
|
||||
pytest.importorskip("numpy")
|
||||
|
||||
# In this graph, once we leave node 0 we cannot return
|
||||
edges = [
|
||||
(0, 1, 10),
|
||||
(0, 2, 11),
|
||||
(0, 3, 12),
|
||||
(1, 2, 4),
|
||||
(1, 3, 6),
|
||||
(2, 1, 3),
|
||||
(2, 3, 2),
|
||||
(3, 1, 5),
|
||||
(3, 2, 1),
|
||||
]
|
||||
|
||||
G = nx.DiGraph()
|
||||
G.add_weighted_edges_from(edges)
|
||||
|
||||
pytest.raises(nx.NetworkXError, nx_app.traveling_salesman_problem, G)
|
||||
280
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_treewidth.py
vendored
Normal file
280
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_treewidth.py
vendored
Normal file
@@ -0,0 +1,280 @@
|
||||
import itertools
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.approximation import (
|
||||
treewidth_min_degree,
|
||||
treewidth_min_fill_in,
|
||||
)
|
||||
from networkx.algorithms.approximation.treewidth import (
|
||||
MinDegreeHeuristic,
|
||||
min_fill_in_heuristic,
|
||||
)
|
||||
|
||||
|
||||
def is_tree_decomp(graph, decomp):
|
||||
"""Check if the given tree decomposition is valid."""
|
||||
for x in graph.nodes():
|
||||
appear_once = False
|
||||
for bag in decomp.nodes():
|
||||
if x in bag:
|
||||
appear_once = True
|
||||
break
|
||||
assert appear_once
|
||||
|
||||
# Check if each connected pair of nodes are at least once together in a bag
|
||||
for x, y in graph.edges():
|
||||
appear_together = False
|
||||
for bag in decomp.nodes():
|
||||
if x in bag and y in bag:
|
||||
appear_together = True
|
||||
break
|
||||
assert appear_together
|
||||
|
||||
# Check if the nodes associated with vertex v form a connected subset of T
|
||||
for v in graph.nodes():
|
||||
subset = []
|
||||
for bag in decomp.nodes():
|
||||
if v in bag:
|
||||
subset.append(bag)
|
||||
sub_graph = decomp.subgraph(subset)
|
||||
assert nx.is_connected(sub_graph)
|
||||
|
||||
|
||||
class TestTreewidthMinDegree:
|
||||
"""Unit tests for the min_degree function"""
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
"""Setup for different kinds of trees"""
|
||||
cls.complete = nx.Graph()
|
||||
cls.complete.add_edge(1, 2)
|
||||
cls.complete.add_edge(2, 3)
|
||||
cls.complete.add_edge(1, 3)
|
||||
|
||||
cls.small_tree = nx.Graph()
|
||||
cls.small_tree.add_edge(1, 3)
|
||||
cls.small_tree.add_edge(4, 3)
|
||||
cls.small_tree.add_edge(2, 3)
|
||||
cls.small_tree.add_edge(3, 5)
|
||||
cls.small_tree.add_edge(5, 6)
|
||||
cls.small_tree.add_edge(5, 7)
|
||||
cls.small_tree.add_edge(6, 7)
|
||||
|
||||
cls.deterministic_graph = nx.Graph()
|
||||
cls.deterministic_graph.add_edge(0, 1) # deg(0) = 1
|
||||
|
||||
cls.deterministic_graph.add_edge(1, 2) # deg(1) = 2
|
||||
|
||||
cls.deterministic_graph.add_edge(2, 3)
|
||||
cls.deterministic_graph.add_edge(2, 4) # deg(2) = 3
|
||||
|
||||
cls.deterministic_graph.add_edge(3, 4)
|
||||
cls.deterministic_graph.add_edge(3, 5)
|
||||
cls.deterministic_graph.add_edge(3, 6) # deg(3) = 4
|
||||
|
||||
cls.deterministic_graph.add_edge(4, 5)
|
||||
cls.deterministic_graph.add_edge(4, 6)
|
||||
cls.deterministic_graph.add_edge(4, 7) # deg(4) = 5
|
||||
|
||||
cls.deterministic_graph.add_edge(5, 6)
|
||||
cls.deterministic_graph.add_edge(5, 7)
|
||||
cls.deterministic_graph.add_edge(5, 8)
|
||||
cls.deterministic_graph.add_edge(5, 9) # deg(5) = 6
|
||||
|
||||
cls.deterministic_graph.add_edge(6, 7)
|
||||
cls.deterministic_graph.add_edge(6, 8)
|
||||
cls.deterministic_graph.add_edge(6, 9) # deg(6) = 6
|
||||
|
||||
cls.deterministic_graph.add_edge(7, 8)
|
||||
cls.deterministic_graph.add_edge(7, 9) # deg(7) = 5
|
||||
|
||||
cls.deterministic_graph.add_edge(8, 9) # deg(8) = 4
|
||||
|
||||
def test_petersen_graph(self):
|
||||
"""Test Petersen graph tree decomposition result"""
|
||||
G = nx.petersen_graph()
|
||||
_, decomp = treewidth_min_degree(G)
|
||||
is_tree_decomp(G, decomp)
|
||||
|
||||
def test_small_tree_treewidth(self):
|
||||
"""Test small tree
|
||||
|
||||
Test if the computed treewidth of the known self.small_tree is 2.
|
||||
As we know which value we can expect from our heuristic, values other
|
||||
than two are regressions
|
||||
"""
|
||||
G = self.small_tree
|
||||
# the order of removal should be [1,2,4]3[5,6,7]
|
||||
# (with [] denoting any order of the containing nodes)
|
||||
# resulting in treewidth 2 for the heuristic
|
||||
treewidth, _ = treewidth_min_fill_in(G)
|
||||
assert treewidth == 2
|
||||
|
||||
def test_heuristic_abort(self):
|
||||
"""Test heuristic abort condition for fully connected graph"""
|
||||
graph = {}
|
||||
for u in self.complete:
|
||||
graph[u] = set()
|
||||
for v in self.complete[u]:
|
||||
if u != v: # ignore self-loop
|
||||
graph[u].add(v)
|
||||
|
||||
deg_heuristic = MinDegreeHeuristic(graph)
|
||||
node = deg_heuristic.best_node(graph)
|
||||
if node is None:
|
||||
pass
|
||||
else:
|
||||
assert False
|
||||
|
||||
def test_empty_graph(self):
|
||||
"""Test empty graph"""
|
||||
G = nx.Graph()
|
||||
_, _ = treewidth_min_degree(G)
|
||||
|
||||
def test_two_component_graph(self):
|
||||
G = nx.Graph()
|
||||
G.add_node(1)
|
||||
G.add_node(2)
|
||||
treewidth, _ = treewidth_min_degree(G)
|
||||
assert treewidth == 0
|
||||
|
||||
def test_not_sortable_nodes(self):
|
||||
G = nx.Graph([(0, "a")])
|
||||
treewidth_min_degree(G)
|
||||
|
||||
def test_heuristic_first_steps(self):
|
||||
"""Test first steps of min_degree heuristic"""
|
||||
graph = {
|
||||
n: set(self.deterministic_graph[n]) - {n} for n in self.deterministic_graph
|
||||
}
|
||||
deg_heuristic = MinDegreeHeuristic(graph)
|
||||
elim_node = deg_heuristic.best_node(graph)
|
||||
print(f"Graph {graph}:")
|
||||
steps = []
|
||||
|
||||
while elim_node is not None:
|
||||
print(f"Removing {elim_node}:")
|
||||
steps.append(elim_node)
|
||||
nbrs = graph[elim_node]
|
||||
|
||||
for u, v in itertools.permutations(nbrs, 2):
|
||||
if v not in graph[u]:
|
||||
graph[u].add(v)
|
||||
|
||||
for u in graph:
|
||||
if elim_node in graph[u]:
|
||||
graph[u].remove(elim_node)
|
||||
|
||||
del graph[elim_node]
|
||||
print(f"Graph {graph}:")
|
||||
elim_node = deg_heuristic.best_node(graph)
|
||||
|
||||
# check only the first 5 elements for equality
|
||||
assert steps[:5] == [0, 1, 2, 3, 4]
|
||||
|
||||
|
||||
class TestTreewidthMinFillIn:
|
||||
"""Unit tests for the treewidth_min_fill_in function."""
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
"""Setup for different kinds of trees"""
|
||||
cls.complete = nx.Graph()
|
||||
cls.complete.add_edge(1, 2)
|
||||
cls.complete.add_edge(2, 3)
|
||||
cls.complete.add_edge(1, 3)
|
||||
|
||||
cls.small_tree = nx.Graph()
|
||||
cls.small_tree.add_edge(1, 2)
|
||||
cls.small_tree.add_edge(2, 3)
|
||||
cls.small_tree.add_edge(3, 4)
|
||||
cls.small_tree.add_edge(1, 4)
|
||||
cls.small_tree.add_edge(2, 4)
|
||||
cls.small_tree.add_edge(4, 5)
|
||||
cls.small_tree.add_edge(5, 6)
|
||||
cls.small_tree.add_edge(5, 7)
|
||||
cls.small_tree.add_edge(6, 7)
|
||||
|
||||
cls.deterministic_graph = nx.Graph()
|
||||
cls.deterministic_graph.add_edge(1, 2)
|
||||
cls.deterministic_graph.add_edge(1, 3)
|
||||
cls.deterministic_graph.add_edge(3, 4)
|
||||
cls.deterministic_graph.add_edge(2, 4)
|
||||
cls.deterministic_graph.add_edge(3, 5)
|
||||
cls.deterministic_graph.add_edge(4, 5)
|
||||
cls.deterministic_graph.add_edge(3, 6)
|
||||
cls.deterministic_graph.add_edge(5, 6)
|
||||
|
||||
def test_petersen_graph(self):
|
||||
"""Test Petersen graph tree decomposition result"""
|
||||
G = nx.petersen_graph()
|
||||
_, decomp = treewidth_min_fill_in(G)
|
||||
is_tree_decomp(G, decomp)
|
||||
|
||||
def test_small_tree_treewidth(self):
|
||||
"""Test if the computed treewidth of the known self.small_tree is 2"""
|
||||
G = self.small_tree
|
||||
# the order of removal should be [1,2,4]3[5,6,7]
|
||||
# (with [] denoting any order of the containing nodes)
|
||||
# resulting in treewidth 2 for the heuristic
|
||||
treewidth, _ = treewidth_min_fill_in(G)
|
||||
assert treewidth == 2
|
||||
|
||||
def test_heuristic_abort(self):
|
||||
"""Test if min_fill_in returns None for fully connected graph"""
|
||||
graph = {}
|
||||
for u in self.complete:
|
||||
graph[u] = set()
|
||||
for v in self.complete[u]:
|
||||
if u != v: # ignore self-loop
|
||||
graph[u].add(v)
|
||||
next_node = min_fill_in_heuristic(graph)
|
||||
if next_node is None:
|
||||
pass
|
||||
else:
|
||||
assert False
|
||||
|
||||
def test_empty_graph(self):
|
||||
"""Test empty graph"""
|
||||
G = nx.Graph()
|
||||
_, _ = treewidth_min_fill_in(G)
|
||||
|
||||
def test_two_component_graph(self):
|
||||
G = nx.Graph()
|
||||
G.add_node(1)
|
||||
G.add_node(2)
|
||||
treewidth, _ = treewidth_min_fill_in(G)
|
||||
assert treewidth == 0
|
||||
|
||||
def test_not_sortable_nodes(self):
|
||||
G = nx.Graph([(0, "a")])
|
||||
treewidth_min_fill_in(G)
|
||||
|
||||
def test_heuristic_first_steps(self):
|
||||
"""Test first steps of min_fill_in heuristic"""
|
||||
graph = {
|
||||
n: set(self.deterministic_graph[n]) - {n} for n in self.deterministic_graph
|
||||
}
|
||||
print(f"Graph {graph}:")
|
||||
elim_node = min_fill_in_heuristic(graph)
|
||||
steps = []
|
||||
|
||||
while elim_node is not None:
|
||||
print(f"Removing {elim_node}:")
|
||||
steps.append(elim_node)
|
||||
nbrs = graph[elim_node]
|
||||
|
||||
for u, v in itertools.permutations(nbrs, 2):
|
||||
if v not in graph[u]:
|
||||
graph[u].add(v)
|
||||
|
||||
for u in graph:
|
||||
if elim_node in graph[u]:
|
||||
graph[u].remove(elim_node)
|
||||
|
||||
del graph[elim_node]
|
||||
print(f"Graph {graph}:")
|
||||
elim_node = min_fill_in_heuristic(graph)
|
||||
|
||||
# check only the first 2 elements for equality
|
||||
assert steps[:2] == [6, 5]
|
||||
68
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_vertex_cover.py
vendored
Normal file
68
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/tests/test_vertex_cover.py
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
import networkx as nx
|
||||
from networkx.algorithms.approximation import min_weighted_vertex_cover
|
||||
|
||||
|
||||
def is_cover(G, node_cover):
|
||||
return all({u, v} & node_cover for u, v in G.edges())
|
||||
|
||||
|
||||
class TestMWVC:
|
||||
"""Unit tests for the approximate minimum weighted vertex cover
|
||||
function,
|
||||
:func:`~networkx.algorithms.approximation.vertex_cover.min_weighted_vertex_cover`.
|
||||
|
||||
"""
|
||||
|
||||
def test_unweighted_directed(self):
|
||||
# Create a star graph in which half the nodes are directed in
|
||||
# and half are directed out.
|
||||
G = nx.DiGraph()
|
||||
G.add_edges_from((0, v) for v in range(1, 26))
|
||||
G.add_edges_from((v, 0) for v in range(26, 51))
|
||||
cover = min_weighted_vertex_cover(G)
|
||||
assert 1 == len(cover)
|
||||
assert is_cover(G, cover)
|
||||
|
||||
def test_unweighted_undirected(self):
|
||||
# create a simple star graph
|
||||
size = 50
|
||||
sg = nx.star_graph(size)
|
||||
cover = min_weighted_vertex_cover(sg)
|
||||
assert 1 == len(cover)
|
||||
assert is_cover(sg, cover)
|
||||
|
||||
def test_weighted(self):
|
||||
wg = nx.Graph()
|
||||
wg.add_node(0, weight=10)
|
||||
wg.add_node(1, weight=1)
|
||||
wg.add_node(2, weight=1)
|
||||
wg.add_node(3, weight=1)
|
||||
wg.add_node(4, weight=1)
|
||||
|
||||
wg.add_edge(0, 1)
|
||||
wg.add_edge(0, 2)
|
||||
wg.add_edge(0, 3)
|
||||
wg.add_edge(0, 4)
|
||||
|
||||
wg.add_edge(1, 2)
|
||||
wg.add_edge(2, 3)
|
||||
wg.add_edge(3, 4)
|
||||
wg.add_edge(4, 1)
|
||||
|
||||
cover = min_weighted_vertex_cover(wg, weight="weight")
|
||||
csum = sum(wg.nodes[node]["weight"] for node in cover)
|
||||
assert 4 == csum
|
||||
assert is_cover(wg, cover)
|
||||
|
||||
def test_unweighted_self_loop(self):
|
||||
slg = nx.Graph()
|
||||
slg.add_node(0)
|
||||
slg.add_node(1)
|
||||
slg.add_node(2)
|
||||
|
||||
slg.add_edge(0, 1)
|
||||
slg.add_edge(2, 2)
|
||||
|
||||
cover = min_weighted_vertex_cover(slg)
|
||||
assert 2 == len(cover)
|
||||
assert is_cover(slg, cover)
|
||||
1434
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/traveling_salesman.py
vendored
Normal file
1434
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/traveling_salesman.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
249
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/treewidth.py
vendored
Normal file
249
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/treewidth.py
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
"""Functions for computing treewidth decomposition.
|
||||
|
||||
Treewidth of an undirected graph is a number associated with the graph.
|
||||
It can be defined as the size of the largest vertex set (bag) in a tree
|
||||
decomposition of the graph minus one.
|
||||
|
||||
`Wikipedia: Treewidth <https://en.wikipedia.org/wiki/Treewidth>`_
|
||||
|
||||
The notions of treewidth and tree decomposition have gained their
|
||||
attractiveness partly because many graph and network problems that are
|
||||
intractable (e.g., NP-hard) on arbitrary graphs become efficiently
|
||||
solvable (e.g., with a linear time algorithm) when the treewidth of the
|
||||
input graphs is bounded by a constant [1]_ [2]_.
|
||||
|
||||
There are two different functions for computing a tree decomposition:
|
||||
:func:`treewidth_min_degree` and :func:`treewidth_min_fill_in`.
|
||||
|
||||
.. [1] Hans L. Bodlaender and Arie M. C. A. Koster. 2010. "Treewidth
|
||||
computations I.Upper bounds". Inf. Comput. 208, 3 (March 2010),259-275.
|
||||
http://dx.doi.org/10.1016/j.ic.2009.03.008
|
||||
|
||||
.. [2] Hans L. Bodlaender. "Discovering Treewidth". Institute of Information
|
||||
and Computing Sciences, Utrecht University.
|
||||
Technical Report UU-CS-2005-018.
|
||||
http://www.cs.uu.nl
|
||||
|
||||
.. [3] K. Wang, Z. Lu, and J. Hicks *Treewidth*.
|
||||
https://web.archive.org/web/20210507025929/http://web.eecs.utk.edu/~cphill25/cs594_spring2015_projects/treewidth.pdf
|
||||
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import sys
|
||||
from heapq import heapify, heappop, heappush
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import not_implemented_for
|
||||
|
||||
__all__ = ["treewidth_min_degree", "treewidth_min_fill_in"]
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
@not_implemented_for("multigraph")
|
||||
def treewidth_min_degree(G):
|
||||
"""Returns a treewidth decomposition using the Minimum Degree heuristic.
|
||||
|
||||
The heuristic chooses the nodes according to their degree, i.e., first
|
||||
the node with the lowest degree is chosen, then the graph is updated
|
||||
and the corresponding node is removed. Next, a new node with the lowest
|
||||
degree is chosen, and so on.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
Treewidth decomposition : (int, Graph) tuple
|
||||
2-tuple with treewidth and the corresponding decomposed tree.
|
||||
"""
|
||||
deg_heuristic = MinDegreeHeuristic(G)
|
||||
return treewidth_decomp(G, lambda graph: deg_heuristic.best_node(graph))
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
@not_implemented_for("multigraph")
|
||||
def treewidth_min_fill_in(G):
|
||||
"""Returns a treewidth decomposition using the Minimum Fill-in heuristic.
|
||||
|
||||
The heuristic chooses a node from the graph, where the number of edges
|
||||
added turning the neighbourhood of the chosen node into clique is as
|
||||
small as possible.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
Returns
|
||||
-------
|
||||
Treewidth decomposition : (int, Graph) tuple
|
||||
2-tuple with treewidth and the corresponding decomposed tree.
|
||||
"""
|
||||
return treewidth_decomp(G, min_fill_in_heuristic)
|
||||
|
||||
|
||||
class MinDegreeHeuristic:
|
||||
"""Implements the Minimum Degree heuristic.
|
||||
|
||||
The heuristic chooses the nodes according to their degree
|
||||
(number of neighbours), i.e., first the node with the lowest degree is
|
||||
chosen, then the graph is updated and the corresponding node is
|
||||
removed. Next, a new node with the lowest degree is chosen, and so on.
|
||||
"""
|
||||
|
||||
def __init__(self, graph):
|
||||
self._graph = graph
|
||||
|
||||
# nodes that have to be updated in the heap before each iteration
|
||||
self._update_nodes = []
|
||||
|
||||
self._degreeq = [] # a heapq with 3-tuples (degree,unique_id,node)
|
||||
self.count = itertools.count()
|
||||
|
||||
# build heap with initial degrees
|
||||
for n in graph:
|
||||
self._degreeq.append((len(graph[n]), next(self.count), n))
|
||||
heapify(self._degreeq)
|
||||
|
||||
def best_node(self, graph):
|
||||
# update nodes in self._update_nodes
|
||||
for n in self._update_nodes:
|
||||
# insert changed degrees into degreeq
|
||||
heappush(self._degreeq, (len(graph[n]), next(self.count), n))
|
||||
|
||||
# get the next valid (minimum degree) node
|
||||
while self._degreeq:
|
||||
(min_degree, _, elim_node) = heappop(self._degreeq)
|
||||
if elim_node not in graph or len(graph[elim_node]) != min_degree:
|
||||
# outdated entry in degreeq
|
||||
continue
|
||||
elif min_degree == len(graph) - 1:
|
||||
# fully connected: abort condition
|
||||
return None
|
||||
|
||||
# remember to update nodes in the heap before getting the next node
|
||||
self._update_nodes = graph[elim_node]
|
||||
return elim_node
|
||||
|
||||
# the heap is empty: abort
|
||||
return None
|
||||
|
||||
|
||||
def min_fill_in_heuristic(graph):
|
||||
"""Implements the Minimum Degree heuristic.
|
||||
|
||||
Returns the node from the graph, where the number of edges added when
|
||||
turning the neighbourhood of the chosen node into clique is as small as
|
||||
possible. This algorithm chooses the nodes using the Minimum Fill-In
|
||||
heuristic. The running time of the algorithm is :math:`O(V^3)` and it uses
|
||||
additional constant memory."""
|
||||
|
||||
if len(graph) == 0:
|
||||
return None
|
||||
|
||||
min_fill_in_node = None
|
||||
|
||||
min_fill_in = sys.maxsize
|
||||
|
||||
# sort nodes by degree
|
||||
nodes_by_degree = sorted(graph, key=lambda x: len(graph[x]))
|
||||
min_degree = len(graph[nodes_by_degree[0]])
|
||||
|
||||
# abort condition (handle complete graph)
|
||||
if min_degree == len(graph) - 1:
|
||||
return None
|
||||
|
||||
for node in nodes_by_degree:
|
||||
num_fill_in = 0
|
||||
nbrs = graph[node]
|
||||
for nbr in nbrs:
|
||||
# count how many nodes in nbrs current nbr is not connected to
|
||||
# subtract 1 for the node itself
|
||||
num_fill_in += len(nbrs - graph[nbr]) - 1
|
||||
if num_fill_in >= 2 * min_fill_in:
|
||||
break
|
||||
|
||||
num_fill_in /= 2 # divide by 2 because of double counting
|
||||
|
||||
if num_fill_in < min_fill_in: # update min-fill-in node
|
||||
if num_fill_in == 0:
|
||||
return node
|
||||
min_fill_in = num_fill_in
|
||||
min_fill_in_node = node
|
||||
|
||||
return min_fill_in_node
|
||||
|
||||
|
||||
def treewidth_decomp(G, heuristic=min_fill_in_heuristic):
|
||||
"""Returns a treewidth decomposition using the passed heuristic.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
heuristic : heuristic function
|
||||
|
||||
Returns
|
||||
-------
|
||||
Treewidth decomposition : (int, Graph) tuple
|
||||
2-tuple with treewidth and the corresponding decomposed tree.
|
||||
"""
|
||||
|
||||
# make dict-of-sets structure
|
||||
graph = {n: set(G[n]) - {n} for n in G}
|
||||
|
||||
# stack containing nodes and neighbors in the order from the heuristic
|
||||
node_stack = []
|
||||
|
||||
# get first node from heuristic
|
||||
elim_node = heuristic(graph)
|
||||
while elim_node is not None:
|
||||
# connect all neighbours with each other
|
||||
nbrs = graph[elim_node]
|
||||
for u, v in itertools.permutations(nbrs, 2):
|
||||
if v not in graph[u]:
|
||||
graph[u].add(v)
|
||||
|
||||
# push node and its current neighbors on stack
|
||||
node_stack.append((elim_node, nbrs))
|
||||
|
||||
# remove node from graph
|
||||
for u in graph[elim_node]:
|
||||
graph[u].remove(elim_node)
|
||||
|
||||
del graph[elim_node]
|
||||
elim_node = heuristic(graph)
|
||||
|
||||
# the abort condition is met; put all remaining nodes into one bag
|
||||
decomp = nx.Graph()
|
||||
first_bag = frozenset(graph.keys())
|
||||
decomp.add_node(first_bag)
|
||||
|
||||
treewidth = len(first_bag) - 1
|
||||
|
||||
while node_stack:
|
||||
# get node and its neighbors from the stack
|
||||
(curr_node, nbrs) = node_stack.pop()
|
||||
|
||||
# find a bag all neighbors are in
|
||||
old_bag = None
|
||||
for bag in decomp.nodes:
|
||||
if nbrs <= bag:
|
||||
old_bag = bag
|
||||
break
|
||||
|
||||
if old_bag is None:
|
||||
# no old_bag was found: just connect to the first_bag
|
||||
old_bag = first_bag
|
||||
|
||||
# create new node for decomposition
|
||||
nbrs.add(curr_node)
|
||||
new_bag = frozenset(nbrs)
|
||||
|
||||
# update treewidth
|
||||
treewidth = max(treewidth, len(new_bag) - 1)
|
||||
|
||||
# add edge to decomposition (implicitly also adds the new node)
|
||||
decomp.add_edge(old_bag, new_bag)
|
||||
|
||||
return treewidth, decomp
|
||||
80
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/vertex_cover.py
vendored
Normal file
80
.CondaPkg/env/Lib/site-packages/networkx/algorithms/approximation/vertex_cover.py
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
"""Functions for computing an approximate minimum weight vertex cover.
|
||||
|
||||
A |vertex cover|_ is a subset of nodes such that each edge in the graph
|
||||
is incident to at least one node in the subset.
|
||||
|
||||
.. _vertex cover: https://en.wikipedia.org/wiki/Vertex_cover
|
||||
.. |vertex cover| replace:: *vertex cover*
|
||||
|
||||
"""
|
||||
|
||||
__all__ = ["min_weighted_vertex_cover"]
|
||||
|
||||
|
||||
def min_weighted_vertex_cover(G, weight=None):
|
||||
r"""Returns an approximate minimum weighted vertex cover.
|
||||
|
||||
The set of nodes returned by this function is guaranteed to be a
|
||||
vertex cover, and the total weight of the set is guaranteed to be at
|
||||
most twice the total weight of the minimum weight vertex cover. In
|
||||
other words,
|
||||
|
||||
.. math::
|
||||
|
||||
w(S) \leq 2 * w(S^*),
|
||||
|
||||
where $S$ is the vertex cover returned by this function,
|
||||
$S^*$ is the vertex cover of minimum weight out of all vertex
|
||||
covers of the graph, and $w$ is the function that computes the
|
||||
sum of the weights of each node in that given set.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
|
||||
weight : string, optional (default = None)
|
||||
If None, every node has weight 1. If a string, use this node
|
||||
attribute as the node weight. A node without this attribute is
|
||||
assumed to have weight 1.
|
||||
|
||||
Returns
|
||||
-------
|
||||
min_weighted_cover : set
|
||||
Returns a set of nodes whose weight sum is no more than twice
|
||||
the weight sum of the minimum weight vertex cover.
|
||||
|
||||
Notes
|
||||
-----
|
||||
For a directed graph, a vertex cover has the same definition: a set
|
||||
of nodes such that each edge in the graph is incident to at least
|
||||
one node in the set. Whether the node is the head or tail of the
|
||||
directed edge is ignored.
|
||||
|
||||
This is the local-ratio algorithm for computing an approximate
|
||||
vertex cover. The algorithm greedily reduces the costs over edges,
|
||||
iteratively building a cover. The worst-case runtime of this
|
||||
implementation is $O(m \log n)$, where $n$ is the number
|
||||
of nodes and $m$ the number of edges in the graph.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Bar-Yehuda, R., and Even, S. (1985). "A local-ratio theorem for
|
||||
approximating the weighted vertex cover problem."
|
||||
*Annals of Discrete Mathematics*, 25, 27–46
|
||||
<http://www.cs.technion.ac.il/~reuven/PDF/vc_lr.pdf>
|
||||
|
||||
"""
|
||||
cost = dict(G.nodes(data=weight, default=1))
|
||||
# While there are uncovered edges, choose an uncovered and update
|
||||
# the cost of the remaining edges.
|
||||
cover = set()
|
||||
for u, v in G.edges():
|
||||
if u in cover or v in cover:
|
||||
continue
|
||||
if cost[u] <= cost[v]:
|
||||
cover.add(u)
|
||||
cost[v] -= cost[u]
|
||||
else:
|
||||
cover.add(v)
|
||||
cost[u] -= cost[v]
|
||||
return cover
|
||||
Reference in New Issue
Block a user