DOLFINx 0.9.0
DOLFINx C++ interface
Loading...
Searching...
No Matches
ADIOS2Writers.h
Go to the documentation of this file.
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
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
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_fides
163{
168void initialize_mesh_attributes(adios2::IO& io, mesh::CellType type);
169
174template <std::floating_point T>
176 const typename adios2_writer::U<T>& u)
177{
178 // Array of function (name, cell association types) for each function
179 // added to the file
180 std::vector<std::array<std::string, 2>> u_data;
181 for (auto& v : u)
182 {
183 std::visit(
184 [&u_data](auto&& u)
185 {
186 using U = std::decay_t<decltype(u)>;
187 using X = typename U::element_type;
188 if constexpr (std::is_floating_point_v<typename X::value_type>)
189 u_data.push_back({u->name, "points"});
190 else
191 {
192 u_data.push_back({u->name + impl_adios2::field_ext[0], "points"});
193 u_data.push_back({u->name + impl_adios2::field_ext[1], "points"});
194 }
195 },
196 v);
197 }
198
199 // Write field associations to file
200 if (adios2::Attribute<std::string> assc
201 = io.InquireAttribute<std::string>("Fides_Variable_Associations");
202 !assc)
203 {
204 std::vector<std::string> u_type;
205 std::ranges::transform(u_data, std::back_inserter(u_type),
206 [](auto f) { return f[1]; });
207 io.DefineAttribute<std::string>("Fides_Variable_Associations",
208 u_type.data(), u_type.size());
209 }
210
211 // Write field pointers to file
212 if (adios2::Attribute<std::string> fields
213 = io.InquireAttribute<std::string>("Fides_Variable_List");
214 !fields)
215 {
216 std::vector<std::string> names;
217 std::ranges::transform(u_data, std::back_inserter(names),
218 [](auto f) { return f[0]; });
219 io.DefineAttribute<std::string>("Fides_Variable_List", names.data(),
220 names.size());
221 }
222}
223
226template <typename T, std::floating_point U>
228{
229 auto V = u.function_space();
230 assert(V);
231 auto dofmap = V->dofmap();
232 assert(dofmap);
233 auto mesh = V->mesh();
234 assert(mesh);
235 const mesh::Geometry<U>& geometry = mesh->geometry();
236 auto topology = mesh->topology();
237 assert(topology);
238
239 // The Function and the mesh must have identical element_dof_layouts
240 // (up to the block size)
241 assert(dofmap->element_dof_layout() == geometry.cmap().create_dof_layout());
242
243 int tdim = topology->dim();
244 auto cell_map = topology->index_map(tdim);
245 assert(cell_map);
246 std::int32_t num_cells = cell_map->size_local() + cell_map->num_ghosts();
247
248 auto vertex_map = topology->index_map(0);
249 assert(vertex_map);
250 std::uint32_t num_vertices
251 = vertex_map->size_local() + vertex_map->num_ghosts();
252
253 std::span<const std::size_t> value_shape = u.function_space()->value_shape();
254 int rank = value_shape.size();
255 int num_components = std::reduce(value_shape.begin(), value_shape.end(), 1,
256 std::multiplies{});
257 if (num_components < std::pow(3, rank))
258 num_components = std::pow(3, rank);
259 else if (num_components > std::pow(3, rank))
260 {
261 throw std::runtime_error(
262 "Fides does not support tensors larger than pow(3, rank)");
263 }
264
265 // Get dof array and pack into array (padded where appropriate)
266 auto dofmap_x = geometry.dofmap();
267 const int bs = dofmap->bs();
268 std::span<const T> u_data = u.x()->array();
269 std::vector<T> data(num_vertices * num_components, 0);
270 for (std::int32_t c = 0; c < num_cells; ++c)
271 {
272 auto dofs = dofmap->cell_dofs(c);
273 auto dofs_x = MDSPAN_IMPL_STANDARD_NAMESPACE::submdspan(
274 dofmap_x, c, MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent);
275 assert(dofs.size() == dofs_x.size());
276 for (std::size_t i = 0; i < dofs.size(); ++i)
277 for (int j = 0; j < bs; ++j)
278 data[num_components * dofs_x[i] + j] = u_data[bs * dofs[i] + j];
279 }
280
281 return data;
282}
283
290template <typename T, std::floating_point U>
291void write_data(adios2::IO& io, adios2::Engine& engine,
292 const fem::Function<T, U>& u)
293{
294 // FIXME: There is an implicit assumptions that u and the mesh have
295 // the same ElementDoflayout
296 auto V = u.function_space();
297 assert(V);
298 auto dofmap = V->dofmap();
299 assert(dofmap);
300 auto mesh = V->mesh();
301 assert(mesh);
302
303 // Pad to 3D if vector/tensor is product of dimensions is smaller than
304 // 3**rank to ensure that we can visualize them correctly in Paraview
305 std::span<const std::size_t> value_shape = u.function_space()->value_shape();
306 int rank = value_shape.size();
307 int num_components = std::reduce(value_shape.begin(), value_shape.end(), 1,
308 std::multiplies{});
309 bool need_padding = false;
310 if (num_components < std::pow(3, rank))
311 {
312 num_components = std::pow(3, rank);
313 need_padding = true;
314 }
315 else if (num_components > std::pow(3, rank))
316 {
317 throw std::runtime_error(
318 "Fides does not support tensors larger than pow(3, rank)");
319 }
320
321 // Get vertex data. If the mesh and function dofmaps are the same we
322 // can work directly with the dof array.
323 std::span<const T> data;
324 std::vector<T> _data;
325 auto eq_check = [](auto x, auto y) -> bool
326 {
327 return x.extents() == y.extents()
328 and std::equal(x.data_handle(), x.data_handle() + x.size(),
329 y.data_handle());
330 };
331
332 if (!need_padding and eq_check(mesh->geometry().dofmap(), dofmap->map()))
333 data = u.x()->array();
334 else
335 {
336 _data = impl_fides::pack_function_data(u);
337 data = std::span<const T>(_data);
338 }
339
340 auto vertex_map = mesh->topology()->index_map(0);
341 assert(vertex_map);
342 std::uint32_t num_vertices
343 = vertex_map->size_local() + vertex_map->num_ghosts();
344
345 // Write each real and imaginary part of the function
346 assert(data.size() % num_components == 0);
347 if constexpr (std::is_scalar_v<T>)
348 {
349 // ---- Real
350 adios2::Variable local_output = impl_adios2::define_variable<T>(
351 io, u.name, {}, {}, {num_vertices, num_components});
352
353 // To reuse out_data, we use sync mode here
354 engine.Put(local_output, data.data());
355 engine.PerformPuts();
356 }
357 else
358 {
359 // ---- Complex
360 using X = typename T::value_type;
361
362 std::vector<X> data_real(data.size()), data_imag(data.size());
363
364 adios2::Variable local_output_r = impl_adios2::define_variable<X>(
365 io, u.name + impl_adios2::field_ext[0], {}, {},
366 {num_vertices, num_components});
367 std::ranges::transform(data, data_real.begin(),
368 [](auto x) -> X { return std::real(x); });
369 engine.Put(local_output_r, data_real.data());
370
371 adios2::Variable local_output_c = impl_adios2::define_variable<X>(
372 io, u.name + impl_adios2::field_ext[1], {}, {},
373 {num_vertices, num_components});
374 std::ranges::transform(data, data_imag.begin(),
375 [](auto x) -> X { return std::imag(x); });
376 engine.Put(local_output_c, data_imag.data());
377 engine.PerformPuts();
378 }
379}
380
385template <std::floating_point T>
386void write_mesh(adios2::IO& io, adios2::Engine& engine,
387 const mesh::Mesh<T>& mesh)
388{
389 const mesh::Geometry<T>& geometry = mesh.geometry();
390 auto topology = mesh.topology();
391 assert(topology);
392
393 // "Put" geometry data
394 auto x_map = geometry.index_map();
395 std::uint32_t num_vertices = x_map->size_local() + x_map->num_ghosts();
396 adios2::Variable local_geometry = impl_adios2::define_variable<T>(
397 io, "points", {}, {}, {num_vertices, 3});
398 engine.Put(local_geometry, geometry.x().data());
399
400 // TODO: The DOLFINx and VTK topology are the same for some cell types
401 // - no need to repack via extract_vtk_connectivity in these cases
402
403 // Get topological dimension, number of cells and number of 'nodes' per
404 // cell, and compute 'VTK' connectivity
405 int tdim = topology->dim();
406 std::int32_t num_cells = topology->index_map(tdim)->size_local();
407 int num_nodes = geometry.cmap().dim();
408 auto [cells, shape] = io::extract_vtk_connectivity(mesh.geometry().dofmap(),
409 topology->cell_type());
410
411 // "Put" topology data in the result in the ADIOS2 file
412 adios2::Variable local_topology = impl_adios2::define_variable<std::int64_t>(
413 io, "connectivity", {}, {}, {std::size_t(num_cells * num_nodes)});
414 engine.Put(local_topology, cells.data());
415 engine.PerformPuts();
416}
417
418} // namespace impl_fides
419
421enum class FidesMeshPolicy
422{
423 update,
424 reuse
426};
427
431template <std::floating_point T>
432class FidesWriter : public ADIOS2Writer
433{
434public:
443 FidesWriter(MPI_Comm comm, const std::filesystem::path& filename,
444 std::shared_ptr<const mesh::Mesh<T>> mesh,
445 std::string engine = "BPFile")
446 : ADIOS2Writer(comm, filename, "Fides mesh writer", engine),
447 _mesh_reuse_policy(FidesMeshPolicy::update), _mesh(mesh)
448 {
449 assert(_io);
450 assert(mesh);
451 auto topology = mesh->topology();
452 assert(topology);
453 mesh::CellType type = topology->cell_type();
454 if (mesh->geometry().cmap().dim() != mesh::cell_num_entities(type, 0))
455 throw std::runtime_error("Fides only supports lowest-order meshes.");
456 impl_fides::initialize_mesh_attributes(*_io, type);
457 }
458
472 FidesWriter(MPI_Comm comm, const std::filesystem::path& filename,
473 const typename adios2_writer::U<T>& u, std::string engine,
474 const FidesMeshPolicy mesh_policy = FidesMeshPolicy::update)
475 : ADIOS2Writer(comm, filename, "Fides function writer", engine),
476 _mesh_reuse_policy(mesh_policy),
477 _mesh(impl_adios2::extract_common_mesh<T>(u)), _u(u)
478 {
479 if (u.empty())
480 throw std::runtime_error("FidesWriter fem::Function list is empty");
481
482 // Extract space and mesh from first function
483 auto V0 = std::visit([](auto& u) { return u->function_space().get(); },
484 u.front());
485 assert(V0);
486 auto mesh = V0->mesh().get();
487 assert(mesh);
488
489 // Extract element from first function
490 auto element0 = V0->element().get();
491 assert(element0);
492
493 // Check if function is mixed
494 if (element0->is_mixed())
495 {
496 throw std::runtime_error(
497 "Mixed functions are not supported by FidesWriter");
498 }
499
500 // FIXME: is the below check adequate for detecting a
501 // Lagrange element? Check that element is Lagrange
502 if (!element0->interpolation_ident())
503 {
504 throw std::runtime_error("Only Lagrange functions are supported. "
505 "Interpolate Functions before output.");
506 }
507
508 // Raise exception if element is DG 0
509 if (element0->space_dimension() / element0->block_size() == 1)
510 {
511 throw std::runtime_error(
512 "Piecewise constants are not (yet) supported by FidesWriter");
513 }
514
515 // Check that all functions are first order Lagrange
516 mesh::CellType cell_type = mesh->topology()->cell_type();
517 int num_vertices_per_cell = mesh::cell_num_entities(cell_type, 0);
518 for (auto& v : _u)
519 {
520 std::visit(
521 [V0, num_vertices_per_cell, element0](auto&& u)
522 {
523 auto element = u->function_space()->element();
524 assert(element);
525 if (*element != *element0)
526 {
527 throw std::runtime_error("All functions in FidesWriter must have "
528 "the same element type");
529 }
530 auto dof_layout
531 = u->function_space()->dofmap()->element_dof_layout();
532 int num_vertex_dofs = dof_layout.num_entity_dofs(0);
533 int num_dofs = element->space_dimension() / element->block_size();
534 if (num_dofs != num_vertices_per_cell or num_vertex_dofs != 1)
535 {
536 throw std::runtime_error("Only first order Lagrange spaces are "
537 "supported by FidesWriter");
538 }
539
540#ifndef NDEBUG
541 auto dmap0 = V0->dofmap()->map();
542 auto dmap = u->function_space()->dofmap()->map();
543 if (dmap0.size() != dmap.size()
544 or !std::equal(dmap0.data_handle(),
545 dmap0.data_handle() + dmap0.size(),
546 dmap.data_handle()))
547 {
548 throw std::runtime_error(
549 "All functions must have the same dofmap for FideWriter.");
550 }
551#endif
552 },
553 v);
554 }
555
556 auto topology = mesh->topology();
557 assert(topology);
558 mesh::CellType type = topology->cell_type();
559 if (mesh->geometry().cmap().dim() != mesh::cell_num_entities(type, 0))
560 throw std::runtime_error("Fides only supports lowest-order meshes.");
561 impl_fides::initialize_mesh_attributes(*_io, type);
562 impl_fides::initialize_function_attributes<T>(*_io, u);
563 }
564
575 FidesWriter(MPI_Comm comm, const std::filesystem::path& filename,
576 const typename adios2_writer::U<T>& u,
577 const FidesMeshPolicy mesh_policy = FidesMeshPolicy::update)
578 : FidesWriter(comm, filename, u, "BPFile", mesh_policy)
579 {
580 }
581
582 // Copy constructor
583 FidesWriter(const FidesWriter&) = delete;
584
586 FidesWriter(FidesWriter&& file) = default;
587
589 ~FidesWriter() = default;
590
592 FidesWriter& operator=(FidesWriter&&) = default;
593
594 // Copy assignment
595 FidesWriter& operator=(const FidesWriter&) = delete;
596
599 void write(double t)
600 {
601 assert(_io);
602 assert(_engine);
603 _engine->BeginStep();
604 adios2::Variable var_step
605 = impl_adios2::define_variable<double>(*_io, "step");
606 _engine->template Put<double>(var_step, t);
607 if (auto v = _io->template InquireVariable<std::int64_t>("connectivity");
608 !v or _mesh_reuse_policy == FidesMeshPolicy::update)
609 {
610 impl_fides::write_mesh(*_io, *_engine, *_mesh);
611 }
612
613 for (auto& v : _u)
614 {
615 std::visit([this](auto&& u)
616 { impl_fides::write_data(*_io, *_engine, *u); }, v);
617 }
618
619 _engine->EndStep();
620 }
621
622private:
623 // Control whether the mesh is written to file once or at every time
624 // step
625 FidesMeshPolicy _mesh_reuse_policy;
626
627 std::shared_ptr<const mesh::Mesh<T>> _mesh;
628 adios2_writer::U<T> _u;
629};
630
632namespace impl_vtx
633{
636std::stringstream create_vtk_schema(const std::vector<std::string>& point_data,
637 const std::vector<std::string>& cell_data);
638
640template <std::floating_point T>
641std::vector<std::string>
642extract_function_names(const typename adios2_writer::U<T>& u)
643{
644 std::vector<std::string> names;
645 for (auto& v : u)
646 {
647 std::visit(
648 [&names](auto&& u)
649 {
650 using U = std::decay_t<decltype(u)>;
651 using X = typename U::element_type;
652 if constexpr (std::is_floating_point_v<typename X::value_type>)
653 names.push_back(u->name);
654 else
655 {
656 names.push_back(u->name + impl_adios2::field_ext[0]);
657 names.push_back(u->name + impl_adios2::field_ext[1]);
658 }
659 },
660 v);
661 }
662
663 return names;
664}
665
675template <typename T, std::floating_point X>
676void vtx_write_data(adios2::IO& io, adios2::Engine& engine,
677 const fem::Function<T, X>& u)
678{
679 // Get function data array and information about layout
680 assert(u.x());
681 std::span<const T> u_vector = u.x()->array();
682
683 // Pad to 3D if vector/tensor is product of dimensions is smaller than
684 // 3**rank to ensure that we can visualize them correctly in Paraview
685 std::span<const std::size_t> value_shape = u.function_space()->value_shape();
686 int rank = value_shape.size();
687 int num_comp = std::reduce(value_shape.begin(), value_shape.end(), 1,
688 std::multiplies{});
689 if (num_comp < std::pow(3, rank))
690 num_comp = std::pow(3, rank);
691
692 std::shared_ptr<const fem::DofMap> dofmap = u.function_space()->dofmap();
693 assert(dofmap);
694 std::shared_ptr<const common::IndexMap> index_map = dofmap->index_map;
695 assert(index_map);
696 int index_map_bs = dofmap->index_map_bs();
697 int dofmap_bs = dofmap->bs();
698 std::uint32_t num_dofs = index_map_bs
699 * (index_map->size_local() + index_map->num_ghosts())
700 / dofmap_bs;
701 if constexpr (std::is_scalar_v<T>)
702 {
703 // ---- Real
704 std::vector<T> data(num_dofs * num_comp, 0);
705 for (std::size_t i = 0; i < num_dofs; ++i)
706 for (int j = 0; j < index_map_bs; ++j)
707 data[i * num_comp + j] = u_vector[i * index_map_bs + j];
708
709 adios2::Variable output = impl_adios2::define_variable<T>(
710 io, u.name, {}, {}, {num_dofs, num_comp});
711 engine.Put(output, data.data(), adios2::Mode::Sync);
712 }
713 else
714 {
715 // ---- Complex
716 using U = typename T::value_type;
717
718 std::vector<U> data(num_dofs * num_comp, 0);
719 for (std::size_t i = 0; i < num_dofs; ++i)
720 for (int j = 0; j < index_map_bs; ++j)
721 data[i * num_comp + j] = std::real(u_vector[i * index_map_bs + j]);
722
723 adios2::Variable output_real = impl_adios2::define_variable<U>(
724 io, u.name + impl_adios2::field_ext[0], {}, {}, {num_dofs, num_comp});
725 engine.Put(output_real, data.data(), adios2::Mode::Sync);
726
727 std::ranges::fill(data, 0);
728 for (std::size_t i = 0; i < num_dofs; ++i)
729 for (int j = 0; j < index_map_bs; ++j)
730 data[i * num_comp + j] = std::imag(u_vector[i * index_map_bs + j]);
731 adios2::Variable output_imag = impl_adios2::define_variable<U>(
732 io, u.name + impl_adios2::field_ext[1], {}, {}, {num_dofs, num_comp});
733 engine.Put(output_imag, data.data(), adios2::Mode::Sync);
734 }
735}
736
741template <std::floating_point T>
742void vtx_write_mesh(adios2::IO& io, adios2::Engine& engine,
743 const mesh::Mesh<T>& mesh)
744{
745 const mesh::Geometry<T>& geometry = mesh.geometry();
746 auto topology = mesh.topology();
747 assert(topology);
748
749 // "Put" geometry
750 std::shared_ptr<const common::IndexMap> x_map = geometry.index_map();
751 std::uint32_t num_vertices = x_map->size_local() + x_map->num_ghosts();
752 adios2::Variable local_geometry = impl_adios2::define_variable<T>(
753 io, "geometry", {}, {}, {num_vertices, 3});
754 engine.Put(local_geometry, geometry.x().data());
755
756 // Put number of nodes. The mesh data is written with local indices,
757 // therefore we need the ghost vertices.
758 adios2::Variable vertices = impl_adios2::define_variable<std::uint32_t>(
759 io, "NumberOfNodes", {adios2::LocalValueDim});
760 engine.Put<std::uint32_t>(vertices, num_vertices);
761
762 auto [vtkcells, shape]
763 = io::extract_vtk_connectivity(geometry.dofmap(), topology->cell_type());
764
765 // Add cell metadata
766 int tdim = topology->dim();
767 adios2::Variable cell_var = impl_adios2::define_variable<std::uint32_t>(
768 io, "NumberOfCells", {adios2::LocalValueDim});
769 engine.Put<std::uint32_t>(cell_var, shape[0]);
770 adios2::Variable celltype_var
771 = impl_adios2::define_variable<std::uint32_t>(io, "types");
772 engine.Put<std::uint32_t>(
773 celltype_var, cells::get_vtk_cell_type(topology->cell_type(), tdim));
774
775 // Pack mesh 'nodes'. Output is written as [N0, v0_0,...., v0_N0, N1,
776 // v1_0,...., v1_N1,....], where N is the number of cell nodes and v0,
777 // etc, is the node index
778 std::vector<std::int64_t> cells(shape[0] * (shape[1] + 1), shape[1]);
779 for (std::size_t c = 0; c < shape[0]; ++c)
780 {
781 std::span vtkcell(vtkcells.data() + c * shape[1], shape[1]);
782 std::span cell(cells.data() + c * (shape[1] + 1), shape[1] + 1);
783 std::ranges::copy(vtkcell, std::next(cell.begin()));
784 }
785
786 // Put topology (nodes)
787 adios2::Variable local_topology = impl_adios2::define_variable<std::int64_t>(
788 io, "connectivity", {}, {}, {shape[0], shape[1] + 1});
789 engine.Put(local_topology, cells.data());
790
791 // Vertex global ids and ghost markers
792 adios2::Variable orig_id = impl_adios2::define_variable<std::int64_t>(
793 io, "vtkOriginalPointIds", {}, {}, {num_vertices});
794 engine.Put(orig_id, geometry.input_global_indices().data());
795
796 std::vector<std::uint8_t> x_ghost(num_vertices, 0);
797 std::fill(std::next(x_ghost.begin(), x_map->size_local()), x_ghost.end(), 1);
798 adios2::Variable ghost = impl_adios2::define_variable<std::uint8_t>(
799 io, "vtkGhostType", {}, {}, {x_ghost.size()});
800 engine.Put(ghost, x_ghost.data());
801 engine.PerformPuts();
802}
803
811template <std::floating_point T>
812std::pair<std::vector<std::int64_t>, std::vector<std::uint8_t>>
813vtx_write_mesh_from_space(adios2::IO& io, adios2::Engine& engine,
814 const fem::FunctionSpace<T>& V)
815{
816 auto mesh = V.mesh();
817 assert(mesh);
818 auto topology = mesh->topology();
819 assert(topology);
820 int tdim = topology->dim();
821
822 // Get a VTK mesh with points at the 'nodes'
823 auto [x, xshape, x_id, x_ghost, vtk, vtkshape] = io::vtk_mesh_from_space(V);
824
825 std::uint32_t num_dofs = xshape[0];
826
827 // -- Pack mesh 'nodes'. Output is written as [N0, v0_0,...., v0_N0, N1,
828 // v1_0,...., v1_N1,....], where N is the number of cell nodes and v0,
829 // etc, is the node index.
830
831 // Create vector, setting all entries to nodes per cell (vtk.shape(1))
832 std::vector<std::int64_t> cells(vtkshape[0] * (vtkshape[1] + 1), vtkshape[1]);
833
834 // Set the [v0_0,...., v0_N0, v1_0,...., v1_N1,....] data
835 for (std::size_t c = 0; c < vtkshape[0]; ++c)
836 {
837 std::span vtkcell(vtk.data() + c * vtkshape[1], vtkshape[1]);
838 std::span cell(cells.data() + c * (vtkshape[1] + 1), vtkshape[1] + 1);
839 std::ranges::copy(vtkcell, std::next(cell.begin()));
840 }
841
842 // Define ADIOS2 variables for geometry, topology, celltypes and
843 // corresponding VTK data
844 adios2::Variable local_geometry
845 = impl_adios2::define_variable<T>(io, "geometry", {}, {}, {num_dofs, 3});
846 adios2::Variable local_topology = impl_adios2::define_variable<std::int64_t>(
847 io, "connectivity", {}, {}, {vtkshape[0], vtkshape[1] + 1});
848 adios2::Variable cell_type
849 = impl_adios2::define_variable<std::uint32_t>(io, "types");
850 adios2::Variable vertices = impl_adios2::define_variable<std::uint32_t>(
851 io, "NumberOfNodes", {adios2::LocalValueDim});
852 adios2::Variable elements = impl_adios2::define_variable<std::uint32_t>(
853 io, "NumberOfEntities", {adios2::LocalValueDim});
854
855 // Write mesh information to file
856 engine.Put<std::uint32_t>(vertices, num_dofs);
857 engine.Put<std::uint32_t>(elements, vtkshape[0]);
858 engine.Put<std::uint32_t>(
859 cell_type, cells::get_vtk_cell_type(topology->cell_type(), tdim));
860 engine.Put(local_geometry, x.data());
861 engine.Put(local_topology, cells.data());
862
863 // Node global ids
864 adios2::Variable orig_id = impl_adios2::define_variable<std::int64_t>(
865 io, "vtkOriginalPointIds", {}, {}, {x_id.size()});
866 engine.Put(orig_id, x_id.data());
867 adios2::Variable ghost = impl_adios2::define_variable<std::uint8_t>(
868 io, "vtkGhostType", {}, {}, {x_ghost.size()});
869 engine.Put(ghost, x_ghost.data());
870
871 engine.PerformPuts();
872 return {std::move(x_id), std::move(x_ghost)};
873}
874} // namespace impl_vtx
875
877enum class VTXMeshPolicy
878{
879 update,
880 reuse
882};
883
889template <std::floating_point T>
890class VTXWriter : public ADIOS2Writer
891{
892public:
904 VTXWriter(MPI_Comm comm, const std::filesystem::path& filename,
905 std::shared_ptr<const mesh::Mesh<T>> mesh,
906 std::string engine = "BPFile")
907 : ADIOS2Writer(comm, filename, "VTX mesh writer", engine), _mesh(mesh),
908 _mesh_reuse_policy(VTXMeshPolicy::update), _is_piecewise_constant(false)
909 {
910 // Define VTK scheme attribute for mesh
911 std::string vtk_scheme = impl_vtx::create_vtk_schema({}, {}).str();
912 impl_adios2::define_attribute<std::string>(*_io, "vtk.xml", vtk_scheme);
913 }
914
930 VTXWriter(MPI_Comm comm, const std::filesystem::path& filename,
931 const typename adios2_writer::U<T>& u, std::string engine,
932 VTXMeshPolicy mesh_policy = VTXMeshPolicy::update)
933 : ADIOS2Writer(comm, filename, "VTX function writer", engine),
934 _mesh(impl_adios2::extract_common_mesh<T>(u)),
935 _mesh_reuse_policy(mesh_policy), _u(u), _is_piecewise_constant(false)
936 {
937 if (u.empty())
938 throw std::runtime_error("VTXWriter fem::Function list is empty.");
939
940 // Extract space from first function
941 auto V0 = std::visit([](auto& u) { return u->function_space().get(); },
942 u.front());
943 assert(V0);
944 auto element0 = V0->element().get();
945 assert(element0);
946
947 // Check if function is mixed
948 if (element0->is_mixed())
949 {
950 throw std::runtime_error(
951 "Mixed functions are not supported by VTXWriter.");
952 }
953
954 // FIXME: is the below check adequate for detecting a Lagrange
955 // element?
956 // Check that element is Lagrange
957 if (!element0->interpolation_ident())
958 {
959 throw std::runtime_error(
960 "Only (discontinuous) Lagrange functions are "
961 "supported. Interpolate Functions before output.");
962 }
963
964 // Check if function is DG 0
965 if (element0->space_dimension() / element0->block_size() == 1)
966 _is_piecewise_constant = true;
967
968 // Check that all functions come from same element type
969 for (auto& v : _u)
970 {
971 std::visit(
972 [V0](auto& u)
973 {
974 auto element = u->function_space()->element();
975 assert(element);
976 if (*element != *V0->element().get())
977 {
978 throw std::runtime_error("All functions in VTXWriter must have "
979 "the same element type.");
980 }
981#ifndef NDEBUG
982 auto dmap0 = V0->dofmap()->map();
983 auto dmap = u->function_space()->dofmap()->map();
984 if (dmap0.size() != dmap.size()
985 or !std::equal(dmap0.data_handle(),
986 dmap0.data_handle() + dmap0.size(),
987 dmap.data_handle()))
988 {
989 throw std::runtime_error(
990 "All functions must have the same dofmap for VTXWriter.");
991 }
992#endif
993 },
994 v);
995 }
996
997 // Define VTK scheme attribute for set of functions
998 std::vector<std::string> names = impl_vtx::extract_function_names<T>(u);
999 std::string vtk_scheme;
1000 if (_is_piecewise_constant)
1001 vtk_scheme = impl_vtx::create_vtk_schema({}, names).str();
1002 else
1003 vtk_scheme = impl_vtx::create_vtk_schema(names, {}).str();
1004
1005 impl_adios2::define_attribute<std::string>(*_io, "vtk.xml", vtk_scheme);
1006 }
1007
1022 VTXWriter(MPI_Comm comm, const std::filesystem::path& filename,
1023 const typename adios2_writer::U<T>& u,
1024 VTXMeshPolicy mesh_policy = VTXMeshPolicy::update)
1025 : VTXWriter(comm, filename, u, "BPFile", mesh_policy)
1026 {
1027 }
1028
1029 // Copy constructor
1030 VTXWriter(const VTXWriter&) = delete;
1031
1033 VTXWriter(VTXWriter&& file) = default;
1034
1036 ~VTXWriter() = default;
1037
1039 VTXWriter& operator=(VTXWriter&&) = default;
1040
1041 // Copy assignment
1042 VTXWriter& operator=(const VTXWriter&) = delete;
1043
1046 void write(double t)
1047 {
1048 assert(_io);
1049 adios2::Variable var_step
1050 = impl_adios2::define_variable<double>(*_io, "step");
1051
1052 assert(_engine);
1053 _engine->BeginStep();
1054 _engine->template Put<double>(var_step, t);
1055
1056 // If we have no functions or DG functions write the mesh to file
1057 if (_is_piecewise_constant or _u.empty())
1058 {
1059 impl_vtx::vtx_write_mesh(*_io, *_engine, *_mesh);
1060 if (_is_piecewise_constant)
1061 {
1062 for (auto& v : _u)
1063 {
1064 std::visit([&](auto& u)
1065 { impl_vtx::vtx_write_data(*_io, *_engine, *u); }, v);
1066 }
1067 }
1068 }
1069 else
1070 {
1071 if (_mesh_reuse_policy == VTXMeshPolicy::update
1072 or !(_io->template InquireVariable<std::int64_t>("connectivity")))
1073 {
1074 // Write a single mesh for functions as they share finite
1075 // element
1076 std::tie(_x_id, _x_ghost) = std::visit(
1077 [&](auto& u)
1078 {
1079 return impl_vtx::vtx_write_mesh_from_space(*_io, *_engine,
1080 *u->function_space());
1081 },
1082 _u[0]);
1083 }
1084 else
1085 {
1086 // Node global ids
1087 adios2::Variable orig_id = impl_adios2::define_variable<std::int64_t>(
1088 *_io, "vtkOriginalPointIds", {}, {}, {_x_id.size()});
1089 _engine->Put(orig_id, _x_id.data());
1090 adios2::Variable ghost = impl_adios2::define_variable<std::uint8_t>(
1091 *_io, "vtkGhostType", {}, {}, {_x_ghost.size()});
1092 _engine->Put(ghost, _x_ghost.data());
1093 _engine->PerformPuts();
1094 }
1095
1096 // Write function data for each function to file
1097 for (auto& v : _u)
1098 {
1099 std::visit([&](auto& u)
1100 { impl_vtx::vtx_write_data(*_io, *_engine, *u); }, v);
1101 }
1102 }
1103
1104 _engine->EndStep();
1105 }
1106
1107private:
1108 std::shared_ptr<const mesh::Mesh<T>> _mesh;
1109 adios2_writer::U<T> _u;
1110
1111 // Control whether the mesh is written to file once or at every time
1112 // step
1113 VTXMeshPolicy _mesh_reuse_policy;
1114 std::vector<std::int64_t> _x_id;
1115 std::vector<std::uint8_t> _x_ghost;
1116
1117 // Special handling of piecewise constant functions
1118 bool _is_piecewise_constant;
1119};
1120
1122template <typename U, typename T>
1123VTXWriter(MPI_Comm comm, U filename, T mesh)
1124 -> VTXWriter<typename std::remove_cvref<
1125 typename T::element_type>::type::geometry_type::value_type>;
1126
1128template <typename U, typename T>
1129FidesWriter(MPI_Comm comm, U filename, T mesh)
1130 -> FidesWriter<typename std::remove_cvref<
1131 typename T::element_type>::type::geometry_type::value_type>;
1132
1133} // namespace dolfinx::io
1134
1135#endif
std::shared_ptr< const mesh::Mesh< T > > extract_common_mesh(const typename adios2_writer::U< T > &u)
Extract common mesh from list of Functions.
Definition ADIOS2Writers.h:133
std::pair< std::vector< std::int64_t >, std::vector< std::uint8_t > > vtx_write_mesh_from_space(adios2::IO &io, adios2::Engine &engine, const fem::FunctionSpace< T > &V)
Given a FunctionSpace, create a topology and geometry based on the function space dof coordinates....
Definition ADIOS2Writers.h:813
adios2::Variable< T > define_variable(adios2::IO &io, std::string name, const adios2::Dims &shape=adios2::Dims(), const adios2::Dims &start=adios2::Dims(), const adios2::Dims &count=adios2::Dims())
Definition ADIOS2Writers.h:115
void vtx_write_mesh(adios2::IO &io, adios2::Engine &engine, const mesh::Mesh< T > &mesh)
Definition ADIOS2Writers.h:742
adios2::Attribute< T > define_attribute(adios2::IO &io, std::string name, const T &value, std::string var_name="", std::string separator="/")
Definition ADIOS2Writers.h:102
std::vector< std::string > extract_function_names(const typename adios2_writer::U< T > &u)
Extract name of functions and split into real and imaginary component.
Definition ADIOS2Writers.h:642
constexpr std::array field_ext
Definition ADIOS2Writers.h:97
void write_data(adios2::IO &io, adios2::Engine &engine, const fem::Function< T, U > &u)
Definition ADIOS2Writers.h:291
void write_mesh(adios2::IO &io, adios2::Engine &engine, const mesh::Mesh< T > &mesh)
Write mesh geometry and connectivity (topology) for Fides.
Definition ADIOS2Writers.h:386
void vtx_write_data(adios2::IO &io, adios2::Engine &engine, const fem::Function< T, X > &u)
Definition ADIOS2Writers.h:676
std::vector< T > pack_function_data(const fem::Function< T, U > &u)
Definition ADIOS2Writers.h:227
std::stringstream create_vtk_schema(const std::vector< std::string > &point_data, const std::vector< std::string > &cell_data)
Definition ADIOS2Writers.cpp:94
void initialize_function_attributes(adios2::IO &io, const typename adios2_writer::U< T > &u)
Definition ADIOS2Writers.h:175
Degree-of-freedom map representations and tools.
This class represents a finite element function space defined by a mesh, a finite element,...
Definition vtk_utils.h:32
Definition XDMFFile.h:29
Base class for ADIOS2-based writers.
Definition ADIOS2Writers.h:56
ADIOS2Writer(MPI_Comm comm, const std::filesystem::path &filename, std::string tag, std::string engine)
Create an ADIOS2-based writer.
Definition ADIOS2Writers.cpp:52
ADIOS2Writer & operator=(ADIOS2Writer &&writer)=default
Move assignment.
void close()
Close the file.
Definition ADIOS2Writers.cpp:64
ADIOS2Writer(const ADIOS2Writer &)=delete
Copy constructor.
ADIOS2Writer(ADIOS2Writer &&writer)=default
Move constructor.
~ADIOS2Writer()
Destructor.
Definition ADIOS2Writers.cpp:62
Geometry stores the geometry imposed on a mesh.
Definition Geometry.h:34
std::shared_ptr< const common::IndexMap > index_map() const
Index map.
Definition Geometry.h:160
const fem::CoordinateElement< value_type > & cmap() const
The element that describes the geometry map.
Definition Geometry.h:181
MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< const std::int32_t, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents< std::size_t, 2 > > dofmap() const
DofMap for the geometry.
Definition Geometry.h:124
const std::vector< std::int64_t > & input_global_indices() const
Global user indices.
Definition Geometry.h:202
std::span< const value_type > x() const
Access geometry degrees-of-freedom data (const version).
Definition Geometry.h:169
A Mesh consists of a set of connected and numbered mesh topological entities, and geometry data.
Definition Mesh.h:23
Finite element method functionality.
Definition assemble_matrix_impl.h:26
std::int8_t get_vtk_cell_type(mesh::CellType cell, int dim)
Definition cells.cpp:682
Support for file IO.
Definition ADIOS2Writers.h:42
std::tuple< std::vector< T >, std::array< std::size_t, 2 >, std::vector< std::int64_t >, std::vector< std::uint8_t >, std::vector< std::int64_t >, std::array< std::size_t, 2 > > vtk_mesh_from_space(const fem::FunctionSpace< T > &V)
Given a FunctionSpace, create a topology and geometry based on the dof coordinates.
Definition vtk_utils.h:192
std::pair< std::vector< std::int64_t >, std::array< std::size_t, 2 > > extract_vtk_connectivity(MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< const std::int32_t, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents< std::size_t, 2 > > dofmap_x, mesh::CellType cell_type)
Extract the cell topology (connectivity) in VTK ordering for all cells the mesh. The 'topology' inclu...
Definition vtk_utils.cpp:23
int cell_num_entities(CellType type, int dim)
Definition cell_types.cpp:139
CellType
Cell type identifier.
Definition cell_types.h:22