9 """Build an even decomposition list expected by Stage I helpers."""
10 leaves_per_subtree = total_leaves // number_of_subtrees
11 remaining_leaves = total_leaves % number_of_subtrees
13 for i
in range(number_of_subtrees):
14 decomposition.append(leaves_per_subtree + (1
if i < remaining_leaves
else 0))
18@pytest.mark.parametrize(
19 "dimensions,number_of_subtrees",
29 Stage I ("Leaf Distribution") invariant test in 2D and 3D.
31 Verify that leaves from subtree 0 are distributed as evenly as possible
32 across `number_of_subtrees` subtrees.
36 name=f
"pytest-tree-stage-i-{dimensions}d", max_level=2, dimensions=dimensions
40 leaf_count_before = tree._leaf_count
44 has_leaves, moved_nodes = split_strategy.distribute_leaves_bottom_up(tree, decomposition)
48 assert sum(moved_nodes) == leaf_count_before
49 assert len(moved_nodes) == number_of_subtrees
52 max_leaves = max(moved_nodes)
53 min_leaves = min(moved_nodes)
54 assert max_leaves - min_leaves <= 1, (
55 f
"Leaf distribution unbalanced: max={max_leaves}, min={min_leaves}, "
56 f
"distribution={moved_nodes}"
60 if leaf_count_before >= number_of_subtrees:
61 assert all(count > 0
for count
in moved_nodes), (
62 f
"Some subtrees are empty: {moved_nodes}"
66@pytest.mark.parametrize(
72 Stage I invariant: non-leaf nodes (internal nodes with children) must NOT
73 be modified by Stage I leaf distribution.
77 name=f
"pytest-tree-nonleaf-check-{dimensions}d", max_level=3, dimensions=dimensions
81 non_leaf_assignments_before = {}
82 for level
in range(tree._max_level - 1):
83 size = 3 ** (level + 1)
87 node = tree._tree_arrays[level][a][b]
90 child_a, child_b = 3 * a, 3 * b
91 if (child_a < 3 ** (level + 2)
and child_b < 3 ** (level + 2)
and
92 tree._tree_arrays[level + 1][child_a][child_b][0] == 1):
94 non_leaf_assignments_before[(level, a, b)] = node[1]
100 node = tree._tree_arrays[level][a][b][c]
103 child_a, child_b, child_c = 3 * a, 3 * b, 3 * c
104 if (child_a < 3 ** (level + 2)
and child_b < 3 ** (level + 2)
and
105 child_c < 3 ** (level + 2)
and
106 tree._tree_arrays[level + 1][child_a][child_b][child_c][0] == 1):
108 non_leaf_assignments_before[(level, a, b, c)] = node[1]
112 split_strategy.distribute_leaves_bottom_up(tree, decomposition)
115 for level_coords, subtree_before
in non_leaf_assignments_before.items():
117 level, a, b = level_coords
118 node_after = tree._tree_arrays[level][a][b]
120 level, a, b, c = level_coords
121 node_after = tree._tree_arrays[level][a][b][c]
123 assert node_after[1] == subtree_before, (
124 f
"Non-leaf node {level_coords} changed from subtree {subtree_before} "
125 f
"to subtree {node_after[1]} during Stage I"
129@pytest.mark.parametrize(
135 Stage I invariant: only leaves in subtree 0 are redistributed. Leaves already
136 in other subtrees must NOT be modified.
140 name=f
"pytest-tree-subtree0-only-{dimensions}d", max_level=2, dimensions=dimensions
144 finest_level = tree._max_level - 1
145 size = 3 ** (finest_level + 1)
148 corner_leaves_subtree_1 = []
150 for a
in range(size // 2, size):
151 for b
in range(size // 2, size):
152 tree._tree_arrays[finest_level][a][b] = (1, 1)
153 corner_leaves_subtree_1.append((a, b))
155 elif dimensions == 3:
156 for a
in range(size // 2, size):
157 for b
in range(size // 2, size):
158 for c
in range(size // 2, size):
159 tree._tree_arrays[finest_level][a][b][c] = (1, 1)
160 corner_leaves_subtree_1.append((a, b, c))
164 split_strategy.distribute_leaves_bottom_up(tree, decomposition)
168 for a, b
in corner_leaves_subtree_1:
169 node_after = tree._tree_arrays[finest_level][a][b]
170 assert node_after[1] == 1, (
171 f
"Leaf ({a},{b}) in subtree 1 was incorrectly modified to subtree {node_after[1]}"
174 elif dimensions == 3:
175 for a, b, c
in corner_leaves_subtree_1:
176 node_after = tree._tree_arrays[finest_level][a][b][c]
177 assert node_after[1] == 1, (
178 f
"Leaf ({a},{b},{c}) in subtree 1 was incorrectly modified to subtree {node_after[1]}"
182@pytest.mark.parametrize(
188 Stage I invariant: node existence flags (weights) must NOT be modified.
189 Stage I only changes subtree assignments, not node existence.
193 name=f
"pytest-tree-weights-{dimensions}d", max_level=2, dimensions=dimensions
198 for level
in range(tree._max_level):
199 size = 3 ** (level + 1)
201 for a
in range(size):
202 for b
in range(size):
203 weight, subtree = tree._tree_arrays[level][a][b]
204 weights_before[(level, a, b)] = weight
206 elif dimensions == 3:
207 for a
in range(size):
208 for b
in range(size):
209 for c
in range(size):
210 weight, subtree = tree._tree_arrays[level][a][b][c]
211 weights_before[(level, a, b, c)] = weight
215 split_strategy.distribute_leaves_bottom_up(tree, decomposition)
218 for level_coords, weight_before
in weights_before.items():
220 level, a, b = level_coords
221 weight_after, _ = tree._tree_arrays[level][a][b]
223 level, a, b, c = level_coords
224 weight_after, _ = tree._tree_arrays[level][a][b][c]
226 assert weight_after == weight_before, (
227 f
"Node {level_coords} weight changed from {weight_before} to {weight_after}"
231@pytest.mark.parametrize(
237 Stage I invariant: the returned `moved_nodes` list must accurately reflect
238 how many leaves were assigned to each subtree.
242 name=f
"pytest-tree-move-count-{dimensions}d", max_level=2, dimensions=dimensions
245 number_of_subtrees = 2
246 finest_level = tree._max_level - 1
250 has_leaves, moved_nodes = split_strategy.distribute_leaves_bottom_up(tree, decomposition)
253 assert len(moved_nodes) == number_of_subtrees
256 size = 3 ** (finest_level + 1)
257 actual_counts = [0] * (number_of_subtrees)
260 for a
in range(size):
261 for b
in range(size):
262 weight, subtree = tree._tree_arrays[finest_level][a][b]
264 if subtree < number_of_subtrees:
265 actual_counts[subtree] += 1
267 elif dimensions == 3:
268 for a
in range(size):
269 for b
in range(size):
270 for c
in range(size):
271 weight, subtree = tree._tree_arrays[finest_level][a][b][c]
273 if subtree < number_of_subtrees:
274 actual_counts[subtree] += 1
277 assert actual_counts == moved_nodes, (
278 f
"Reported moved_nodes {moved_nodes} does not match actual counts {actual_counts}"