Peano
Loading...
Searching...
No Matches
Visualiser.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 os
4
5from tarch.plotter.api.input.PatchFileParser import PatchFileParser
6
7
9 u_cell_data,
10 u_dof,
11 u_dimensions,
12 u_unknowns,
13 u_is_data_associated_to_cell,
14 u_description,
15 u_mapping,
16):
17 """
18 All the parsers in a data set have to yield the same type of data, i.e.
19 with the same number of unknowns and dofs/patch. I return the crucial
20 data afterwards, i.e. this routine validates and returns the quantities
21 of interest.
22 """
23
24 # These are the data to be returned
25 dimensions = -1
26 unknowns = -1
27 dof = -1
28 description = ""
29 is_data_associated_to_cell = True
30 mapping = ""
31
32 # Take copies
33 (
34 snapshot_cell_data,
35 snapshot_dof,
36 snapshot_dimensions,
37 snapshot_unknowns,
38 is_data_associated_to_cell,
39 description,
40 mapping,
41 ) = (
42 u_cell_data,
43 u_dof,
44 u_dimensions,
45 u_unknowns,
46 u_is_data_associated_to_cell,
47 u_description,
48 u_mapping,
49 )
50
51 if snapshot_dimensions not in [2, 3]:
52 print("File parsing unsuccessful. Supported dimensions are d=2 and d=3")
53 return
54 if snapshot_dof == 0:
55 print("File parsing unsuccessful. No dof specified")
56 return
57 if snapshot_unknowns == 0:
58 print("File parsing unsuccessful. No unknowns specified")
59 return
60
61 if snapshot_dimensions != dimensions and dimensions > 0:
62 print(
63 "Dimensions not compatible with dimensions from previous files in the snapshot"
64 )
65 return
66 dimensions = snapshot_dimensions
67 if snapshot_dof != dof and dof > 0:
68 print("DoF not compatible with dof from previous files in the snapshot")
69 return
70 dof = snapshot_dof
71
72 if snapshot_unknowns != unknowns and unknowns > 0:
73 print(
74 "Unknowns not compatible with unknowns from previous files in the snapshot"
75 )
76 return
77 unknowns = snapshot_unknowns
78 return (
79 snapshot_cell_data,
80 dof,
81 dimensions,
82 unknowns,
83 is_data_associated_to_cell,
84 description,
85 mapping,
86 )
87
88
90 """!
91 Small class to capture all of the snapshots in a Patch meta file.
92 Just need the timestamp this is associated with and all of the
93 patch files that this file includes. Add them using the right
94 member function.
95 """
96
97 def __init__(self):
98 self.timestamp = -1
99 self.filenames = []
100
101 def add_patch_file(self, patch_file_name):
102 """!"""
103 # For debugging whilst in testing phase
104 assert patch_file_name not in self.filenames, "Already added this patch file!"
105 self.filenames.append(patch_file_name)
106
107 @property
109 """!
110 Property to indicate number of patch files in this snapshot
111 """
112 return len(self.filenames)
113
114 def set_timestamp(self, timestamp):
115 assert self.timestamp == -1, "Should only set this property once!"
116 self.timestamp = timestamp
117
118 def __repr__(self):
119 return f"Timestamp: {self.timestamp}, {self.include_file_counter} subdomains"
120
121
123 """!
124 Helper class to capture the important data coming from a patch file
125
126 This is what we use to capture the combined patch file data from.
127 We could keep everything in the helper class from PatchFileParser,
128 but the intention during the rewrite was to keep everything fairly
129 close to the way the original vis scripts worked.
130 """
131
132 def __init__(self, unknown_name, dimensions):
133 self._dimensions = dimensions
134 self._unknown_name = unknown_name
135 self._cell_data = []
136 self._dof = 0
137 self._unknowns = 0
139 self._description = ""
140 self._mapping = None
141
142 def copy_data_from_parser_object(self, unknown_attributes):
143 """!
144 pass in a UnknownAttributes object from PatchFileParser.py,
145 steal everything and return
146 """
147 assert (
148 self._unknown_name == unknown_attributes.unknown_name
149 ), "Comparing the wrong objects. something must have gone wrong..."
150 # Append the cell data to this
151 self._cell_data += unknown_attributes.cell_data
152
153 # Make sure all other attributes are the same
154 self._dof = unknown_attributes.dof
155 # self._dimensions = unknown_attributes.dimensions # This one was not captured
156 self._unknowns = unknown_attributes.unknowns
157 self._is_data_associated_to_cell = unknown_attributes.is_data_associated_to_cell
158 self._description = unknown_attributes.description
159 self._mapping = unknown_attributes.mapping
160
161 def apply_renderer_to_data(self, renderer):
162 """!
163 Pass in a renderer object which will modify everything
164 in the way that the filter.render() function intends
165
166 This is basically the same as render_or_validate_for_each_unknown() from
167 the PatchFileParser class. The reason why we don't need
168 to do it for each unknown here is that the different unknowns
169 have been captured in by the caller of this function
170
171 We could rename this to something more general, since i
172 think we modify the same attributes in the same order
173 as the validate function
174 """
175 (
176 self._cell_data,
177 self._dof,
178 self._dimensions,
179 self._unknowns,
181 self._description,
182 self._mapping,
183 ) = renderer(
184 self._cell_data,
185 self._dof,
186 self._dimensions,
187 self._unknowns,
189 self._description,
190 self._mapping,
191 )
192
193 def __repr__(self):
194 return f"{self._unknown_name}: {self._unknowns} unknowns"
195
196
198 """!
199 The visualiser is first and foremost a persistency layer around
200 datasets. It serves as abstract base class for real output formats,
201 i.e. it can parse files and hold them, but it does not do anything
202 with them.
203
204 The flow should work like this:
205 - Pass the file name of the patch file meta data to this class.
206 We then use read_metadata() to read this file, and gather, for
207 each timestamp, the list of patch files that we need to read.
208 For each timestamp, we create an instance of the Snapshot
209 helper class, which captures the timestamp, plus a list of
210 patch files to parse
211 - We then pass back to display(). Then, for each snapshot, we
212 do the following:
213
214 - Create a PatchFileParser object for each patch file within
215 this timestamp
216 - Read all the patch file data and combine it into the
217 PatchFileData class in this file.
218 - Return this data to child class, so that it can be rendered
219 by display()
220
221 Currently, the only working child class is the VTU renderer.
222 """
223
224 def __init__(self, file_name, start, end, verbose=False):
225 """!
226 Abstract superclass of a visualiser
227
228 file_name: String
229 Name of a Peano patch file. This does not
230 need to be a meta file.
231
232 If we run render.py with the argument -s (to denote we are using single
233 file), then we will use the file_name to create a member of the
234 snapshot class (which captures the patch file to read, with default
235 timestamp of 0). This is then parsed by parse_and_render, and handed
236 back to child class.
237
238 If this is a meta file, then display() will be used.
239
240 verbose: Boolean
241 Controls how much the class writes out.
242
243 ## Attributes
244
245 self.identifier: String
246 Pick one subidentifier from the input file. If this one is empty, we take
247 every input data set. However, some output formats (such as ParaView)
248 don't support multiple output data sets, so we might end up with either
249 the first or last one from the file - depending on the implementations.
250 """
251 self._file_name = file_name # Either for meta file or single file we pass in
252 self._start = start
253 self._end = end
254 self._filter = []
255
256 # This is used for writing patch files
257 self.identifier = (
258 "" # This identifier is also used in helper class. pass it along
259 )
260 self.verbose = verbose
261
262 # Whether PatchFileParser instances created below should print a
263 # live, in-place per-file progress bar (@see PatchFileParser.
264 # show_progress). Defaults to on, since this class' own two entry
265 # points - display_single_file() and display() - both process
266 # files strictly sequentially in this process unless a subclass
267 # (@see VTUUnstructuredGrid.display()) farms work out to worker
268 # processes, in which case it must switch this off first: several
269 # processes writing their own \r-updated line to the same shared
270 # terminal would otherwise interleave into garbage.
271 self.show_progress = True
272
273 # Optional predicate handed over to each PatchFileParser, so
274 # subclasses can skip patches they are not interested in
275 # (@see PatchFileParser.patch_filter, PointProbe)
276 self.patch_filter = None
277
278 self._dimensions = 0
279 self._description = "" #
281
282 # Holds all of the timestamps and file names of
283 # all the snapshots that we wanna visualise
284 self.snapshots = []
285
286 # for debugging
288
289 def display(self):
290 """
291 This is the one routine the visualisers typically do overwrite.
292 """
293 # First, read metadata file. Won't be doing this for single file
294 self.read_metadata()
295 return
296
298 """!
299 If we are here we've supplied a single file which is
300 itself a snapshot. Turn it into an instance of the
301 snapshot helper class, set the timestamp to 0 and
302 pass it back. We can just call parse_and_render_snapshot()
303 on the result
304 """
305 snapshot = SnapShot()
306 snapshot.add_patch_file(self._file_name)
307 snapshot.set_timestamp(0)
308 return snapshot
309
310 def read_metadata(self):
311 """!
312 Read the patch file metadata. In this routine we should:
313 - Count the number of timesteps this patch file records
314 - Get each of the file paths for the timestep
315
316 The meta file includes the snapshot files relative to its own
317 location. We therefore prefix each included file with the directory
318 of the meta file, so the visualiser works from any working directory.
319 """
320 found_snapshot = False
321 found_snapshots = []
322
323 print(f"Reading metadata for {self._file_name}")
324 with open(self._file_name, "r") as f:
325 line = f.readline()
326 while line: # While there is another line to read, increment at bottom
327 if "begin dataset" in line:
328 # Encountered a dataset. Get the snapshot and all the filenames associated
329
330 # Create a new snapshot instance
331 s = SnapShot()
332 while (
333 "end dataset" not in line
334 ): # Read until the end of the dataset
335 if "timestamp" in line:
336 s.set_timestamp(float(list(line.split(" "))[2][:-1]))
337
338 if "include" in line:
339 file_name = os.path.join(
340 os.path.dirname(self._file_name), line.split('"')[1]
341 )
342 # print(f"Adding {file_name} to snapshot")
343 s.add_patch_file(file_name)
344 line = f.readline()
345
346 # We have left the while loop for dataset. add snapshot to list
347 found_snapshots.append(s)
348 found_snapshot = True
349 line = f.readline()
350
351 assert (
352 found_snapshot
353 ), "No snapshots found. Did you mean to run just for a single file? If so, try using the argument -s"
354
355 # Remove when working
356 self.meta_data_has_been_read = True
357 self.snapshots = found_snapshots[self._start : self._end + 1]
358 for snapshot in self.snapshots:
359 print(f"Got snapshot: {snapshot}")
360
361 def append_filter(self, filter):
362 """ """
363 self._filter.append(filter)
364
365 def remove_filters(self):
366 """ """
367 self._filter = []
368
369 def parse_and_render_snapshot(self, snapshot):
370 """!
371 We pass in a snapshot object here, which should contain
372 time stamp and filenames for everything we want to render here.
373
374 Behaviour is:
375 - parse all the files listed in snapshot (use the method we wrote)
376 - return a dict, where the keys are unknown names, and the values are cell data to be rendered
377 - child class should handle the rest.
378 """
379
380 data_to_be_rendered = self.parse_snapshot(snapshot)
381
382 # Hand back to child class
383 return data_to_be_rendered
384
385 def parse_snapshot(self, snapshot):
386 """!
387 We rewrite "reload" from previous version of this file.
388
389 We take in a argument of "snapshot", which is a helper class
390 with two attributes: timestamp and a list of patch files to parse.
391
392 We need to ensure that self.identifier is set correctly so that
393 the correct unknown is picked by the PatchFileParser. Remove this
394 line of comment when this is done.
395
396 Also need to handle removal of relative file paths here
397
398 The reason we (possibly) apply filters on individual pieces of data,
399 or once the patches have been concatenated is because we may have
400 more than one file to parse per snapshot. The choice is is about
401 whether we want to apply filters before combining all the patches
402 into one list, or afterwards?
403
404 What does this function do?
405 - Get filename for each patch file in this timestamp
406 - Produce a PatchFileParser object for each
407 - Parse each file in parallel
408 - Apply all the filters specified in render.py
409 - Amalgamate data as in original reload() method
410 - Returns a dict, where the keys are unknown names, and the values are patches that can be placed into individual patch files
411 """
412 parsers = []
413
414 for file_counter, patch_filename in enumerate(snapshot.filenames):
415 if self.verbose:
416 print(f"Parsing file {patch_filename}")
417 parser = PatchFileParser(
418 patch_filename,
419 self.identifier,
420 file_counter,
421 show_progress=self.show_progress,
422 patch_filter=self.patch_filter,
423 )
424 parsers.append(parser)
425
426 for parser in parsers:
427 parser.parse_file()
428
429 if self.verbose:
430 print("All individual files are parsed")
431 print("Apply filter(s) to individual files (where appropriate)")
432
433 for parser in parsers:
434 for fil in self._filter:
435 if fil.run_on_individual_pieces_of_data:
436 if self.verbose:
437 print(f"Apply filter {fil} to snapshot")
438 # We modify the attributes of parser here, and amalgamate after.
439
440 # Let's hide what it does.
441 # Pass in the function to be evaluated. Handle each of the unknowns in patch file parser class
442
443 # By default, we don't go here.
444 parser.render_or_validate_for_each_unknown(fil.render)
445
446 # Let's create a dict of pairs unknown_name, patch_file_data
447 patch_file_data_to_render = {}
448
449 # Let's get all of the unknown names seen by the parsers
450 # This is obviously redundant, but let's not assume (at this stage)
451 # that all of the unknown names have been seen in all of the patch files
452 # for this snapshot.
453 for parser in parsers:
454 for unknown_name, unknown_data in parser.unknown_attributes.items():
455 # ie we create empty helper class with the unknown name
456 if unknown_name not in patch_file_data_to_render.keys():
457 # Not seen it yet, add for first time.
458 patch_file_data_to_render[unknown_name] = PatchFileData(
459 unknown_name, unknown_data.dimensions
460 )
461 # Steal data
462 # This will amalgamate cell data on a like-for-like basis
463 patch_file_data_to_render[unknown_name].copy_data_from_parser_object(
464 parser.unknown_attributes[unknown_name]
465 )
466
467 # Now we have amalgamated all of the patch data, let's apply the filters
468 for unknown_name, patch_file_data in patch_file_data_to_render.items():
469 for fil in self._filter:
470 if fil.run_on_concatenated_data:
471 # Pass this function in, rest is handled inside
472 patch_file_data.apply_renderer_to_data(fil.render)
473 if self.verbose:
474 print(
475 f"Parsing {unknown_name} complete. Found {len(patch_file_data._cell_data)} cells, {patch_file_data._dof} dofs/cell with {patch_file_data._unknowns} unknowns per dof"
476 )
477
478 for parser in parsers:
479 # Pass the validate function along to validate that the
480 # attributes have been correctly set.
481 parser.render_or_validate_for_each_unknown(validate)
482 return patch_file_data_to_render
#define validate(booleanExpr)
Definition TestMacros.h:37
Helper class to capture the important data coming from a patch file.
apply_renderer_to_data(self, renderer)
Pass in a renderer object which will modify everything in the way that the filter....
__init__(self, unknown_name, dimensions)
copy_data_from_parser_object(self, unknown_attributes)
pass in a UnknownAttributes object from PatchFileParser.py, steal everything and return
Small class to capture all of the snapshots in a Patch meta file.
Definition Visualiser.py:89
include_file_counter(self)
Property to indicate number of patch files in this snapshot.
The visualiser is first and foremost a persistency layer around datasets.
__init__(self, file_name, start, end, verbose=False)
Abstract superclass of a visualiser.
parse_snapshot(self, snapshot)
We rewrite "reload" from previous version of this file.
parse_and_render_snapshot(self, snapshot)
We pass in a snapshot object here, which should contain time stamp and filenames for everything we wa...
display_single_file(self)
If we are here we've supplied a single file which is itself a snapshot.
display(self)
This is the one routine the visualisers typically do overwrite.
read_metadata(self)
Read the patch file metadata.