using for loop to install conda package
This commit is contained in:
6
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__init__.py
vendored
Normal file
6
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__init__.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
from .branchings import *
|
||||
from .coding import *
|
||||
from .mst import *
|
||||
from .recognition import *
|
||||
from .operations import *
|
||||
from .decomposition import *
|
||||
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__pycache__/branchings.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__pycache__/branchings.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__pycache__/coding.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__pycache__/coding.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__pycache__/decomposition.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__pycache__/decomposition.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__pycache__/mst.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__pycache__/mst.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__pycache__/operations.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__pycache__/operations.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__pycache__/recognition.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/__pycache__/recognition.cpython-311.pyc
vendored
Normal file
Binary file not shown.
1048
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/branchings.py
vendored
Normal file
1048
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/branchings.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
398
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/coding.py
vendored
Normal file
398
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/coding.py
vendored
Normal file
@@ -0,0 +1,398 @@
|
||||
"""Functions for encoding and decoding trees.
|
||||
|
||||
Since a tree is a highly restricted form of graph, it can be represented
|
||||
concisely in several ways. This module includes functions for encoding
|
||||
and decoding trees in the form of nested tuples and Prüfer
|
||||
sequences. The former requires a rooted tree, whereas the latter can be
|
||||
applied to unrooted trees. Furthermore, there is a bijection from Prüfer
|
||||
sequences to labeled trees.
|
||||
|
||||
"""
|
||||
from collections import Counter
|
||||
from itertools import chain
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import not_implemented_for
|
||||
|
||||
__all__ = [
|
||||
"from_nested_tuple",
|
||||
"from_prufer_sequence",
|
||||
"NotATree",
|
||||
"to_nested_tuple",
|
||||
"to_prufer_sequence",
|
||||
]
|
||||
|
||||
|
||||
class NotATree(nx.NetworkXException):
|
||||
"""Raised when a function expects a tree (that is, a connected
|
||||
undirected graph with no cycles) but gets a non-tree graph as input
|
||||
instead.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
def to_nested_tuple(T, root, canonical_form=False):
|
||||
"""Returns a nested tuple representation of the given tree.
|
||||
|
||||
The nested tuple representation of a tree is defined
|
||||
recursively. The tree with one node and no edges is represented by
|
||||
the empty tuple, ``()``. A tree with ``k`` subtrees is represented
|
||||
by a tuple of length ``k`` in which each element is the nested tuple
|
||||
representation of a subtree.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
T : NetworkX graph
|
||||
An undirected graph object representing a tree.
|
||||
|
||||
root : node
|
||||
The node in ``T`` to interpret as the root of the tree.
|
||||
|
||||
canonical_form : bool
|
||||
If ``True``, each tuple is sorted so that the function returns
|
||||
a canonical form for rooted trees. This means "lighter" subtrees
|
||||
will appear as nested tuples before "heavier" subtrees. In this
|
||||
way, each isomorphic rooted tree has the same nested tuple
|
||||
representation.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple
|
||||
A nested tuple representation of the tree.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function is *not* the inverse of :func:`from_nested_tuple`; the
|
||||
only guarantee is that the rooted trees are isomorphic.
|
||||
|
||||
See also
|
||||
--------
|
||||
from_nested_tuple
|
||||
to_prufer_sequence
|
||||
|
||||
Examples
|
||||
--------
|
||||
The tree need not be a balanced binary tree::
|
||||
|
||||
>>> T = nx.Graph()
|
||||
>>> T.add_edges_from([(0, 1), (0, 2), (0, 3)])
|
||||
>>> T.add_edges_from([(1, 4), (1, 5)])
|
||||
>>> T.add_edges_from([(3, 6), (3, 7)])
|
||||
>>> root = 0
|
||||
>>> nx.to_nested_tuple(T, root)
|
||||
(((), ()), (), ((), ()))
|
||||
|
||||
Continuing the above example, if ``canonical_form`` is ``True``, the
|
||||
nested tuples will be sorted::
|
||||
|
||||
>>> nx.to_nested_tuple(T, root, canonical_form=True)
|
||||
((), ((), ()), ((), ()))
|
||||
|
||||
Even the path graph can be interpreted as a tree::
|
||||
|
||||
>>> T = nx.path_graph(4)
|
||||
>>> root = 0
|
||||
>>> nx.to_nested_tuple(T, root)
|
||||
((((),),),)
|
||||
|
||||
"""
|
||||
|
||||
def _make_tuple(T, root, _parent):
|
||||
"""Recursively compute the nested tuple representation of the
|
||||
given rooted tree.
|
||||
|
||||
``_parent`` is the parent node of ``root`` in the supertree in
|
||||
which ``T`` is a subtree, or ``None`` if ``root`` is the root of
|
||||
the supertree. This argument is used to determine which
|
||||
neighbors of ``root`` are children and which is the parent.
|
||||
|
||||
"""
|
||||
# Get the neighbors of `root` that are not the parent node. We
|
||||
# are guaranteed that `root` is always in `T` by construction.
|
||||
children = set(T[root]) - {_parent}
|
||||
if len(children) == 0:
|
||||
return ()
|
||||
nested = (_make_tuple(T, v, root) for v in children)
|
||||
if canonical_form:
|
||||
nested = sorted(nested)
|
||||
return tuple(nested)
|
||||
|
||||
# Do some sanity checks on the input.
|
||||
if not nx.is_tree(T):
|
||||
raise nx.NotATree("provided graph is not a tree")
|
||||
if root not in T:
|
||||
raise nx.NodeNotFound(f"Graph {T} contains no node {root}")
|
||||
|
||||
return _make_tuple(T, root, None)
|
||||
|
||||
|
||||
def from_nested_tuple(sequence, sensible_relabeling=False):
|
||||
"""Returns the rooted tree corresponding to the given nested tuple.
|
||||
|
||||
The nested tuple representation of a tree is defined
|
||||
recursively. The tree with one node and no edges is represented by
|
||||
the empty tuple, ``()``. A tree with ``k`` subtrees is represented
|
||||
by a tuple of length ``k`` in which each element is the nested tuple
|
||||
representation of a subtree.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sequence : tuple
|
||||
A nested tuple representing a rooted tree.
|
||||
|
||||
sensible_relabeling : bool
|
||||
Whether to relabel the nodes of the tree so that nodes are
|
||||
labeled in increasing order according to their breadth-first
|
||||
search order from the root node.
|
||||
|
||||
Returns
|
||||
-------
|
||||
NetworkX graph
|
||||
The tree corresponding to the given nested tuple, whose root
|
||||
node is node 0. If ``sensible_labeling`` is ``True``, nodes will
|
||||
be labeled in breadth-first search order starting from the root
|
||||
node.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function is *not* the inverse of :func:`to_nested_tuple`; the
|
||||
only guarantee is that the rooted trees are isomorphic.
|
||||
|
||||
See also
|
||||
--------
|
||||
to_nested_tuple
|
||||
from_prufer_sequence
|
||||
|
||||
Examples
|
||||
--------
|
||||
Sensible relabeling ensures that the nodes are labeled from the root
|
||||
starting at 0::
|
||||
|
||||
>>> balanced = (((), ()), ((), ()))
|
||||
>>> T = nx.from_nested_tuple(balanced, sensible_relabeling=True)
|
||||
>>> edges = [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]
|
||||
>>> all((u, v) in T.edges() or (v, u) in T.edges() for (u, v) in edges)
|
||||
True
|
||||
|
||||
"""
|
||||
|
||||
def _make_tree(sequence):
|
||||
"""Recursively creates a tree from the given sequence of nested
|
||||
tuples.
|
||||
|
||||
This function employs the :func:`~networkx.tree.join` function
|
||||
to recursively join subtrees into a larger tree.
|
||||
|
||||
"""
|
||||
# The empty sequence represents the empty tree, which is the
|
||||
# (unique) graph with a single node. We mark the single node
|
||||
# with an attribute that indicates that it is the root of the
|
||||
# graph.
|
||||
if len(sequence) == 0:
|
||||
return nx.empty_graph(1)
|
||||
# For a nonempty sequence, get the subtrees for each child
|
||||
# sequence and join all the subtrees at their roots. After
|
||||
# joining the subtrees, the root is node 0.
|
||||
return nx.tree.join([(_make_tree(child), 0) for child in sequence])
|
||||
|
||||
# Make the tree and remove the `is_root` node attribute added by the
|
||||
# helper function.
|
||||
T = _make_tree(sequence)
|
||||
if sensible_relabeling:
|
||||
# Relabel the nodes according to their breadth-first search
|
||||
# order, starting from the root node (that is, the node 0).
|
||||
bfs_nodes = chain([0], (v for u, v in nx.bfs_edges(T, 0)))
|
||||
labels = {v: i for i, v in enumerate(bfs_nodes)}
|
||||
# We would like to use `copy=False`, but `relabel_nodes` doesn't
|
||||
# allow a relabel mapping that can't be topologically sorted.
|
||||
T = nx.relabel_nodes(T, labels)
|
||||
return T
|
||||
|
||||
|
||||
@not_implemented_for("directed")
|
||||
def to_prufer_sequence(T):
|
||||
r"""Returns the Prüfer sequence of the given tree.
|
||||
|
||||
A *Prüfer sequence* is a list of *n* - 2 numbers between 0 and
|
||||
*n* - 1, inclusive. The tree corresponding to a given Prüfer
|
||||
sequence can be recovered by repeatedly joining a node in the
|
||||
sequence with a node with the smallest potential degree according to
|
||||
the sequence.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
T : NetworkX graph
|
||||
An undirected graph object representing a tree.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
The Prüfer sequence of the given tree.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXPointlessConcept
|
||||
If the number of nodes in `T` is less than two.
|
||||
|
||||
NotATree
|
||||
If `T` is not a tree.
|
||||
|
||||
KeyError
|
||||
If the set of nodes in `T` is not {0, …, *n* - 1}.
|
||||
|
||||
Notes
|
||||
-----
|
||||
There is a bijection from labeled trees to Prüfer sequences. This
|
||||
function is the inverse of the :func:`from_prufer_sequence`
|
||||
function.
|
||||
|
||||
Sometimes Prüfer sequences use nodes labeled from 1 to *n* instead
|
||||
of from 0 to *n* - 1. This function requires nodes to be labeled in
|
||||
the latter form. You can use :func:`~networkx.relabel_nodes` to
|
||||
relabel the nodes of your tree to the appropriate format.
|
||||
|
||||
This implementation is from [1]_ and has a running time of
|
||||
$O(n)$.
|
||||
|
||||
See also
|
||||
--------
|
||||
to_nested_tuple
|
||||
from_prufer_sequence
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Wang, Xiaodong, Lei Wang, and Yingjie Wu.
|
||||
"An optimal algorithm for Prufer codes."
|
||||
*Journal of Software Engineering and Applications* 2.02 (2009): 111.
|
||||
<https://doi.org/10.4236/jsea.2009.22016>
|
||||
|
||||
Examples
|
||||
--------
|
||||
There is a bijection between Prüfer sequences and labeled trees, so
|
||||
this function is the inverse of the :func:`from_prufer_sequence`
|
||||
function:
|
||||
|
||||
>>> edges = [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)]
|
||||
>>> tree = nx.Graph(edges)
|
||||
>>> sequence = nx.to_prufer_sequence(tree)
|
||||
>>> sequence
|
||||
[3, 3, 3, 4]
|
||||
>>> tree2 = nx.from_prufer_sequence(sequence)
|
||||
>>> list(tree2.edges()) == edges
|
||||
True
|
||||
|
||||
"""
|
||||
# Perform some sanity checks on the input.
|
||||
n = len(T)
|
||||
if n < 2:
|
||||
msg = "Prüfer sequence undefined for trees with fewer than two nodes"
|
||||
raise nx.NetworkXPointlessConcept(msg)
|
||||
if not nx.is_tree(T):
|
||||
raise nx.NotATree("provided graph is not a tree")
|
||||
if set(T) != set(range(n)):
|
||||
raise KeyError("tree must have node labels {0, ..., n - 1}")
|
||||
|
||||
degree = dict(T.degree())
|
||||
|
||||
def parents(u):
|
||||
return next(v for v in T[u] if degree[v] > 1)
|
||||
|
||||
index = u = next(k for k in range(n) if degree[k] == 1)
|
||||
result = []
|
||||
for i in range(n - 2):
|
||||
v = parents(u)
|
||||
result.append(v)
|
||||
degree[v] -= 1
|
||||
if v < index and degree[v] == 1:
|
||||
u = v
|
||||
else:
|
||||
index = u = next(k for k in range(index + 1, n) if degree[k] == 1)
|
||||
return result
|
||||
|
||||
|
||||
def from_prufer_sequence(sequence):
|
||||
r"""Returns the tree corresponding to the given Prüfer sequence.
|
||||
|
||||
A *Prüfer sequence* is a list of *n* - 2 numbers between 0 and
|
||||
*n* - 1, inclusive. The tree corresponding to a given Prüfer
|
||||
sequence can be recovered by repeatedly joining a node in the
|
||||
sequence with a node with the smallest potential degree according to
|
||||
the sequence.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sequence : list
|
||||
A Prüfer sequence, which is a list of *n* - 2 integers between
|
||||
zero and *n* - 1, inclusive.
|
||||
|
||||
Returns
|
||||
-------
|
||||
NetworkX graph
|
||||
The tree corresponding to the given Prüfer sequence.
|
||||
|
||||
Notes
|
||||
-----
|
||||
There is a bijection from labeled trees to Prüfer sequences. This
|
||||
function is the inverse of the :func:`from_prufer_sequence` function.
|
||||
|
||||
Sometimes Prüfer sequences use nodes labeled from 1 to *n* instead
|
||||
of from 0 to *n* - 1. This function requires nodes to be labeled in
|
||||
the latter form. You can use :func:`networkx.relabel_nodes` to
|
||||
relabel the nodes of your tree to the appropriate format.
|
||||
|
||||
This implementation is from [1]_ and has a running time of
|
||||
$O(n)$.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Wang, Xiaodong, Lei Wang, and Yingjie Wu.
|
||||
"An optimal algorithm for Prufer codes."
|
||||
*Journal of Software Engineering and Applications* 2.02 (2009): 111.
|
||||
<https://doi.org/10.4236/jsea.2009.22016>
|
||||
|
||||
See also
|
||||
--------
|
||||
from_nested_tuple
|
||||
to_prufer_sequence
|
||||
|
||||
Examples
|
||||
--------
|
||||
There is a bijection between Prüfer sequences and labeled trees, so
|
||||
this function is the inverse of the :func:`to_prufer_sequence`
|
||||
function:
|
||||
|
||||
>>> edges = [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)]
|
||||
>>> tree = nx.Graph(edges)
|
||||
>>> sequence = nx.to_prufer_sequence(tree)
|
||||
>>> sequence
|
||||
[3, 3, 3, 4]
|
||||
>>> tree2 = nx.from_prufer_sequence(sequence)
|
||||
>>> list(tree2.edges()) == edges
|
||||
True
|
||||
|
||||
"""
|
||||
n = len(sequence) + 2
|
||||
# `degree` stores the remaining degree (plus one) for each node. The
|
||||
# degree of a node in the decoded tree is one more than the number
|
||||
# of times it appears in the code.
|
||||
degree = Counter(chain(sequence, range(n)))
|
||||
T = nx.empty_graph(n)
|
||||
# `not_orphaned` is the set of nodes that have a parent in the
|
||||
# tree. After the loop, there should be exactly two nodes that are
|
||||
# not in this set.
|
||||
not_orphaned = set()
|
||||
index = u = next(k for k in range(n) if degree[k] == 1)
|
||||
for v in sequence:
|
||||
T.add_edge(u, v)
|
||||
not_orphaned.add(u)
|
||||
degree[v] -= 1
|
||||
if v < index and degree[v] == 1:
|
||||
u = v
|
||||
else:
|
||||
index = u = next(k for k in range(index + 1, n) if degree[k] == 1)
|
||||
# At this point, there must be exactly two orphaned nodes; join them.
|
||||
orphans = set(T) - not_orphaned
|
||||
u, v = orphans
|
||||
T.add_edge(u, v)
|
||||
return T
|
||||
87
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/decomposition.py
vendored
Normal file
87
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/decomposition.py
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
r"""Function for computing a junction tree of a graph."""
|
||||
|
||||
from itertools import combinations
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms import chordal_graph_cliques, complete_to_chordal_graph, moral
|
||||
from networkx.utils import not_implemented_for
|
||||
|
||||
__all__ = ["junction_tree"]
|
||||
|
||||
|
||||
@not_implemented_for("multigraph")
|
||||
def junction_tree(G):
|
||||
r"""Returns a junction tree of a given graph.
|
||||
|
||||
A junction tree (or clique tree) is constructed from a (un)directed graph G.
|
||||
The tree is constructed based on a moralized and triangulated version of G.
|
||||
The tree's nodes consist of maximal cliques and sepsets of the revised graph.
|
||||
The sepset of two cliques is the intersection of the nodes of these cliques,
|
||||
e.g. the sepset of (A,B,C) and (A,C,E,F) is (A,C). These nodes are often called
|
||||
"variables" in this literature. The tree is bipartitie with each sepset
|
||||
connected to its two cliques.
|
||||
|
||||
Junction Trees are not unique as the order of clique consideration determines
|
||||
which sepsets are included.
|
||||
|
||||
The junction tree algorithm consists of five steps [1]_:
|
||||
|
||||
1. Moralize the graph
|
||||
2. Triangulate the graph
|
||||
3. Find maximal cliques
|
||||
4. Build the tree from cliques, connecting cliques with shared
|
||||
nodes, set edge-weight to number of shared variables
|
||||
5. Find maximum spanning tree
|
||||
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : networkx.Graph
|
||||
Directed or undirected graph.
|
||||
|
||||
Returns
|
||||
-------
|
||||
junction_tree : networkx.Graph
|
||||
The corresponding junction tree of `G`.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXNotImplemented
|
||||
Raised if `G` is an instance of `MultiGraph` or `MultiDiGraph`.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Junction tree algorithm:
|
||||
https://en.wikipedia.org/wiki/Junction_tree_algorithm
|
||||
|
||||
.. [2] Finn V. Jensen and Frank Jensen. 1994. Optimal
|
||||
junction trees. In Proceedings of the Tenth international
|
||||
conference on Uncertainty in artificial intelligence (UAI’94).
|
||||
Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 360–366.
|
||||
"""
|
||||
|
||||
clique_graph = nx.Graph()
|
||||
|
||||
if G.is_directed():
|
||||
G = moral.moral_graph(G)
|
||||
chordal_graph, _ = complete_to_chordal_graph(G)
|
||||
|
||||
cliques = [tuple(sorted(i)) for i in chordal_graph_cliques(chordal_graph)]
|
||||
clique_graph.add_nodes_from(cliques, type="clique")
|
||||
|
||||
for edge in combinations(cliques, 2):
|
||||
set_edge_0 = set(edge[0])
|
||||
set_edge_1 = set(edge[1])
|
||||
if not set_edge_0.isdisjoint(set_edge_1):
|
||||
sepset = tuple(sorted(set_edge_0.intersection(set_edge_1)))
|
||||
clique_graph.add_edge(edge[0], edge[1], weight=len(sepset), sepset=sepset)
|
||||
|
||||
junction_tree = nx.maximum_spanning_tree(clique_graph)
|
||||
|
||||
for edge in list(junction_tree.edges(data=True)):
|
||||
junction_tree.add_node(edge[2]["sepset"], type="sepset")
|
||||
junction_tree.add_edge(edge[0], edge[2]["sepset"])
|
||||
junction_tree.add_edge(edge[1], edge[2]["sepset"])
|
||||
junction_tree.remove_edge(edge[0], edge[1])
|
||||
|
||||
return junction_tree
|
||||
1122
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/mst.py
vendored
Normal file
1122
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/mst.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
106
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/operations.py
vendored
Normal file
106
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/operations.py
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
"""Operations on trees."""
|
||||
from functools import partial
|
||||
from itertools import accumulate, chain
|
||||
|
||||
import networkx as nx
|
||||
|
||||
__all__ = ["join"]
|
||||
|
||||
|
||||
def join(rooted_trees, label_attribute=None):
|
||||
"""Returns a new rooted tree with a root node joined with the roots
|
||||
of each of the given rooted trees.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rooted_trees : list
|
||||
A list of pairs in which each left element is a NetworkX graph
|
||||
object representing a tree and each right element is the root
|
||||
node of that tree. The nodes of these trees will be relabeled to
|
||||
integers.
|
||||
|
||||
label_attribute : str
|
||||
If provided, the old node labels will be stored in the new tree
|
||||
under this node attribute. If not provided, the node attribute
|
||||
``'_old'`` will store the original label of the node in the
|
||||
rooted trees given in the input.
|
||||
|
||||
Returns
|
||||
-------
|
||||
NetworkX graph
|
||||
The rooted tree whose subtrees are the given rooted trees. The
|
||||
new root node is labeled 0. Each non-root node has an attribute,
|
||||
as described under the keyword argument ``label_attribute``,
|
||||
that indicates the label of the original node in the input tree.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Graph, edge, and node attributes are propagated from the given
|
||||
rooted trees to the created tree. If there are any overlapping graph
|
||||
attributes, those from later trees will overwrite those from earlier
|
||||
trees in the tuple of positional arguments.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Join two full balanced binary trees of height *h* to get a full
|
||||
balanced binary tree of depth *h* + 1::
|
||||
|
||||
>>> h = 4
|
||||
>>> left = nx.balanced_tree(2, h)
|
||||
>>> right = nx.balanced_tree(2, h)
|
||||
>>> joined_tree = nx.join([(left, 0), (right, 0)])
|
||||
>>> nx.is_isomorphic(joined_tree, nx.balanced_tree(2, h + 1))
|
||||
True
|
||||
|
||||
"""
|
||||
if len(rooted_trees) == 0:
|
||||
return nx.empty_graph(1)
|
||||
|
||||
# Unzip the zipped list of (tree, root) pairs.
|
||||
trees, roots = zip(*rooted_trees)
|
||||
|
||||
# The join of the trees has the same type as the type of the first
|
||||
# tree.
|
||||
R = type(trees[0])()
|
||||
|
||||
# Relabel the nodes so that their union is the integers starting at 1.
|
||||
if label_attribute is None:
|
||||
label_attribute = "_old"
|
||||
relabel = partial(
|
||||
nx.convert_node_labels_to_integers, label_attribute=label_attribute
|
||||
)
|
||||
lengths = (len(tree) for tree in trees[:-1])
|
||||
first_labels = chain([0], accumulate(lengths))
|
||||
trees = [
|
||||
relabel(tree, first_label=first_label + 1)
|
||||
for tree, first_label in zip(trees, first_labels)
|
||||
]
|
||||
|
||||
# Get the relabeled roots.
|
||||
roots = [
|
||||
next(v for v, d in tree.nodes(data=True) if d.get("_old") == root)
|
||||
for tree, root in zip(trees, roots)
|
||||
]
|
||||
|
||||
# Remove the old node labels.
|
||||
for tree in trees:
|
||||
for v in tree:
|
||||
tree.nodes[v].pop("_old")
|
||||
|
||||
# Add all sets of nodes and edges, with data.
|
||||
nodes = (tree.nodes(data=True) for tree in trees)
|
||||
edges = (tree.edges(data=True) for tree in trees)
|
||||
R.add_nodes_from(chain.from_iterable(nodes))
|
||||
R.add_edges_from(chain.from_iterable(edges))
|
||||
|
||||
# Add graph attributes; later attributes take precedent over earlier
|
||||
# attributes.
|
||||
for tree in trees:
|
||||
R.graph.update(tree.graph)
|
||||
|
||||
# Finally, join the subtrees at the root. We know 0 is unused by the
|
||||
# way we relabeled the subtrees.
|
||||
R.add_node(0)
|
||||
R.add_edges_from((0, root) for root in roots)
|
||||
|
||||
return R
|
||||
269
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/recognition.py
vendored
Normal file
269
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/recognition.py
vendored
Normal file
@@ -0,0 +1,269 @@
|
||||
"""
|
||||
Recognition Tests
|
||||
=================
|
||||
|
||||
A *forest* is an acyclic, undirected graph, and a *tree* is a connected forest.
|
||||
Depending on the subfield, there are various conventions for generalizing these
|
||||
definitions to directed graphs.
|
||||
|
||||
In one convention, directed variants of forest and tree are defined in an
|
||||
identical manner, except that the direction of the edges is ignored. In effect,
|
||||
each directed edge is treated as a single undirected edge. Then, additional
|
||||
restrictions are imposed to define *branchings* and *arborescences*.
|
||||
|
||||
In another convention, directed variants of forest and tree correspond to
|
||||
the previous convention's branchings and arborescences, respectively. Then two
|
||||
new terms, *polyforest* and *polytree*, are defined to correspond to the other
|
||||
convention's forest and tree.
|
||||
|
||||
Summarizing::
|
||||
|
||||
+-----------------------------+
|
||||
| Convention A | Convention B |
|
||||
+=============================+
|
||||
| forest | polyforest |
|
||||
| tree | polytree |
|
||||
| branching | forest |
|
||||
| arborescence | tree |
|
||||
+-----------------------------+
|
||||
|
||||
Each convention has its reasons. The first convention emphasizes definitional
|
||||
similarity in that directed forests and trees are only concerned with
|
||||
acyclicity and do not have an in-degree constraint, just as their undirected
|
||||
counterparts do not. The second convention emphasizes functional similarity
|
||||
in the sense that the directed analog of a spanning tree is a spanning
|
||||
arborescence. That is, take any spanning tree and choose one node as the root.
|
||||
Then every edge is assigned a direction such there is a directed path from the
|
||||
root to every other node. The result is a spanning arborescence.
|
||||
|
||||
NetworkX follows convention "A". Explicitly, these are:
|
||||
|
||||
undirected forest
|
||||
An undirected graph with no undirected cycles.
|
||||
|
||||
undirected tree
|
||||
A connected, undirected forest.
|
||||
|
||||
directed forest
|
||||
A directed graph with no undirected cycles. Equivalently, the underlying
|
||||
graph structure (which ignores edge orientations) is an undirected forest.
|
||||
In convention B, this is known as a polyforest.
|
||||
|
||||
directed tree
|
||||
A weakly connected, directed forest. Equivalently, the underlying graph
|
||||
structure (which ignores edge orientations) is an undirected tree. In
|
||||
convention B, this is known as a polytree.
|
||||
|
||||
branching
|
||||
A directed forest with each node having, at most, one parent. So the maximum
|
||||
in-degree is equal to 1. In convention B, this is known as a forest.
|
||||
|
||||
arborescence
|
||||
A directed tree with each node having, at most, one parent. So the maximum
|
||||
in-degree is equal to 1. In convention B, this is known as a tree.
|
||||
|
||||
For trees and arborescences, the adjective "spanning" may be added to designate
|
||||
that the graph, when considered as a forest/branching, consists of a single
|
||||
tree/arborescence that includes all nodes in the graph. It is true, by
|
||||
definition, that every tree/arborescence is spanning with respect to the nodes
|
||||
that define the tree/arborescence and so, it might seem redundant to introduce
|
||||
the notion of "spanning". However, the nodes may represent a subset of
|
||||
nodes from a larger graph, and it is in this context that the term "spanning"
|
||||
becomes a useful notion.
|
||||
|
||||
"""
|
||||
|
||||
import networkx as nx
|
||||
|
||||
__all__ = ["is_arborescence", "is_branching", "is_forest", "is_tree"]
|
||||
|
||||
|
||||
@nx.utils.not_implemented_for("undirected")
|
||||
def is_arborescence(G):
|
||||
"""
|
||||
Returns True if `G` is an arborescence.
|
||||
|
||||
An arborescence is a directed tree with maximum in-degree equal to 1.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
The graph to test.
|
||||
|
||||
Returns
|
||||
-------
|
||||
b : bool
|
||||
A boolean that is True if `G` is an arborescence.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.DiGraph([(0, 1), (0, 2), (2, 3), (3, 4)])
|
||||
>>> nx.is_arborescence(G)
|
||||
True
|
||||
>>> G.remove_edge(0, 1)
|
||||
>>> G.add_edge(1, 2) # maximum in-degree is 2
|
||||
>>> nx.is_arborescence(G)
|
||||
False
|
||||
|
||||
Notes
|
||||
-----
|
||||
In another convention, an arborescence is known as a *tree*.
|
||||
|
||||
See Also
|
||||
--------
|
||||
is_tree
|
||||
|
||||
"""
|
||||
return is_tree(G) and max(d for n, d in G.in_degree()) <= 1
|
||||
|
||||
|
||||
@nx.utils.not_implemented_for("undirected")
|
||||
def is_branching(G):
|
||||
"""
|
||||
Returns True if `G` is a branching.
|
||||
|
||||
A branching is a directed forest with maximum in-degree equal to 1.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : directed graph
|
||||
The directed graph to test.
|
||||
|
||||
Returns
|
||||
-------
|
||||
b : bool
|
||||
A boolean that is True if `G` is a branching.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 4)])
|
||||
>>> nx.is_branching(G)
|
||||
True
|
||||
>>> G.remove_edge(2, 3)
|
||||
>>> G.add_edge(3, 1) # maximum in-degree is 2
|
||||
>>> nx.is_branching(G)
|
||||
False
|
||||
|
||||
Notes
|
||||
-----
|
||||
In another convention, a branching is also known as a *forest*.
|
||||
|
||||
See Also
|
||||
--------
|
||||
is_forest
|
||||
|
||||
"""
|
||||
return is_forest(G) and max(d for n, d in G.in_degree()) <= 1
|
||||
|
||||
|
||||
def is_forest(G):
|
||||
"""
|
||||
Returns True if `G` is a forest.
|
||||
|
||||
A forest is a graph with no undirected cycles.
|
||||
|
||||
For directed graphs, `G` is a forest if the underlying graph is a forest.
|
||||
The underlying graph is obtained by treating each directed edge as a single
|
||||
undirected edge in a multigraph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
The graph to test.
|
||||
|
||||
Returns
|
||||
-------
|
||||
b : bool
|
||||
A boolean that is True if `G` is a forest.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXPointlessConcept
|
||||
If `G` is empty.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.Graph()
|
||||
>>> G.add_edges_from([(1, 2), (1, 3), (2, 4), (2, 5)])
|
||||
>>> nx.is_forest(G)
|
||||
True
|
||||
>>> G.add_edge(4, 1)
|
||||
>>> nx.is_forest(G)
|
||||
False
|
||||
|
||||
Notes
|
||||
-----
|
||||
In another convention, a directed forest is known as a *polyforest* and
|
||||
then *forest* corresponds to a *branching*.
|
||||
|
||||
See Also
|
||||
--------
|
||||
is_branching
|
||||
|
||||
"""
|
||||
if len(G) == 0:
|
||||
raise nx.exception.NetworkXPointlessConcept("G has no nodes.")
|
||||
|
||||
if G.is_directed():
|
||||
components = (G.subgraph(c) for c in nx.weakly_connected_components(G))
|
||||
else:
|
||||
components = (G.subgraph(c) for c in nx.connected_components(G))
|
||||
|
||||
return all(len(c) - 1 == c.number_of_edges() for c in components)
|
||||
|
||||
|
||||
def is_tree(G):
|
||||
"""
|
||||
Returns True if `G` is a tree.
|
||||
|
||||
A tree is a connected graph with no undirected cycles.
|
||||
|
||||
For directed graphs, `G` is a tree if the underlying graph is a tree. The
|
||||
underlying graph is obtained by treating each directed edge as a single
|
||||
undirected edge in a multigraph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
The graph to test.
|
||||
|
||||
Returns
|
||||
-------
|
||||
b : bool
|
||||
A boolean that is True if `G` is a tree.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXPointlessConcept
|
||||
If `G` is empty.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.Graph()
|
||||
>>> G.add_edges_from([(1, 2), (1, 3), (2, 4), (2, 5)])
|
||||
>>> nx.is_tree(G) # n-1 edges
|
||||
True
|
||||
>>> G.add_edge(3, 4)
|
||||
>>> nx.is_tree(G) # n edges
|
||||
False
|
||||
|
||||
Notes
|
||||
-----
|
||||
In another convention, a directed tree is known as a *polytree* and then
|
||||
*tree* corresponds to an *arborescence*.
|
||||
|
||||
See Also
|
||||
--------
|
||||
is_arborescence
|
||||
|
||||
"""
|
||||
if len(G) == 0:
|
||||
raise nx.exception.NetworkXPointlessConcept("G has no nodes.")
|
||||
|
||||
if G.is_directed():
|
||||
is_connected = nx.is_weakly_connected
|
||||
else:
|
||||
is_connected = nx.is_connected
|
||||
|
||||
# A connected graph with no cycles has n-1 edges.
|
||||
return len(G) - 1 == G.number_of_edges() and is_connected(G)
|
||||
0
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/__init__.py
vendored
Normal file
0
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/__init__.py
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/__pycache__/test_mst.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/__pycache__/test_mst.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
609
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/test_branchings.py
vendored
Normal file
609
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/test_branchings.py
vendored
Normal file
@@ -0,0 +1,609 @@
|
||||
import math
|
||||
from operator import itemgetter
|
||||
|
||||
import pytest
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
|
||||
import networkx as nx
|
||||
from networkx.algorithms.tree import branchings, recognition
|
||||
|
||||
#
|
||||
# Explicitly discussed examples from Edmonds paper.
|
||||
#
|
||||
|
||||
# Used in Figures A-F.
|
||||
#
|
||||
# fmt: off
|
||||
G_array = np.array([
|
||||
# 0 1 2 3 4 5 6 7 8
|
||||
[0, 0, 12, 0, 12, 0, 0, 0, 0], # 0
|
||||
[4, 0, 0, 0, 0, 13, 0, 0, 0], # 1
|
||||
[0, 17, 0, 21, 0, 12, 0, 0, 0], # 2
|
||||
[5, 0, 0, 0, 17, 0, 18, 0, 0], # 3
|
||||
[0, 0, 0, 0, 0, 0, 0, 12, 0], # 4
|
||||
[0, 0, 0, 0, 0, 0, 14, 0, 12], # 5
|
||||
[0, 0, 21, 0, 0, 0, 0, 0, 15], # 6
|
||||
[0, 0, 0, 19, 0, 0, 15, 0, 0], # 7
|
||||
[0, 0, 0, 0, 0, 0, 0, 18, 0], # 8
|
||||
], dtype=int)
|
||||
|
||||
|
||||
# fmt: on
|
||||
|
||||
|
||||
def G1():
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.MultiDiGraph)
|
||||
return G
|
||||
|
||||
|
||||
def G2():
|
||||
# Now we shift all the weights by -10.
|
||||
# Should not affect optimal arborescence, but does affect optimal branching.
|
||||
Garr = G_array.copy()
|
||||
Garr[np.nonzero(Garr)] -= 10
|
||||
G = nx.from_numpy_array(Garr, create_using=nx.MultiDiGraph)
|
||||
return G
|
||||
|
||||
|
||||
# An optimal branching for G1 that is also a spanning arborescence. So it is
|
||||
# also an optimal spanning arborescence.
|
||||
#
|
||||
optimal_arborescence_1 = [
|
||||
(0, 2, 12),
|
||||
(2, 1, 17),
|
||||
(2, 3, 21),
|
||||
(1, 5, 13),
|
||||
(3, 4, 17),
|
||||
(3, 6, 18),
|
||||
(6, 8, 15),
|
||||
(8, 7, 18),
|
||||
]
|
||||
|
||||
# For G2, the optimal branching of G1 (with shifted weights) is no longer
|
||||
# an optimal branching, but it is still an optimal spanning arborescence
|
||||
# (just with shifted weights). An optimal branching for G2 is similar to what
|
||||
# appears in figure G (this is greedy_subopt_branching_1a below), but with the
|
||||
# edge (3, 0, 5), which is now (3, 0, -5), removed. Thus, the optimal branching
|
||||
# is not a spanning arborescence. The code finds optimal_branching_2a.
|
||||
# An alternative and equivalent branching is optimal_branching_2b. We would
|
||||
# need to modify the code to iterate through all equivalent optimal branchings.
|
||||
#
|
||||
# These are maximal branchings or arborescences.
|
||||
optimal_branching_2a = [
|
||||
(5, 6, 4),
|
||||
(6, 2, 11),
|
||||
(6, 8, 5),
|
||||
(8, 7, 8),
|
||||
(2, 1, 7),
|
||||
(2, 3, 11),
|
||||
(3, 4, 7),
|
||||
]
|
||||
optimal_branching_2b = [
|
||||
(8, 7, 8),
|
||||
(7, 3, 9),
|
||||
(3, 4, 7),
|
||||
(3, 6, 8),
|
||||
(6, 2, 11),
|
||||
(2, 1, 7),
|
||||
(1, 5, 3),
|
||||
]
|
||||
optimal_arborescence_2 = [
|
||||
(0, 2, 2),
|
||||
(2, 1, 7),
|
||||
(2, 3, 11),
|
||||
(1, 5, 3),
|
||||
(3, 4, 7),
|
||||
(3, 6, 8),
|
||||
(6, 8, 5),
|
||||
(8, 7, 8),
|
||||
]
|
||||
|
||||
# Two suboptimal maximal branchings on G1 obtained from a greedy algorithm.
|
||||
# 1a matches what is shown in Figure G in Edmonds's paper.
|
||||
greedy_subopt_branching_1a = [
|
||||
(5, 6, 14),
|
||||
(6, 2, 21),
|
||||
(6, 8, 15),
|
||||
(8, 7, 18),
|
||||
(2, 1, 17),
|
||||
(2, 3, 21),
|
||||
(3, 0, 5),
|
||||
(3, 4, 17),
|
||||
]
|
||||
greedy_subopt_branching_1b = [
|
||||
(8, 7, 18),
|
||||
(7, 6, 15),
|
||||
(6, 2, 21),
|
||||
(2, 1, 17),
|
||||
(2, 3, 21),
|
||||
(1, 5, 13),
|
||||
(3, 0, 5),
|
||||
(3, 4, 17),
|
||||
]
|
||||
|
||||
|
||||
def build_branching(edges):
|
||||
G = nx.DiGraph()
|
||||
for u, v, weight in edges:
|
||||
G.add_edge(u, v, weight=weight)
|
||||
return G
|
||||
|
||||
|
||||
def sorted_edges(G, attr="weight", default=1):
|
||||
edges = [(u, v, data.get(attr, default)) for (u, v, data) in G.edges(data=True)]
|
||||
edges = sorted(edges, key=lambda x: (x[2], x[1], x[0]))
|
||||
return edges
|
||||
|
||||
|
||||
def assert_equal_branchings(G1, G2, attr="weight", default=1):
|
||||
edges1 = list(G1.edges(data=True))
|
||||
edges2 = list(G2.edges(data=True))
|
||||
assert len(edges1) == len(edges2)
|
||||
|
||||
# Grab the weights only.
|
||||
e1 = sorted_edges(G1, attr, default)
|
||||
e2 = sorted_edges(G2, attr, default)
|
||||
|
||||
for a, b in zip(e1, e2):
|
||||
assert a[:2] == b[:2]
|
||||
np.testing.assert_almost_equal(a[2], b[2])
|
||||
|
||||
|
||||
################
|
||||
|
||||
|
||||
def test_optimal_branching1():
|
||||
G = build_branching(optimal_arborescence_1)
|
||||
assert recognition.is_arborescence(G), True
|
||||
assert branchings.branching_weight(G) == 131
|
||||
|
||||
|
||||
def test_optimal_branching2a():
|
||||
G = build_branching(optimal_branching_2a)
|
||||
assert recognition.is_arborescence(G), True
|
||||
assert branchings.branching_weight(G) == 53
|
||||
|
||||
|
||||
def test_optimal_branching2b():
|
||||
G = build_branching(optimal_branching_2b)
|
||||
assert recognition.is_arborescence(G), True
|
||||
assert branchings.branching_weight(G) == 53
|
||||
|
||||
|
||||
def test_optimal_arborescence2():
|
||||
G = build_branching(optimal_arborescence_2)
|
||||
assert recognition.is_arborescence(G), True
|
||||
assert branchings.branching_weight(G) == 51
|
||||
|
||||
|
||||
def test_greedy_suboptimal_branching1a():
|
||||
G = build_branching(greedy_subopt_branching_1a)
|
||||
assert recognition.is_arborescence(G), True
|
||||
assert branchings.branching_weight(G) == 128
|
||||
|
||||
|
||||
def test_greedy_suboptimal_branching1b():
|
||||
G = build_branching(greedy_subopt_branching_1b)
|
||||
assert recognition.is_arborescence(G), True
|
||||
assert branchings.branching_weight(G) == 127
|
||||
|
||||
|
||||
def test_greedy_max1():
|
||||
# Standard test.
|
||||
#
|
||||
G = G1()
|
||||
B = branchings.greedy_branching(G)
|
||||
# There are only two possible greedy branchings. The sorting is such
|
||||
# that it should equal the second suboptimal branching: 1b.
|
||||
B_ = build_branching(greedy_subopt_branching_1b)
|
||||
assert_equal_branchings(B, B_)
|
||||
|
||||
|
||||
def test_greedy_branching_kwarg_kind():
|
||||
G = G1()
|
||||
with pytest.raises(nx.NetworkXException, match="Unknown value for `kind`."):
|
||||
B = branchings.greedy_branching(G, kind="lol")
|
||||
|
||||
|
||||
def test_greedy_branching_for_unsortable_nodes():
|
||||
G = nx.DiGraph()
|
||||
G.add_weighted_edges_from([((2, 3), 5, 1), (3, "a", 1), (2, 4, 5)])
|
||||
edges = [(u, v, data.get("weight", 1)) for (u, v, data) in G.edges(data=True)]
|
||||
with pytest.raises(TypeError):
|
||||
edges.sort(key=itemgetter(2, 0, 1), reverse=True)
|
||||
B = branchings.greedy_branching(G, kind="max").edges(data=True)
|
||||
assert list(B) == [
|
||||
((2, 3), 5, {"weight": 1}),
|
||||
(3, "a", {"weight": 1}),
|
||||
(2, 4, {"weight": 5}),
|
||||
]
|
||||
|
||||
|
||||
def test_greedy_max2():
|
||||
# Different default weight.
|
||||
#
|
||||
G = G1()
|
||||
del G[1][0][0]["weight"]
|
||||
B = branchings.greedy_branching(G, default=6)
|
||||
# Chosen so that edge (3,0,5) is not selected and (1,0,6) is instead.
|
||||
|
||||
edges = [
|
||||
(1, 0, 6),
|
||||
(1, 5, 13),
|
||||
(7, 6, 15),
|
||||
(2, 1, 17),
|
||||
(3, 4, 17),
|
||||
(8, 7, 18),
|
||||
(2, 3, 21),
|
||||
(6, 2, 21),
|
||||
]
|
||||
B_ = build_branching(edges)
|
||||
assert_equal_branchings(B, B_)
|
||||
|
||||
|
||||
def test_greedy_max3():
|
||||
# All equal weights.
|
||||
#
|
||||
G = G1()
|
||||
B = branchings.greedy_branching(G, attr=None)
|
||||
|
||||
# This is mostly arbitrary...the output was generated by running the algo.
|
||||
edges = [
|
||||
(2, 1, 1),
|
||||
(3, 0, 1),
|
||||
(3, 4, 1),
|
||||
(5, 8, 1),
|
||||
(6, 2, 1),
|
||||
(7, 3, 1),
|
||||
(7, 6, 1),
|
||||
(8, 7, 1),
|
||||
]
|
||||
B_ = build_branching(edges)
|
||||
assert_equal_branchings(B, B_, default=1)
|
||||
|
||||
|
||||
def test_greedy_min():
|
||||
G = G1()
|
||||
B = branchings.greedy_branching(G, kind="min")
|
||||
|
||||
edges = [
|
||||
(1, 0, 4),
|
||||
(0, 2, 12),
|
||||
(0, 4, 12),
|
||||
(2, 5, 12),
|
||||
(4, 7, 12),
|
||||
(5, 8, 12),
|
||||
(5, 6, 14),
|
||||
(7, 3, 19),
|
||||
]
|
||||
B_ = build_branching(edges)
|
||||
assert_equal_branchings(B, B_)
|
||||
|
||||
|
||||
def test_edmonds1_maxbranch():
|
||||
G = G1()
|
||||
x = branchings.maximum_branching(G)
|
||||
x_ = build_branching(optimal_arborescence_1)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
def test_edmonds1_maxarbor():
|
||||
G = G1()
|
||||
x = branchings.maximum_spanning_arborescence(G)
|
||||
x_ = build_branching(optimal_arborescence_1)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
def test_edmonds2_maxbranch():
|
||||
G = G2()
|
||||
x = branchings.maximum_branching(G)
|
||||
x_ = build_branching(optimal_branching_2a)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
def test_edmonds2_maxarbor():
|
||||
G = G2()
|
||||
x = branchings.maximum_spanning_arborescence(G)
|
||||
x_ = build_branching(optimal_arborescence_2)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
def test_edmonds2_minarbor():
|
||||
G = G1()
|
||||
x = branchings.minimum_spanning_arborescence(G)
|
||||
# This was obtained from algorithm. Need to verify it independently.
|
||||
# Branch weight is: 96
|
||||
edges = [
|
||||
(3, 0, 5),
|
||||
(0, 2, 12),
|
||||
(0, 4, 12),
|
||||
(2, 5, 12),
|
||||
(4, 7, 12),
|
||||
(5, 8, 12),
|
||||
(5, 6, 14),
|
||||
(2, 1, 17),
|
||||
]
|
||||
x_ = build_branching(edges)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
def test_edmonds3_minbranch1():
|
||||
G = G1()
|
||||
x = branchings.minimum_branching(G)
|
||||
edges = []
|
||||
x_ = build_branching(edges)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
def test_edmonds3_minbranch2():
|
||||
G = G1()
|
||||
G.add_edge(8, 9, weight=-10)
|
||||
x = branchings.minimum_branching(G)
|
||||
edges = [(8, 9, -10)]
|
||||
x_ = build_branching(edges)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
# Need more tests
|
||||
|
||||
|
||||
def test_mst():
|
||||
# Make sure we get the same results for undirected graphs.
|
||||
# Example from: https://en.wikipedia.org/wiki/Kruskal's_algorithm
|
||||
G = nx.Graph()
|
||||
edgelist = [
|
||||
(0, 3, [("weight", 5)]),
|
||||
(0, 1, [("weight", 7)]),
|
||||
(1, 3, [("weight", 9)]),
|
||||
(1, 2, [("weight", 8)]),
|
||||
(1, 4, [("weight", 7)]),
|
||||
(3, 4, [("weight", 15)]),
|
||||
(3, 5, [("weight", 6)]),
|
||||
(2, 4, [("weight", 5)]),
|
||||
(4, 5, [("weight", 8)]),
|
||||
(4, 6, [("weight", 9)]),
|
||||
(5, 6, [("weight", 11)]),
|
||||
]
|
||||
G.add_edges_from(edgelist)
|
||||
G = G.to_directed()
|
||||
x = branchings.minimum_spanning_arborescence(G)
|
||||
|
||||
edges = [
|
||||
({0, 1}, 7),
|
||||
({0, 3}, 5),
|
||||
({3, 5}, 6),
|
||||
({1, 4}, 7),
|
||||
({4, 2}, 5),
|
||||
({4, 6}, 9),
|
||||
]
|
||||
|
||||
assert x.number_of_edges() == len(edges)
|
||||
for u, v, d in x.edges(data=True):
|
||||
assert ({u, v}, d["weight"]) in edges
|
||||
|
||||
|
||||
def test_mixed_nodetypes():
|
||||
# Smoke test to make sure no TypeError is raised for mixed node types.
|
||||
G = nx.Graph()
|
||||
edgelist = [(0, 3, [("weight", 5)]), (0, "1", [("weight", 5)])]
|
||||
G.add_edges_from(edgelist)
|
||||
G = G.to_directed()
|
||||
x = branchings.minimum_spanning_arborescence(G)
|
||||
|
||||
|
||||
def test_edmonds1_minbranch():
|
||||
# Using -G_array and min should give the same as optimal_arborescence_1,
|
||||
# but with all edges negative.
|
||||
edges = [(u, v, -w) for (u, v, w) in optimal_arborescence_1]
|
||||
|
||||
G = nx.from_numpy_array(-G_array, create_using=nx.DiGraph)
|
||||
|
||||
# Quickly make sure max branching is empty.
|
||||
x = branchings.maximum_branching(G)
|
||||
x_ = build_branching([])
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
# Now test the min branching.
|
||||
x = branchings.minimum_branching(G)
|
||||
x_ = build_branching(edges)
|
||||
assert_equal_branchings(x, x_)
|
||||
|
||||
|
||||
def test_edge_attribute_preservation_normal_graph():
|
||||
# Test that edge attributes are preserved when finding an optimum graph
|
||||
# using the Edmonds class for normal graphs.
|
||||
G = nx.Graph()
|
||||
|
||||
edgelist = [
|
||||
(0, 1, [("weight", 5), ("otherattr", 1), ("otherattr2", 3)]),
|
||||
(0, 2, [("weight", 5), ("otherattr", 2), ("otherattr2", 2)]),
|
||||
(1, 2, [("weight", 6), ("otherattr", 3), ("otherattr2", 1)]),
|
||||
]
|
||||
G.add_edges_from(edgelist)
|
||||
|
||||
ed = branchings.Edmonds(G)
|
||||
B = ed.find_optimum("weight", preserve_attrs=True, seed=1)
|
||||
|
||||
assert B[0][1]["otherattr"] == 1
|
||||
assert B[0][1]["otherattr2"] == 3
|
||||
|
||||
|
||||
def test_edge_attribute_preservation_multigraph():
|
||||
# Test that edge attributes are preserved when finding an optimum graph
|
||||
# using the Edmonds class for multigraphs.
|
||||
G = nx.MultiGraph()
|
||||
|
||||
edgelist = [
|
||||
(0, 1, [("weight", 5), ("otherattr", 1), ("otherattr2", 3)]),
|
||||
(0, 2, [("weight", 5), ("otherattr", 2), ("otherattr2", 2)]),
|
||||
(1, 2, [("weight", 6), ("otherattr", 3), ("otherattr2", 1)]),
|
||||
]
|
||||
G.add_edges_from(edgelist * 2) # Make sure we have duplicate edge paths
|
||||
|
||||
ed = branchings.Edmonds(G)
|
||||
B = ed.find_optimum("weight", preserve_attrs=True)
|
||||
|
||||
assert B[0][1][0]["otherattr"] == 1
|
||||
assert B[0][1][0]["otherattr2"] == 3
|
||||
|
||||
|
||||
def test_Edmond_kind():
|
||||
G = nx.MultiGraph()
|
||||
|
||||
edgelist = [
|
||||
(0, 1, [("weight", 5), ("otherattr", 1), ("otherattr2", 3)]),
|
||||
(0, 2, [("weight", 5), ("otherattr", 2), ("otherattr2", 2)]),
|
||||
(1, 2, [("weight", 6), ("otherattr", 3), ("otherattr2", 1)]),
|
||||
]
|
||||
G.add_edges_from(edgelist * 2) # Make sure we have duplicate edge paths
|
||||
ed = branchings.Edmonds(G)
|
||||
with pytest.raises(nx.NetworkXException, match="Unknown value for `kind`."):
|
||||
ed.find_optimum(kind="lol", preserve_attrs=True)
|
||||
|
||||
|
||||
def test_MultiDiGraph_EdgeKey():
|
||||
# test if more than one edges has the same key
|
||||
G = branchings.MultiDiGraph_EdgeKey()
|
||||
G.add_edge(1, 2, "A")
|
||||
with pytest.raises(Exception, match="Key 'A' is already in use."):
|
||||
G.add_edge(3, 4, "A")
|
||||
# test if invalid edge key was specified
|
||||
with pytest.raises(KeyError, match="Invalid edge key 'B'"):
|
||||
G.remove_edge_with_key("B")
|
||||
# test remove_edge_with_key works
|
||||
if G.remove_edge_with_key("A"):
|
||||
assert list(G.edges(data=True)) == []
|
||||
# test that remove_edges_from doesn't work
|
||||
G.add_edge(1, 3, "A")
|
||||
with pytest.raises(NotImplementedError):
|
||||
G.remove_edges_from([(1, 3)])
|
||||
|
||||
|
||||
def test_edge_attribute_discard():
|
||||
# Test that edge attributes are discarded if we do not specify to keep them
|
||||
G = nx.Graph()
|
||||
|
||||
edgelist = [
|
||||
(0, 1, [("weight", 5), ("otherattr", 1), ("otherattr2", 3)]),
|
||||
(0, 2, [("weight", 5), ("otherattr", 2), ("otherattr2", 2)]),
|
||||
(1, 2, [("weight", 6), ("otherattr", 3), ("otherattr2", 1)]),
|
||||
]
|
||||
G.add_edges_from(edgelist)
|
||||
|
||||
ed = branchings.Edmonds(G)
|
||||
B = ed.find_optimum("weight", preserve_attrs=False)
|
||||
|
||||
edge_dict = B[0][1]
|
||||
with pytest.raises(KeyError):
|
||||
_ = edge_dict["otherattr"]
|
||||
|
||||
|
||||
def test_partition_spanning_arborescence():
|
||||
"""
|
||||
Test that we can generate minimum spanning arborescences which respect the
|
||||
given partition.
|
||||
"""
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.DiGraph)
|
||||
G[3][0]["partition"] = nx.EdgePartition.EXCLUDED
|
||||
G[2][3]["partition"] = nx.EdgePartition.INCLUDED
|
||||
G[7][3]["partition"] = nx.EdgePartition.EXCLUDED
|
||||
G[0][2]["partition"] = nx.EdgePartition.EXCLUDED
|
||||
G[6][2]["partition"] = nx.EdgePartition.INCLUDED
|
||||
|
||||
actual_edges = [
|
||||
(0, 4, 12),
|
||||
(1, 0, 4),
|
||||
(1, 5, 13),
|
||||
(2, 3, 21),
|
||||
(4, 7, 12),
|
||||
(5, 6, 14),
|
||||
(5, 8, 12),
|
||||
(6, 2, 21),
|
||||
]
|
||||
|
||||
B = branchings.minimum_spanning_arborescence(G, partition="partition")
|
||||
assert_equal_branchings(build_branching(actual_edges), B)
|
||||
|
||||
|
||||
def test_arborescence_iterator_min():
|
||||
"""
|
||||
Tests the arborescence iterator.
|
||||
|
||||
A brute force method found 680 arboresecences in this graph.
|
||||
This test will not verify all of them individually, but will check two
|
||||
things
|
||||
|
||||
* The iterator returns 680 arboresecences
|
||||
* The weight of the arborescences is non-strictly increasing
|
||||
|
||||
for more information please visit
|
||||
https://mjschwenne.github.io/2021/06/10/implementing-the-iterators.html
|
||||
"""
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.DiGraph)
|
||||
|
||||
arborescence_count = 0
|
||||
arborescence_weight = -math.inf
|
||||
for B in branchings.ArborescenceIterator(G):
|
||||
arborescence_count += 1
|
||||
new_arborescence_weight = B.size(weight="weight")
|
||||
assert new_arborescence_weight >= arborescence_weight
|
||||
arborescence_weight = new_arborescence_weight
|
||||
|
||||
assert arborescence_count == 680
|
||||
|
||||
|
||||
def test_arborescence_iterator_max():
|
||||
"""
|
||||
Tests the arborescence iterator.
|
||||
|
||||
A brute force method found 680 arboresecences in this graph.
|
||||
This test will not verify all of them individually, but will check two
|
||||
things
|
||||
|
||||
* The iterator returns 680 arboresecences
|
||||
* The weight of the arborescences is non-strictly decreasing
|
||||
|
||||
for more information please visit
|
||||
https://mjschwenne.github.io/2021/06/10/implementing-the-iterators.html
|
||||
"""
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.DiGraph)
|
||||
|
||||
arborescence_count = 0
|
||||
arborescence_weight = math.inf
|
||||
for B in branchings.ArborescenceIterator(G, minimum=False):
|
||||
arborescence_count += 1
|
||||
new_arborescence_weight = B.size(weight="weight")
|
||||
assert new_arborescence_weight <= arborescence_weight
|
||||
arborescence_weight = new_arborescence_weight
|
||||
|
||||
assert arborescence_count == 680
|
||||
|
||||
|
||||
def test_arborescence_iterator_initial_partition():
|
||||
"""
|
||||
Tests the arborescence iterator with three included edges and three excluded
|
||||
in the initial partition.
|
||||
|
||||
A brute force method similar to the one used in the above tests found that
|
||||
there are 16 arborescences which contain the included edges and not the
|
||||
excluded edges.
|
||||
"""
|
||||
G = nx.from_numpy_array(G_array, create_using=nx.DiGraph)
|
||||
included_edges = [(1, 0), (5, 6), (8, 7)]
|
||||
excluded_edges = [(0, 2), (3, 6), (1, 5)]
|
||||
|
||||
arborescence_count = 0
|
||||
arborescence_weight = -math.inf
|
||||
for B in branchings.ArborescenceIterator(
|
||||
G, init_partition=(included_edges, excluded_edges)
|
||||
):
|
||||
arborescence_count += 1
|
||||
new_arborescence_weight = B.size(weight="weight")
|
||||
assert new_arborescence_weight >= arborescence_weight
|
||||
arborescence_weight = new_arborescence_weight
|
||||
for e in included_edges:
|
||||
assert e in B.edges
|
||||
for e in excluded_edges:
|
||||
assert e not in B.edges
|
||||
assert arborescence_count == 16
|
||||
113
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/test_coding.py
vendored
Normal file
113
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/test_coding.py
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
"""Unit tests for the :mod:`~networkx.algorithms.tree.coding` module."""
|
||||
from itertools import product
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import edges_equal, nodes_equal
|
||||
|
||||
|
||||
class TestPruferSequence:
|
||||
"""Unit tests for the Prüfer sequence encoding and decoding
|
||||
functions.
|
||||
|
||||
"""
|
||||
|
||||
def test_nontree(self):
|
||||
with pytest.raises(nx.NotATree):
|
||||
G = nx.cycle_graph(3)
|
||||
nx.to_prufer_sequence(G)
|
||||
|
||||
def test_null_graph(self):
|
||||
with pytest.raises(nx.NetworkXPointlessConcept):
|
||||
nx.to_prufer_sequence(nx.null_graph())
|
||||
|
||||
def test_trivial_graph(self):
|
||||
with pytest.raises(nx.NetworkXPointlessConcept):
|
||||
nx.to_prufer_sequence(nx.trivial_graph())
|
||||
|
||||
def test_bad_integer_labels(self):
|
||||
with pytest.raises(KeyError):
|
||||
T = nx.Graph(nx.utils.pairwise("abc"))
|
||||
nx.to_prufer_sequence(T)
|
||||
|
||||
def test_encoding(self):
|
||||
"""Tests for encoding a tree as a Prüfer sequence using the
|
||||
iterative strategy.
|
||||
|
||||
"""
|
||||
# Example from Wikipedia.
|
||||
tree = nx.Graph([(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)])
|
||||
sequence = nx.to_prufer_sequence(tree)
|
||||
assert sequence == [3, 3, 3, 4]
|
||||
|
||||
def test_decoding(self):
|
||||
"""Tests for decoding a tree from a Prüfer sequence."""
|
||||
# Example from Wikipedia.
|
||||
sequence = [3, 3, 3, 4]
|
||||
tree = nx.from_prufer_sequence(sequence)
|
||||
assert nodes_equal(list(tree), list(range(6)))
|
||||
edges = [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)]
|
||||
assert edges_equal(list(tree.edges()), edges)
|
||||
|
||||
def test_decoding2(self):
|
||||
# Example from "An Optimal Algorithm for Prufer Codes".
|
||||
sequence = [2, 4, 0, 1, 3, 3]
|
||||
tree = nx.from_prufer_sequence(sequence)
|
||||
assert nodes_equal(list(tree), list(range(8)))
|
||||
edges = [(0, 1), (0, 4), (1, 3), (2, 4), (2, 5), (3, 6), (3, 7)]
|
||||
assert edges_equal(list(tree.edges()), edges)
|
||||
|
||||
def test_inverse(self):
|
||||
"""Tests that the encoding and decoding functions are inverses."""
|
||||
for T in nx.nonisomorphic_trees(4):
|
||||
T2 = nx.from_prufer_sequence(nx.to_prufer_sequence(T))
|
||||
assert nodes_equal(list(T), list(T2))
|
||||
assert edges_equal(list(T.edges()), list(T2.edges()))
|
||||
|
||||
for seq in product(range(4), repeat=2):
|
||||
seq2 = nx.to_prufer_sequence(nx.from_prufer_sequence(seq))
|
||||
assert list(seq) == seq2
|
||||
|
||||
|
||||
class TestNestedTuple:
|
||||
"""Unit tests for the nested tuple encoding and decoding functions."""
|
||||
|
||||
def test_nontree(self):
|
||||
with pytest.raises(nx.NotATree):
|
||||
G = nx.cycle_graph(3)
|
||||
nx.to_nested_tuple(G, 0)
|
||||
|
||||
def test_unknown_root(self):
|
||||
with pytest.raises(nx.NodeNotFound):
|
||||
G = nx.path_graph(2)
|
||||
nx.to_nested_tuple(G, "bogus")
|
||||
|
||||
def test_encoding(self):
|
||||
T = nx.full_rary_tree(2, 2**3 - 1)
|
||||
expected = (((), ()), ((), ()))
|
||||
actual = nx.to_nested_tuple(T, 0)
|
||||
assert nodes_equal(expected, actual)
|
||||
|
||||
def test_canonical_form(self):
|
||||
T = nx.Graph()
|
||||
T.add_edges_from([(0, 1), (0, 2), (0, 3)])
|
||||
T.add_edges_from([(1, 4), (1, 5)])
|
||||
T.add_edges_from([(3, 6), (3, 7)])
|
||||
root = 0
|
||||
actual = nx.to_nested_tuple(T, root, canonical_form=True)
|
||||
expected = ((), ((), ()), ((), ()))
|
||||
assert actual == expected
|
||||
|
||||
def test_decoding(self):
|
||||
balanced = (((), ()), ((), ()))
|
||||
expected = nx.full_rary_tree(2, 2**3 - 1)
|
||||
actual = nx.from_nested_tuple(balanced)
|
||||
assert nx.is_isomorphic(expected, actual)
|
||||
|
||||
def test_sensible_relabeling(self):
|
||||
balanced = (((), ()), ((), ()))
|
||||
T = nx.from_nested_tuple(balanced, sensible_relabeling=True)
|
||||
edges = [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]
|
||||
assert nodes_equal(list(T), list(range(2**3 - 1)))
|
||||
assert edges_equal(list(T.edges()), edges)
|
||||
79
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/test_decomposition.py
vendored
Normal file
79
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/test_decomposition.py
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
import networkx as nx
|
||||
from networkx.algorithms.tree.decomposition import junction_tree
|
||||
|
||||
|
||||
def test_junction_tree_directed_confounders():
|
||||
B = nx.DiGraph()
|
||||
B.add_edges_from([("A", "C"), ("B", "C"), ("C", "D"), ("C", "E")])
|
||||
|
||||
G = junction_tree(B)
|
||||
J = nx.Graph()
|
||||
J.add_edges_from(
|
||||
[
|
||||
(("C", "E"), ("C",)),
|
||||
(("C",), ("A", "B", "C")),
|
||||
(("A", "B", "C"), ("C",)),
|
||||
(("C",), ("C", "D")),
|
||||
]
|
||||
)
|
||||
|
||||
assert nx.is_isomorphic(G, J)
|
||||
|
||||
|
||||
def test_junction_tree_directed_unconnected_nodes():
|
||||
B = nx.DiGraph()
|
||||
B.add_nodes_from([("A", "B", "C", "D")])
|
||||
G = junction_tree(B)
|
||||
|
||||
J = nx.Graph()
|
||||
J.add_nodes_from([("A", "B", "C", "D")])
|
||||
|
||||
assert nx.is_isomorphic(G, J)
|
||||
|
||||
|
||||
def test_junction_tree_directed_cascade():
|
||||
B = nx.DiGraph()
|
||||
B.add_edges_from([("A", "B"), ("B", "C"), ("C", "D")])
|
||||
G = junction_tree(B)
|
||||
|
||||
J = nx.Graph()
|
||||
J.add_edges_from(
|
||||
[
|
||||
(("A", "B"), ("B",)),
|
||||
(("B",), ("B", "C")),
|
||||
(("B", "C"), ("C",)),
|
||||
(("C",), ("C", "D")),
|
||||
]
|
||||
)
|
||||
assert nx.is_isomorphic(G, J)
|
||||
|
||||
|
||||
def test_junction_tree_directed_unconnected_edges():
|
||||
B = nx.DiGraph()
|
||||
B.add_edges_from([("A", "B"), ("C", "D"), ("E", "F")])
|
||||
G = junction_tree(B)
|
||||
|
||||
J = nx.Graph()
|
||||
J.add_nodes_from([("A", "B"), ("C", "D"), ("E", "F")])
|
||||
|
||||
assert nx.is_isomorphic(G, J)
|
||||
|
||||
|
||||
def test_junction_tree_undirected():
|
||||
B = nx.Graph()
|
||||
B.add_edges_from([("A", "C"), ("A", "D"), ("B", "C"), ("C", "E")])
|
||||
G = junction_tree(B)
|
||||
|
||||
J = nx.Graph()
|
||||
J.add_edges_from(
|
||||
[
|
||||
(("A", "D"), ("A",)),
|
||||
(("A",), ("A", "C")),
|
||||
(("A", "C"), ("C",)),
|
||||
(("C",), ("B", "C")),
|
||||
(("B", "C"), ("C",)),
|
||||
(("C",), ("C", "E")),
|
||||
]
|
||||
)
|
||||
|
||||
assert nx.is_isomorphic(G, J)
|
||||
671
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/test_mst.py
vendored
Normal file
671
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/test_mst.py
vendored
Normal file
@@ -0,0 +1,671 @@
|
||||
"""Unit tests for the :mod:`networkx.algorithms.tree.mst` module."""
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import edges_equal, nodes_equal
|
||||
|
||||
|
||||
def test_unknown_algorithm():
|
||||
with pytest.raises(ValueError):
|
||||
nx.minimum_spanning_tree(nx.Graph(), algorithm="random")
|
||||
|
||||
|
||||
class MinimumSpanningTreeTestBase:
|
||||
"""Base class for test classes for minimum spanning tree algorithms.
|
||||
This class contains some common tests that will be inherited by
|
||||
subclasses. Each subclass must have a class attribute
|
||||
:data:`algorithm` that is a string representing the algorithm to
|
||||
run, as described under the ``algorithm`` keyword argument for the
|
||||
:func:`networkx.minimum_spanning_edges` function. Subclasses can
|
||||
then implement any algorithm-specific tests.
|
||||
"""
|
||||
|
||||
def setup_method(self, method):
|
||||
"""Creates an example graph and stores the expected minimum and
|
||||
maximum spanning tree edges.
|
||||
"""
|
||||
# This stores the class attribute `algorithm` in an instance attribute.
|
||||
self.algo = self.algorithm
|
||||
# This example graph comes from Wikipedia:
|
||||
# https://en.wikipedia.org/wiki/Kruskal's_algorithm
|
||||
edges = [
|
||||
(0, 1, 7),
|
||||
(0, 3, 5),
|
||||
(1, 2, 8),
|
||||
(1, 3, 9),
|
||||
(1, 4, 7),
|
||||
(2, 4, 5),
|
||||
(3, 4, 15),
|
||||
(3, 5, 6),
|
||||
(4, 5, 8),
|
||||
(4, 6, 9),
|
||||
(5, 6, 11),
|
||||
]
|
||||
self.G = nx.Graph()
|
||||
self.G.add_weighted_edges_from(edges)
|
||||
self.minimum_spanning_edgelist = [
|
||||
(0, 1, {"weight": 7}),
|
||||
(0, 3, {"weight": 5}),
|
||||
(1, 4, {"weight": 7}),
|
||||
(2, 4, {"weight": 5}),
|
||||
(3, 5, {"weight": 6}),
|
||||
(4, 6, {"weight": 9}),
|
||||
]
|
||||
self.maximum_spanning_edgelist = [
|
||||
(0, 1, {"weight": 7}),
|
||||
(1, 2, {"weight": 8}),
|
||||
(1, 3, {"weight": 9}),
|
||||
(3, 4, {"weight": 15}),
|
||||
(4, 6, {"weight": 9}),
|
||||
(5, 6, {"weight": 11}),
|
||||
]
|
||||
|
||||
def test_minimum_edges(self):
|
||||
edges = nx.minimum_spanning_edges(self.G, algorithm=self.algo)
|
||||
# Edges from the spanning edges functions don't come in sorted
|
||||
# orientation, so we need to sort each edge individually.
|
||||
actual = sorted((min(u, v), max(u, v), d) for u, v, d in edges)
|
||||
assert edges_equal(actual, self.minimum_spanning_edgelist)
|
||||
|
||||
def test_maximum_edges(self):
|
||||
edges = nx.maximum_spanning_edges(self.G, algorithm=self.algo)
|
||||
# Edges from the spanning edges functions don't come in sorted
|
||||
# orientation, so we need to sort each edge individually.
|
||||
actual = sorted((min(u, v), max(u, v), d) for u, v, d in edges)
|
||||
assert edges_equal(actual, self.maximum_spanning_edgelist)
|
||||
|
||||
def test_without_data(self):
|
||||
edges = nx.minimum_spanning_edges(self.G, algorithm=self.algo, data=False)
|
||||
# Edges from the spanning edges functions don't come in sorted
|
||||
# orientation, so we need to sort each edge individually.
|
||||
actual = sorted((min(u, v), max(u, v)) for u, v in edges)
|
||||
expected = [(u, v) for u, v, d in self.minimum_spanning_edgelist]
|
||||
assert edges_equal(actual, expected)
|
||||
|
||||
def test_nan_weights(self):
|
||||
# Edge weights NaN never appear in the spanning tree. see #2164
|
||||
G = self.G
|
||||
G.add_edge(0, 12, weight=float("nan"))
|
||||
edges = nx.minimum_spanning_edges(
|
||||
G, algorithm=self.algo, data=False, ignore_nan=True
|
||||
)
|
||||
actual = sorted((min(u, v), max(u, v)) for u, v in edges)
|
||||
expected = [(u, v) for u, v, d in self.minimum_spanning_edgelist]
|
||||
assert edges_equal(actual, expected)
|
||||
# Now test for raising exception
|
||||
edges = nx.minimum_spanning_edges(
|
||||
G, algorithm=self.algo, data=False, ignore_nan=False
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
list(edges)
|
||||
# test default for ignore_nan as False
|
||||
edges = nx.minimum_spanning_edges(G, algorithm=self.algo, data=False)
|
||||
with pytest.raises(ValueError):
|
||||
list(edges)
|
||||
|
||||
def test_nan_weights_order(self):
|
||||
# now try again with a nan edge at the beginning of G.nodes
|
||||
edges = [
|
||||
(0, 1, 7),
|
||||
(0, 3, 5),
|
||||
(1, 2, 8),
|
||||
(1, 3, 9),
|
||||
(1, 4, 7),
|
||||
(2, 4, 5),
|
||||
(3, 4, 15),
|
||||
(3, 5, 6),
|
||||
(4, 5, 8),
|
||||
(4, 6, 9),
|
||||
(5, 6, 11),
|
||||
]
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from([(u + 1, v + 1, wt) for u, v, wt in edges])
|
||||
G.add_edge(0, 7, weight=float("nan"))
|
||||
edges = nx.minimum_spanning_edges(
|
||||
G, algorithm=self.algo, data=False, ignore_nan=True
|
||||
)
|
||||
actual = sorted((min(u, v), max(u, v)) for u, v in edges)
|
||||
shift = [(u + 1, v + 1) for u, v, d in self.minimum_spanning_edgelist]
|
||||
assert edges_equal(actual, shift)
|
||||
|
||||
def test_isolated_node(self):
|
||||
# now try again with an isolated node
|
||||
edges = [
|
||||
(0, 1, 7),
|
||||
(0, 3, 5),
|
||||
(1, 2, 8),
|
||||
(1, 3, 9),
|
||||
(1, 4, 7),
|
||||
(2, 4, 5),
|
||||
(3, 4, 15),
|
||||
(3, 5, 6),
|
||||
(4, 5, 8),
|
||||
(4, 6, 9),
|
||||
(5, 6, 11),
|
||||
]
|
||||
G = nx.Graph()
|
||||
G.add_weighted_edges_from([(u + 1, v + 1, wt) for u, v, wt in edges])
|
||||
G.add_node(0)
|
||||
edges = nx.minimum_spanning_edges(
|
||||
G, algorithm=self.algo, data=False, ignore_nan=True
|
||||
)
|
||||
actual = sorted((min(u, v), max(u, v)) for u, v in edges)
|
||||
shift = [(u + 1, v + 1) for u, v, d in self.minimum_spanning_edgelist]
|
||||
assert edges_equal(actual, shift)
|
||||
|
||||
def test_minimum_tree(self):
|
||||
T = nx.minimum_spanning_tree(self.G, algorithm=self.algo)
|
||||
actual = sorted(T.edges(data=True))
|
||||
assert edges_equal(actual, self.minimum_spanning_edgelist)
|
||||
|
||||
def test_maximum_tree(self):
|
||||
T = nx.maximum_spanning_tree(self.G, algorithm=self.algo)
|
||||
actual = sorted(T.edges(data=True))
|
||||
assert edges_equal(actual, self.maximum_spanning_edgelist)
|
||||
|
||||
def test_disconnected(self):
|
||||
G = nx.Graph([(0, 1, {"weight": 1}), (2, 3, {"weight": 2})])
|
||||
T = nx.minimum_spanning_tree(G, algorithm=self.algo)
|
||||
assert nodes_equal(list(T), list(range(4)))
|
||||
assert edges_equal(list(T.edges()), [(0, 1), (2, 3)])
|
||||
|
||||
def test_empty_graph(self):
|
||||
G = nx.empty_graph(3)
|
||||
T = nx.minimum_spanning_tree(G, algorithm=self.algo)
|
||||
assert nodes_equal(sorted(T), list(range(3)))
|
||||
assert T.number_of_edges() == 0
|
||||
|
||||
def test_attributes(self):
|
||||
G = nx.Graph()
|
||||
G.add_edge(1, 2, weight=1, color="red", distance=7)
|
||||
G.add_edge(2, 3, weight=1, color="green", distance=2)
|
||||
G.add_edge(1, 3, weight=10, color="blue", distance=1)
|
||||
G.graph["foo"] = "bar"
|
||||
T = nx.minimum_spanning_tree(G, algorithm=self.algo)
|
||||
assert T.graph == G.graph
|
||||
assert nodes_equal(T, G)
|
||||
for u, v in T.edges():
|
||||
assert T.adj[u][v] == G.adj[u][v]
|
||||
|
||||
def test_weight_attribute(self):
|
||||
G = nx.Graph()
|
||||
G.add_edge(0, 1, weight=1, distance=7)
|
||||
G.add_edge(0, 2, weight=30, distance=1)
|
||||
G.add_edge(1, 2, weight=1, distance=1)
|
||||
G.add_node(3)
|
||||
T = nx.minimum_spanning_tree(G, algorithm=self.algo, weight="distance")
|
||||
assert nodes_equal(sorted(T), list(range(4)))
|
||||
assert edges_equal(sorted(T.edges()), [(0, 2), (1, 2)])
|
||||
T = nx.maximum_spanning_tree(G, algorithm=self.algo, weight="distance")
|
||||
assert nodes_equal(sorted(T), list(range(4)))
|
||||
assert edges_equal(sorted(T.edges()), [(0, 1), (0, 2)])
|
||||
|
||||
|
||||
class TestBoruvka(MinimumSpanningTreeTestBase):
|
||||
"""Unit tests for computing a minimum (or maximum) spanning tree
|
||||
using Borůvka's algorithm.
|
||||
"""
|
||||
|
||||
algorithm = "boruvka"
|
||||
|
||||
def test_unicode_name(self):
|
||||
"""Tests that using a Unicode string can correctly indicate
|
||||
Borůvka's algorithm.
|
||||
"""
|
||||
edges = nx.minimum_spanning_edges(self.G, algorithm="borůvka")
|
||||
# Edges from the spanning edges functions don't come in sorted
|
||||
# orientation, so we need to sort each edge individually.
|
||||
actual = sorted((min(u, v), max(u, v), d) for u, v, d in edges)
|
||||
assert edges_equal(actual, self.minimum_spanning_edgelist)
|
||||
|
||||
|
||||
class MultigraphMSTTestBase(MinimumSpanningTreeTestBase):
|
||||
# Abstract class
|
||||
|
||||
def test_multigraph_keys_min(self):
|
||||
"""Tests that the minimum spanning edges of a multigraph
|
||||
preserves edge keys.
|
||||
"""
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge(0, 1, key="a", weight=2)
|
||||
G.add_edge(0, 1, key="b", weight=1)
|
||||
min_edges = nx.minimum_spanning_edges
|
||||
mst_edges = min_edges(G, algorithm=self.algo, data=False)
|
||||
assert edges_equal([(0, 1, "b")], list(mst_edges))
|
||||
|
||||
def test_multigraph_keys_max(self):
|
||||
"""Tests that the maximum spanning edges of a multigraph
|
||||
preserves edge keys.
|
||||
"""
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge(0, 1, key="a", weight=2)
|
||||
G.add_edge(0, 1, key="b", weight=1)
|
||||
max_edges = nx.maximum_spanning_edges
|
||||
mst_edges = max_edges(G, algorithm=self.algo, data=False)
|
||||
assert edges_equal([(0, 1, "a")], list(mst_edges))
|
||||
|
||||
|
||||
class TestKruskal(MultigraphMSTTestBase):
|
||||
"""Unit tests for computing a minimum (or maximum) spanning tree
|
||||
using Kruskal's algorithm.
|
||||
"""
|
||||
|
||||
algorithm = "kruskal"
|
||||
|
||||
def test_key_data_bool(self):
|
||||
"""Tests that the keys and data values are included in
|
||||
MST edges based on whether keys and data parameters are
|
||||
true or false"""
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge(1, 2, key=1, weight=2)
|
||||
G.add_edge(1, 2, key=2, weight=3)
|
||||
G.add_edge(3, 2, key=1, weight=2)
|
||||
G.add_edge(3, 1, key=1, weight=4)
|
||||
|
||||
# keys are included and data is not included
|
||||
mst_edges = nx.minimum_spanning_edges(
|
||||
G, algorithm=self.algo, keys=True, data=False
|
||||
)
|
||||
assert edges_equal([(1, 2, 1), (2, 3, 1)], list(mst_edges))
|
||||
|
||||
# keys are not included and data is included
|
||||
mst_edges = nx.minimum_spanning_edges(
|
||||
G, algorithm=self.algo, keys=False, data=True
|
||||
)
|
||||
assert edges_equal(
|
||||
[(1, 2, {"weight": 2}), (2, 3, {"weight": 2})], list(mst_edges)
|
||||
)
|
||||
|
||||
# both keys and data are not included
|
||||
mst_edges = nx.minimum_spanning_edges(
|
||||
G, algorithm=self.algo, keys=False, data=False
|
||||
)
|
||||
assert edges_equal([(1, 2), (2, 3)], list(mst_edges))
|
||||
|
||||
|
||||
class TestPrim(MultigraphMSTTestBase):
|
||||
"""Unit tests for computing a minimum (or maximum) spanning tree
|
||||
using Prim's algorithm.
|
||||
"""
|
||||
|
||||
algorithm = "prim"
|
||||
|
||||
def test_ignore_nan(self):
|
||||
"""Tests that the edges with NaN weights are ignored or
|
||||
raise an Error based on ignore_nan is true or false"""
|
||||
H = nx.MultiGraph()
|
||||
H.add_edge(1, 2, key=1, weight=float("nan"))
|
||||
H.add_edge(1, 2, key=2, weight=3)
|
||||
H.add_edge(3, 2, key=1, weight=2)
|
||||
H.add_edge(3, 1, key=1, weight=4)
|
||||
|
||||
# NaN weight edges are ignored when ignore_nan=True
|
||||
mst_edges = nx.minimum_spanning_edges(H, algorithm=self.algo, ignore_nan=True)
|
||||
assert edges_equal(
|
||||
[(1, 2, 2, {"weight": 3}), (2, 3, 1, {"weight": 2})], list(mst_edges)
|
||||
)
|
||||
|
||||
# NaN weight edges raise Error when ignore_nan=False
|
||||
with pytest.raises(ValueError):
|
||||
list(nx.minimum_spanning_edges(H, algorithm=self.algo, ignore_nan=False))
|
||||
|
||||
def test_multigraph_keys_tree(self):
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge(0, 1, key="a", weight=2)
|
||||
G.add_edge(0, 1, key="b", weight=1)
|
||||
T = nx.minimum_spanning_tree(G, algorithm=self.algo)
|
||||
assert edges_equal([(0, 1, 1)], list(T.edges(data="weight")))
|
||||
|
||||
def test_multigraph_keys_tree_max(self):
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge(0, 1, key="a", weight=2)
|
||||
G.add_edge(0, 1, key="b", weight=1)
|
||||
T = nx.maximum_spanning_tree(G, algorithm=self.algo)
|
||||
assert edges_equal([(0, 1, 2)], list(T.edges(data="weight")))
|
||||
|
||||
|
||||
class TestSpanningTreeIterator:
|
||||
"""
|
||||
Tests the spanning tree iterator on the example graph in the 2005 Sörensen
|
||||
and Janssens paper An Algorithm to Generate all Spanning Trees of a Graph in
|
||||
Order of Increasing Cost
|
||||
"""
|
||||
|
||||
def setup_method(self):
|
||||
# Original Graph
|
||||
edges = [(0, 1, 5), (1, 2, 4), (1, 4, 6), (2, 3, 5), (2, 4, 7), (3, 4, 3)]
|
||||
self.G = nx.Graph()
|
||||
self.G.add_weighted_edges_from(edges)
|
||||
# List of lists of spanning trees in increasing order
|
||||
self.spanning_trees = [
|
||||
# 1, MST, cost = 17
|
||||
[
|
||||
(0, 1, {"weight": 5}),
|
||||
(1, 2, {"weight": 4}),
|
||||
(2, 3, {"weight": 5}),
|
||||
(3, 4, {"weight": 3}),
|
||||
],
|
||||
# 2, cost = 18
|
||||
[
|
||||
(0, 1, {"weight": 5}),
|
||||
(1, 2, {"weight": 4}),
|
||||
(1, 4, {"weight": 6}),
|
||||
(3, 4, {"weight": 3}),
|
||||
],
|
||||
# 3, cost = 19
|
||||
[
|
||||
(0, 1, {"weight": 5}),
|
||||
(1, 4, {"weight": 6}),
|
||||
(2, 3, {"weight": 5}),
|
||||
(3, 4, {"weight": 3}),
|
||||
],
|
||||
# 4, cost = 19
|
||||
[
|
||||
(0, 1, {"weight": 5}),
|
||||
(1, 2, {"weight": 4}),
|
||||
(2, 4, {"weight": 7}),
|
||||
(3, 4, {"weight": 3}),
|
||||
],
|
||||
# 5, cost = 20
|
||||
[
|
||||
(0, 1, {"weight": 5}),
|
||||
(1, 2, {"weight": 4}),
|
||||
(1, 4, {"weight": 6}),
|
||||
(2, 3, {"weight": 5}),
|
||||
],
|
||||
# 6, cost = 21
|
||||
[
|
||||
(0, 1, {"weight": 5}),
|
||||
(1, 4, {"weight": 6}),
|
||||
(2, 4, {"weight": 7}),
|
||||
(3, 4, {"weight": 3}),
|
||||
],
|
||||
# 7, cost = 21
|
||||
[
|
||||
(0, 1, {"weight": 5}),
|
||||
(1, 2, {"weight": 4}),
|
||||
(2, 3, {"weight": 5}),
|
||||
(2, 4, {"weight": 7}),
|
||||
],
|
||||
# 8, cost = 23
|
||||
[
|
||||
(0, 1, {"weight": 5}),
|
||||
(1, 4, {"weight": 6}),
|
||||
(2, 3, {"weight": 5}),
|
||||
(2, 4, {"weight": 7}),
|
||||
],
|
||||
]
|
||||
|
||||
def test_minimum_spanning_tree_iterator(self):
|
||||
"""
|
||||
Tests that the spanning trees are correctly returned in increasing order
|
||||
"""
|
||||
tree_index = 0
|
||||
for tree in nx.SpanningTreeIterator(self.G):
|
||||
actual = sorted(tree.edges(data=True))
|
||||
assert edges_equal(actual, self.spanning_trees[tree_index])
|
||||
tree_index += 1
|
||||
|
||||
def test_maximum_spanning_tree_iterator(self):
|
||||
"""
|
||||
Tests that the spanning trees are correctly returned in decreasing order
|
||||
"""
|
||||
tree_index = 7
|
||||
for tree in nx.SpanningTreeIterator(self.G, minimum=False):
|
||||
actual = sorted(tree.edges(data=True))
|
||||
assert edges_equal(actual, self.spanning_trees[tree_index])
|
||||
tree_index -= 1
|
||||
|
||||
|
||||
def test_random_spanning_tree_multiplicative_small():
|
||||
"""
|
||||
Using a fixed seed, sample one tree for repeatability.
|
||||
"""
|
||||
from math import exp
|
||||
|
||||
pytest.importorskip("scipy")
|
||||
|
||||
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 gamma
|
||||
G = nx.Graph()
|
||||
for u, v in gamma:
|
||||
G.add_edge(u, v, lambda_key=exp(gamma[(u, v)]))
|
||||
|
||||
solution_edges = [(2, 3), (3, 4), (0, 5), (5, 4), (4, 1)]
|
||||
solution = nx.Graph()
|
||||
solution.add_edges_from(solution_edges)
|
||||
|
||||
sampled_tree = nx.random_spanning_tree(G, "lambda_key", seed=42)
|
||||
|
||||
assert nx.utils.edges_equal(solution.edges, sampled_tree.edges)
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_random_spanning_tree_multiplicative_large():
|
||||
"""
|
||||
Sample many trees from the distribution created in the last test
|
||||
"""
|
||||
from math import exp
|
||||
from random import Random
|
||||
|
||||
pytest.importorskip("numpy")
|
||||
stats = pytest.importorskip("scipy.stats")
|
||||
|
||||
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 gamma
|
||||
G = nx.Graph()
|
||||
for u, v in gamma:
|
||||
G.add_edge(u, v, lambda_key=exp(gamma[(u, v)]))
|
||||
|
||||
# Find the multiplicative weight for each tree.
|
||||
total_weight = 0
|
||||
tree_expected = {}
|
||||
for t in nx.SpanningTreeIterator(G):
|
||||
# Find the multiplicative weight of the spanning tree
|
||||
weight = 1
|
||||
for u, v, d in t.edges(data="lambda_key"):
|
||||
weight *= d
|
||||
tree_expected[t] = weight
|
||||
total_weight += weight
|
||||
|
||||
# Assert that every tree has an entry in the expected distribution
|
||||
assert len(tree_expected) == 75
|
||||
|
||||
# Set the sample size and then calculate the expected number of times we
|
||||
# expect to see each tree. This test uses a near minimum sample size where
|
||||
# the most unlikely tree has an expected frequency of 5.15.
|
||||
# (Minimum required is 5)
|
||||
#
|
||||
# Here we also initialize the tree_actual dict so that we know the keys
|
||||
# match between the two. We will later take advantage of the fact that since
|
||||
# python 3.7 dict order is guaranteed so the expected and actual data will
|
||||
# have the same order.
|
||||
sample_size = 1200
|
||||
tree_actual = {}
|
||||
for t in tree_expected:
|
||||
tree_expected[t] = (tree_expected[t] / total_weight) * sample_size
|
||||
tree_actual[t] = 0
|
||||
|
||||
# Sample the spanning trees
|
||||
#
|
||||
# Assert that they are actually trees and record which of the 75 trees we
|
||||
# have sampled.
|
||||
#
|
||||
# For repeatability, we want to take advantage of the decorators in NetworkX
|
||||
# to randomly sample the same sample each time. However, if we pass in a
|
||||
# constant seed to sample_spanning_tree we will get the same tree each time.
|
||||
# Instead, we can create our own random number generator with a fixed seed
|
||||
# and pass those into sample_spanning_tree.
|
||||
rng = Random(37)
|
||||
for _ in range(sample_size):
|
||||
sampled_tree = nx.random_spanning_tree(G, "lambda_key", seed=rng)
|
||||
assert nx.is_tree(sampled_tree)
|
||||
|
||||
for t in tree_expected:
|
||||
if nx.utils.edges_equal(t.edges, sampled_tree.edges):
|
||||
tree_actual[t] += 1
|
||||
break
|
||||
|
||||
# Conduct a Chi squared test to see if the actual distribution matches the
|
||||
# expected one at an alpha = 0.05 significance level.
|
||||
#
|
||||
# H_0: The distribution of trees in tree_actual matches the normalized product
|
||||
# of the edge weights in the tree.
|
||||
#
|
||||
# H_a: The distribution of trees in tree_actual follows some other
|
||||
# distribution of spanning trees.
|
||||
_, p = stats.chisquare(list(tree_actual.values()), list(tree_expected.values()))
|
||||
|
||||
# Assert that p is greater than the significance level so that we do not
|
||||
# reject the null hypothesis
|
||||
assert not p < 0.05
|
||||
|
||||
|
||||
def test_random_spanning_tree_additive_small():
|
||||
"""
|
||||
Sample a single spanning tree from the additive method.
|
||||
"""
|
||||
pytest.importorskip("scipy")
|
||||
|
||||
edges = {
|
||||
(0, 1): 1,
|
||||
(0, 2): 1,
|
||||
(0, 5): 3,
|
||||
(1, 2): 2,
|
||||
(1, 4): 3,
|
||||
(2, 3): 3,
|
||||
(5, 3): 4,
|
||||
(5, 4): 5,
|
||||
(4, 3): 4,
|
||||
}
|
||||
|
||||
# Build the graph
|
||||
G = nx.Graph()
|
||||
for u, v in edges:
|
||||
G.add_edge(u, v, weight=edges[(u, v)])
|
||||
|
||||
solution_edges = [(0, 2), (1, 2), (2, 3), (3, 4), (3, 5)]
|
||||
solution = nx.Graph()
|
||||
solution.add_edges_from(solution_edges)
|
||||
|
||||
sampled_tree = nx.random_spanning_tree(
|
||||
G, weight="weight", multiplicative=False, seed=37
|
||||
)
|
||||
|
||||
assert nx.utils.edges_equal(solution.edges, sampled_tree.edges)
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_random_spanning_tree_additive_large():
|
||||
"""
|
||||
Sample many spanning trees from the additive method.
|
||||
"""
|
||||
from random import Random
|
||||
|
||||
pytest.importorskip("numpy")
|
||||
stats = pytest.importorskip("scipy.stats")
|
||||
|
||||
edges = {
|
||||
(0, 1): 1,
|
||||
(0, 2): 1,
|
||||
(0, 5): 3,
|
||||
(1, 2): 2,
|
||||
(1, 4): 3,
|
||||
(2, 3): 3,
|
||||
(5, 3): 4,
|
||||
(5, 4): 5,
|
||||
(4, 3): 4,
|
||||
}
|
||||
|
||||
# Build the graph
|
||||
G = nx.Graph()
|
||||
for u, v in edges:
|
||||
G.add_edge(u, v, weight=edges[(u, v)])
|
||||
|
||||
# Find the additive weight for each tree.
|
||||
total_weight = 0
|
||||
tree_expected = {}
|
||||
for t in nx.SpanningTreeIterator(G):
|
||||
# Find the multiplicative weight of the spanning tree
|
||||
weight = 0
|
||||
for u, v, d in t.edges(data="weight"):
|
||||
weight += d
|
||||
tree_expected[t] = weight
|
||||
total_weight += weight
|
||||
|
||||
# Assert that every tree has an entry in the expected distribution
|
||||
assert len(tree_expected) == 75
|
||||
|
||||
# Set the sample size and then calculate the expected number of times we
|
||||
# expect to see each tree. This test uses a near minimum sample size where
|
||||
# the most unlikely tree has an expected frequency of 5.07.
|
||||
# (Minimum required is 5)
|
||||
#
|
||||
# Here we also initialize the tree_actual dict so that we know the keys
|
||||
# match between the two. We will later take advantage of the fact that since
|
||||
# python 3.7 dict order is guaranteed so the expected and actual data will
|
||||
# have the same order.
|
||||
sample_size = 500
|
||||
tree_actual = {}
|
||||
for t in tree_expected:
|
||||
tree_expected[t] = (tree_expected[t] / total_weight) * sample_size
|
||||
tree_actual[t] = 0
|
||||
|
||||
# Sample the spanning trees
|
||||
#
|
||||
# Assert that they are actually trees and record which of the 75 trees we
|
||||
# have sampled.
|
||||
#
|
||||
# For repeatability, we want to take advantage of the decorators in NetworkX
|
||||
# to randomly sample the same sample each time. However, if we pass in a
|
||||
# constant seed to sample_spanning_tree we will get the same tree each time.
|
||||
# Instead, we can create our own random number generator with a fixed seed
|
||||
# and pass those into sample_spanning_tree.
|
||||
rng = Random(37)
|
||||
for _ in range(sample_size):
|
||||
sampled_tree = nx.random_spanning_tree(
|
||||
G, "weight", multiplicative=False, seed=rng
|
||||
)
|
||||
assert nx.is_tree(sampled_tree)
|
||||
|
||||
for t in tree_expected:
|
||||
if nx.utils.edges_equal(t.edges, sampled_tree.edges):
|
||||
tree_actual[t] += 1
|
||||
break
|
||||
|
||||
# Conduct a Chi squared test to see if the actual distribution matches the
|
||||
# expected one at an alpha = 0.05 significance level.
|
||||
#
|
||||
# H_0: The distribution of trees in tree_actual matches the normalized product
|
||||
# of the edge weights in the tree.
|
||||
#
|
||||
# H_a: The distribution of trees in tree_actual follows some other
|
||||
# distribution of spanning trees.
|
||||
_, p = stats.chisquare(list(tree_actual.values()), list(tree_expected.values()))
|
||||
|
||||
# Assert that p is greater than the significance level so that we do not
|
||||
# reject the null hypothesis
|
||||
assert not p < 0.05
|
||||
37
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/test_operations.py
vendored
Normal file
37
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/test_operations.py
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Unit tests for the :mod:`networkx.algorithms.tree.operations` module.
|
||||
|
||||
"""
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import edges_equal, nodes_equal
|
||||
|
||||
|
||||
class TestJoin:
|
||||
"""Unit tests for the :func:`networkx.tree.join` function."""
|
||||
|
||||
def test_empty_sequence(self):
|
||||
"""Tests that joining the empty sequence results in the tree
|
||||
with one node.
|
||||
|
||||
"""
|
||||
T = nx.join([])
|
||||
assert len(T) == 1
|
||||
assert T.number_of_edges() == 0
|
||||
|
||||
def test_single(self):
|
||||
"""Tests that joining just one tree yields a tree with one more
|
||||
node.
|
||||
|
||||
"""
|
||||
T = nx.empty_graph(1)
|
||||
actual = nx.join([(T, 0)])
|
||||
expected = nx.path_graph(2)
|
||||
assert nodes_equal(list(expected), list(actual))
|
||||
assert edges_equal(list(expected.edges()), list(actual.edges()))
|
||||
|
||||
def test_basic(self):
|
||||
"""Tests for joining multiple subtrees at a root node."""
|
||||
trees = [(nx.full_rary_tree(2, 2**2 - 1), 0) for i in range(2)]
|
||||
actual = nx.join(trees)
|
||||
expected = nx.full_rary_tree(2, 2**3 - 1)
|
||||
assert nx.is_isomorphic(actual, expected)
|
||||
162
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/test_recognition.py
vendored
Normal file
162
.CondaPkg/env/Lib/site-packages/networkx/algorithms/tree/tests/test_recognition.py
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
|
||||
|
||||
class TestTreeRecognition:
|
||||
graph = nx.Graph
|
||||
multigraph = nx.MultiGraph
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.T1 = cls.graph()
|
||||
|
||||
cls.T2 = cls.graph()
|
||||
cls.T2.add_node(1)
|
||||
|
||||
cls.T3 = cls.graph()
|
||||
cls.T3.add_nodes_from(range(5))
|
||||
edges = [(i, i + 1) for i in range(4)]
|
||||
cls.T3.add_edges_from(edges)
|
||||
|
||||
cls.T5 = cls.multigraph()
|
||||
cls.T5.add_nodes_from(range(5))
|
||||
edges = [(i, i + 1) for i in range(4)]
|
||||
cls.T5.add_edges_from(edges)
|
||||
|
||||
cls.T6 = cls.graph()
|
||||
cls.T6.add_nodes_from([6, 7])
|
||||
cls.T6.add_edge(6, 7)
|
||||
|
||||
cls.F1 = nx.compose(cls.T6, cls.T3)
|
||||
|
||||
cls.N4 = cls.graph()
|
||||
cls.N4.add_node(1)
|
||||
cls.N4.add_edge(1, 1)
|
||||
|
||||
cls.N5 = cls.graph()
|
||||
cls.N5.add_nodes_from(range(5))
|
||||
|
||||
cls.N6 = cls.graph()
|
||||
cls.N6.add_nodes_from(range(3))
|
||||
cls.N6.add_edges_from([(0, 1), (1, 2), (2, 0)])
|
||||
|
||||
cls.NF1 = nx.compose(cls.T6, cls.N6)
|
||||
|
||||
def test_null_tree(self):
|
||||
with pytest.raises(nx.NetworkXPointlessConcept):
|
||||
nx.is_tree(self.graph())
|
||||
|
||||
def test_null_tree2(self):
|
||||
with pytest.raises(nx.NetworkXPointlessConcept):
|
||||
nx.is_tree(self.multigraph())
|
||||
|
||||
def test_null_forest(self):
|
||||
with pytest.raises(nx.NetworkXPointlessConcept):
|
||||
nx.is_forest(self.graph())
|
||||
|
||||
def test_null_forest2(self):
|
||||
with pytest.raises(nx.NetworkXPointlessConcept):
|
||||
nx.is_forest(self.multigraph())
|
||||
|
||||
def test_is_tree(self):
|
||||
assert nx.is_tree(self.T2)
|
||||
assert nx.is_tree(self.T3)
|
||||
assert nx.is_tree(self.T5)
|
||||
|
||||
def test_is_not_tree(self):
|
||||
assert not nx.is_tree(self.N4)
|
||||
assert not nx.is_tree(self.N5)
|
||||
assert not nx.is_tree(self.N6)
|
||||
|
||||
def test_is_forest(self):
|
||||
assert nx.is_forest(self.T2)
|
||||
assert nx.is_forest(self.T3)
|
||||
assert nx.is_forest(self.T5)
|
||||
assert nx.is_forest(self.F1)
|
||||
assert nx.is_forest(self.N5)
|
||||
|
||||
def test_is_not_forest(self):
|
||||
assert not nx.is_forest(self.N4)
|
||||
assert not nx.is_forest(self.N6)
|
||||
assert not nx.is_forest(self.NF1)
|
||||
|
||||
|
||||
class TestDirectedTreeRecognition(TestTreeRecognition):
|
||||
graph = nx.DiGraph
|
||||
multigraph = nx.MultiDiGraph
|
||||
|
||||
|
||||
def test_disconnected_graph():
|
||||
# https://github.com/networkx/networkx/issues/1144
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(0, 1), (1, 2), (2, 0), (3, 4)])
|
||||
assert not nx.is_tree(G)
|
||||
|
||||
G = nx.DiGraph()
|
||||
G.add_edges_from([(0, 1), (1, 2), (2, 0), (3, 4)])
|
||||
assert not nx.is_tree(G)
|
||||
|
||||
|
||||
def test_dag_nontree():
|
||||
G = nx.DiGraph()
|
||||
G.add_edges_from([(0, 1), (0, 2), (1, 2)])
|
||||
assert not nx.is_tree(G)
|
||||
assert nx.is_directed_acyclic_graph(G)
|
||||
|
||||
|
||||
def test_multicycle():
|
||||
G = nx.MultiDiGraph()
|
||||
G.add_edges_from([(0, 1), (0, 1)])
|
||||
assert not nx.is_tree(G)
|
||||
assert nx.is_directed_acyclic_graph(G)
|
||||
|
||||
|
||||
def test_emptybranch():
|
||||
G = nx.DiGraph()
|
||||
G.add_nodes_from(range(10))
|
||||
assert nx.is_branching(G)
|
||||
assert not nx.is_arborescence(G)
|
||||
|
||||
|
||||
def test_path():
|
||||
G = nx.DiGraph()
|
||||
nx.add_path(G, range(5))
|
||||
assert nx.is_branching(G)
|
||||
assert nx.is_arborescence(G)
|
||||
|
||||
|
||||
def test_notbranching1():
|
||||
# Acyclic violation.
|
||||
G = nx.MultiDiGraph()
|
||||
G.add_nodes_from(range(10))
|
||||
G.add_edges_from([(0, 1), (1, 0)])
|
||||
assert not nx.is_branching(G)
|
||||
assert not nx.is_arborescence(G)
|
||||
|
||||
|
||||
def test_notbranching2():
|
||||
# In-degree violation.
|
||||
G = nx.MultiDiGraph()
|
||||
G.add_nodes_from(range(10))
|
||||
G.add_edges_from([(0, 1), (0, 2), (3, 2)])
|
||||
assert not nx.is_branching(G)
|
||||
assert not nx.is_arborescence(G)
|
||||
|
||||
|
||||
def test_notarborescence1():
|
||||
# Not an arborescence due to not spanning.
|
||||
G = nx.MultiDiGraph()
|
||||
G.add_nodes_from(range(10))
|
||||
G.add_edges_from([(0, 1), (0, 2), (1, 3), (5, 6)])
|
||||
assert nx.is_branching(G)
|
||||
assert not nx.is_arborescence(G)
|
||||
|
||||
|
||||
def test_notarborescence2():
|
||||
# Not an arborescence due to in-degree violation.
|
||||
G = nx.MultiDiGraph()
|
||||
nx.add_path(G, range(5))
|
||||
G.add_edge(6, 4)
|
||||
assert not nx.is_branching(G)
|
||||
assert not nx.is_arborescence(G)
|
||||
Reference in New Issue
Block a user