MFEM v4.9.0
Finite element discretization library
Loading...
Searching...
No Matches
vtkhdf.cpp
Go to the documentation of this file.
1// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
2// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3// LICENSE and NOTICE for details. LLNL-CODE-806117.
4//
5// This file is part of the MFEM library. For more information and source code
6// availability visit https://mfem.org.
7//
8// MFEM is free software; you can redistribute it and/or modify it under the
9// terms of the BSD-3 license. We welcome feedback and contributions, see file
10// CONTRIBUTING.md for details.
11
12#include "vtkhdf.hpp"
13
14#ifdef MFEM_USE_HDF5
15
17
18#include <algorithm>
19#include <numeric>
20#include <hdf5_hl.h>
21
22namespace mfem
23{
24
25namespace
26{
27
28// Template class for HDF5 type IDs (specialized for each type T).
29template <typename T> struct TypeID { };
30
31template <> struct TypeID<float> { static hid_t Get() { return H5T_NATIVE_FLOAT; } };
32template <> struct TypeID<double> { static hid_t Get() { return H5T_NATIVE_DOUBLE; } };
33template <> struct TypeID<int32_t> { static hid_t Get() { return H5T_NATIVE_INT32; } };
34template <> struct TypeID<uint64_t> { static hid_t Get() { return H5T_NATIVE_UINT64; } };
35template <> struct TypeID<unsigned char> { static hid_t Get() { return H5T_NATIVE_UCHAR; } };
36
37}
38
39hsize_t VTKHDF::Dims::TotalSize() const
40{
41 return std::accumulate(data.begin(), data.begin() + ndims, 1,
42 std::multiplies<hsize_t>());
43}
44
45template <typename T>
46hid_t VTKHDF::GetTypeID() { return TypeID<typename std::decay<T>::type>::Get(); }
47
48void VTKHDF::SetupVTKHDF()
49{
50 vtk = H5Gcreate2(file, "VTKHDF", H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
51
52 // Set attributes: version and type
53 const long version_buf[2] = {2, 2}; // VTKHDF version 2.2
54 H5LTset_attribute_long(vtk, ".", "Version", version_buf, 2);
55
56 // Note: we don't use the high-level API here since it will write out the
57 // null terminator, which confuses the VTKHDF reader in ParaView. Fixed in
58 // VTK MR !12044, https://gitlab.kitware.com/vtk/vtk/-/merge_requests/12044.
59 const std::string type_str = "UnstructuredGrid";
60 const hid_t type_id = H5Tcopy(H5T_C_S1);
61 H5Tset_size(type_id, type_str.size());
62 H5Tset_strpad(type_id, H5T_STR_NULLPAD);
63
64 const hid_t data_space = H5Screate(H5S_SCALAR);
65 const hid_t type_attr = H5Acreate2(vtk, "Type", type_id, data_space,
66 H5P_DEFAULT, H5P_DEFAULT);
67 H5Awrite(type_attr, type_id, type_str.data());
68
69 H5Aclose(type_attr);
70 H5Sclose(data_space);
71 H5Tclose(type_id);
72}
73
74void VTKHDF::EnsureSteps()
75{
76 // If the Steps group has already been created, return early.
77 if (steps != H5I_INVALID_HID) { return; }
78
79 // Otherwise, create the group and its datasets.
80 EnsureGroup("Steps", steps);
81 hid_t pd_offsets = H5I_INVALID_HID;
82 EnsureGroup("Steps/PointDataOffsets", pd_offsets);
83 H5Gclose(pd_offsets);
84}
85
86hid_t VTKHDF::EnsureDataset(hid_t f, const std::string &name, hid_t type,
87 Dims &dims)
88{
89 const char *name_c = name.c_str();
90
91 const herr_t status = H5LTfind_dataset(f, name_c);
92 Barrier();
93
94 if (status == 0)
95 {
96 // Dataset does not exist, create it.
97 const int ndims = dims.ndims;
98 // The dataset is allowed to grow in the first dimension, but is fixed
99 // in size in all other dimesions; the maximum dataset size is same as
100 // dims, but unlimited in first dimension.
101 Dims max_dims = dims;
102 max_dims[0] = H5S_UNLIMITED;
103 const hid_t fspace = H5Screate_simple(ndims, dims, max_dims);
104
105 Dims chunk(ndims);
106 size_t chunk_size_bytes = 1024 * 1024 / 2; // 0.5 MB
107 const size_t t_bytes = H5Tget_size(type);
108 for (int i = 1; i < ndims; ++i)
109 {
110 chunk[i] = dims[i];
111 chunk_size_bytes /= dims[i];
112 }
113 chunk[0] = chunk_size_bytes / t_bytes;
114 const hid_t dcpl = H5Pcreate(H5P_DATASET_CREATE);
115 H5Pset_chunk(dcpl, ndims, chunk);
116 if (compression_level >= 0)
117 {
118 H5Pset_shuffle(dcpl);
119 H5Pset_deflate(dcpl, compression_level);
120 }
121
122 const hid_t d = H5Dcreate2(f, name_c, type, fspace, H5P_DEFAULT,
123 dcpl, H5P_DEFAULT);
124 H5Pclose(dcpl);
125 return d;
126 }
127 else if (status > 0)
128 {
129 // Dataset exists, open it.
130 const hid_t d = H5Dopen2(f, name_c, H5P_DEFAULT);
131
132 // Resize the dataset, set dims to its new size.
133 Dims old_dims(dims.ndims);
134 const hid_t dspace = H5Dget_space(d);
135 const int ndims_dset = H5Sget_simple_extent_ndims(dspace);
136 MFEM_VERIFY(ndims_dset == dims.ndims, "");
137 H5Sget_simple_extent_dims(dspace, old_dims, NULL);
138 H5Sclose(dspace);
139 dims[0] += old_dims[0];
140 H5Dset_extent(d, dims);
141
142 return d;
143 }
144 else
145 {
146 // Error occurred in H5LTfind_dataset.
147 MFEM_ABORT("Error finding HDF5 dataset " << name);
148 }
149}
150
151void VTKHDF::EnsureGroup(const std::string &name, hid_t &group)
152{
153 if (group != H5I_INVALID_HID) { return; }
154
155 const char *cname = name.c_str();
156 const htri_t found = H5Lexists(vtk, cname, H5P_DEFAULT);
157 Barrier();
158
159 if (found > 0)
160 {
161 group = H5Gopen(vtk, cname, H5P_DEFAULT);
162 }
163 else if (found == 0)
164 {
165 group = H5Gcreate2(vtk, cname, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT);
166 }
167 else
168 {
169 MFEM_ABORT("Error finding HDF5 group " << name);
170 }
171}
172
173template <typename T>
174void VTKHDF::AppendParData(hid_t f, const std::string &name, hsize_t locsize,
175 hsize_t offset, Dims globsize, T *data)
176{
177 const int ndims = globsize.ndims;
178 Dims dims = globsize;
179 const hid_t d = EnsureDataset(f, name, GetTypeID<T>(), dims);
180
181 // Write the new entry.
182 const hid_t dspace = H5Dget_space(d);
183 Dims start(ndims);
184 start[0] = dims[0] - globsize[0] + offset;
185 Dims count(ndims);
186 count[0] = locsize;
187 for (int i = 1; i < ndims; ++i) { count[i] = globsize[i]; }
188
189 H5Sselect_hyperslab(dspace, H5S_SELECT_SET, start, NULL, count, NULL);
190 H5Dwrite(d, GetTypeID<T>(), H5S_BLOCK, dspace, dxpl, data);
191 H5Sclose(dspace);
192
193 H5Dclose(d);
194}
195
196template <typename T>
197std::vector<T> VTKHDF::AllGather(const T loc) const
198{
199 std::vector<T> all(mpi_size);
200 if (UsingMpi())
201 {
202#ifdef MFEM_USE_MPI
203 const MPI_Datatype type = MPITypeMap<T>::mpi_type;
204 MPI_Allgather(&loc, 1, type, all.data(), 1, type, comm);
205#endif
206 }
207 else
208 {
209 all[0] = loc;
210 }
211 return all;
212}
213
214VTKHDF::OffsetTotal VTKHDF::GetOffsetAndTotal(const size_t loc) const
215{
216 const auto all = AllGather(uint64_t(loc));
217
218 size_t offset = 0;
219 for (int i = 0; i < mpi_rank; ++i)
220 {
221 offset += all[i];
222 }
223 size_t total = offset;
224 for (int i = mpi_rank; i < mpi_size; ++i)
225 {
226 total += all[i];
227 }
228
229 return {offset, total};
230}
231
232template <typename T>
233VTKHDF::OffsetTotal VTKHDF::AppendParVector(
234 hid_t f, const std::string &name, const std::vector<T> &data, Dims dims)
235{
236 const size_t locsize = data.size();
237 const auto offset_total = GetOffsetAndTotal(locsize);
238 const auto offset = offset_total.offset;
239 const auto total = offset_total.total;
240
241 hsize_t m = 1;
242 for (int i = 1; i < dims.ndims; ++i) { m *= dims[i]; }
243 dims[0] = total/m;
244
245 AppendParData(f, name, locsize/m, offset/m, dims, data.data());
246
247 return {offset/m, total/m};
248}
249
250bool VTKHDF::UsingMpi() const
251{
252#ifdef MFEM_USE_MPI
253 return comm != MPI_COMM_NULL;
254#else
255 return false;
256#endif
257}
258
259void VTKHDF::Barrier() const
260{
261#ifdef MFEM_USE_MPI
262 if (UsingMpi()) { MPI_Barrier(comm); }
263#endif
264}
265
266template <typename T>
267std::vector<T> VTKHDF::ReadDataset(const std::string &name) const
268{
269 const char *cname = name.c_str();
270 int ndims;
271 H5LTget_dataset_ndims(vtk, cname, &ndims);
272 Dims dims(ndims);
273 H5LTget_dataset_info(vtk, cname, dims, nullptr, nullptr);
274 std::vector<T> vals(dims.TotalSize());
275 H5LTread_dataset(vtk, cname, GetTypeID<T>(), vals.data());
276 return vals;
277}
278
279template <typename T>
280T VTKHDF::ReadValue(const std::string &name, hsize_t index) const
281{
282 const char *cname = name.c_str();
283
284 int ndims;
285 H5LTget_dataset_ndims(vtk, cname, &ndims);
286
287 const hid_t d = H5Dopen(vtk, cname, H5P_DEFAULT);
288
289 // Write the new entry.
290 const hid_t dspace = H5Dget_space(d);
291 Dims start(ndims);
292 start[0] = index;
293 Dims count(ndims);
294 for (int i = 0; i < ndims; ++i) { count[i] = 1; }
295
296 H5Sselect_hyperslab(dspace, H5S_SELECT_SET, start, NULL, count, NULL);
297 const hid_t memspace = H5Screate_simple(ndims, count, count);
298
299 T value;
300 H5Dread(d, GetTypeID<T>(), memspace, dspace, dxpl, &value);
301
302 H5Sclose(memspace);
303 H5Sclose(dspace);
304 H5Dclose(d);
305
306 return value;
307}
308
309void VTKHDF::TruncateDataset(const std::string &name, hsize_t size)
310{
311 const hid_t d = H5Dopen2(vtk, name.c_str(), H5P_DEFAULT);
312 const hid_t dspace = H5Dget_space(d);
313 const int ndims = H5Sget_simple_extent_ndims(dspace);
314 Dims dims(ndims);
315 H5Sget_simple_extent_dims(dspace, dims, NULL);
316 H5Sclose(dspace);
317 dims[0] = size;
318 H5Dset_extent(d, dims);
319 H5Dclose(d);
320}
321
322void VTKHDF::Truncate(const real_t t)
323{
324 // Find the first time step 'i' at least as large as 't'. Truncate all
325 // datasets at the corresponding offsets.
326 const std::vector<real_t> tvals = ReadDataset<real_t>("Steps/Values");
327 auto it = std::find_if(tvals.begin(), tvals.end(), [t](real_t t2) { return t2 >= t; });
328
329 // Sanity check: we can only use restart mode with the same number of MPI
330 // ranks (mesh partitions) as the originally save file.
331 {
332 Dims dims(1);
333 H5LTget_dataset_info(vtk, "NumberOfCells", dims, nullptr, nullptr);
334 MFEM_VERIFY(dims[0] == tvals.size() * mpi_size, "Incompatible VTKHDF sizes.");
335 }
336
337 // Index of found time index (may be 'one-past-the-end' if not found)
338 const ptrdiff_t i = std::distance(tvals.begin(), it);
339
340 // Only truncate if needed
341 const bool truncate = it != tvals.end();
342
343 // Number of steps we are keeping
344 nsteps = i;
345 H5LTset_attribute_ulong(vtk, "Steps", "NSteps", &nsteps, 1);
346
347 // We want to continue writing immediately after step 'i - 1'. If i = 0,
348 // then this is at the beginning of the file, and the offsets do not need
349 // to be updated.
350 hsize_t npoints = 0;
351 if (i > 0)
352 {
353 point_offsets.next = ReadValue<hsize_t>("Steps/PointOffsets", i - 1);
354 cell_offsets.next = ReadValue<hsize_t>("Steps/CellOffsets", i - 1);
355 connectivity_offsets.next =
356 ReadValue<hsize_t>("Steps/ConnectivityIdOffsets", i - 1);
357
358 for (int part = 0; part < mpi_size; ++part)
359 {
360 const hsize_t p_i = ReadValue<hsize_t>("Steps/PartOffsets", i - 1 + part);
361 npoints += ReadValue<hsize_t>("NumberOfPoints", p_i);
362 cell_offsets.next += ReadValue<hsize_t>("NumberOfCells", p_i);
363 connectivity_offsets.next +=
364 ReadValue<hsize_t>("NumberOfConnectivityIds", p_i);
365 }
366 point_offsets.next += npoints;
367 }
368
369 // Find the offsets associated with all saved grid functions.
370 const hid_t g = H5Gopen2(vtk, "Steps/PointDataOffsets", H5P_DEFAULT);
371 if (g != H5I_INVALID_HID)
372 {
373 std::vector<std::string> names;
374 auto itfn = [](hid_t, const char *name, const H5L_info2_t*, void *data)
375 {
376 auto names_ptr = static_cast<std::vector<std::string>*>(data);
377 names_ptr->emplace_back(name);
378 return herr_t(0);
379 };
380 H5Literate2(g, H5_INDEX_NAME, H5_ITER_NATIVE, nullptr, itfn, &names);
381 H5Gclose(g);
382
383 for (auto name : names)
384 {
385 const std::string dset_name = "Steps/PointDataOffsets/" + name;
386 hsize_t offset = 0;
387 if (i > 0)
388 {
389 offset = ReadValue<hsize_t>(dset_name, i - 1) + npoints;
390 }
391 point_data_offsets[name].next = offset;
392 if (truncate)
393 {
394 TruncateDataset(dset_name, nsteps);
395 TruncateDataset("PointData/" + name, offset);
396 }
397 }
398 }
399
400 if (truncate)
401 {
402 TruncateDataset("Steps/Values", nsteps);
403 TruncateDataset("Steps/PartOffsets", nsteps);
404 TruncateDataset("Steps/PointOffsets", nsteps);
405 TruncateDataset("Steps/CellOffsets", nsteps);
406 TruncateDataset("Steps/ConnectivityIdOffsets", nsteps);
407
408 TruncateDataset("NumberOfCells", nsteps * mpi_size);
409 TruncateDataset("NumberOfConnectivityIds", nsteps * mpi_size);
410 TruncateDataset("NumberOfPoints", nsteps * mpi_size);
411
412 TruncateDataset("CellData/attribute", cell_offsets.next);
413 TruncateDataset("Types", cell_offsets.next);
414 TruncateDataset("Points", point_offsets.next);
415 TruncateDataset("Connectivity", connectivity_offsets.next);
416 TruncateDataset("Offsets", cell_offsets.next + nsteps * mpi_size);
417 }
418}
419
420void VTKHDF::CreateFile(const std::string &filename, Restart restart)
421{
422 if (restart.enabled)
423 {
424 bool file_exists = mpi_rank == 0 && [&filename]()
425 {
426 std::ifstream f(filename);
427 return f.good();
428 }();
429
430#ifdef MFEM_USE_MPI
431 if (UsingMpi())
432 {
433 MPI_Allreduce(MPI_IN_PLACE, &file_exists, 1, MPI_CXX_BOOL, MPI_LOR, comm);
434 }
435#endif
436
437 if (file_exists)
438 {
439 // Disable file locking, allowing modification to files that may be
440 // open in ParaView (otherwise writes will fail).
441 H5Pset_file_locking(fapl, false, true);
442
443 file = H5Fopen(filename.c_str(), H5F_ACC_RDWR, fapl);
444 vtk = H5Gopen(file, "VTKHDF", H5P_DEFAULT);
445 Truncate(restart.time);
446 return;
447 }
448 }
449
450 // At this point, either restart is disabled, or file doesn't exist
451
452 // Delete the file if it exists
453 std::remove(filename.c_str());
454 // Create the new file
455 file = H5Fcreate(filename.c_str(), H5F_ACC_TRUNC, H5P_DEFAULT, fapl);
456 // Setup 'VTKHDF' group
457 SetupVTKHDF();
458}
459
460VTKHDF::VTKHDF(const std::string &filename, Restart restart)
461{
462 fapl = H5Pcreate(H5P_FILE_ACCESS);
463 CreateFile(filename, restart);
464}
465
466#ifdef MFEM_PARALLEL_HDF5
467
468static int MpiCommSize(MPI_Comm comm)
469{
470 int comm_size;
471 MPI_Comm_size(comm, &comm_size);
472 return comm_size;
473}
474
475static int MpiCommRank(MPI_Comm comm)
476{
477 int rank;
478 MPI_Comm_rank(comm, &rank);
479 return rank;
480}
481
482VTKHDF::VTKHDF(const std::string &filename, MPI_Comm comm_, Restart restart)
483 : comm(comm_),
484 mpi_size(MpiCommSize(comm)),
485 mpi_rank(MpiCommRank(comm))
486{
487 // Create file access property list, needed for parallel I/O
488 fapl = H5Pcreate(H5P_FILE_ACCESS);
489 const MPI_Info info = MPI_INFO_NULL;
490 H5Pset_fapl_mpio(fapl, comm, info);
491 // Create parallel data transfer property list
492 dxpl = H5Pcreate(H5P_DATASET_XFER);
493 H5Pset_dxpl_mpio(dxpl, H5FD_MPIO_COLLECTIVE);
494
495 CreateFile(filename, restart);
496}
497
498#endif
499
500template <typename T>
501void VTKHDF::AppendValue(const hid_t f, const std::string &name, T value)
502{
503 const hsize_t locsize = (mpi_rank == 0) ? 1 : 0;
504 AppendParData(f, name, locsize, 0, Dims({1}), &value);
505}
506
508{
509 EnsureSteps();
510
511 // Set the NSteps attribute
512 ++nsteps;
513 H5LTset_attribute_ulong(steps, ".", "NSteps", &nsteps, 1);
514
515 AppendValue(steps, "Values", t);
516 AppendValue(steps, "PartOffsets", part_offset);
517 AppendValue(steps, "PointOffsets", point_offsets.current);
518 AppendValue(steps, "CellOffsets", cell_offsets.current);
519 AppendValue(steps, "ConnectivityIdOffsets", connectivity_offsets.current);
520
521 if (!point_data_offsets.empty())
522 {
523 const hid_t g = H5Gopen2(steps, "PointDataOffsets", H5P_DEFAULT);
524 for (const auto &pd : point_data_offsets)
525 {
526 const char *name = pd.first.c_str();
527 AppendValue(g, name, pd.second.current);
528 }
529 H5Gclose(g);
530 }
531}
532
533template <typename FP_T>
534void VTKHDF::SaveMesh(const Mesh &mesh, bool high_order, int ref)
535{
536 // If refinement level not set, set to default value
537 if (ref <= 0)
538 {
539 ref = 1;
540 if (high_order)
541 {
542 if (auto *nodal_space = mesh.GetNodalFESpace())
543 {
544 ref = nodal_space->GetMaxElementOrder();
545 }
546 }
547 }
548
549 const Dims mpi_dims({mpi_size});
550
551 // If the mesh hasn't changed, we can return early.
552 if (!mesh_id.HasChanged(mesh, high_order, ref))
553 {
554 // The HDF5 format assumes that the "NumberOf" datasets will have size
555 // given by the number of parts (number of MPI ranks) times the number of
556 // time steps (see
557 // https://gitlab.kitware.com/vtk/vtk/-/issues/18981#note_1366124).
558 //
559 // If the mesh doesn't change, we don't increment the value in
560 // 'PartOffsets', and so these values in the "NumberOf" datasets will
561 // never be read, so we just fill them with a dummy value.
562 const hsize_t zero = 0;
563 AppendParData(vtk, "NumberOfPoints", 1, mpi_rank, mpi_dims, &zero);
564 AppendParData(vtk, "NumberOfCells", 1, mpi_rank, mpi_dims, &zero);
565 AppendParData(vtk, "NumberOfConnectivityIds", 1, mpi_rank, mpi_dims, &zero);
566 const int zero_int = 0;
567 AppendParData(vtk, "Offsets", 1, mpi_rank, mpi_dims, &zero_int);
568 return;
569 }
570
571 // Set the cached MeshId
572 mesh_id.Set(mesh, high_order, ref);
573
574 // Update the part offsets
575 part_offset = nsteps * mpi_size;
576
577 // Number of times to refine each element
578 const int ref_0 = high_order ? 1 : ref;
579 // Return the RefinementGeometry object for element 'e'
580 auto get_ref_geom = [&](int e, int r) -> RefinedGeometry&
581 {
582 const Geometry::Type geom = mesh.GetElementGeometry(e);
583 return *GlobGeometryRefiner.Refine(geom, r, 1);
584 };
585 // Return the number of vertices in element 'e'
586 auto get_nv = [&](int e)
587 {
588 return Geometries.NumVerts[mesh.GetElementGeometry(e)];
589 };
590 // Return the number of refined elements for element 'e'
591 auto get_ne_ref = [&](int e, int r)
592 {
593 return get_ref_geom(e, r).RefGeoms.Size() / get_nv(e);
594 };
595
596 // Count the points (and number of refined elements, needed if high_order is
597 // false).
598 std::vector<FP_T> points;
599 hsize_t ne_ref = 0;
600 hsize_t np = 0;
601 {
602 const int ne = mesh.GetNE();
603 for (int e = 0; e < ne; e++)
604 {
605 RefinedGeometry &ref_geom = get_ref_geom(e, ref);
606 np += ref_geom.RefPts.GetNPoints();
607 ne_ref += ref_geom.RefGeoms.Size() / get_nv(e);
608 }
609
610 points.reserve(np * 3);
611
613 DenseMatrix pmat;
614 for (int e = 0; e < ne; ++e)
615 {
616 RefinedGeometry &ref_geom = get_ref_geom(e, ref);
617 mesh.GetElementTransformation(e, &Tr);
618 Tr.Transform(ref_geom.RefPts, pmat);
619
620 for (int i = 0; i < pmat.Width(); i++)
621 {
622 points.push_back(FP_T(pmat(0,i)));
623 if (pmat.Height() > 1) { points.push_back(FP_T(pmat(1,i))); }
624 else { points.push_back(0.0); }
625 if (pmat.Height() > 2) { points.push_back(FP_T(pmat(2,i))); }
626 else { points.push_back(0.0); }
627 }
628 }
629 }
630
631 const int ne_0 = mesh.GetNE();
632 const hsize_t ne = high_order ? ne_0 : ne_ref;
633
634 AppendParData(vtk, "NumberOfPoints", 1, mpi_rank, mpi_dims, &np);
635 AppendParData(vtk, "NumberOfCells", 1, mpi_rank, mpi_dims, &ne);
636
637 // Save the number of points written
638 last_np = np;
639
640 // Write out 2D data for points
641 auto point_offset_total = AppendParVector(vtk, "Points", points, Dims({0, 3}));
642 point_offsets.Update(point_offset_total.total);
643
644 // Cell data
645 {
646 const auto e_offset_total = GetOffsetAndTotal(ne);
647 const auto e_offset = e_offset_total.offset;
648 const auto ne_total = e_offset_total.total;
649
650 cell_offsets.Update(ne_total);
651
652 // Offsets and connectivity
653 {
654 std::vector<int> offsets(ne + 1);
655 std::vector<int> connectivity;
656
657 int off = 0;
658 if (high_order)
659 {
660 Array<int> local_connectivity;
661 for (int e = 0; e < int(ne); ++e)
662 {
663 offsets[e] = off;
664 const Geometry::Type geom = mesh.GetElementGeometry(e);
665 CreateVTKElementConnectivity(local_connectivity, geom, ref);
666 const int nnodes = local_connectivity.Size();
667 for (int i = 0; i < nnodes; ++i)
668 {
669 connectivity.push_back(off + local_connectivity[i]);
670 }
671 off += nnodes;
672 }
673 offsets.back() = off;
674 }
675 else
676 {
677 int off_0 = 0;
678 int e_ref = 0;
679 for (int e = 0; e < ne_0; ++e)
680 {
681 const Geometry::Type geom = mesh.GetElementGeometry(e);
682 const int nv = get_nv(e);
683 RefinedGeometry &ref_geom = get_ref_geom(e, ref_0);
684 Array<int> &rg = ref_geom.RefGeoms;
685 for (int r = 0; r < rg.Size(); ++e_ref)
686 {
687 offsets[e_ref] = off;
688 off += nv;
689 const int *p = VTKGeometry::VertexPermutation[geom];
690 for (int k = 0; k < nv; ++k, ++r)
691 {
692 connectivity.push_back(off_0 + rg[p ? (r - k + p[k]) : r]);
693 }
694 }
695 off_0 += ref_geom.RefPts.Size();
696 }
697 offsets.back() = off;
698 }
699
700 const hsize_t n = connectivity.size();
701 AppendParData(vtk, "NumberOfConnectivityIds", 1, mpi_rank, mpi_dims, &n);
702
703 auto connectivity_offset_total
704 = AppendParVector(vtk, "Connectivity", connectivity);
705 connectivity_offsets.Update(connectivity_offset_total.total);
706
707 AppendParData(vtk, "Offsets", ne + 1, e_offset + mpi_rank,
708 Dims({ne_total + mpi_size}), offsets.data());
709
710 }
711
712 // Cell types
713 {
714 std::vector<unsigned char> cell_types(ne);
715 const int *vtk_geom_map =
717 int e_ref = 0;
718 for (int e = 0; e < ne_0; ++e)
719 {
720 const int ne_ref_e = get_ne_ref(e, ref_0);
721 for (int i = 0; i < ne_ref_e; ++i, ++e_ref)
722 {
723 cell_types[e_ref] = static_cast<unsigned char>(
724 vtk_geom_map[mesh.GetElementGeometry(e)]);
725 }
726 }
727 AppendParData(vtk, "Types", ne, e_offset, Dims({ne_total}),
728 cell_types.data());
729 }
730
731 // Attributes
732 {
733 // Ensure cell data group exists
734 EnsureGroup("CellData", cell_data);
735 std::vector<int> attributes(ne);
736 hsize_t e_ref = 0;
737 for (int e = 0; e < ne_0; ++e)
738 {
739 const int attr = mesh.GetAttribute(e);
740 const int ne_ref_e = get_ne_ref(e, ref_0);
741 for (int i = 0; i < ne_ref_e; ++i, ++e_ref)
742 {
743 attributes[e_ref] = attr;
744 }
745 }
746 AppendParData(cell_data, "attribute", ne, e_offset, Dims({ne_total}),
747 attributes.data());
748 }
749 }
750}
751
752template <typename FP_T>
753void VTKHDF::SaveGridFunction(const GridFunction &gf, const std::string &name)
754{
755 // Create the point data group if needed
756 EnsureGroup("PointData", point_data);
757
758 const Mesh &mesh = *gf.FESpace()->GetMesh();
759
760 MFEM_VERIFY(!mesh_id.HasChanged(mesh), "Mesh must be saved first");
761 const int ref = mesh_id.GetRefinementLevel();
762
763 const int vdim = gf.VectorDim();
764
765 std::vector<FP_T> point_values(vdim * last_np);
766 DenseMatrix vec_val, pmat;
767 int off = 0;
768 for (int e = 0; e < mesh.GetNE(); e++)
769 {
771 mesh.GetElementBaseGeometry(e), ref, 1);
772 gf.GetVectorValues(e, ref_geom.RefPts, vec_val, pmat);
773 for (int i = 0; i < vec_val.Width(); ++i)
774 {
775 for (int vd = 0; vd < vdim; ++vd)
776 {
777 point_values[off] = FP_T(vec_val(vd, i));
778 ++off;
779 }
780 }
781 }
782
783 Dims dims(vdim == 1 ? 1 : 2);
784 if (vdim > 1) { dims[1] = vdim; }
785 auto offset_total = AppendParVector(point_data, name, point_values, dims);
786 point_data_offsets[name].Update(offset_total.total);
787}
788
790{
791 H5Fflush(file, H5F_SCOPE_GLOBAL);
792}
793
795{
796 if (steps != H5I_INVALID_HID) { H5Gclose(steps); }
797 if (cell_data != H5I_INVALID_HID) { H5Gclose(cell_data); }
798 if (point_data != H5I_INVALID_HID) { H5Gclose(point_data); }
799 if (dxpl != H5P_DEFAULT) { H5Pclose(dxpl); }
800 if (vtk != H5I_INVALID_HID) { H5Gclose(vtk); }
801 if (fapl != H5I_INVALID_HID) { H5Pclose(fapl); }
802 if (file != H5I_INVALID_HID) { H5Fclose(file); }
803}
804
805template void VTKHDF::SaveMesh<float>(const Mesh&, bool, int);
806template void VTKHDF::SaveMesh<double>(const Mesh&, bool, int);
808 const std::string&);
810 const std::string&);
811
812} // namespace mfem
813
814#endif // MFEM_USE_HDF5
int Size() const
Return the logical size of the array.
Definition array.hpp:166
Data type dense matrix using column-major storage.
Definition densemat.hpp:24
Mesh * GetMesh() const
Returns the mesh.
Definition fespace.hpp:644
RefinedGeometry * Refine(Geometry::Type Geom, int Times, int ETimes=1)
Definition geom.cpp:1136
static const int NumVerts[NumGeom]
Definition geom.hpp:53
Class for grid function - Vector with associated FE space.
Definition gridfunc.hpp:32
FiniteElementSpace * FESpace()
int VectorDim() const
Shortcut for calling FiniteElementSpace::GetVectorDim() on the underlying fes.
Definition gridfunc.cpp:347
void GetVectorValues(int i, const IntegrationRule &ir, DenseMatrix &vals, DenseMatrix &tr) const
Definition gridfunc.cpp:707
int GetNPoints() const
Returns the number of the points in the integration rule.
Definition intrules.hpp:256
A standard isoparametric element transformation.
Definition eltrans.hpp:629
void Transform(const IntegrationPoint &, Vector &) override
Transform integration point from reference coordinates to physical coordinates and store them in the ...
Definition eltrans.cpp:532
Mesh data type.
Definition mesh.hpp:65
Geometry::Type GetElementGeometry(int i) const
Definition mesh.hpp:1535
int GetAttribute(int i) const
Return the attribute of element i.
Definition mesh.hpp:1484
const FiniteElementSpace * GetNodalFESpace() const
Definition mesh.cpp:6794
int GetNE() const
Returns number of elements.
Definition mesh.hpp:1377
void GetElementTransformation(int i, IsoparametricTransformation *ElTr) const
Builds the transformation defining the i-th element in ElTr. ElTr must be allocated in advance and wi...
Definition mesh.cpp:360
Geometry::Type GetElementBaseGeometry(int i) const
Definition mesh.hpp:1556
int Height() const
Get the height (size of output) of the Operator. Synonym with NumRows().
Definition operator.hpp:66
int Width() const
Get the width (size of input) of the Operator. Synonym with NumCols().
Definition operator.hpp:72
IntegrationRule RefPts
Definition geom.hpp:321
Array< int > RefGeoms
Definition geom.hpp:322
void SaveGridFunction(const GridFunction &gf, const std::string &name)
Save the grid function with the given name, appending as a new time step.
Definition vtkhdf.cpp:753
void SaveMesh(const Mesh &mesh, bool high_order=true, int ref=-1)
Save the mesh, appending as a new time step.
Definition vtkhdf.cpp:534
void UpdateSteps(real_t t)
Update the time step data after saving the mesh and grid functions.
Definition vtkhdf.cpp:507
VTKHDF(const std::string &filename, Restart restart=Restart::Disabled())
Create a new VTKHDF file for serial I/O.
Definition vtkhdf.cpp:460
void Flush()
Flush the file.
Definition vtkhdf.cpp:789
int index(int i, int j, int nx, int ny)
Definition life.cpp:236
mfem::real_t real_t
MFEM_HOST_DEVICE constexpr auto type(const tuple< T... > &values)
a function intended to be used for extracting the ith type from a tuple.
Definition tuple.hpp:347
GeometryRefiner GlobGeometryRefiner
Definition geom.cpp:2014
Geometry Geometries
Definition fe.cpp:49
float real_t
Definition config.hpp:46
void CreateVTKElementConnectivity(Array< int > &con, Geometry::Type geom, int ref)
Create the VTK element connectivity array for a given element geometry and refinement level.
Definition vtk.cpp:497
std::function< real_t(const Vector &)> f(real_t mass_coeff)
Definition lor_mms.hpp:30
real_t p(const Vector &x, real_t t)
static const int HighOrderMap[Geometry::NUM_GEOMETRIES]
Map from MFEM's Geometry::Type to arbitrary-order Lagrange VTK geometries.
Definition vtk.hpp:83
static const int * VertexPermutation[Geometry::NUM_GEOMETRIES]
Permutation from MFEM's vertex ordering to VTK's vertex ordering.
Definition vtk.hpp:76
static const int Map[Geometry::NUM_GEOMETRIES]
Map from MFEM's Geometry::Type to linear VTK geometries.
Definition vtk.hpp:79
Helper used in VTKHDF::VTKHDF for enabling/disabling restart mode.
Definition vtkhdf.hpp:41