Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/networkx/generators/nonisomorphic_trees.py: 11%
96 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
1"""
2Implementation of the Wright, Richmond, Odlyzko and McKay (WROM)
3algorithm for the enumeration of all non-isomorphic free trees of a
4given order. Rooted trees are represented by level sequences, i.e.,
5lists in which the i-th element specifies the distance of vertex i to
6the root.
8"""
10__all__ = ["nonisomorphic_trees", "number_of_nonisomorphic_trees"]
12import networkx as nx
15@nx._dispatch(graphs=None)
16def nonisomorphic_trees(order, create="graph"):
17 """Returns a list of nonisomorphic trees
19 Parameters
20 ----------
21 order : int
22 order of the desired tree(s)
24 create : graph or matrix (default="Graph)
25 If graph is selected a list of trees will be returned,
26 if matrix is selected a list of adjacency matrix will
27 be returned
29 Returns
30 -------
31 G : List of NetworkX Graphs
33 M : List of Adjacency matrices
35 References
36 ----------
38 """
40 if order < 2:
41 raise ValueError
42 # start at the path graph rooted at its center
43 layout = list(range(order // 2 + 1)) + list(range(1, (order + 1) // 2))
45 while layout is not None:
46 layout = _next_tree(layout)
47 if layout is not None:
48 if create == "graph":
49 yield _layout_to_graph(layout)
50 elif create == "matrix":
51 yield _layout_to_matrix(layout)
52 layout = _next_rooted_tree(layout)
55@nx._dispatch(graphs=None)
56def number_of_nonisomorphic_trees(order):
57 """Returns the number of nonisomorphic trees
59 Parameters
60 ----------
61 order : int
62 order of the desired tree(s)
64 Returns
65 -------
66 length : Number of nonisomorphic graphs for the given order
68 References
69 ----------
71 """
72 return sum(1 for _ in nonisomorphic_trees(order))
75def _next_rooted_tree(predecessor, p=None):
76 """One iteration of the Beyer-Hedetniemi algorithm."""
78 if p is None:
79 p = len(predecessor) - 1
80 while predecessor[p] == 1:
81 p -= 1
82 if p == 0:
83 return None
85 q = p - 1
86 while predecessor[q] != predecessor[p] - 1:
87 q -= 1
88 result = list(predecessor)
89 for i in range(p, len(result)):
90 result[i] = result[i - p + q]
91 return result
94def _next_tree(candidate):
95 """One iteration of the Wright, Richmond, Odlyzko and McKay
96 algorithm."""
98 # valid representation of a free tree if:
99 # there are at least two vertices at layer 1
100 # (this is always the case because we start at the path graph)
101 left, rest = _split_tree(candidate)
103 # and the left subtree of the root
104 # is less high than the tree with the left subtree removed
105 left_height = max(left)
106 rest_height = max(rest)
107 valid = rest_height >= left_height
109 if valid and rest_height == left_height:
110 # and, if left and rest are of the same height,
111 # if left does not encompass more vertices
112 if len(left) > len(rest):
113 valid = False
114 # and, if they have the same number or vertices,
115 # if left does not come after rest lexicographically
116 elif len(left) == len(rest) and left > rest:
117 valid = False
119 if valid:
120 return candidate
121 else:
122 # jump to the next valid free tree
123 p = len(left)
124 new_candidate = _next_rooted_tree(candidate, p)
125 if candidate[p] > 2:
126 new_left, new_rest = _split_tree(new_candidate)
127 new_left_height = max(new_left)
128 suffix = range(1, new_left_height + 2)
129 new_candidate[-len(suffix) :] = suffix
130 return new_candidate
133def _split_tree(layout):
134 """Returns a tuple of two layouts, one containing the left
135 subtree of the root vertex, and one containing the original tree
136 with the left subtree removed."""
138 one_found = False
139 m = None
140 for i in range(len(layout)):
141 if layout[i] == 1:
142 if one_found:
143 m = i
144 break
145 else:
146 one_found = True
148 if m is None:
149 m = len(layout)
151 left = [layout[i] - 1 for i in range(1, m)]
152 rest = [0] + [layout[i] for i in range(m, len(layout))]
153 return (left, rest)
156def _layout_to_matrix(layout):
157 """Create the adjacency matrix for the tree specified by the
158 given layout (level sequence)."""
160 result = [[0] * len(layout) for i in range(len(layout))]
161 stack = []
162 for i in range(len(layout)):
163 i_level = layout[i]
164 if stack:
165 j = stack[-1]
166 j_level = layout[j]
167 while j_level >= i_level:
168 stack.pop()
169 j = stack[-1]
170 j_level = layout[j]
171 result[i][j] = result[j][i] = 1
172 stack.append(i)
173 return result
176def _layout_to_graph(layout):
177 """Create a NetworkX Graph for the tree specified by the
178 given layout(level sequence)"""
179 G = nx.Graph()
180 stack = []
181 for i in range(len(layout)):
182 i_level = layout[i]
183 if stack:
184 j = stack[-1]
185 j_level = layout[j]
186 while j_level >= i_level:
187 stack.pop()
188 j = stack[-1]
189 j_level = layout[j]
190 G.add_edge(i, j)
191 stack.append(i)
192 return G