Peano
Loading...
Searching...
No Matches
OfflineDecomposition.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
4import sys, time, os
5import argparse
6
7from toolbox.loadbalancing.api.Tree import Tree
11from toolbox.loadbalancing.api.visualize.TreeVisualizer import TreeVisualizer
12import toolbox.loadbalancing.api.strategies.SplitTrees as split_strategy
13from toolbox.loadbalancing.api.strategies.Decomposition import equidistant_splitting
14
15
16"""
17Offline Tree Decomposition for Load Balancing
18
19This script provides the main command-line interface for the Peano offline load balancing API.
20It processes adaptive mesh trees and applies optimisation strategies to achieve better load
21distribution across compute resources.
22
23## Command-Line Arguments
24
25### Input Options
26-f, --file PATH Path to input grid patch file directory
27 Example: -f /path/to/checkpoint/grid/files
28 Requires: Grid files with 'grid-' prefix from Peano checkpoints
29
30-d, --dimensions DIM Spatial dimensions (REQUIRED)
31 Values: 2 (for 2D grids) or 3 (for 3D grids)
32
33-l, --max-level LEVEL Maximum refinement level for artificial grid generation
34 Used with: Regular grid generation (when no -f specified)
35 Range: 1-10 (higher values require significant memory)
36
37### Processing Options
38-s, --subtrees COUNT Total number of subtrees to create
39 Example: -s 5 creates 5 total subtrees
40 Default: No splitting (analysis only)
41
42-p, --plot Generate visualisation plots
43 Outputs: PDF and PNG files showing tree structure
44 Default: No visualisation
45
46-o, --output FILE Output YAML file path
47 Default: Automatic naming based on input type
48 Example: "TreeStructure_l4_s3_split.yaml"
49
50## Usage Examples
51
52### Analyse Existing Simulation Data
53```bash
54# Process 3D checkpoint files with visualisation
55python3 OfflineDecomposition.py -f /path/to/grid/files -d 3 -p
56
57# Process and split into 4 subtrees
58python3 OfflineDecomposition.py -f /path/to/grid/files -d 3 -s 4 -p
59```
60
61### Generate Test Cases
62```bash
63# Create 2D regular grid with 4 levels
64python3 OfflineDecomposition.py -d 2 -l 4 -p
65
66# Create and split 3D grid for performance testing
67python3 OfflineDecomposition.py -d 3 -l 3 -s 8 -p
68```
69
70## File Requirements
71
72### Input Files (when using -f)
73- Directory containing Peano grid patch files
74- Files must have 'grid-' prefix
75- Generated with plot_grid_properties=True in Peano solver
76- Multiple files indicate existing domain decomposition
77
78### Output Files
79- **Original YAML**: Complete tree structure before optimisation
80- **Split YAML**: Optimised tree structure (if splitting applied)
81- **Visualisation**: PDF/PNG plots (if -p flag used)
82
83## Performance Considerations
84
85- **Memory Usage**: Grows as 3^(dimensions × max_level) for regular grids
86- **Processing Time**: Splitting algorithms scale with tree size
87- **File I/O**: Large checkpoint directories may require significant read time
88
89## Error Handling
90
91The script validates:
92- Required dimensions parameter
93- Valid input source (either -f or -l must be specified)
94- File accessibility and format
95- Memory constraints for large grids
96
97For integration with Peano workflows and theoretical background, see the load balancing API documentation.
98"""
99
100# This function will display split parameters to the console
101def display_hardcoded_split_parameters(peano_tree: Tree, title: str) -> None:
102 """
103 Extract and display hardcoded split parameters from a tree for Peano's Hardcoded load balancer.
104
105 Args:
106 peano_tree: Tree object with applied splits
107 title: Title to display before the parameters
108 """
109 if peano_tree._subtree_count <= 1:
110 print("No splits to extract - tree has only one subtree")
111 return
112
113 # Get the split parameters
114 splitting_trees, cell_counts, subtrees, bottom_up_flags = split_strategy.extract_split_instructions(peano_tree)
115
116 print("\n" + title)
117 print("-" * len(title))
118 print(f"Total subtrees: {peano_tree._subtree_count}")
119 print(f"Total leaf nodes: {int(peano_tree._leaf_count)}")
120 print()
121
122 # Display parameters as C++ initializer lists for direct copy-paste into code
123 print("Hardcoded Constructor Parameters:")
124 print(f" splittingTrees = {{{', '.join(map(str, splitting_trees))}}}")
125 print(f" numberOfCells = {{{', '.join(map(str, cell_counts))}}}")
126 print(f" subTrees = {{{', '.join(map(str, subtrees))}}}")
127 print()
128
129 # Display as individual tuples for better readability
130 print("Individual Split Operations:")
131 for i in range(len(subtrees)):
132 mode = "bottom-up" if bottom_up_flags[i] else "top-down"
133 print(f" split {i}: tree {splitting_trees[i]} → tree {subtrees[i]}; {cell_counts[i]} cells; {mode}")
134 print()
135
136
137def main():
138 parser = argparse.ArgumentParser(description='Offline Tree Decomposition for Load Balancing')
139 parser.add_argument("-f", "--file", dest="file_path", type=str, help="Path to input grid patch file (e.g., Input/example2)" )
140 parser.add_argument("-y", "--yaml", dest="yaml_path", type=str, help="Path to input YAML file created by generate_tree_yaml()" )
141 parser.add_argument("-p", "--plot", dest="plot_flag", action="store_true", default=False, help="Generate and save visualization plots of the tree structure" )
142 parser.add_argument("-d", "--dimensions", dest="grid_dimensions", type=int, help="Spatial dimensions of the grid (2 for 2D, 3 for 3D) - auto-detected from YAML if not specified" )
143 parser.add_argument("-l", "--max-level", dest="max_level", type=int, default=0, help="Maximum refinement level for artificial regular grid generation" )
144 parser.add_argument("-s", "--subtrees", dest="subtrees", type=int, help="Total number of subtrees to create via splitting")
145 parser.add_argument("-o", "--output", dest="output_yaml", type=str, default="output.yaml", help="Output YAML file path for storing split results and tree structure")
146 parser.add_argument("-t", "--traversal", dest="traversal_mode", type=str, choices=["bfs", "bfs_finest_first", "dfs"], default="bfs_finest_first", help="Stage I traversal mode for split_tree(): 'bfs', 'bfs_finest_first' (default) or 'dfs'")
147 parser.add_argument("--no-topology-update", dest="topology_update", action="store_false", default=True, help="Disable topology preservation (skip non-leaf subtree propagation) during split_tree()")
148
149 args = parser.parse_args()
150
151 time1 = time.time()
152
153 if args.yaml_path != None:
154 name = "YAML-"+os.path.basename(args.yaml_path)
155 filename = args.yaml_path
156 tree = CustomGridGenerator(name=name, yaml_file_path=args.yaml_path)
157 # Auto-detect dimensions from loaded tree if not specified
158 if args.grid_dimensions is None:
159 args.grid_dimensions = tree._dimension
160 elif args.file_path != None:
161 if args.grid_dimensions is None:
162 print("Error: -d/--dimensions is required when using grid patch files")
163 sys.exit(1)
164 name = "Files-"+args.file_path
165 filename = args.file_path
166 tree = GridPatchFileReader(name=name, file_path=args.file_path, dimensions=args.grid_dimensions)
167 elif args.max_level > 0:
168 if args.grid_dimensions is None:
169 print("Error: -d/--dimensions is required when generating regular grids")
170 sys.exit(1)
171 name = "Regular-Grid-Generator"
172 filename="TreeStructure"
173 tree = RegularGridGenerator(name=name, max_level=args.max_level, dimensions=args.grid_dimensions)
174 else:
175 print("No input specified. Use one of: -f (grid files), -y (YAML file), or -l (regular grid generation)")
176 sys.exit(1)
177
178 time2 = time.time()
179
180 print("Original tree:")
181 print(tree)
182
183 # Generate YAML output for the original tree
184 print("\n" + "="*50)
185 print("GENERATING ORIGINAL TREE YAML")
186 print("="*50)
187
188 # Create original tree output filename
189 if args.file_path:
190 original_yaml_filename = f"Files-{os.path.basename(args.file_path)}_original.yaml"
191 else:
192 original_yaml_filename = f"TreeStructure_l{args.max_level}_original.yaml"
193
194 # Replace path separators to create a safe filename
195 original_yaml_filename = original_yaml_filename.replace('/', '_').replace('\\', '_')
196
197 split_strategy.generate_tree_yaml(tree, original_yaml_filename)
198
199 # Display hardcoded split parameters for original tree
200 if tree._subtree_count > 1: # Only display if there are multiple subtrees
201 print("\n" + "="*50)
202 print("HARDCODED SPLIT PARAMETERS FOR ORIGINAL TREE")
203 print("="*50)
204
205 display_hardcoded_split_parameters(tree, "Original Tree Split Parameters")
206 else:
207 print("\n" + "="*50)
208 print("SKIPPING ORIGINAL TREE HARDCODED SPLIT PARAMETERS")
209 print("="*50)
210 print("Original tree has only one subtree - no split parameters to extract")
211
212 # Save original tree visualization if plotting is enabled
213 viz_start_time = time.time()
214 if args.plot_flag:
215 # Generate filename for original tree
216 if args.file_path:
217 base_name = f"Files-{os.path.basename(args.file_path)}"
218 original_plot_filename = f"{base_name}_original"
219 elif args.yaml_path:
220 base_name = f"YAML-{os.path.splitext(os.path.basename(args.yaml_path))[0]}"
221 original_plot_filename = f"{base_name}_original"
222 else:
223 base_name = "TreeStructure"
224 original_plot_filename = f"{base_name}_l{args.max_level}_original"
225
226 # Replace path separators to create a safe filename
227 original_plot_filename = original_plot_filename.replace('/', '_').replace('\\', '_')
228
229 print("\n" + "="*50)
230 print("GENERATING ORIGINAL TREE VISUALIZATION")
231 print("="*50)
232
233 TreeVisualizer(tree, args.grid_dimensions, original_plot_filename)
234 print(f"Original tree visualization saved as {original_plot_filename}.pdf and {original_plot_filename}.png")
235
236 viz_end_time = time.time()
237
238 # Initialize split timing variables
239 split_start_time = 0.0
240 split_end_time = 0.0
241
242 # Apply splitting if number of subtrees is provided
243 if args.subtrees is not None:
244 print("\n" + "="*50)
245 print("INITIALLY RESETTING TREE TO SINGLE SUBTREE")
246 print("="*50)
247
248 # Reset tree to single subtree
249 tree = split_strategy.reset_tree_to_single_subtree(tree)
250
251 print("\n" + "="*50)
252 print("APPLYING FINEST LEVEL SPLITTING")
253 print("="*50)
254
255 split_start_time = time.time()
256
257 # Build the decomposition plan (per-subtree leaf counts)
258 number_of_subtrees = args.subtrees
259 decomposition = equidistant_splitting(
260 tree,
261 number_of_subtrees,
262 verbose=True,
263 )
264
265 split_strategy.split_tree(
266 tree,
267 decomposition,
268 traversal=args.traversal_mode,
269 enable_propagate_bottom_up=args.topology_update,
270 )
271
272 split_end_time = time.time()
273
274 print(f"Splitting Time Cost: {(split_end_time - split_start_time):.4f} seconds")
275
276 print("\nTree after splitting:")
277 print(tree)
278
279 # Generate YAML output for the split results
280 print("\n" + "="*50)
281 print("GENERATING SPLIT RESULTS YAML")
282 print("="*50)
283
284 # Create split tree output filename based on input type and number of subtrees
285 if args.file_path:
286 split_yaml_filename = f"Files-{os.path.basename(args.file_path)}_s{args.subtrees}_split.yaml"
287 elif args.yaml_path:
288 base_name = os.path.splitext(os.path.basename(args.yaml_path))[0]
289 split_yaml_filename = f"YAML-{base_name}_s{args.subtrees}_split.yaml"
290 else:
291 split_yaml_filename = f"TreeStructure_l{args.max_level}_s{args.subtrees}_split.yaml"
292
293 # Replace path separators to create a safe filename
294 split_yaml_filename = split_yaml_filename.replace('/', '_').replace('\\', '_')
295
296 # Use the generated filename instead of args.output_yaml
297 split_strategy.generate_tree_yaml(tree, split_yaml_filename)
298 print(f"Split tree YAML saved as {split_yaml_filename}")
299
300 # Display hardcoded split parameters
301 print("\n" + "="*50)
302 print("HARDCODED SPLIT PARAMETERS FOR SPLIT TREE")
303 print("="*50)
304
305 display_hardcoded_split_parameters(tree, "Split Tree Split Parameters")
306
307 # Save split tree visualization if plotting is enabled
308 if args.plot_flag:
309 if args.file_path:
310 base_name = f"Files-{os.path.basename(args.file_path)}"
311 split_plot_filename = f"{base_name}_s{args.subtrees}"
312 elif args.yaml_path:
313 base_name = f"YAML-{os.path.splitext(os.path.basename(args.yaml_path))[0]}"
314 split_plot_filename = f"{base_name}_s{args.subtrees}"
315 else:
316 base_name = "TreeStructure"
317 split_plot_filename = f"{base_name}_l{args.max_level}_s{args.subtrees}"
318
319 split_plot_filename = split_plot_filename.replace('/', '_').replace('\\', '_')
320
321 print("\n" + "="*50)
322 print("GENERATING SPLIT TREE VISUALIZATION")
323 print("="*50)
324
325 TreeVisualizer(tree, args.grid_dimensions, split_plot_filename)
326 print(f"Split tree visualization saved as {split_plot_filename}.pdf and {split_plot_filename}.png")
327
328 time3 = time.time()
329 else:
330 time3 = viz_end_time
331
332 print('='*40)
333 print(f"Input Time Cost: {(time2-time1):.4f} seconds")
334 if args.plot_flag:
335 print(f"Visualization Time Cost: {(viz_end_time-viz_start_time):.4f} seconds")
336 if args.subtrees is not None:
337 print(f"Splitting Time Cost: {(split_end_time-split_start_time):.4f} seconds")
338 print(f"Total Time Cost: {(time3-time1):.4f} seconds")
339
340
341if __name__ == "__main__":
342 main()
None display_hardcoded_split_parameters(Tree peano_tree, str title)
Extract and display hardcoded split parameters from a tree for Peano's Hardcoded load balancer.