DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
VTKHDF.h
1// Copyright (C) 2024-2025 Chris Richardson, Jørgen S. Dokken
2//
3// This file is part of DOLFINx (https://www.fenicsproject.org)
4//
5// SPDX-License-Identifier: LGPL-3.0-or-later
6
7#include "HDF5Interface.h"
8#include <algorithm>
9#include <concepts>
10#include <dolfinx/common/IndexMap.h>
11#include <dolfinx/graph/partition.h>
12#include <dolfinx/io/cells.h>
13#include <dolfinx/mesh/Mesh.h>
14#include <dolfinx/mesh/Topology.h>
15#include <dolfinx/mesh/utils.h>
16#include <format>
17#include <map>
18#include <stdexcept>
19#include <string_view>
20#include <vector>
21
22namespace dolfinx::io::VTKHDF
23{
31template <std::floating_point U>
32void write_mesh(const std::filesystem::path& filename,
33 const mesh::Mesh<U>& mesh)
34{
35 hid_t h5file = hdf5::open_file(mesh.comm(), filename, "w", true);
36
37 // Create VTKHDF group
38 hdf5::add_group(h5file, "VTKHDF");
39 hid_t vtk_group = H5Gopen(h5file, "VTKHDF", H5P_DEFAULT);
40 hdf5::set_attribute(vtk_group, "Version", std::vector{2, 2});
41 hdf5::set_attribute(vtk_group, "Type", "UnstructuredGrid");
42 H5Gclose(vtk_group);
43
44 // Extract topology information for each cell type
45 std::vector<mesh::CellType> cell_types
46 = mesh.topology()->entity_types(mesh.topology()->dim());
47
48 std::vector cell_index_maps
49 = mesh.topology()->index_maps(mesh.topology()->dim());
50 std::vector<std::int32_t> num_cells;
51 std::vector<std::int64_t> num_cells_global;
52 for (auto& im : cell_index_maps)
53 {
54 num_cells.push_back(im->size_local());
55 num_cells_global.push_back(im->size_global());
56 }
57
58 // Geometry dofmap and points
59 std::shared_ptr<const common::IndexMap> geom_imap
60 = mesh.geometry().index_map();
61 std::int64_t size_global = geom_imap->size_global();
62 std::vector<std::int64_t> geom_global_shape = {size_global, 3};
63 std::array<std::int64_t, 2> geom_irange = geom_imap->local_range();
64 hdf5::write_dataset(h5file, "/VTKHDF/Points", mesh.geometry().x().data(),
65 geom_irange, geom_global_shape, true, false);
66 hdf5::write_dataset(h5file, "VTKHDF/NumberOfPoints", &size_global, {0, 1},
67 {1}, true, false);
68
69 // Note: VTKHDF stores the cells as an adjacency list, where cell
70 // types might be jumbled up
71 std::vector<std::int64_t> topology_flattened;
72 std::vector<std::int64_t> topology_offsets;
73 std::vector<std::uint8_t> vtkcelltypes;
74 for (std::size_t i = 0; i < cell_index_maps.size(); ++i)
75 {
76 md::mdspan<const std::int32_t, md::dextents<std::size_t, 2>> g_dofmap
77 = mesh.geometry().dofmaps().at(i);
78
79 std::vector<std::uint16_t> perm
80 = cells::perm_vtk(cell_types[i], g_dofmap.extent(1));
81 std::vector<std::uint16_t> inverse_perm = cells::transpose(perm);
82 std::vector<std::int32_t> local_dm;
83 local_dm.reserve(g_dofmap.extent(1) * num_cells[i]);
84 for (int j = 0; j < num_cells[i]; ++j)
85 for (std::size_t k = 0; k < g_dofmap.extent(1); ++k)
86 local_dm.push_back(g_dofmap(j, inverse_perm[k]));
87
88 std::vector<std::int64_t> global_dm(local_dm.size());
89 geom_imap->local_to_global(local_dm, global_dm);
90
91 topology_flattened.insert(topology_flattened.end(), global_dm.begin(),
92 global_dm.end());
93 topology_offsets.insert(topology_offsets.end(), g_dofmap.extent(0),
94 g_dofmap.extent(1));
95 vtkcelltypes.insert(
96 vtkcelltypes.end(), cell_index_maps[i]->size_local(),
97 cells::get_vtk_cell_type(cell_types[i], mesh.topology()->dim()));
98 }
99
100 // Create topo_offsets
101 std::partial_sum(topology_offsets.cbegin(), topology_offsets.cend(),
102 topology_offsets.begin());
103
104 std::vector<int> num_nodes_per_cell;
105 std::vector<std::int64_t> cell_start_pos;
106 std::vector<std::int64_t> cell_stop_pos;
107 for (std::size_t i = 0; i < cell_index_maps.size(); ++i)
108 {
109 num_nodes_per_cell.push_back(mesh.geometry().cmaps().at(i).dim());
110 std::array<std::int64_t, 2> r = cell_index_maps[i]->local_range();
111 cell_start_pos.push_back(r[0]);
112 cell_stop_pos.push_back(r[1]);
113 }
114
115 // Compute overall cell offset from offsets for each cell type
116 std::int64_t offset_start_position
117 = std::accumulate(cell_start_pos.begin(), cell_start_pos.end(), 0);
118 std::int64_t offset_stop_position
119 = std::accumulate(cell_stop_pos.begin(), cell_stop_pos.end(), 0);
120
121 // Compute overall topology offset from offsets for each cell type
122 std::int64_t topology_start
123 = std::inner_product(num_nodes_per_cell.begin(), num_nodes_per_cell.end(),
124 cell_start_pos.begin(), 0);
125
126 std::transform(topology_offsets.cbegin(), topology_offsets.cend(),
127 topology_offsets.begin(),
128 [topology_start](auto x) { return x + topology_start; });
129
130 std::int64_t num_all_cells_global
131 = std::accumulate(num_cells_global.begin(), num_cells_global.end(), 0);
132 hdf5::write_dataset(h5file, "/VTKHDF/Offsets", topology_offsets.data(),
133 {offset_start_position + 1, offset_stop_position + 1},
134 {num_all_cells_global + 1}, true, false);
135
136 // Store global mesh connectivity
137 std::int64_t topology_size_global
138 = std::inner_product(num_nodes_per_cell.begin(), num_nodes_per_cell.end(),
139 num_cells_global.begin(), 0);
140
141 std::int64_t topology_stop = topology_start + topology_flattened.size();
142 hdf5::write_dataset(h5file, "/VTKHDF/Connectivity", topology_flattened.data(),
143 {topology_start, topology_stop}, {topology_size_global},
144 true, false);
145
146 // Store cell types
147 hdf5::write_dataset(h5file, "/VTKHDF/Types", vtkcelltypes.data(),
148 {offset_start_position, offset_stop_position},
149 {num_all_cells_global}, true, false);
150 hdf5::write_dataset(h5file, "/VTKHDF/NumberOfConnectivityIds",
151 &topology_size_global, {0, 1}, {1}, true, false);
152 hdf5::write_dataset(h5file, "/VTKHDF/NumberOfCells", &num_all_cells_global,
153 {0, 1}, {1}, true, false);
154 hdf5::close_file(h5file);
155}
156
178template <std::floating_point U>
179void write_data(std::string_view point_or_cell,
180 const std::filesystem::path& filename,
181 const mesh::Mesh<U>& mesh, const std::vector<U>& data,
182 double time)
183{
184 std::vector<std::shared_ptr<const common::IndexMap>> index_maps;
185 if (point_or_cell == "Point")
186 index_maps = {mesh.geometry().index_map()};
187 else if (point_or_cell == "Cell")
188 index_maps = mesh.topology()->index_maps(mesh.topology()->dim());
189 else
190 throw std::runtime_error("Selection must be Point or Cell");
191
192 const std::string poc(point_or_cell);
193 std::string dataset_name = std::format("/VTKHDF/{}Data/u", poc);
194 int npoints
195 = std::accumulate(index_maps.begin(), index_maps.end(), 0,
196 [](int a, auto im) { return a + im->size_local(); });
197 int data_width = data.size() / npoints;
198 if (data.size() % npoints != 0)
199 {
200 throw std::runtime_error(
201 "Data size mismatch with number of local vertices/cells");
202 }
203 spdlog::debug("Data vector width={}", data_width);
204
205 hid_t h5file = hdf5::open_file(mesh.comm(), filename, "a", true);
206 hdf5::add_group(h5file, "VTKHDF/Steps");
207 hid_t vtk_group = H5Gopen(h5file, "VTKHDF/Steps", H5P_DEFAULT);
208
209 std::int64_t point_data_offset = 0;
210 if (htri_t attr_exists = H5Aexists(vtk_group, "NSteps"); attr_exists < 0)
211 throw std::runtime_error("Error checking attribute");
212 else if (attr_exists == 0)
213 hdf5::set_attribute(vtk_group, "NSteps", 1);
214 else
215 {
216 // Read and increment attribute
217 std::int32_t nsteps = 0;
218 hid_t attr_id = H5Aopen(vtk_group, "NSteps", H5P_DEFAULT);
219 H5Aread(attr_id, H5T_NATIVE_INT32, &nsteps);
220 nsteps++;
221 H5Awrite(attr_id, H5T_NATIVE_INT32, &nsteps);
222 H5Aclose(attr_id);
223
224 std::vector<std::int64_t> data_shape
225 = hdf5::get_dataset_shape(h5file, dataset_name);
226 assert(data_shape.size() == 2);
227 point_data_offset = data_shape[0];
228 }
229 H5Gclose(vtk_group);
230
231 // Add a single value to end of a 1D dataset
232 auto append_dataset
233 = [&h5file]<typename T>(const std::string& dset_name, T value)
234 {
235 std::int32_t s = 0;
236 if (hdf5::has_dataset(h5file, dset_name))
237 {
238 std::vector<std::int64_t> shape
239 = hdf5::get_dataset_shape(h5file, dset_name);
240 assert(shape.size() == 1);
241 s = shape[0];
242 }
243 hdf5::write_dataset(h5file, dset_name, &value, {s, s + 1}, {s + 1}, true,
244 true);
245 };
246
247 // Mesh remains the same, so these values are the same for each time step
248 append_dataset("/VTKHDF/Steps/CellOffsets", 0);
249 append_dataset("/VTKHDF/Steps/ConnectivityIdOffsets", 0);
250 append_dataset("/VTKHDF/Steps/NumberOfParts", 1);
251 append_dataset("/VTKHDF/Steps/PartOffsets", 0);
252 append_dataset("/VTKHDF/Steps/PointOffsets", 0);
253
254 // Add the current data size to the end of the offset array
255 hdf5::add_group(h5file, std::format("/VTKHDF/Steps/{}DataOffsets", poc));
256 append_dataset(std::format("/VTKHDF/Steps/{}DataOffsets/u", poc),
257 point_data_offset);
258
259 // Time values
260 // FIXME: check these are increasing?
261 append_dataset("/VTKHDF/Steps/Values", time);
262
263 std::string group_name = std::format("/VTKHDF/{}Data", poc);
264 hdf5::add_group(h5file, group_name);
265
266 // Add point/cell data into dataset, extending each time by
267 // global_size with each process writing its own part.
268 std::int64_t range0 = std::accumulate(index_maps.begin(), index_maps.end(), 0,
269 [](int a, auto im)
270 { return a + im->local_range()[0]; });
271 std::array<std::int64_t, 2> range{range0, range0 + npoints};
272
273 std::int64_t global_size = std::accumulate(
274 index_maps.begin(), index_maps.end(), 0,
275 [](std::int64_t a, auto im) { return a + im->size_global(); });
276
277 std::vector<std::int64_t> shape0 = {global_size, data_width};
278 if (hdf5::has_dataset(h5file, dataset_name))
279 {
280 std::vector<std::int64_t> shape
281 = hdf5::get_dataset_shape(h5file, dataset_name);
282 assert(shape.size() == 2);
283 std::int64_t offset = shape[0];
284 range[0] += offset;
285 range[1] += offset;
286 shape0[0] += offset;
287 hdf5::write_dataset(h5file, dataset_name, data.data(), range, shape0, true,
288 true);
289 }
290 else
291 {
292 hdf5::write_dataset(h5file, dataset_name, data.data(), range, shape0, true,
293 true);
294 if (data_width > 1)
295 {
296 hid_t dset_id = hdf5::open_dataset(h5file, dataset_name);
297 hdf5::set_attribute(dset_id, "NumberOfComponents", data_width);
298 H5Dclose(dset_id);
299 hid_t vtk_group = H5Gopen(h5file, group_name.c_str(), H5P_DEFAULT);
300 hdf5::set_attribute(vtk_group, "Vectors", "u");
301 H5Gclose(vtk_group);
302 }
303 }
304
305 hdf5::close_file(h5file);
306}
307
319template <std::floating_point U>
320mesh::Mesh<U> read_mesh(MPI_Comm comm, const std::filesystem::path& filename,
321 std::size_t gdim = 3,
322 std::optional<std::int32_t> max_facet_to_cell_links = 2)
323{
324 hid_t h5file = hdf5::open_file(comm, filename, "r", true);
325
326 std::vector<std::int64_t> shape
327 = hdf5::get_dataset_shape(h5file, "/VTKHDF/Types");
328 int rank = dolfinx::MPI::rank(comm);
329 int mpi_size = dolfinx::MPI::size(comm);
330 std::array<std::int64_t, 2> local_cell_range
331 = common::local_range(rank, shape[0], mpi_size);
332
333 hid_t dset_id = hdf5::open_dataset(h5file, "/VTKHDF/Types");
334 std::vector<std::uint8_t> types
335 = hdf5::read_dataset<std::uint8_t>(dset_id, local_cell_range, true);
336 H5Dclose(dset_id);
337
338 // Read in offsets to determine the different cell-types in the mesh
339 dset_id = hdf5::open_dataset(h5file, "/VTKHDF/Offsets");
340 std::vector<std::int64_t> offsets = hdf5::read_dataset<std::int64_t>(
341 dset_id, {local_cell_range[0], local_cell_range[1] + 1}, true);
342 H5Dclose(dset_id);
343
344 // Convert cell offsets to cell type and cell degree tuples
345 std::vector<std::array<std::uint8_t, 2>> types_unique;
346 std::vector<std::uint8_t> cell_degrees;
347 for (std::size_t i = 0; i < types.size(); ++i)
348 {
349 std::int64_t num_nodes = offsets[i + 1] - offsets[i];
350 auto [cell_type, degree] = io::cells::vtk_to_dolfinx(types[i]);
351 // If arbitrary order Lagrange VTK cell (indicated by -1), determine degree
352 // from number of nodes
353
354 std::uint8_t cell_degree
355 = degree == -1 ? io::cells::cell_degree(cell_type, num_nodes)
356 : (std::uint8_t)degree;
357 types_unique.push_back({types[i], cell_degree});
358 cell_degrees.push_back(cell_degree);
359 }
360 {
361 std::ranges::sort(types_unique);
362 auto [unique_end, range_end] = std::ranges::unique(types_unique);
363 types_unique.erase(unique_end, range_end);
364 }
365
366 // Share cell types with all processes to make global list of cell
367 // types
368 // FIXME: amount of data is small, but number of connections does not
369 // scale
370 int count = 2 * types_unique.size();
371 std::vector<std::int32_t> recv_count(mpi_size);
372 MPI_Allgather(&count, 1, MPI_INT32_T, recv_count.data(), 1, MPI_INT32_T,
373 comm);
374 std::vector<std::int32_t> recv_offsets(mpi_size + 1, 0);
375 std::partial_sum(recv_count.begin(), recv_count.end(),
376 recv_offsets.begin() + 1);
377
378 std::vector<std::array<std::uint8_t, 2>> recv_types;
379 {
380 std::vector<std::uint8_t> send_types;
381 for (std::array<std::uint8_t, 2> t : types_unique)
382 send_types.insert(send_types.end(), t.begin(), t.end());
383
384 std::vector<std::uint8_t> recv_types_buffer(recv_offsets.back());
385 MPI_Allgatherv(send_types.data(), send_types.size(), MPI_UINT8_T,
386 recv_types_buffer.data(), recv_count.data(),
387 recv_offsets.data(), MPI_UINT8_T, comm);
388
389 for (std::size_t i = 0; i < recv_types_buffer.size(); i += 2)
390 recv_types.push_back({recv_types_buffer[i], recv_types_buffer[i + 1]});
391
392 std::ranges::sort(recv_types);
393 auto [unique_end, range_end] = std::ranges::unique(recv_types);
394 recv_types.erase(unique_end, range_end);
395 }
396
397 // Map from VTKCellType to index in list of (cell types, degree)
398 std::map<std::array<std::uint8_t, 2>, std::int32_t> type_to_index;
399 std::vector<mesh::CellType> dolfinx_cell_type;
400 std::vector<std::uint8_t> dolfinx_cell_degree;
401 for (std::array<std::uint8_t, 2> ct : recv_types)
402 {
403 mesh::CellType cell_type = std::get<0>(io::cells::vtk_to_dolfinx(ct[0]));
404 type_to_index.insert({ct, dolfinx_cell_degree.size()});
405 dolfinx_cell_degree.push_back(ct[1]);
406 dolfinx_cell_type.push_back(cell_type);
407 }
408
409 dset_id = hdf5::open_dataset(h5file, "/VTKHDF/NumberOfPoints");
410 std::vector npoints = hdf5::read_dataset<std::int64_t>(dset_id, {0, 1}, true);
411 H5Dclose(dset_id);
412 spdlog::info("Mesh with {} points", npoints[0]);
413 std::array<std::int64_t, 2> local_point_range
414 = common::local_range(rank, npoints[0], mpi_size);
415
416 std::vector<std::int64_t> x_shape
417 = hdf5::get_dataset_shape(h5file, "/VTKHDF/Points");
418 dset_id = hdf5::open_dataset(h5file, "/VTKHDF/Points");
419 std::vector<U> points_local
420 = hdf5::read_dataset<U>(dset_id, local_point_range, true);
421 H5Dclose(dset_id);
422
423 // Remove coordinates if gdim != 3
424 if (gdim > 3)
425 {
426 throw std::runtime_error("Geometric dimension must be less than or equal "
427 "to 3.");
428 }
429 std::vector<U> points_pruned((local_point_range[1] - local_point_range[0])
430 * gdim);
431 for (std::int64_t i = 0; i < local_point_range[1] - local_point_range[0]; ++i)
432 {
433 std::copy_n(points_local.begin() + i * 3, gdim,
434 points_pruned.begin() + i * gdim);
435 }
436
437 dset_id = hdf5::open_dataset(h5file, "/VTKHDF/Connectivity");
438 std::vector<std::int64_t> topology = hdf5::read_dataset<std::int64_t>(
439 dset_id, {offsets.front(), offsets.back()}, true);
440 H5Dclose(dset_id);
441 std::transform(offsets.cbegin(), offsets.cend(), offsets.begin(),
442 [offset = offsets.front()](auto x) { return x - offset; });
443 hdf5::close_file(h5file);
444
445 // Create cell topologies for each celltype in mesh
446 std::vector<std::vector<std::int64_t>> cells_local(recv_types.size());
447 for (std::size_t j = 0; j < types.size(); ++j)
448 {
449 std::int32_t type_index = type_to_index.at({types[j], cell_degrees[j]});
450 mesh::CellType cell_type = dolfinx_cell_type[type_index];
451 std::vector<std::uint16_t> perm
452 = cells::perm_vtk(cell_type, offsets[j + 1] - offsets[j]);
453 for (std::int64_t k = 0; k < offsets[j + 1] - offsets[j]; ++k)
454 cells_local[type_index].push_back(topology[perm[k] + offsets[j]]);
455 }
456
457 // Make coordinate elements
458 std::vector<fem::CoordinateElement<U>> coordinate_elements;
459 std::transform(
460 dolfinx_cell_type.cbegin(), dolfinx_cell_type.cend(),
461 dolfinx_cell_degree.cbegin(), std::back_inserter(coordinate_elements),
462 [](auto cell_type, auto cell_degree)
463 {
464 basix::element::lagrange_variant variant
465 = (cell_degree > 2) ? basix::element::lagrange_variant::equispaced
466 : basix::element::lagrange_variant::unset;
467 return fem::CoordinateElement<U>(cell_type, cell_degree, variant);
468 });
469
470 std::vector<std::span<const std::int64_t>> cells_span(cells_local.begin(),
471 cells_local.end());
472 return mesh::create_mesh(comm, comm, cells_span, coordinate_elements, comm,
473 points_pruned, {(std::size_t)x_shape[0], gdim},
474 graph::Partitioner{}, mesh::GhostMode::none,
475 max_facet_to_cell_links, 1);
476}
477} // namespace dolfinx::io::VTKHDF
Functions supporting mesh operations.
int size(MPI_Comm comm)
Definition MPI.cpp:81
int rank(MPI_Comm comm)
Return process rank for the communicator.
Definition MPI.cpp:73
constexpr std::array< std::int64_t, 2 > local_range(int index, std::int64_t N, int size)
Partition a global range [0, N - 1] across callers into non-overlapping sub-partitions of almost equa...
Definition local_range.h:26
std::tuple< mesh::CellType, std::int8_t > vtk_to_dolfinx(std::int8_t vtk_cell_type)
Get DOLFINx cell type and degree from VTK cell type.
Definition cells.h:186
std::int8_t get_vtk_cell_type(mesh::CellType cell, int dim)
Get VTK cell identifier.
Definition cells.cpp:717
std::vector< std::uint16_t > perm_vtk(mesh::CellType type, int num_nodes)
Permutation array to map from VTK to DOLFINx node ordering.
Definition cells.cpp:534
std::vector< std::uint16_t > transpose(std::span< const std::uint16_t > map)
Compute the transpose of a re-ordering map.
Definition cells.cpp:688
int cell_degree(mesh::CellType type, int num_nodes)
Get the Lagrange order of a given cell with a given number of nodes.
Definition cells.cpp:610
CellType
Cell type identifier.
Definition cell_types.h:22
Mesh< typename std::remove_reference_t< typename U::value_type > > create_mesh(MPI_Comm comm, MPI_Comm commt, std::vector< std::span< const std::int64_t > > cells, const std::vector< fem::CoordinateElement< typename std::remove_reference_t< typename U::value_type > > > &elements, MPI_Comm commg, const U &x, std::array< std::size_t, 2 > xshape, const graph::Partitioner &partitioner, GhostMode ghost_mode, std::optional< std::int32_t > max_facet_to_cell_links, int num_threads, const CellReorderFunction &reorder_fn=graph::reorder_rcm)
Create a distributed mesh::Mesh from mesh data and using the provided graph partitioning function for...
Definition utils.h:1246