Peano
Loading...
Searching...
No Matches
ParticleVTUReader.py
Go to the documentation of this file.
1"""!
2This file contains the ParticleVTUReader class, which hosts
3methods to extract particle data from .vtu or .pvd files
4generated by Peano.
5"""
6
7import os
8import errno
9import numpy as np
10from typing import Union
11
12try:
13 import vtuIO
14except ModuleNotFoundError:
15 raise ModuleNotFoundError(
16 "You need the 'VTUinterface' package to use the ParticleVTUReader"
17 )
18
19
21 """!
22 Clean up a string intended to be a particle attribute
23 from illegal characters.
24
25 Returns
26 -------
27
28 cleaned_attr: str
29 cleaned attribute string.
30 """
31
32 cleaned_attr = attr.strip()
33
34 # keep all illegal characters and their replacements
35 # in this dict. Keys will be replaced by values.
36 illegal_character_replacements = {
37 "-": "_",
38 " ": "_",
39 }
40
41 for key in illegal_character_replacements:
42 cleaned_attr = cleaned_attr.replace(key, illegal_character_replacements[key])
43
44 return cleaned_attr
45
46
47class VTUParticleSet(object):
48 """!
49 Container for particle data. All particle fields shall
50 have the same name as specified in the .vtu files, and
51 be accessible as an attribute of the instantiated object.
52
53 The individual particle values are instances of numpy arrays.
54
55 Methods
56 -------
57
58 get_attribute_list(self):
59 return list of all available particle fields
60
61 show_attribute_list(self):
62 prints out all available particle fields
63
64 merge(self, other):
65 merge another VTUParticleSet into this one.
66 """
67
68 def __init__(self, particle_attributes: list):
69 """
70 Parameters
71 ----------
72 particle_attributes : list of strings
73 contains names of all particle fields
74 """
75
76 self._nattributes = 0
77 # "translation" from particle fields in input .vtu file to
78 # cleaned up attributes of this object
80
81 if len(particle_attributes) == 0:
82 raise ValueError(
83 "Got empty particle attributes list. Can't work with that."
84 )
85
86 cleaned_attributes = []
87 for att in particle_attributes:
88 if not isinstance(att, str):
89 raise ValueError("Attribute '", att, "' is not a string?")
90 cleaned_attr = _clean_attribute_string(att)
91 setattr(self, cleaned_attr, None)
92
93 self._nattributes += 1
94 self._attribute_translator[att] = cleaned_attr
95 cleaned_attributes.append(cleaned_attr)
96
97 self._particle_attribute_list = cleaned_attributes
98
99 return
100
102 """!
103 Return the list of available attributes.
104 """
105 return self._particle_attribute_list
106
108 """!
109 Print the list of available attributes.
110 """
111 print("Available particle fields are:", self._particle_attribute_list)
112 return
113
114 def merge(self, other):
115 """!
116 Merge two VTUParticleSet objects into one (into self).
117 """
118
119 if self._nattributes != other._nattributes:
120 raise ValueError("Unequal number of attributes in datasets, can't merge")
121
122 for attr in self._particle_attribute_list:
123 this_arr = getattr(self, attr)
124 that_arr = getattr(other, attr)
125 newattr = np.concatenate((this_arr, that_arr))
126 setattr(self, attr, newattr)
127
128 return
129
130 def __str__(self):
131 return "Particle Data Container"
132
133 def __repr__(self):
134 return self.__str__()
135
136
137class ParticleVTUReader(object):
138 """!
139 Reads in particle fields from a single .vtu file, or all .vtu
140 at a given snapshot time specified in the provided .pvd file.
141
142
143 Methods
144 -------
145
146 get_all_vtufiles(self):
147 Read in and return all .vtu file names from the .pvd file
148 provided by pvdfilename at initialisation.
149
150 load(self):
151 actually read in the data, and return the particle data as a
152 VTUParticleSet object.
153
154 read_single_vtu_file(self, vtufilename: str):
155 Read in particle data from a single .vtu file, and return the filled
156 out VTUParticleSet object containing the particle data.
157 """
158
160 self,
161 vtufile: Union[str, None] = None,
162 snapshot_time: Union[float, None] = None,
163 pvdfile: Union[str, None] = None,
164 verbose: bool = False,
165 ):
166 """!
167 At initialization, either the ``vtufile`` argument needs to
168 be provided, or both the ``snapshot_time`` and ``pvdfile``
169 arguments in order to specify what data to read.
170
171 If ``vtufile`` is given, the ParticleVTUReader will read in a
172 single .vtu file.
173
174 If ``snapshot_time`` and ``pvdfile`` arguments are given, the
175 ParticleVTUReader will read in all .vtu files corresponding to the
176 snapshot at the provided ``snapshot_time``.
177
178 Parameters
179 ----------
180
181 vtufile: str
182 path to .vtu file to be read in
183
184 snapshot_time: float
185 time of the snapshot to extract
186
187 pvdfile: str
188 path to .pvd file to be read in
189
190 verbose: bool
191 how talkative the reading process should be
192
193 """
194
195 self._verbose = verbose
196 self._mode = None
197 self._vtufile = vtufile
198 self._pvdfile = pvdfile
199 self._snapshot_time = snapshot_time
200 # have we read in the snapshot time?
201 self._read_time = False
203
204 # First check that we have all necessary requirements
205 if vtufile is None:
206 if (snapshot_time is None) or (pvdfile is None):
207 raise ValueError(
208 "You need to specify either the `vtufile` argument, or "
209 + "*both* the `snapshot_time` and `pvdfile` arguments so I "
210 + "know what to read."
211 )
212 else:
213 self._mode = "multiple"
214 if self._verbose:
215 print("Setting mode to 'multiple'")
216 else:
217 self._mode = "single"
218 if self._verbose:
219 print("Setting mode to 'single'")
220
221 return
222
223 def load(self):
224 """!
225 Actually read in the data.
226
227 Returns
228 -------
229
230 particleData: VTUParticleSet
231 object containing all read-in particle data as attributes.
232 """
233
234 if self._mode == "single":
235 # read in the single file
236 return self.read_single_vtu_file(self._vtufile)
237
238 elif self._mode == "multiple":
239 vtufiles = self._get_snapshot_vtu_files_from_pvd(
240 self._pvdfile, self._snapshot_time
241 )
242 partdata = None
243 for f in vtufiles:
244 fullpath = self._get_full_vtufile_path(f)
245 new_data = self.read_single_vtu_file(fullpath)
246 if partdata is None:
247 partdata = new_data
248 else:
249 partdata.merge(new_data)
250
251 return partdata
252
253 else:
254 raise ValueError("Unknown mode? How did we get here?")
255
257 """!
258 Return the time/timestep information of the read snapshot.
259 This is only available if you read in the .pvd file, not
260 the .vtu file. If the .vtu file has been read, this method
261 returns `None`. Otherwise, it returns the time of the
262 snapshot, which may vary (slightly) from the snapshot time
263 you specified with when loading the data.
264 """
265
266 if self._read_time:
267 return self._snapshot_time_read
268 else:
269 return None
270
272 """!
273 Read in and return all .vtu file names from the .pvd file
274 provided by pvdfilename at initialisation.
275
276 Returns
277 -------
278
279 vtufilenames: list
280 list of .vtu filenames in the .pvd file, with their
281 full path.
282 """
283
284 if self._pvdfile is None:
285 raise ValueError(
286 """
287No .pvd file has been specified.
288You need to provide a .pvd file name during initialisation of
289the ParticleVTUReader() object.
290 """
291 )
292
293 # backend specified to vtuIO default to avoid warning messages
294 pvdfile = vtuIO.PVDIO(self._pvdfile, interpolation_backend="vtk")
295 allvtufiles = pvdfile.vtufilenames
296 fullpathfiles = [self._get_full_vtufile_path(f) for f in allvtufiles]
297 return fullpathfiles
298
299 def _get_full_vtufile_path(self, vtufile: str):
300 """!
301 The .pvd file will provide relative path names to its
302 own file path for the .vtu files.
303 This function returns the full path of the .vtu file.
304 """
305 if self._pvdfile is None:
306 raise ValueError(
307 """
308No .pvd file has been specified.
309You need to provide a .pvd file name during initialisation of
310the ParticleVTUReader() object.
311 """
312 )
313 pathprefix = os.path.dirname(self._pvdfile)
314 fullpath = os.path.join(pathprefix, vtufile)
315 return fullpath
316
317 def _get_snapshot_vtu_files_from_pvd(self, pvdfilename: str, time: float):
318 """!
319 Read in data from the .pvd file provided by pvdfilename.
320
321 Parameters
322 ----------
323
324 pvdfilename: str
325 filename to read in from
326
327 time: float
328 snapshot time to read in
329
330
331 Returns
332 -------
333
334 vtufilenames: list
335 list of .vtu filenames to read from
336 """
337
338 if not os.path.exists(pvdfilename):
339 raise FileNotFoundError(
340 errno.ENOENT, os.strerror(errno.ENOENT), pvdfilename
341 )
342
343 # 'interpolation_backend' is used to generate plots with seemingly
344 # continuous data. That's not what we're using this module for. We
345 # only want to read in particle data. So I use here the default
346 # 'interpolation_backend', which gets set anyway if it weren't
347 # specified, with the bonus that this way, no annoying warnings are
348 # printed.
349 pvdfile = vtuIO.PVDIO(pvdfilename, interpolation_backend="vtk")
350 timesteps = pvdfile.timesteps
351 if self._verbose:
352 print("Available snapshot time steps:", np.unique(timesteps))
353 allvtufiles = pvdfile.vtufilenames
354
355 # Find snapshots with time closest to desired output time
356 timediff = np.abs(timesteps - time)
357 snap_indexes = timediff == timediff.min()
358
359 # make sure we found something
360 if not snap_indexes.any():
361 errmsg = f"""
362 Couldn't find any snapshots?
363 Looking for snashots with time={self._snapshot_time}
364 pvd file provides following times: {timesteps}
365 pvd file provides following file names: {allvtufiles}
366 """
367 raise ValueError(errmsg)
368
369 # check that we found the correct snapshot time
370 close_enough = True
371 if self._snapshot_time == 0.0:
372 if timediff.min() > 1.0e-5:
373 close_enough = False
374
375 else:
376 if np.abs(timediff.min() / self._snapshot_time) > 1.0e-4:
377 close_enough = False
378
379 if self._verbose or not close_enough:
380 print(f"WARNING: You requested snapshot at time={self._snapshot_time}")
381 print(
382 "WARNING: Closest snapshot time I found is:",
383 timesteps[snap_indexes][0],
384 "; reading that in now",
385 )
386
387 vtufiles = np.array(allvtufiles)[snap_indexes]
388 vtufiles = vtufiles.tolist()
389
390 # store snapshot time
391 self._read_time = True
392 self._snapshot_time_read = timesteps[snap_indexes][0]
393
394 return vtufiles
395
396 def read_single_vtu_file(self, vtufilename: str):
397 """!
398 Read in particle data from a single .vtu file, and return the filled
399 out VTUParticleSet object containing the particle data.
400
401 Parameters
402 ----------
403
404 vtufilename: str
405 .vtu filename to read in from.
406
407
408 Returns
409 -------
410
411 particleData: VTUParticleSet
412 object containing all read-in particle data as attributes.
413 """
414
415 if not os.path.exists(vtufilename):
416 raise FileNotFoundError(
417 errno.ENOENT, os.strerror(errno.ENOENT), vtufilename
418 )
419
420 # 'interpolation_backend' is used to generate plots with seemingly
421 # continuous data. That's not what we're using this module for. We
422 # only want to read in particle data. So I use here the default
423 # 'interpolation_backend', which gets set anyway if it weren't
424 # specified, with the bonus that this way, no annoying warnings are
425 # printed.
426 vtufile = vtuIO.VTUIO(vtufilename, interpolation_backend="vtk")
427 point_field_names = vtufile.get_point_field_names()
428
429 print("-- Reading from", vtufilename)
430 if self._verbose:
431 print("File", vtufilename, "has particle fields:", point_field_names)
432
433 data = VTUParticleSet(point_field_names)
434
435 for attr in point_field_names:
436 part_fields = vtufile.get_point_field(attr)
437 setattr(data, data._attribute_translator[attr], part_fields)
438
439 if self._verbose:
440 print(
441 "File",
442 vtufilename,
443 "read in field",
444 attr,
445 "with shape",
446 part_fields.shape,
447 )
448
449 return data
Reads in particle fields from a single .vtu file, or all .vtu at a given snapshot time specified in t...
get_snapshot_time(self)
Return the time/timestep information of the read snapshot.
_get_full_vtufile_path(self, str vtufile)
The .pvd file will provide relative path names to its own file path for the .vtu files.
read_single_vtu_file(self, str vtufilename)
Read in particle data from a single .vtu file, and return the filled out VTUParticleSet object contai...
__init__(self, Union[str, None] vtufile=None, Union[float, None] snapshot_time=None, Union[str, None] pvdfile=None, bool verbose=False)
At initialization, either the vtufile argument needs to be provided, or both the snapshot_time and pv...
get_all_vtufiles(self)
Read in and return all .vtu file names from the .pvd file provided by pvdfilename at initialisation.
_get_snapshot_vtu_files_from_pvd(self, str pvdfilename, float time)
Read in data from the .pvd file provided by pvdfilename.
merge(self, other)
Merge two VTUParticleSet objects into one (into self).
_clean_attribute_string(str attr)
Clean up a string intended to be a particle attribute from illegal characters.