Peano
Loading...
Searching...
No Matches
Tree.py
Go to the documentation of this file.
1# This file is part of the ExaHyPE2 project. For conditions of distribution and
2# use, please see the copyright notice at www.peano-framework.org
3import numpy as np
4from .strategies.SFC import Peano_to_Cartesian_2d
5from .strategies.SFC import Peano_to_Cartesian_3d
6
7
8class Tree(object):
9 """
10 Data structure for adaptive mesh trees in Peano load balancing.
11
12 This class represents adaptive mesh trees as sequences of grid levels,
13 where each level is stored as a 2D or 3D array. The tree structure is
14 designed to support offline decomposition and load balancing analysis.
15
16 ## Tree Structure Overview
17
18 The main content of this class is a series of 2D or 3D arrays with dimension sizes
19 of 3^(level+1), where level ranges from 1 to the maximum depth of the tree. Each entry
20 in the arrays is a tuple of 2 numbers:
21
22 - **Weight**: 1 if the cell exists, 0 if it doesn't (enables representation of adaptive meshes)
23 - **Subtree ID**: Integer indicating which compute resource/subtree owns the cell
24
25 ## Data Representation Examples
26
27 ### 2D Example: Uniform 2-level tree with 4 subtrees
28
29 Level 1 (3x3 array):
30 ```
31 {(1,0), (1,0), (1,1)
32 (1,1), (1,1), (1,3)
33 (1,2), (1,2), (1,3)}
34 ```
35
36 Level 2 (9x9 array):
37 ```
38 {(1,0), (1,0), (1,0), (1,0), (1,0), (1,0), (1,1), (1,1), (1,1),
39 (1,0), (1,0), (1,0), (1,0), (1,0), (1,0), (1,1), (1,1), (1,1),
40 (1,0), (1,0), (1,0), (1,0), (1,0), (1,0), (1,1), (1,1), (1,1),
41 (1,1), (1,1), (1,1), (1,1), (1,1), (1,1), (1,3), (1,3), (1,3),
42 (1,1), (1,1), (1,1), (1,1), (1,1), (1,1), (1,3), (1,3), (1,3),
43 (1,1), (1,1), (1,1), (1,1), (1,1), (1,1), (1,3), (1,3), (1,3),
44 (1,2), (1,2), (1,2), (1,2), (1,2), (1,2), (1,3), (1,3), (1,3),
45 (1,2), (1,2), (1,2), (1,2), (1,2), (1,2), (1,3), (1,3), (1,3),
46 (1,2), (1,2), (1,2), (1,2), (1,2), (1,2), (1,3), (1,3), (1,3)}
47 ```
48
49 ### Adaptive Mesh Representation
50
51 For adaptive meshes where some regions are not refined, entries have weight 0:
52 ```
53 Level 1: {(1,0), (0,0), (1,0) # Middle cell not refined
54 (1,0), (1,0), (1,0)
55 (1,0), (1,0), (1,0)}
56
57 Level 2: Corresponding 9x9 array with zeros in the middle 3x3 block
58 ```
59
60 ## Key Properties
61
62 - **Hierarchical Structure**: Each level refines the previous by factor of 3 in each dimension
63 - **Adaptive Support**: Zero weights represent non-existent cells in adaptive meshes
64 - **Load Balancing Ready**: Subtree IDs enable domain decomposition analysis
65
66 ## Usage in Load Balancing
67
68 This data structure supports various load balancing operations:
69
70 1. **Tree Analysis**: Count leaf nodes, analyze refinement patterns
71 2. **Splitting Strategies**: Redistribute subtree assignments for load balancing
72 3. **Visualization**: Generate spatial representations of tree structure
73 4. **Export**: Create YAML outputs for integration with Peano's hardcoded load balancer
74
75 ## Coordinate System
76
77 - **2D**: Arrays use (a,b) indexing where a,b ∈ [0, 3^(level+1)-1]
78 - **3D**: Arrays use (a,b,c) indexing where a,b,c ∈ [0, 3^(level+1)-1]
79 - **Level Numbering**: Levels start from 0 (coarsest) to max_level-1 (finest)
80 - **Array Sizes**: Level k has size 3^(k+1) in each dimension
81
82 ## Important Notes
83
84 - The lowest level is 1 with size 3x3 (2D) or 3x3x3 (3D)
85 - All arrays are stored as numpy arrays with dtype=object for tuple storage
86 - Tree metadata (leaf count, split patterns) is automatically updated after modifications
87 - Domain offset and size define the physical coordinate mapping
88
89 ## Related Components
90
91 This class works with:
92 - `GridPatchFileReader`: Reads Peano grid files into Tree format
93 - `RegularGridGenerator`: Creates artificial uniform trees for testing
94 - `SplitTrees`: Implements splitting algorithms for load balancing
95 - `TreeVisualizer`: Generates visual representations of tree structures
96
97 ## Example Usage
98
99 ```python
100 # Create a tree from artificial regular grid
101 tree = RegularGridGenerator("test", max_level=3, dimensions=2)
102
103 # Apply load balancing splitting
104 split_tree(tree, number_of_subtrees=3)
105
106 # Generate visualization
107 TreeVisualizer(tree, dimensions=2, filename="balanced_tree")
108
109 # Export to YAML
110 generate_tree_yaml(tree, "output.yaml")
111 ```
112 """
113
114 def __init__(self, name, dimension, domain_offset, domain_size):
115 """
116 Initialize a new Tree object.
117
118 Args:
119 name (str): Descriptive name for the tree (e.g., "RegularGrid" or filename)
120 dimension (int): Spatial dimension (2 for 2D, 3 for 3D)
121 domain_offset (tuple): Physical coordinates of domain origin
122 domain_size (tuple): Physical size of domain in each dimension
123 """
124 self._name = name
125 self._dimension = dimension
126 self._domain_offset = domain_offset
127 self._domain_size = domain_size
128
129 # @todo Dmitry: I realise that there's this field and then there is a routine that computes the number of leaves in Decomposition
130 # In my opinion, there should be a query on the tree object which holds that algorithm, i.e. neither should there
131 # be a routine in Decomposition nor a field in this type.
132
133 self._tree_arrays = [] # contains the series of arrays
134 self._leaf_count = 0 # not yet calculated
135 self._subtree_count = 0 # not yet calculated
136 self._split_pattern = [] # not yet calculated
137
138 self._max_level = -1
139 self._weighted = False
140
141
142 @property
144 return self._subtree_count
145
146 @property
148 return self._leaf_count
149
150 def __str__(self):
151 result = (
152 """
153Peano Tree Structure From """ + self._name + """
154Domain offset: """
155 + str(self._domain_offset)
156 + """
157Domain size: """
158 + str(self._domain_size)
159 + """
160Dimension: """
161 + str(self._dimension)
162 + """
163Max Level: """
164 + str(self._max_level)
165 + """
166Leaf count: """
167 + str(self._leaf_count)
168 + """
169Subtree count: """
170 + str(self._subtree_count)
171 + """
172Split pattern: """
173 + str(self._split_pattern)
174 + """
175"""
176 )
177
178 return result
179
180
181 def read_in_tree_from_arrays(self, input_tree_arrays, max_level, weighted=False):
182
183 self._subtree_count = len(input_tree_arrays)
184 self._max_level = max_level
185
186 #initialization
187 for level in range(self._max_level):
188 size = 3**(level+1)
189 if self._dimension==2:
190 new_level_array = np.empty((size, size), dtype=object)
191 for a in range(size):
192 for b in range(size):
193 new_level_array[a][b]=(0,0)
194 elif self._dimension==3:
195 new_level_array = np.empty((size, size, size), dtype=object)
196 for a in range(size):
197 for b in range(size):
198 for c in range(size):
199 new_level_array[a][b][c]=(0,0)
200
201 self._tree_arrays.append(new_level_array)
202
203 #actual read in
204 for i in range(self._subtree_count):
205 for level in range(self._max_level):
206 self.read_in_level_array(input_tree_arrays[i][level], i, level)
207
209
210 if self._weighted:
211 self.update_weight()
212
213
214 def read_in_level_array(self, input_level_arrays, subtree_id, level):
215 if self._dimension==2:
216 for a in range(3**(level+1)):
217 for b in range(3**(level+1)):
218 if input_level_arrays[a][b]==1:
219 self._tree_arrays[level][a][b] = (input_level_arrays[a][b], subtree_id)
220 elif self._dimension==3:
221 for a in range(3**(level+1)):
222 for b in range(3**(level+1)):
223 for c in range(3**(level+1)):
224 if input_level_arrays[a][b][c]==1:
225 self._tree_arrays[level][a][b][c] = (input_level_arrays[a][b][c], subtree_id)
226
227
228 def read_in_tree_from_artificial(self, input_artificial_arrays, max_level, weighted=False):
229 self._subtree_count = 1
230 self._max_level = max_level
231
232 self._tree_arrays = input_artificial_arrays
233
235
236 if self._weighted:
237 self.update_weight()
238
239
241 self._leaf_count = 0
242 self._split_pattern = np.zeros(self._subtree_count)
243
244 for level in range(self._max_level):
245 if self._dimension==2:
246 for a in range(3**(level+1)):
247 for b in range(3**(level+1)):
248 if self._tree_arrays[level][a][b][0]==1:
249 if level==self._max_level-1: #the finest level
250 self._leaf_count += 1
251 self._split_pattern[self._tree_arrays[level][a][b][1]] += 1
252 elif self._tree_arrays[level+1][3*a][3*b][0]==0: #no children
253 self._leaf_count += 1
254 self._split_pattern[self._tree_arrays[level][a][b][1]] += 1
255 elif self._dimension==3:
256 for a in range(3**(level+1)):
257 for b in range(3**(level+1)):
258 for c in range(3**(level+1)):
259 if self._tree_arrays[level][a][b][c][0]==1:
260 if level==self._max_level-1: #the finest level
261 self._leaf_count += 1
262 self._split_pattern[self._tree_arrays[level][a][b][c][1]] += 1
263 elif self._tree_arrays[level+1][3*a][3*b][3*c][0]==0: #no children
264 self._leaf_count += 1
265 self._split_pattern[self._tree_arrays[level][a][b][c][1]] += 1
266
267
268 def update_weight(self):
269 #todo
270 return 0
Data structure for adaptive mesh trees in Peano load balancing.
Definition Tree.py:8
update_weight(self)
Definition Tree.py:268
read_in_tree_from_artificial(self, input_artificial_arrays, max_level, weighted=False)
Definition Tree.py:228
read_in_level_array(self, input_level_arrays, subtree_id, level)
Definition Tree.py:214
read_in_tree_from_arrays(self, input_tree_arrays, max_level, weighted=False)
Definition Tree.py:181
__init__(self, name, dimension, domain_offset, domain_size)
Initialize a new Tree object.
Definition Tree.py:114
update_split_pattern(self)
Definition Tree.py:240
number_of_leaves(self)
Definition Tree.py:147
number_of_subtrees(self)
Definition Tree.py:143
__str__(self)
Definition Tree.py:150