7from .Patch
import Patch
10def unknown_names_from_description(description, unknowns):
13 Names of the unknowns as stored in the description of a patch file
15 Plotters such as exahype2.api.fv.PlotAsVTKFile write the names of the
16 unknowns as comma-separated list into the description, e.g.
17 "h, hu, hv, b". A variable with multiplicity k shows up as k names
21 Description of an unknown as read from the patch file.
24 Number of unknowns per degree of freedom.
26 Returns the list of names, or None if the description does not hold
27 exactly one name per unknown. Names are C++ identifiers, as they stem
28 from the Variables struct of a solver, optionally followed by an index
29 [i]. Any other description is free text, which we do not interpret.
32 names = [name.strip()
for name
in description.split(
",")]
33 if len(names) == unknowns
and all(
34 re.fullmatch(
r"[A-Za-z_]\w*(\[\d+\])?", name)
for name
in names
42 Helper class to capture all of the attributes we need for each
43 unknown, but were originally fixed to the PatchFileParser class
46 For instance, at time of writing the vis scripts cannot handle
47 displaying separate vtu files for different data that is produced
48 in Peano patch files. An excerpt:
51 offset 9.629630e-01 9.629630e-01
52 size 3.703704e-02 3.703704e-02
53 begin vertex-values "value"
56 begin vertex-values "rhs"
61 Previously, the only thing that would get parsed is the "value"
62 field, before moving on. We aim to get it to do the "rhs" part
65 However, we cannot assume that the number of dofs per axis or
66 unknowns per patch volume will be the same for both. So we
67 create this helper class to hold those.
69 We fix is_data_associated_to_cell to be the same as the
70 PatchFileParser object which, in turn, means they must
71 be the same for all of the unknowns
77 String of input file path (including filename and extension)
79 cell_data: list of patches
80 List of Patches with file data
86 Number of degrees of freedom per axis
89 Number of unknowns per patch volume
91 mapping: series of d-dimensional tuples
94 set_identifier: String
95 Can be empty to parse everything.
99 def __init__(self, unknown_name, dimensions, is_data_associated_to_cell):
114 assert self.
unknowns == 0,
"attribute already set!"
118 assert self.
dof == 0,
"attribute already set!"
125 """! @see unknown_names_from_description()"""
129 assert not self.
cell_data,
"attribute already set!"
133 assert not self.
mapping,
"attribute already set!"
137 assert not self.
mapping,
"attribute already set!"
138 vertices_per_axis = self.
dof
140 vertices_per_axis += 1
142 for y
in range(vertices_per_axis):
143 for x
in range(vertices_per_axis):
144 self.
mapping.append(x * 1.0 / (vertices_per_axis - 1))
145 self.
mapping.append(y * 1.0 / (vertices_per_axis - 1))
147 for z
in range(vertices_per_axis):
148 for y
in range(vertices_per_axis):
149 for x
in range(vertices_per_axis):
150 self.
mapping.append(x * 1.0 / (vertices_per_axis - 1))
151 self.
mapping.append(y * 1.0 / (vertices_per_axis - 1))
152 self.
mapping.append(z * 1.0 / (vertices_per_axis - 1))
158 return f
"{self.unknown_name}: unknowns:{self.unknowns}, dof:{self.dof}"
164 Binary-safe view onto a Peano patch file
166 A patch file is a text file, but the quantities of the patches of a
167 file with `format Binary` are raw doubles. We hence open every file in
168 binary mode, hand out the text lines as decoded strings, and let the
169 parser fetch or skip exactly as many bytes as the quantities of a patch
170 occupy (@ref tarch::plotter::griddata::blockstructured::PeanoBinaryPatchFileWriter).
171 For an ASCII file, the class behaves like a text file.
185 return self.
_file.
readline().decode(
"utf-8", errors=
"replace")
190 Read count doubles in the byte order of this machine, plus the line
191 break behind them. Returns a writable numpy array.
197 raw = self.
_file.read(8 * count)
198 if len(raw) != 8 * count:
199 raise Exception(
"file ended within the binary quantities of a patch")
201 return np.frombuffer(raw, dtype=np.float64).copy()
204 self.
_file.seek(8 * count, 1)
210 Parser for Peano block file output.
212 This class should be instantiated from within the Visualiser class.
213 This class should be called once per patch file that contains
214 patch data that we want to render. The main method that is called
217 This method doesn't return anything. Rather, it sets unknown_attributes
218 to capture all the patch data for each of the unknowns that we find.
220 Then, the Visualiser class will steal this data, and combine it into
221 its own helper class.
227 String of input file path (including filename and extension)
232 mapping: series of d-dimensional tuples
235 set_identifier: String
236 Can be empty to parse everything.
238 unknown_attributes: Dict{ String, UnknownAttributes }
239 Dictionary that has the names of each unknown we wanna capture
240 as its keys, and the values are the helper class which captures
241 dimensions, cell data etc. Each of the unknowns should be
242 listed during the metadata part of the patch file, ie between
243 "begin cell-metadata" and "end cell-metadata".
256 show_progress: Boolean
257 Print a single-line, in-place progress bar (percentage plus
258 patch count) to the terminal while parse_file() works through
259 this file's patches - @see __count_patches()/__print_progress().
260 Off by default: it writes \\r-terminated lines, so it only
261 produces clean output when nothing else concurrently writes to
262 the same terminal - the caller (Visualiser.show_progress) is
263 responsible for only turning this on when that is guaranteed
264 (e.g. sequential, single-worker processing).
266 patch_filter: Callable (offset, size) -> Boolean, or None
267 If set, parse_file() calls this predicate on each patch as soon as
268 it has read the patch's offset and size, and skips the patch's
269 values without converting them if the predicate returns False.
270 This is way cheaper than parsing everything and throwing the data
271 away afterwards, if a postprocessing script needs only a few
272 patches (@see output.PointProbe). With multiple worker processes,
273 the predicate has to be picklable, i.e. no lambda.
293 Here we expected the keys to be a list of unknown names that we
294 have associated with each cell/vertex, and the values to be
295 instances of the helper class UnknownAttributes
300 return f
"Parser for {self.file_path}, subdomain {self.subdomain_number}, identifier {self.set_identifier}"
304 Pass in file object, move it along and return it
307 unknown_name = current_line.strip().split()[-1][1:-1]
317 current_line = file_object.readline().strip()
319 use_default_mapping =
True
322 while not end_condition
in current_line:
323 if "number-of-unknowns" in current_line:
324 patch_unknown.set_unknowns(int(current_line.strip().split()[1]))
325 if "number-of-dofs-per-axis" in current_line:
326 patch_unknown.set_dof(int(current_line.strip().split()[1]))
327 if "description" in current_line:
328 patch_unknown.set_description(
329 current_line.strip().split(
"description")[1].strip()
331 if '"' in patch_unknown.description:
333 "Warning: data description field "
334 + patch_unknown.description
335 +
" holds \" which might lead to issues with VTK's XML export. Remove them"
337 patch_unknown.description = patch_unknown.description.replace(
340 if "mapping" in current_line:
341 use_default_mapping =
False
342 values = current_line.strip().split(
"mapping")[1]
343 patch_unknown.set_mapping(np.fromstring(values, dtype=float, sep=
" "))
345 current_line = file_object.readline().strip()
347 if use_default_mapping:
348 patch_unknown._initialise_default_mapping_if_no_mapping_specified()
353 patch_unknown.unknowns * patch_unknown.dof**self.
dimensions
356 return file_object, current_line
361 Read the quantities of one patch of one dataset. The line
362 "begin cell-values" or "begin vertex-values" has just been read.
367 return np.fromstring(file_object.readline(), dtype=float, sep=
" ")
372 If current_line opens the quantities of a dataset in a binary file,
377 current_line.startswith(
"begin cell-values")
378 or current_line.startswith(
"begin vertex-values")
380 unknown_name = current_line.split()[-1][1:-1]
386 Binary files only: Run through a metadata block of a dataset which
387 we do not parse and remember how many doubles each of its patches
388 holds, so we can step over them.
391 unknown_name = current_line.strip().split()[-1][1:-1]
394 current_line = file_object.readline().strip()
395 while end_condition
not in current_line:
396 if "number-of-unknowns" in current_line:
397 unknowns = int(current_line.split()[1])
398 if "number-of-dofs-per-axis" in current_line:
399 dof = int(current_line.split()[1])
400 current_line = file_object.readline().strip()
402 return file_object, current_line
405 self, current_line, file_object, end_condition="end patch"
408 Pass in file object, run it until we encounter the end condition,
411 We assume that current_line == "begin patch file" at this stage
413 This is where we read the actual data
417 current_line = file_object.readline().strip()
418 line = current_line.strip().split()
420 offset = (float(line[1]), float(line[2]))
422 offset = (float(line[1]), float(line[2]), float(line[3]))
425 current_line = file_object.readline().strip()
426 line = current_line.strip().split()
428 size = (float(line[1]), float(line[2]))
430 size = (float(line[1]), float(line[2]), float(line[3]))
433 while end_condition
not in current_line:
434 current_line = file_object.readline().strip()
436 return file_object, current_line
440 while end_condition
not in current_line:
441 current_line = file_object.readline().strip()
446 if current_line.startswith(
"begin cell-values")
and (
452 ),
"is_data_associated_to_cell flag set incorrectly"
453 unknown_name = current_line.strip().split()[-1][1:-1]
458 if current_line.startswith(
"begin vertex-values")
and (
464 ),
"is_data_associated_to_cell flag set incorrectly"
465 unknown_name = current_line.strip().split()[-1][1:-1]
475 if values
is not None:
481 return file_object, current_line
486 Quick pre-scan (just a substring search per line, no numpy
487 parsing) to count "begin patch" occurrences, i.e. the number of
488 cells/patches this file holds, so parse_file() can show progress
489 against a known total instead of an estimate. Reads the file a
490 second time, but that is negligible next to the actual parse
491 below, which parses a numpy array per patch.
497 if line.startswith(b
"begin patch"):
504 Render current/total as an in-place, single-line percentage/bar
505 on the terminal (\\r-terminated, so nothing else may write to
506 stdout while this runs - @see show_progress).
509 fraction = current / total
if total > 0
else 1.0
511 filled = int(bar_length * fraction)
512 bar =
"#" * filled +
"-" * (bar_length - filled)
514 f
"\rParsing {self.file_path}: [{bar}] {fraction * 100:5.1f}% ({current}/{total} cells)",
521 Read file and return cell data, dimensions, number of degrees of freedom and number of unknowns
525 set_identifier: String
526 Name of the set of unknowns we want to extract. If this is the empty string
527 then I parse all content.
529 @todo: clean up the loop below. we can separate into getting metadata, and then reading patches
539 last_printed_permille = -1
542 current_line = data_file.readline()
546 the reason we keep track of 2 variables current_line and
547 line is because we want current_line to eval to False when we reach
548 EOF. However, we need to strip the whitespaces and newline
549 characters off the line for the data processing, without
550 exiting the loop if we have empty lines.
552 current_line = data_file.readline()
553 stripped_line = current_line.strip()
554 if stripped_line.startswith(
"format"):
561 if stripped_line.startswith(
"dimensions"):
562 self.
dimensions = int(stripped_line.strip().split()[1])
566 ],
"Only 2d and 3d patches are supported"
569 if stripped_line.startswith(
"begin cell-metadata")
and (
576 stripped_line, data_file,
"end cell-metadata"
579 if stripped_line.startswith(
"begin vertex-metadata")
and (
586 stripped_line, data_file,
"end vertex-metadata"
590 stripped_line.startswith(
"begin cell-metadata")
591 or stripped_line.startswith(
"begin vertex-metadata")
598 "end " + stripped_line.split()[1],
601 elif stripped_line.startswith(
"begin patch"):
606 stripped_line, data_file
610 permille = (patches_parsed * 1000) // total_patches
612 permille != last_printed_permille
613 or patches_parsed == total_patches
615 last_printed_permille = permille
620 except Exception
as e:
622 "Error: was not able to read "
644 We need to call the render function for each filter
645 on a variety of attributes, and then return them if they
648 We do this in a functional style, ie the Visualiser class
649 contains all of the filters we wish to apply, and we pass
650 the render function that we intend to use back to this
651 layer. Ugly, but we need to do some looping over the
652 different unknowns we see in the patch files, and I thought
653 it best to hide that from the parse_snapshot region of
656 The same apply for the validate function from the Visualiser
665 unknown_data.cell_data,
667 unknown_data.dimensions,
668 unknown_data.unknowns,
669 unknown_data.is_data_associated_to_cell,
670 unknown_data.description,
671 unknown_data.mapping,
672 ) = render_or_validate(
673 unknown_data.cell_data,
675 unknown_data.dimensions,
676 unknown_data.unknowns,
677 unknown_data.is_data_associated_to_cell,
678 unknown_data.description,
679 unknown_data.mapping,