Peano
Loading...
Searching...
No Matches
tree_builders.py
Go to the documentation of this file.
1"""
2Reusable tree builder functions for integration tests.
3
4Each function constructs and returns a fresh :class:`Tree` instance with all
5cells initially assigned to subtree 0. The trees are intentionally kept small
6and correspond to the reference diagrams under
7``documentation/toolbox/loadbalancing/``.
8"""
9
10import numpy as np
11
12from toolbox.loadbalancing.api.Tree import Tree
13from toolbox.loadbalancing.api.input.RegularGridGenerator import RegularGridGenerator
14
15
17 """
18 Build the adaptive 2D tree described by the tree3_stage*_fine_first_bfs.dot
19 documentation files.
20
21 The tree starts with a **single root node on Level 0** and then embeds the
22 1D ternary structure in the first row of deeper 2D levels (all cells outside
23 row 0 have weight=0).
24
25 Mapping:
26 Level 0 (3x3): (0,0)=Root (single node)
27 Level 1 (9x9): (0,0)=L1A (0,1)=L1B (0,2)=L1C
28 Level 2 (27x27): (0,0)=L2A1 (0,1)=L2A2 (0,2)=L2A3 [childless leaf]
29 (0,3)=L2B1 (0,4)=L2B2 (0,5)=L2B3 [childless leaf]
30 (0,6)=L2C1 (0,7)=L2C2 (0,8)=L2C3
31 Level 3 (81x81): (0,0-2)=L3A1* (0,3-5)=L3A2*
32 (0,6-8)=weight=0 [L2A3 has no children]
33 (0,9-11)=L3B1* (0,12-14)=L3B2*
34 (0,15-17)=weight=0 [L2B3 has no children]
35 (0,18-20)=L3C1* (0,21-23)=L3C2* (0,24-26)=L3C3*
36
37 Total leaves initially (all in subtree 0):
38 21 finest-level leaves + 2 childless level-1 leaves = 23
39
40 The tree uses 4 total subtrees in the tests below.
41 """
42 tree = Tree("tree3-adaptive", 2, (0.0, 0.0), (1.0, 1.0))
43 tree._max_level = 4
44
45 # Initialise all levels with non-existent cells (weight=0)
46 for level in range(4):
47 size = 3 ** (level + 1)
48 arr = np.empty((size, size), dtype=object)
49 for a in range(size):
50 for b in range(size):
51 arr[a][b] = (0, 0)
52 tree._tree_arrays.append(arr)
53
54 # Level 0: single root node
55 tree._tree_arrays[0][0][0] = (1, 0)
56
57 # Level 1: L1A, L1B, L1C in first row (all subtree 0)
58 tree._tree_arrays[1][0][0] = (1, 0)
59 tree._tree_arrays[1][0][1] = (1, 0)
60 tree._tree_arrays[1][0][2] = (1, 0)
61
62 # Level 2: 9 nodes in first row (all subtree 0).
63 # L2A3=(0,2) and L2B3=(0,5) are childless leaves (no level-2 children).
64 for col in range(9):
65 tree._tree_arrays[2][0][col] = (1, 0)
66
67 # Level 3: finest leaves in first row, skipping children of the childless
68 # L2 nodes (columns 6-8 under L2A3 and columns 15-17 under L2B3 stay at
69 # weight=0 so that L2A3 and L2B3 are detected as childless leaves).
70 active_level2_cols = list(range(0, 6)) + list(range(9, 15)) + list(range(18, 27))
71 for col in active_level2_cols:
72 tree._tree_arrays[3][0][col] = (1, 0)
73
74 tree._subtree_count = 1
75 tree.update_split_pattern()
76
77 return tree
78
79
81 """
82 Build a 2D variant where each depicted node in the tree3 diagram is
83 represented by all rows with respect to its parent (3 cells in 2D), while
84 Level 0 still starts from a single root node.
85
86 Representation rule in 2D:
87 - For each conceptual parent-child edge, one conceptual child node is
88 represented as a vertical stripe of 3 cells in the parent's 3x3 child
89 patch.
90 - Conceptual child indices 0,1,2 map to local child columns 0,1,2.
91
92 This means all represented non-leaf parents have all 9 children present,
93 which allows Stage III to optimise mixed parents at Level 1.
94 """
95 tree = Tree("tree3-all-rows", 2, (0.0, 0.0), (1.0, 1.0))
96 tree._max_level = 4
97
98 # Initialise all levels with non-existent cells (weight=0)
99 for level in range(4):
100 size = 3 ** (level + 1)
101 arr = np.empty((size, size), dtype=object)
102 for a in range(size):
103 for b in range(size):
104 arr[a][b] = (0, 0)
105 tree._tree_arrays.append(arr)
106
107 def _fill_child_stripes(parent_level: int, parent_coords: tuple[int, int], child_cols: tuple[int, ...]):
108 """Fill 3-row stripes for selected conceptual child columns."""
109 child_level = parent_level + 1
110 parent_a, parent_b = parent_coords
111 base_a = 3 * parent_a
112 base_b = 3 * parent_b
113 for local_col in child_cols:
114 for local_row in range(3):
115 a = base_a + local_row
116 b = base_b + local_col
117 tree._tree_arrays[child_level][a][b] = (1, 0)
118
119 # Level 0: single root
120 tree._tree_arrays[0][0][0] = (1, 0)
121
122 # Root -> L1A/L1B/L1C as full-row stripes (all 9 children exist at level 1)
123 _fill_child_stripes(parent_level=0, parent_coords=(0, 0), child_cols=(0, 1, 2))
124
125 # Level 1 -> Level 2 (L2*): all three conceptual children exist for each L1 node
126 for a in range(3):
127 for b in range(3):
128 if tree._tree_arrays[1][a][b][0] == 1:
129 _fill_child_stripes(parent_level=1, parent_coords=(a, b), child_cols=(0, 1, 2))
130
131 # Level 2 -> Level 3 (L3*):
132 # - For A3 and B3 conceptual nodes (parent group A/B with local child index 2),
133 # keep them childless by not creating level-3 children.
134 # - All other represented L2 nodes are refined with three conceptual children,
135 # each expanded to a 3-row stripe.
136 for a in range(9):
137 for b in range(9):
138 if tree._tree_arrays[2][a][b][0] != 1:
139 continue
140
141 parent_group = b // 3 # 0=A, 1=B, 2=C
142 local_index = b % 3 # 0=1, 1=2, 2=3
143
144 is_childless = parent_group in (0, 1) and local_index == 2
145 if not is_childless:
146 _fill_child_stripes(parent_level=2, parent_coords=(a, b), child_cols=(0, 1, 2))
147
148 tree._subtree_count = 1
149 tree.update_split_pattern()
150
151 return tree
152
153
155 """
156 Build the 2D sparse tree used by ``visualise_tree_split_example3.png``.
157
158 The tree has 10 leaves in total and is arranged as a single branch that is
159 refined twice on the left-most child path:
160
161 - 4 finest-level leaves on the deepest branch
162 - 3 level-2 leaves on the middle branch
163 - 3 level-1 leaves directly under the root
164
165 This is the example that splits best into subtrees 4-3-3.
166 """
167 tree = Tree("visualise-tree-split-example3", 2, (0.0, 0.0), (1.0, 1.0))
168 tree._max_level = 4
169
170 # Initialise all levels with non-existent cells (weight=0)
171 for level in range(4):
172 size = 3 ** (level + 1)
173 arr = np.empty((size, size), dtype=object)
174 for a in range(size):
175 for b in range(size):
176 arr[a][b] = (0, 0)
177 tree._tree_arrays.append(arr)
178
179 def _add_branch(level: int, a: int, b: int) -> None:
180 """Create one refined node and its four corner children."""
181 tree._tree_arrays[level][a][b] = (1, 0)
182 if level == 3:
183 return
184
185 next_level = level + 1
186 base_a = 3 * a
187 base_b = 3 * b
188 child_coords = [(base_a + da, base_b + db) for da in (0, 2) for db in (0, 2)]
189
190 for child_a, child_b in child_coords:
191 tree._tree_arrays[next_level][child_a][child_b] = (1, 0)
192
193 # Refine the left-most child only, matching the documentation figure.
194 _add_branch(next_level, child_coords[0][0], child_coords[0][1])
195
196 _add_branch(0, 0, 0)
197
198 tree._subtree_count = 1
199 tree.update_split_pattern()
200
201 return tree
202
203
204def build_stage3_mixed_parent_case_2d() -> tuple[Tree, tuple[int, int]]:
205 """
206 Build a 2D tree where Stage III can promote a mixed parent.
207
208 The returned parent coordinates identify the level-0 parent whose children
209 are configured with a dominant subtree assignment.
210 """
211 tree = RegularGridGenerator(name="pytest-stage3-integration-2d", max_level=2, dimensions=2)
212
213 parent_level = 0
214 child_level = 1
215 parent_coords = (0, 0)
216
217 # Parent starts in subtree 0.
218 tree._tree_arrays[parent_level][parent_coords] = (1, 0)
219
220 children = [
221 (a, b)
222 for a in range(3 * parent_coords[0], 3 * parent_coords[0] + 3)
223 for b in range(3 * parent_coords[1], 3 * parent_coords[1] + 3)
224 ]
225
226 # Mixed children with subtree 2 dominant.
227 for coords in children[:5]:
228 tree._tree_arrays[child_level][coords] = (1, 2)
229 for coords in children[5:8]:
230 tree._tree_arrays[child_level][coords] = (1, 0)
231 tree._tree_arrays[child_level][children[8]] = (1, 1)
232
233 return tree, parent_coords
Tree build_tree3_adaptive_2d()
Build the adaptive 2D tree described by the tree3_stage*_fine_first_bfs.dot documentation files.
Tree build_visualise_tree_split_example3_2d()
Build the 2D sparse tree used by visualise_tree_split_example3.png.
Tree build_tree3_all_rows_2d()
Build a 2D variant where each depicted node in the tree3 diagram is represented by all rows with resp...
tuple[Tree, tuple[int, int]] build_stage3_mixed_parent_case_2d()
Build a 2D tree where Stage III can promote a mixed parent.