Peano
Loading...
Searching...
No Matches
GridPatchFileReader.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 os, math
5import re
6from collections import defaultdict
7
8from ..Tree import Tree
9
10"""
11Grid Patch File Reader for Peano Load Balancing API
12
13This module provides functionality to read existing Peano grid patch files and convert them
14into the Tree data structure used by the offline load balancing API. It serves as one of the
15primary input generators for the load balancing framework.
16
17## Purpose and Context
18
19The GridPatchFileReader is part of the offline load balancing approach described in the
20load balancing API documentation. Instead of making splitting decisions during mesh
21construction, this module enables:
22
231. **Post-construction Analysis**: Read checkpointed grid files after complete mesh construction
242. **Tree Structure Recovery**: Convert Peano patch files into analyzable tree representations
253. **Load Balancing Preparation**: Prepare tree data for splitting strategy application
264. **Offline Optimization**: Enable informed load balancing decisions based on complete mesh knowledge
27
28## Integration with Load Balancing API
29
30This module integrates with other components of the load balancing API:
31
32- **Tree Class**: Converts patch data into Tree objects (see Tree.py)
33- **OfflineDecomposition**: Used by the main script for file-based input processing
34- **SplitTrees**: Provides tree data for splitting strategy application
35- **TreeVisualizer**: Enables visualization of read tree structures
36
37## File Format Support
38
39The reader supports Peano grid patch files with the following characteristics:
40
41- **File Naming**: Files with 'grid-' prefix in the specified directory
42- **Patch Format**: Standard Peano patch format with 'begin patch' blocks
43- **Spatial Information**: Extracts offset and size data for each patch
44- **Multi-tree Support**: Handles multiple subtrees from separate files
45- **Dimension Support**: Supports both 2D and 3D grid structures
46
47## Workflow Integration
48
49This module fits into the standard offline decomposition workflow:
50
511. **Mesh Construction**: Peano constructs complete mesh without load balancing
522. **Checkpoint Creation**: Grid files are written with plot_grid_properties=True
533. **File Reading**: GridPatchFileReader processes the checkpoint files
544. **Tree Analysis**: Resulting Tree objects are analyzed for load distribution
555. **Strategy Application**: Splitting strategies are applied to optimize load balance
566. **Result Export**: Optimized decomposition is exported in YAML format
57
58## Usage Examples
59
60```python
61# Read from checkpoint directory
62tree = GridPatchFileReader(
63 name="ExperimentData",
64 file_path="path/to/grid/files",
65 dimensions=3
66)
67
68# Integrate with splitting strategies
69from strategies.SplitTrees import split_tree
70split_tree(tree, number_of_subtrees=4)
71
72# Generate the optimised tree structure in YAML format
73from strategies.SplitTrees import generate_tree_yaml
74generate_tree_yaml(tree, "optimized_decomposition.yaml")
75```
76
77See the load balancing API documentation for complete workflow details.
78"""
79
80def round_sig(x, sig):
81 """
82 Round a number to a specified number of significant figures.
83
84 Args:
85 x (float): Number to round
86 sig (int): Number of significant figures
87
88 Returns:
89 float: Rounded number
90 """
91 return float(np.format_float_positional(x, precision=sig, unique=False, trim='k'))
92
93def read_patches(file_path, size_group, dimensions):
94 """
95 Parse a single Peano grid patch file and extract spatial information.
96
97 This function reads a grid patch file and extracts offset and size information
98 for each patch using regular expressions. Patches are grouped by size to
99 determine refinement levels.
100
101 Args:
102 file_path (str): Path to the grid patch file
103 size_group (defaultdict): Dictionary to store {size: [offsets]} mapping
104 dimensions (int): Spatial dimensions count (2 or 3)
105
106 Returns:
107 defaultdict: Updated size_group with patch information
108
109 Note:
110 The regular expression patterns match the standard Peano patch format:
111 - 'begin patch' blocks with offset and size specifications
112 - Floating point numbers in scientific notation
113 - Space-separated coordinate values
114 """
115 # Files with binary quantities hold raw bytes between the text lines.
116 # Latin-1 decodes any byte sequence, and the pattern below only matches text.
117 with open(file_path, "r", encoding="latin-1") as f:
118 content = f.read()
119
120 # Regular expression to extract offset and size
121 if dimensions==3:
122 patch_pattern = re.compile(
123 r'begin patch.*?offset ([\d.eE+\-]+ [\d.eE+\-]+ [\d.eE+\-]+).*?'
124 r'size ([\d.eE+\-]+ [\d.eE+\-]+ [\d.eE+\-]+)',
125 re.DOTALL
126 )
127 elif dimensions==2:
128 patch_pattern = re.compile(
129 r'begin patch.*?offset ([\d.eE+\-]+ [\d.eE+\-]+).*?'
130 r'size ([\d.eE+\-]+ [\d.eE+\-]+)',
131 re.DOTALL
132 )
133
134 # Find all matches
135 for match in patch_pattern.findall(content):
136 offset, size = match
137 size_group[size].append(offset) # Group offsets by size
138
139 return size_group
140
141def GridPatchFileReader(name=None, file_path=None, dimensions=None):
142 """
143 Main function to read Peano grid patch files and convert to Tree structure.
144
145 This function processes a directory of Peano grid patch files and constructs
146 a Tree object suitable for load balancing analysis. It handles multiple subtrees,
147 determines domain boundaries, and creates the hierarchical array structure.
148
149 ## Processing Steps
150
151 1. **File Discovery**: Scans directory for files with 'grid-' prefix
152 2. **Patch Extraction**: Parses each file to extract spatial information
153 3. **Level Determination**: Groups patches by size to identify refinement levels
154 4. **Domain Analysis**: Determines overall domain boundaries from largest patches
155 5. **Tree Construction**: Builds hierarchical array structure
156 6. **Subtree Assignment**: Assigns patches to subtrees based on source files
157
158 ## Multi-subtree Handling
159
160 Each 'grid-' file represents a different subtree in the domain decomposition.
161 The function automatically:
162 - Processes all subtree files in the directory
163 - Assigns appropriate subtree IDs based on file order
164 - Merges spatial information into unified tree structure
165 - Reports distribution statistics for analysis
166
167 Args:
168 name (str, optional): Descriptive name for the resulting tree.
169 If not provided, a deterministic default name is generated.
170 file_path (str): Path to directory containing grid patch files
171 dimensions (int): Spatial dimensions count (2 for 2D, 3 for 3D)
172
173 Returns:
174 Tree: Tree object with complete spatial structure and subtree assignments
175
176 Example:
177 ```python
178 # Read 3D grid data from checkpoint
179 tree = GridPatchFileReader(
180 name="CCZ4_Simulation_T100",
181 file_path="/path/to/checkpoint/grid/files",
182 dimensions=3
183 )
184
185 print(f"Loaded tree with {tree._leaf_count} leaves")
186 print(f"Domain: {tree._domain_offset} to {tree._domain_size}")
187 print(f"Subtrees: {tree._subtree_count}")
188 ```
189 """
190 if file_path is None:
191 raise ValueError("file_path must be provided")
192
193 if dimensions is None:
194 raise ValueError("dimensions must be provided")
195
196 if dimensions not in [2, 3]:
197 raise ValueError(f"dimensions must be 2 or 3, got {dimensions}")
198
199 if name is None:
200 name = f"grid-patch-reader-{dimensions}d"
201
202 folder_path=file_path
203
204 if folder_path[-1] != '/':
205 folder_path += '/'
206
207 prefix = 'grid-'
208 # List all files in the folder with the specified prefix
209 datafiles = [f for f in os.listdir(folder_path) if f.startswith(prefix)]
210
211 #extract grid infomation
212 size_groups = [] # Dictionary to store {size: [offsets]}
213 for file_path in datafiles:
214 size_group = defaultdict(list)
215 read_patches(folder_path+'/'+file_path, size_group, dimensions)
216 size_groups.append(size_group)
217
218 total_level=-1
219 for size_group in size_groups:
220 total_level=max(total_level, len(size_group.keys()))
221
222 patch_number_for_each_level = defaultdict(list)
223 subtree_NO=0
224 for size_group in size_groups:
225 print("Subtree", subtree_NO, "from File")
226 for size, offsets in size_group.items():
227 print(f"Size: {size} -> Count: {len(offsets)}")
228 patch_number_for_each_level[size].append(len(offsets))
229 print("-" * 40)
230 subtree_NO += 1
231 print("Total Cell Count:")
232 for size, count in patch_number_for_each_level.items():
233 print(f"Size: {size} -> Count: {sum(count)}")
234 print("=" * 40)
235
236 #find the domain info directly from grid plot
237 max_size=0
238
239 for size_group in size_groups:
240 for size in size_group.keys():
241 size_tuple = tuple(map(float, size.split()))
242 if size_tuple[0]>max_size:
243 max_size=size_tuple[0]
244 max_size_key=size
245 #print(max_size)
246
247 if dimensions==3:
248 leftmost_offset=[999,999,999]
249 elif dimensions==2:
250 leftmost_offset=[999,999]
251 for size_group in size_groups:
252 for offset in size_group[max_size_key]:
253 offset_tuple = tuple(map(float, offset.split()))
254 for m in range(dimensions):
255 if offset_tuple[m]<leftmost_offset[m]:
256 leftmost_offset[m]=offset_tuple[m]
257 leftmost_offset_key=offset
258
259 domain_offset = tuple(leftmost_offset)
260 domain_size = tuple(map(float, max_size_key.split()))
261 domain_size = tuple( round_sig(x * 3,5) for x in domain_size)
262
263 tree_matrices = []
264
265 #calculate the tree matrices
266 index=0
267 for size_group in size_groups:
268 tree_matrix = [None]*total_level
269 for size, offsets in size_group.items():
270 size_tuple = tuple(map(float, size.split()))
271 level=int( math.log( int(round_sig(domain_size[0]/size_tuple[0],1)), 3) )
272 matrix_size=(3**level)
273 #print(level, matrix_size)
274
275 if dimensions == 3:
276 matrix = np.zeros((matrix_size, matrix_size, matrix_size), dtype=int)
277 elif dimensions == 2:
278 matrix = np.zeros((matrix_size, matrix_size), dtype=int)
279 else:
280 raise ValueError(f"Unsupported dimensions: {dimensions}")
281
282 for offset in offsets:
283 offset_tuple = tuple(map(float, offset.split()))
284 if dimensions==3:
285 index=[-1,-1,-1]
286 elif dimensions==2:
287 index=[-1,-1]
288 for i in range(len(offset_tuple)):
289 index[i]=round((offset_tuple[i]-domain_offset[i])/size_tuple[i])
290 matrix[tuple(index)]=1
291
292 #print(len(tree_matrix))
293 tree_matrix[level-1]=matrix
294 tree_matrices.append(tree_matrix)
295
296 #create the Tree class and assign properly
297 peano_tree = Tree(name, dimensions, domain_offset, domain_size)
298 peano_tree.read_in_tree_from_arrays(input_tree_arrays=tree_matrices, max_level=total_level, weighted=False)
299
300 return peano_tree
read_patches(file_path, size_group, dimensions)
Parse a single Peano grid patch file and extract spatial information.
round_sig(x, sig)
Round a number to a specified number of significant figures.