Peano
Loading...
Searching...
No Matches
PatchFileParser.py
Go to the documentation of this file.
1# This file is part of the Peano project. For conditions of distribution and
2# use, please see the copyright notice at www.peano-framework.org
3import numpy as np
4
5from .Patch import Patch
6
7
9 """!
10 Helper class to capture all of the attributes we need for each
11 unknown, but were originally fixed to the PatchFileParser class
12 itself.
13
14 For instance, at time of writing the vis scripts cannot handle
15 displaying separate vtu files for different data that is produced
16 in Peano patch files. An excerpt:
17
18 begin patch
19 offset 9.629630e-01 9.629630e-01
20 size 3.703704e-02 3.703704e-02
21 begin vertex-values "value"
22 0 0 0 0
23 end vertex-values
24 begin vertex-values "rhs"
25 0 0 0 0
26 end vertex-values
27 end patch
28
29 Previously, the only thing that would get parsed is the "value"
30 field, before moving on. We aim to get it to do the "rhs" part
31 as well.
32
33 However, we cannot assume that the number of dofs per axis or
34 unknowns per patch volume will be the same for both. So we
35 create this helper class to hold those.
36
37 We fix is_data_associated_to_cell to be the same as the
38 PatchFileParser object which, in turn, means they must
39 be the same for all of the unknowns
40
41 Attributes:
42 ----------
43
44 file_path: String
45 String of input file path (including filename and extension)
46
47 cell_data: list of patches
48 List of Patches with file data
49
50 dimensions: Integer
51 Dimension of domains
52
53 dof: Integer
54 Number of degrees of freedom per axis
55
56 unknowns: Integer
57 Number of unknowns per patch volume
58
59 mapping: series of d-dimensional tuples
60 Distorts the domain
61
62 set_identifier: String
63 Can be empty to parse everything.
64
65 """
66
67 def __init__(self, unknown_name, dimensions, is_data_associated_to_cell):
68 # this is the name of the unknown we want to capture
69 self.unknown_name = unknown_name
70
71 # this is the number of unknowns per patch volume
72 self.unknowns = 0
73 # number of dofs per axis
74 self.dof = 0
75 self.description = ""
76 self.cell_data = []
77 self.mapping = []
78 self.is_data_associated_to_cell = is_data_associated_to_cell
79 self.dimensions = dimensions
80
81 def set_unknowns(self, unknowns):
82 assert self.unknowns == 0, "attribute already set!"
83 self.unknowns = unknowns
84
85 def set_dof(self, dof):
86 assert self.dof == 0, "attribute already set!"
87 self.dof = dof
88
89 def set_description(self, description):
90 self.description = description
91
92 def set_celldata(self, celldata):
93 assert not self.cell_data, "attribute already set!"
94 self.cell_data = celldata
95
96 def set_mapping(self, mapping):
97 assert not self.mapping, "attribute already set!"
98 self.mapping = mapping
99
101 assert not self.mapping, "attribute already set!"
102 vertices_per_axis = self.dof
104 vertices_per_axis += 1
105 if self.mapping == [] and self.dimensions == 2:
106 for y in range(vertices_per_axis):
107 for x in range(vertices_per_axis):
108 self.mapping.append(x * 1.0 / (vertices_per_axis - 1))
109 self.mapping.append(y * 1.0 / (vertices_per_axis - 1))
110 if self.mapping == [] and self.dimensions == 3:
111 for z in range(vertices_per_axis):
112 for y in range(vertices_per_axis):
113 for x in range(vertices_per_axis):
114 self.mapping.append(x * 1.0 / (vertices_per_axis - 1))
115 self.mapping.append(y * 1.0 / (vertices_per_axis - 1))
116 self.mapping.append(z * 1.0 / (vertices_per_axis - 1))
117
118 def append_patch(self, patch):
119 self.cell_data.append(patch)
120
121 def __repr__(self):
122 return f"{self.unknown_name}: unknowns:{self.unknowns}, dof:{self.dof}"
123
124
126 """!
127 Parser for Peano block file output.
128
129 This class should be instantiated from within the Visualiser class.
130 This class should be called once per patch file that contains
131 patch data that we want to render. The main method that is called
132 is parse_file().
133
134 This method doesn't return anything. Rather, it sets unknown_attributes
135 to capture all the patch data for each of the unknowns that we find.
136
137 Then, the Visualiser class will steal this data, and combine it into
138 its own helper class.
139
140 Attributes:
141 ----------
142
143 file_path: String
144 String of input file path (including filename and extension)
145
146 dimensions: Integer
147 Dimension of domains
148
149 mapping: series of d-dimensional tuples
150 Distorts the domain
151
152 set_identifier: String
153 Can be empty to parse everything.
154
155 unknown_attributes: Dict{ String, UnknownAttributes }
156 Dictionary that has the names of each unknown we wanna capture
157 as its keys, and the values are the helper class which captures
158 dimensions, cell data etc. Each of the unknowns should be
159 listed during the metadata part of the patch file, ie between
160 "begin cell-metadata" and "end cell-metadata".
161 """
162
163 def __init__(self, file_path, set_identifier, subdomain_number):
164 """ """
165 self.file_path = file_path
167 self.dimensions = -1
168
169 self.set_identifier = set_identifier
170 self.subdomain_number = subdomain_number
171 self.parsed = False
173
174 """
175 Here we expected the keys to be a list of unknown names that we
176 have associated with each cell/vertex, and the values to be
177 instances of the helper class UnknownAttributes
178 """
180
181 def __repr__(self):
182 return f"Parser for {self.file_path}, subdomain {self.subdomain_number}, identifier {self.set_identifier}"
183
184 def __parse_meta_data_region(self, current_line, file_object, end_condition):
185 """!
186 Pass in file object, move it along and return it
187 """
188 # unknown_name is contained at end of "begin x-metadata" line
189 unknown_name = current_line.strip().split()[-1][1:-1]
190
191 # create new helper class for this
192 # set the bool for data associated with cell to be the same as self.
193 # we set self.is_data_associated_to_cell already, during parse_file(),
194 # just before we came into this function.
195 patch_unknown = UnknownAttributes(
196 unknown_name, self.dimensions, self.is_data_associated_to_cell
197 )
198
199 current_line = file_object.readline().strip()
200
201 use_default_mapping = True
202
203 # do everything that we did in old line reading function
204 while not end_condition in current_line:
205 if "number-of-unknowns" in current_line:
206 patch_unknown.set_unknowns(int(current_line.strip().split()[1]))
207 if "number-of-dofs-per-axis" in current_line:
208 patch_unknown.set_dof(int(current_line.strip().split()[1]))
209 if "description" in current_line:
210 patch_unknown.set_description(
211 current_line.strip().split("description")[1]
212 )
213 if '"' in patch_unknown.description:
214 print(
215 "Warning: data description field "
216 + patch_unknown.description
217 + " holds \" which might lead to issues with VTK's XML export. Remove them"
218 )
219 patch_unknown.description = patch_unknown.description.replace(
220 '"', ""
221 )
222 if "mapping" in current_line:
223 use_default_mapping = False
224 values = current_line.strip().split("mapping")[1]
225 patch_unknown.set_mapping(np.fromstring(values, dtype=float, sep=" "))
226
227 current_line = file_object.readline().strip()
228
229 if use_default_mapping:
230 patch_unknown._initialise_default_mapping_if_no_mapping_specified()
231
232 # finally, add this unknown to our dictionary
233 self.unknown_attributes[unknown_name] = patch_unknown
234
235 return file_object, current_line
236
238 self, current_line, file_object, end_condition="end patch"
239 ):
240 """!
241 Pass in file object, run it until we encounter the end condition,
242 and then return it
243
244 We assume that current_line == "begin patch file" at this stage
245
246 This is where we read the actual data
247 """
248 self.file_contains_patches = True
249 # Get patch offset
250 current_line = file_object.readline().strip()
251 line = current_line.strip().split()
252 if self.dimensions == 2:
253 offset = (float(line[1]), float(line[2]))
254 elif self.dimensions == 3:
255 offset = (float(line[1]), float(line[2]), float(line[3]))
256
257 # Get patch size
258 current_line = file_object.readline().strip()
259 line = current_line.strip().split()
260 if self.dimensions == 2:
261 size = (float(line[1]), float(line[2]))
262 elif self.dimensions == 3:
263 size = (float(line[1]), float(line[2]), float(line[3]))
264
265 # run until we have found all of the fields in this
266 # end condition is "end patch" by default
267 while end_condition not in current_line:
268 current_line = file_object.readline().strip()
269 values = None # prevents us adding empty patch to list at end of loop
270 unknown_name = ""
271
272 # get cell data
273 if current_line.startswith("begin cell-values") and (
274 self.set_identifier == ""
275 or current_line.endswith('"' + self.set_identifier + '"')
276 ):
277 assert (
279 ), "is_data_associated_to_cell flag set incorrectly"
280 unknown_name = current_line.strip().split()[-1][1:-1]
281 current_line = file_object.readline()
282 values = np.fromstring(current_line, dtype=float, sep=" ")
283
284 # do same for vertex data
285 if current_line.startswith("begin vertex-values") and (
286 self.set_identifier == ""
287 or current_line.endswith('"' + self.set_identifier + '"')
288 ):
289 assert (
291 ), "is_data_associated_to_cell flag set incorrectly"
292 unknown_name = current_line.strip().split()[-1][1:-1]
293 current_line = file_object.readline()
294 values = np.fromstring(current_line, dtype=float, sep=" ")
295
296 # if we have read some data, let's add it to our list
297 if values is not None:
298 patch = Patch(offset, size, values, unknown_name, self.subdomain_number)
299
300 # by this point, all the unknown_names should be present
301 self.unknown_attributes[unknown_name].append_patch(patch)
302
303 return file_object, current_line
304
305 def parse_file(self):
306 """!
307 Read file and return cell data, dimensions, number of degrees of freedom and number of unknowns
308
309 Parameters:
310 ----------
311 set_identifier: String
312 Name of the set of unknowns we want to extract. If this is the empty string
313 then I parse all content.
314
315 @todo: clean up the loop below. we can separate into getting metadata, and then reading patches
316 """
317 # print("Reading " + self.file_path + " as subdomain " + str(self.subdomain_number))
318 current_line = ""
319 try:
320 with open(self.file_path, "r") as data_file:
321 current_line = data_file.readline()
322 while current_line:
323 # when we reach EOF, this_line will eval to False
324 """
325 the reason we keep track of 2 variables current_line and
326 line is because we want current_line to eval to False when we reach
327 EOF. However, we need to strip the whitespaces and newline
328 characters off the line for the data processing, without
329 exiting the loop if we have empty lines.
330 """
331 current_line = data_file.readline()
332 stripped_line = current_line.strip()
333 if stripped_line.startswith("dimensions"):
334 self.dimensions = int(stripped_line.strip().split()[1])
335 assert self.dimensions in [
336 2,
337 3,
338 ], "Only 2d and 3d patches are supported"
339
340 # Read out meta data
341 if stripped_line.startswith("begin cell-metadata") and (
342 self.set_identifier == ""
343 or stripped_line.endswith('"' + self.set_identifier + '"')
344 ):
346 # this function advances us along the data file
347 data_file, stripped_line = self.__parse_meta_data_region(
348 stripped_line, data_file, "end cell-metadata"
349 )
350
351 if stripped_line.startswith("begin vertex-metadata") and (
352 self.set_identifier == ""
353 or stripped_line.endswith('"' + self.set_identifier + '"')
354 ):
355 self.is_data_associated_to_cell = False
356 # this function advances us along the data file
357 data_file, stripped_line = self.__parse_meta_data_region(
358 stripped_line, data_file, "end vertex-metadata"
359 )
360
361 elif stripped_line.startswith("begin patch"):
362 # pass data_file into self.__parse_patch_region, which will modify and return it
363 # ie we move further along the file inside this function
364 # appending new patches happens in this function
365 data_file, stripped_line = self.__parse_patch_region(
366 stripped_line, data_file
367 )
368
369 # self._initialise_default_mapping_if_no_mapping_specified() # handled in meta data reader
370 self.parsed = True
371 except Exception as e:
372 print(
373 "Error: was not able to read "
374 + self.file_path
375 + ": "
376 + str(e)
377 + " in line "
378 + current_line
379 )
380 self.parsed = False
381
382 # COMMENTING OUT DEBUG STATEMENTS
383 # print("finished parsing the file.")
384 # print(f"we now have {len(self.unknown_attributes.keys())} unknown names, and we have seen a total of {sum([len(v.cell_data) for _,v in self.unknown_attributes.items()])} patches...")
385 # print("we pipe the data into UnknownAttributes objects")
386
387 def render_or_validate_for_each_unknown(self, render_or_validate):
388 """!
389 We need to call the render function for each filter
390 on a variety of attributes, and then return them if they
391 are modified.
392
393 We do this in a functional style, ie the Visualiser class
394 contains all of the filters we wish to apply, and we pass
395 the render function that we intend to use back to this
396 layer. Ugly, but we need to do some looping over the
397 different unknowns we see in the patch files, and I thought
398 it best to hide that from the parse_snapshot region of
399 the vis code.
400
401 The same apply for the validate function from the Visualiser
402 file as well.
403 """
404
405 for unknown_name, unknown_data in self.unknown_attributes.items():
406 # this is horrible to read. but we need to pass all these
407 # attributes in, and capture their return values from the
408 # renderer function
409 (
410 unknown_data.cell_data,
411 unknown_data.dof,
412 unknown_data.dimensions,
413 unknown_data.unknowns,
414 unknown_data.is_data_associated_to_cell,
415 unknown_data.description,
416 unknown_data.mapping,
417 ) = render_or_validate(
418 unknown_data.cell_data,
419 unknown_data.dof,
420 unknown_data.dimensions,
421 unknown_data.unknowns,
422 unknown_data.is_data_associated_to_cell,
423 unknown_data.description,
424 unknown_data.mapping,
425 )
__init__(self, file_path, set_identifier, subdomain_number)
parse_file(self)
Read file and return cell data, dimensions, number of degrees of freedom and number of unknowns.
__parse_meta_data_region(self, current_line, file_object, end_condition)
Pass in file object, move it along and return it.
render_or_validate_for_each_unknown(self, render_or_validate)
We need to call the render function for each filter on a variety of attributes, and then return them ...
__parse_patch_region(self, current_line, file_object, end_condition="end patch")
Pass in file object, run it until we encounter the end condition, and then return it.
Helper class to capture all of the attributes we need for each unknown, but were originally fixed to ...
__init__(self, unknown_name, dimensions, is_data_associated_to_cell)
Patch class that contains offset, size and value of a Peano patch.
Definition Patch.py:5