DOLFINx 0.10.0
DOLFINx C++ interface
Loading...
Searching...
No Matches
ADIOS2Writers.h
1// Copyright (C) 2021-2023 Jørgen S. Dokken and Garth N. Wells
2//
3// This file is part of DOLFINX (https://www.fenicsproject.org)
4//
5// SPDX-License-Identifier: LGPL-3.0-or-later
6
7#pragma once
8
9#ifdef HAS_ADIOS2
10
11#include "vtk_utils.h"
12#include <adios2.h>
13#include <algorithm>
14#include <basix/mdspan.hpp>
15#include <cassert>
16#include <complex>
17#include <concepts>
18#include <dolfinx/common/IndexMap.h>
19#include <dolfinx/fem/DofMap.h>
20#include <dolfinx/fem/FiniteElement.h>
21#include <dolfinx/fem/Function.h>
22#include <dolfinx/mesh/Geometry.h>
23#include <dolfinx/mesh/Mesh.h>
24#include <filesystem>
25#include <memory>
26#include <mpi.h>
27#include <string>
28#include <type_traits>
29#include <variant>
30#include <vector>
31
34
35namespace dolfinx::fem
36{
37template <dolfinx::scalar T, std::floating_point U>
38class Function;
39}
40
41namespace dolfinx::io
42{
43namespace adios2_writer
44{
46template <std::floating_point T>
47using U = std::vector<std::variant<
48 std::shared_ptr<const fem::Function<float, T>>,
49 std::shared_ptr<const fem::Function<double, T>>,
50 std::shared_ptr<const fem::Function<std::complex<float>, T>>,
51 std::shared_ptr<const fem::Function<std::complex<double>, T>>>>;
52} // namespace adios2_writer
53
55class ADIOS2Writer
56{
57protected:
64 ADIOS2Writer(MPI_Comm comm, const std::filesystem::path& filename,
65 std::string tag, std::string engine);
66
68 ADIOS2Writer(ADIOS2Writer&& writer) = default;
69
71 ADIOS2Writer(const ADIOS2Writer&) = delete;
72
74 ~ADIOS2Writer();
75
77 ADIOS2Writer& operator=(ADIOS2Writer&& writer) = default;
78
79 // Copy assignment
80 ADIOS2Writer& operator=(const ADIOS2Writer&) = delete;
81
82public:
84 void close();
85
86protected:
87 std::unique_ptr<adios2::ADIOS> _adios;
88 std::unique_ptr<adios2::IO> _io;
89 std::unique_ptr<adios2::Engine> _engine;
90};
91
93namespace impl_adios2
94{
97constexpr std::array field_ext = {"_real", "_imag"};
98
101template <class T>
102adios2::Attribute<T> define_attribute(adios2::IO& io, std::string name,
103 const T& value, std::string var_name = "",
104 std::string separator = "/")
105{
106 if (adios2::Attribute<T> attr = io.InquireAttribute<T>(name); attr)
107 return attr;
108 else
109 return io.DefineAttribute<T>(name, value, var_name, separator);
110}
111
114template <class T>
115adios2::Variable<T> define_variable(adios2::IO& io, std::string name,
116 const adios2::Dims& shape = adios2::Dims(),
117 const adios2::Dims& start = adios2::Dims(),
118 const adios2::Dims& count = adios2::Dims())
119{
120 if (adios2::Variable v = io.InquireVariable<T>(name); v)
121 {
122 if (v.Count() != count and v.ShapeID() == adios2::ShapeID::LocalArray)
123 v.SetSelection({start, count});
124 return v;
125 }
126 else
127 return io.DefineVariable<T>(name, shape, start, count);
128}
129
131template <std::floating_point T>
132std::shared_ptr<const mesh::Mesh<T>>
133extract_common_mesh(const typename adios2_writer::U<T>& u)
134{
135 // Extract mesh from first function
136 assert(!u.empty());
137 auto mesh = std::visit([](auto&& u) { return u->function_space()->mesh(); },
138 u.front());
139 assert(mesh);
140
141 // Check that all functions share the same mesh
142 for (auto& v : u)
143 {
144 std::visit(
145 [&mesh](auto&& u)
146 {
147 if (mesh != u->function_space()->mesh())
148 {
149 throw std::runtime_error(
150 "ADIOS2Writer only supports functions sharing the same mesh");
151 }
152 },
153 v);
154 }
155
156 return mesh;
157}
158
159} // namespace impl_adios2
160
162namespace impl_vtx
163{
166std::stringstream create_vtk_schema(const std::vector<std::string>& point_data,
167 const std::vector<std::string>& cell_data);
168
170template <std::floating_point T>
171std::tuple<std::vector<std::string>, std::vector<std::string>>
172extract_function_names(const typename adios2_writer::U<T>& u)
173{
174 std::vector<std::string> names, dg0_names;
175 for (auto& v : u)
176 {
177 std::visit(
178 [&names, &dg0_names](auto&& u)
179 {
180 using U = std::decay_t<decltype(u)>;
181 using X = typename U::element_type;
182 std::vector<std::string>* fnames = &names;
183 if (impl::is_cellwise(*(u->function_space()->element())))
184 {
185 fnames = &dg0_names;
186 }
187 if constexpr (std::is_floating_point_v<typename X::value_type>)
188 fnames->push_back(u->name);
189 else
190 {
191 fnames->push_back(u->name + impl_adios2::field_ext[0]);
192 fnames->push_back(u->name + impl_adios2::field_ext[1]);
193 }
194 },
195 v);
196 }
197
198 return {names, dg0_names};
199}
200
210template <typename T, std::floating_point X>
211void vtx_write_data(adios2::IO& io, adios2::Engine& engine,
212 const fem::Function<T, X>& u)
213{
214 // Get function data array and information about layout
215 assert(u.x());
216 std::span<const T> u_vector = u.x()->array();
217
218 // Pad to 3D if vector/tensor is product of dimensions is smaller than
219 // 3**rank to ensure that we can visualize them correctly in Paraview
220 std::span<const std::size_t> value_shape
221 = u.function_space()->element()->value_shape();
222 std::size_t rank = value_shape.size();
223 std::size_t num_comp = std::reduce(value_shape.begin(), value_shape.end(), 1,
224 std::multiplies{});
225 if (num_comp < std::pow(3, rank))
226 num_comp = std::pow(3, rank);
227
228 std::shared_ptr<const fem::DofMap> dofmap = u.function_space()->dofmap();
229 assert(dofmap);
230 std::shared_ptr<const common::IndexMap> index_map = dofmap->index_map;
231 assert(index_map);
232 int index_map_bs = dofmap->index_map_bs();
233 int dofmap_bs = dofmap->bs();
234 std::uint32_t num_dofs = index_map_bs
235 * (index_map->size_local() + index_map->num_ghosts())
236 / dofmap_bs;
237 if constexpr (std::is_scalar_v<T>)
238 {
239 // ---- Real
240 std::vector<T> data(num_dofs * num_comp, 0);
241 for (std::size_t i = 0; i < num_dofs; ++i)
242 for (int j = 0; j < index_map_bs; ++j)
243 data[i * num_comp + j] = u_vector[i * index_map_bs + j];
244
245 adios2::Variable output = impl_adios2::define_variable<T>(
246 io, u.name, {}, {}, {num_dofs, num_comp});
247 spdlog::debug("Output data size={}", data.size());
248 engine.Put(output, data.data(), adios2::Mode::Sync);
249 }
250 else
251 {
252 // ---- Complex
253 using U = typename T::value_type;
254
255 std::vector<U> data(num_dofs * num_comp, 0);
256 for (std::size_t i = 0; i < num_dofs; ++i)
257 for (int j = 0; j < index_map_bs; ++j)
258 data[i * num_comp + j] = std::real(u_vector[i * index_map_bs + j]);
259
260 adios2::Variable output_real = impl_adios2::define_variable<U>(
261 io, u.name + impl_adios2::field_ext[0], {}, {}, {num_dofs, num_comp});
262 engine.Put(output_real, data.data(), adios2::Mode::Sync);
263
264 std::ranges::fill(data, 0);
265 for (std::size_t i = 0; i < num_dofs; ++i)
266 for (int j = 0; j < index_map_bs; ++j)
267 data[i * num_comp + j] = std::imag(u_vector[i * index_map_bs + j]);
268 adios2::Variable output_imag = impl_adios2::define_variable<U>(
269 io, u.name + impl_adios2::field_ext[1], {}, {}, {num_dofs, num_comp});
270 engine.Put(output_imag, data.data(), adios2::Mode::Sync);
271 }
272}
273
278template <std::floating_point T>
279void vtx_write_mesh(adios2::IO& io, adios2::Engine& engine,
280 const mesh::Mesh<T>& mesh)
281{
282 const mesh::Geometry<T>& geometry = mesh.geometry();
283 auto topology = mesh.topology();
284 assert(topology);
285
286 // "Put" geometry
287 std::shared_ptr<const common::IndexMap> x_map = geometry.index_map();
288 std::uint32_t num_vertices = x_map->size_local() + x_map->num_ghosts();
289 adios2::Variable local_geometry = impl_adios2::define_variable<T>(
290 io, "geometry", {}, {}, {num_vertices, 3});
291 spdlog::debug("Put local_geometry: {}x3", num_vertices);
292 engine.Put(local_geometry, geometry.x().data());
293
294 // Put number of nodes. The mesh data is written with local indices,
295 // therefore we need the ghost vertices.
296 adios2::Variable vertices = impl_adios2::define_variable<std::uint32_t>(
297 io, "NumberOfNodes", {adios2::LocalValueDim});
298 engine.Put<std::uint32_t>(vertices, num_vertices);
299
300 auto [vtkcells, shape]
301 = io::extract_vtk_connectivity(geometry.dofmap(), topology->cell_type());
302
303 // Add cell metadata
304 int tdim = topology->dim();
305 adios2::Variable cell_var = impl_adios2::define_variable<std::uint32_t>(
306 io, "NumberOfCells", {adios2::LocalValueDim});
307 engine.Put<std::uint32_t>(cell_var, shape[0]);
308 spdlog::debug("Put local_cells: {}", shape[0]);
309
310 adios2::Variable celltype_var
311 = impl_adios2::define_variable<std::uint32_t>(io, "types");
312 engine.Put<std::uint32_t>(
313 celltype_var, cells::get_vtk_cell_type(topology->cell_type(), tdim));
314
315 // Pack mesh 'nodes'. Output is written as [N0, v0_0,...., v0_N0, N1,
316 // v1_0,...., v1_N1,....], where N is the number of cell nodes and v0,
317 // etc, is the node index
318 std::vector<std::int64_t> cells(shape[0] * (shape[1] + 1), shape[1]);
319 for (std::size_t c = 0; c < shape[0]; ++c)
320 {
321 std::span vtkcell(vtkcells.data() + c * shape[1], shape[1]);
322 std::span cell(cells.data() + c * (shape[1] + 1), shape[1] + 1);
323 std::ranges::copy(vtkcell, std::next(cell.begin()));
324 }
325
326 // Put topology (nodes)
327 adios2::Variable local_topology = impl_adios2::define_variable<std::int64_t>(
328 io, "connectivity", {}, {}, {shape[0], shape[1] + 1});
329 spdlog::debug("Put local_topology: {}x{}", shape[0], shape[1] + 1);
330
331 engine.Put(local_topology, cells.data());
332
333 // Vertex global ids and ghost markers
334 adios2::Variable orig_id = impl_adios2::define_variable<std::int64_t>(
335 io, "vtkOriginalPointIds", {}, {}, {num_vertices});
336 engine.Put(orig_id, geometry.input_global_indices().data());
337
338 std::vector<std::uint8_t> x_ghost(num_vertices, 0);
339 std::fill(std::next(x_ghost.begin(), x_map->size_local()), x_ghost.end(), 1);
340 adios2::Variable ghost = impl_adios2::define_variable<std::uint8_t>(
341 io, "vtkGhostType", {}, {}, {x_ghost.size()});
342 engine.Put(ghost, x_ghost.data());
343 engine.PerformPuts();
344}
345
353template <std::floating_point T>
354std::pair<std::vector<std::int64_t>, std::vector<std::uint8_t>>
355vtx_write_mesh_from_space(adios2::IO& io, adios2::Engine& engine,
356 const fem::FunctionSpace<T>& V)
357{
358 auto mesh = V.mesh();
359 assert(mesh);
360 auto topology = mesh->topology();
361 assert(topology);
362 int tdim = topology->dim();
363
364 // Get a VTK mesh with points at the 'nodes'
365 auto [x, xshape, x_id, x_ghost, vtk, vtkshape] = io::vtk_mesh_from_space(V);
366
367 spdlog::debug("x={}, xshape={}x{}, x_id={}, x_ghost={}", x.size(), xshape[0],
368 xshape[1], x_id.size(), x_ghost.size());
369
370 std::uint32_t num_dofs = xshape[0];
371
372 // -- Pack mesh 'nodes'. Output is written as [N0, v0_0,...., v0_N0, N1,
373 // v1_0,...., v1_N1,....], where N is the number of cell nodes and v0,
374 // etc, is the node index.
375
376 spdlog::debug("Create cells: [{}x{}]", vtkshape[0], vtkshape[1]);
377
378 // Create vector, setting all entries to nodes per cell (vtk.shape(1))
379 std::vector<std::int64_t> cells(vtkshape[0] * (vtkshape[1] + 1), vtkshape[1]);
380
381 // Set the [v0_0,...., v0_N0, v1_0,...., v1_N1,....] data
382 for (std::size_t c = 0; c < vtkshape[0]; ++c)
383 {
384 std::span vtkcell(vtk.data() + c * vtkshape[1], vtkshape[1]);
385 std::span cell(cells.data() + c * (vtkshape[1] + 1), vtkshape[1] + 1);
386 std::ranges::copy(vtkcell, std::next(cell.begin()));
387 }
388
389 // Define ADIOS2 variables for geometry, topology, celltypes and
390 // corresponding VTK data
391 adios2::Variable local_geometry
392 = impl_adios2::define_variable<T>(io, "geometry", {}, {}, {num_dofs, 3});
393 adios2::Variable local_topology = impl_adios2::define_variable<std::int64_t>(
394 io, "connectivity", {}, {}, {vtkshape[0], vtkshape[1] + 1});
395 adios2::Variable cell_type
396 = impl_adios2::define_variable<std::uint32_t>(io, "types");
397 adios2::Variable vertices = impl_adios2::define_variable<std::uint32_t>(
398 io, "NumberOfNodes", {adios2::LocalValueDim});
399 adios2::Variable elements = impl_adios2::define_variable<std::uint32_t>(
400 io, "NumberOfEntities", {adios2::LocalValueDim});
401
402 // Write mesh information to file
403 spdlog::debug("vertices={}, elements={}, local_geom={}, local_cells={}",
404 num_dofs, vtkshape[0], x.size(), cells.size());
405 engine.Put<std::uint32_t>(vertices, num_dofs);
406 engine.Put<std::uint32_t>(elements, vtkshape[0]);
407 engine.Put<std::uint32_t>(
408 cell_type, cells::get_vtk_cell_type(topology->cell_type(), tdim));
409 engine.Put(local_geometry, x.data());
410 engine.Put(local_topology, cells.data());
411
412 // Node global ids
413 adios2::Variable orig_id = impl_adios2::define_variable<std::int64_t>(
414 io, "vtkOriginalPointIds", {}, {}, {x_id.size(), 1});
415 engine.Put(orig_id, x_id.data());
416 adios2::Variable ghost = impl_adios2::define_variable<std::uint8_t>(
417 io, "vtkGhostType", {}, {}, {x_ghost.size(), 1});
418 engine.Put(ghost, x_ghost.data());
419
420 engine.PerformPuts();
421 return {std::move(x_id), std::move(x_ghost)};
422}
423} // namespace impl_vtx
424
426enum class VTXMeshPolicy : std::int8_t
427{
428 update,
429 reuse
431};
432
438template <std::floating_point T>
439class VTXWriter : public ADIOS2Writer
440{
441public:
453 VTXWriter(MPI_Comm comm, const std::filesystem::path& filename,
454 std::shared_ptr<const mesh::Mesh<T>> mesh,
455 std::string engine = "BPFile")
456 : ADIOS2Writer(comm, filename, "VTX mesh writer", engine), _mesh(mesh),
457 _mesh_reuse_policy(VTXMeshPolicy::update),
458 _has_piecewise_constant(false)
459 {
460 // Define VTK scheme attribute for mesh
461 std::string vtk_scheme = impl_vtx::create_vtk_schema({}, {}).str();
462 impl_adios2::define_attribute<std::string>(*_io, "vtk.xml", vtk_scheme);
463 }
464
480 VTXWriter(MPI_Comm comm, const std::filesystem::path& filename,
481 const typename adios2_writer::U<T>& u, std::string engine,
482 VTXMeshPolicy mesh_policy = VTXMeshPolicy::update)
483 : ADIOS2Writer(comm, filename, "VTX function writer", engine),
484 _mesh(impl_adios2::extract_common_mesh<T>(u)), _u(u),
485 _mesh_reuse_policy(mesh_policy), _has_piecewise_constant(false)
486 {
487 if (u.empty())
488 throw std::runtime_error("VTXWriter fem::Function list is empty.");
489
490 // Extract space from first function
491 auto V0 = std::visit([](auto& u) { return u->function_space().get(); },
492 u.front());
493 assert(V0);
494
495 // Replace first function space if it is a space for piecewise constants
496 bool has_V0_changed = false;
497 for (auto& v : u)
498 {
499 std::visit(
500 [&V0, &has_V0_changed](auto& u)
501 {
502 auto V = u->function_space().get();
503 assert(V);
504 if (!impl::is_cellwise(*V->element()))
505 {
506 V0 = V;
507 has_V0_changed = true;
508 }
509 },
510 v);
511 if (has_V0_changed)
512 break;
513 }
514 auto element0 = V0->element().get();
515 assert(element0);
516
517 // Check if function is mixed
518 if (element0->is_mixed())
519 {
520 throw std::runtime_error(
521 "Mixed functions are not supported by VTXWriter.");
522 }
523
524 // FIXME: is the below check adequate for detecting a Lagrange
525 // element?
526 // Check that element is Lagrange
527 if (!element0->interpolation_ident())
528 {
529 throw std::runtime_error(
530 "Only (discontinuous) Lagrange functions are "
531 "supported. Interpolate Functions before output.");
532 }
533
534 // Check that all functions come from same element type
535 for (auto& v : _u)
536 {
537 std::visit(
538 [V0, this](auto& u)
539 {
540 auto element = u->function_space()->element();
541 assert(element);
542 bool is_piecewise_constant = impl::is_cellwise(*element);
543 _has_piecewise_constant
544 = _has_piecewise_constant || is_piecewise_constant;
545 if (*element != *V0->element().get() and !is_piecewise_constant)
546 {
547 throw std::runtime_error("All functions in VTXWriter must have "
548 "the same element type.");
549 }
550#ifndef NDEBUG
551 auto dmap0 = V0->dofmap()->map();
552 auto dmap = u->function_space()->dofmap()->map();
553 if ((dmap0.size() != dmap.size()
554 or !std::equal(dmap0.data_handle(),
555 dmap0.data_handle() + dmap0.size(),
556 dmap.data_handle()))
557 and !is_piecewise_constant)
558 {
559 throw std::runtime_error(
560 "All functions must have the same dofmap for VTXWriter.");
561 }
562#endif
563 },
564 v);
565 }
566
567 // Define VTK scheme attribute for set of functions
568 auto [names, dg0_names] = impl_vtx::extract_function_names<T>(u);
569 std::string vtk_scheme;
570 vtk_scheme = impl_vtx::create_vtk_schema(names, dg0_names).str();
571
572 impl_adios2::define_attribute<std::string>(*_io, "vtk.xml", vtk_scheme);
573 }
574
589 VTXWriter(MPI_Comm comm, const std::filesystem::path& filename,
590 const typename adios2_writer::U<T>& u,
591 VTXMeshPolicy mesh_policy = VTXMeshPolicy::update)
592 : VTXWriter(comm, filename, u, "BPFile", mesh_policy)
593 {
594 }
595
596 // Copy constructor
597 VTXWriter(const VTXWriter&) = delete;
598
600 VTXWriter(VTXWriter&& file) = default;
601
603 ~VTXWriter() = default;
604
606 VTXWriter& operator=(VTXWriter&&) = default;
607
608 // Copy assignment
609 VTXWriter& operator=(const VTXWriter&) = delete;
610
613 void write(double t)
614 {
615 assert(_io);
616 adios2::Variable var_step
617 = impl_adios2::define_variable<double>(*_io, "step");
618
619 spdlog::debug("ADIOS2: step");
620 assert(_engine);
621 _engine->BeginStep();
622 _engine->template Put<double>(var_step, t);
623
624 // If we have no non-constant functions write the mesh to file
625 auto [names, dg0_names] = impl_vtx::extract_function_names<T>(_u);
626 if ((names.size() == 0) or _u.empty())
627 {
628 spdlog::debug("ADIOS2: write_mesh");
629 impl_vtx::vtx_write_mesh(*_io, *_engine, *_mesh);
630 }
631 else
632 {
633 if (_mesh_reuse_policy == VTXMeshPolicy::update
634 or !(_io->template InquireVariable<std::int64_t>("connectivity")))
635 {
636 // Write a single mesh for functions as they share finite
637 // element
638 std::tie(_x_id, _x_ghost) = std::visit(
639 [&](auto& u)
640 {
641 spdlog::debug("ADIOS2: write_mesh_from_space");
642 return impl_vtx::vtx_write_mesh_from_space(*_io, *_engine,
643 *u->function_space());
644 },
645 _u[0]);
646 }
647 else
648 {
649 // Node global ids
650 adios2::Variable orig_id = impl_adios2::define_variable<std::int64_t>(
651 *_io, "vtkOriginalPointIds", {}, {}, {_x_id.size()});
652 _engine->Put(orig_id, _x_id.data());
653 adios2::Variable ghost = impl_adios2::define_variable<std::uint8_t>(
654 *_io, "vtkGhostType", {}, {}, {_x_ghost.size()});
655 _engine->Put(ghost, _x_ghost.data());
656 _engine->PerformPuts();
657 }
658 }
659
660 spdlog::debug("Write function data");
661 // Write function data for each function to file
662 for (auto& v : _u)
663 {
664 std::visit([&](auto& u) { impl_vtx::vtx_write_data(*_io, *_engine, *u); },
665 v);
666 }
667
668 _engine->EndStep();
669 }
670
671private:
672 std::shared_ptr<const mesh::Mesh<T>> _mesh;
673 adios2_writer::U<T> _u;
674
675 // Control whether the mesh is written to file once or at every time
676 // step
677 VTXMeshPolicy _mesh_reuse_policy;
678 std::vector<std::int64_t> _x_id;
679 std::vector<std::uint8_t> _x_ghost;
680
681 // Special handling of piecewise constant functions
682 bool _has_piecewise_constant;
683};
684
686template <typename U, typename T>
687VTXWriter(MPI_Comm comm, U filename, T mesh)
688 -> VTXWriter<typename std::remove_cvref<
689 typename T::element_type>::type::geometry_type::value_type>;
690
691} // namespace dolfinx::io
692
693#endif
Degree-of-freedom map representations and tools.
Definition Function.h:47
int rank(MPI_Comm comm)
Return process rank for the communicator.
Definition MPI.cpp:64
void cells(la::SparsityPattern &pattern, std::array< std::span< const std::int32_t >, 2 > cells, std::array< std::reference_wrapper< const DofMap >, 2 > dofmaps)
Iterate over cells and insert entries into sparsity pattern.
Definition sparsitybuild.cpp:16
Finite element method functionality.
Definition assemble_expression_impl.h:23
Support for file IO.
Definition cells.h:119