43namespace adios2_writer
46template <std::
floating_po
int 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>>>>;
64 ADIOS2Writer(MPI_Comm comm,
const std::filesystem::path& filename,
65 std::string tag, std::string engine);
87 std::unique_ptr<adios2::ADIOS> _adios;
88 std::unique_ptr<adios2::IO> _io;
89 std::unique_ptr<adios2::Engine> _engine;
103 const T& value, std::string var_name =
"",
104 std::string separator =
"/")
106 if (adios2::Attribute<T> attr = io.InquireAttribute<T>(name); attr)
109 return io.DefineAttribute<T>(name, value, var_name, separator);
116 const adios2::Dims& shape = adios2::Dims(),
117 const adios2::Dims& start = adios2::Dims(),
118 const adios2::Dims& count = adios2::Dims())
120 if (adios2::Variable v = io.InquireVariable<T>(name); v)
122 if (v.Count() != count and v.ShapeID() == adios2::ShapeID::LocalArray)
123 v.SetSelection({start, count});
127 return io.DefineVariable<T>(name, shape, start, count);
131template <std::
floating_po
int T>
132std::shared_ptr<const mesh::Mesh<T>>
137 auto mesh = std::visit([](
auto&& u) {
return u->function_space()->mesh(); },
147 if (mesh != u->function_space()->mesh())
149 throw std::runtime_error(
150 "ADIOS2Writer only supports functions sharing the same mesh");
168void initialize_mesh_attributes(adios2::IO& io,
mesh::CellType type);
174template <std::
floating_po
int T>
176 const typename adios2_writer::U<T>& u)
180 std::vector<std::array<std::string, 2>> u_data;
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"});
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"});
200 if (adios2::Attribute<std::string> assc
201 = io.InquireAttribute<std::string>(
"Fides_Variable_Associations");
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());
212 if (adios2::Attribute<std::string> fields
213 = io.InquireAttribute<std::string>(
"Fides_Variable_List");
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(),
226template <
typename T, std::
floating_po
int U>
229 auto V = u.function_space();
231 auto dofmap = V->dofmap();
233 auto mesh = V->mesh();
236 auto topology = mesh->topology();
241 assert(dofmap->element_dof_layout() == geometry.
cmap().create_dof_layout());
243 int tdim = topology->dim();
244 auto cell_map = topology->index_map(tdim);
246 std::int32_t num_cells = cell_map->size_local() + cell_map->num_ghosts();
248 auto vertex_map = topology->index_map(0);
250 std::uint32_t num_vertices
251 = vertex_map->size_local() + vertex_map->num_ghosts();
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,
257 if (num_components < std::pow(3, rank))
258 num_components = std::pow(3, rank);
259 else if (num_components > std::pow(3, rank))
261 throw std::runtime_error(
262 "Fides does not support tensors larger than pow(3, rank)");
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)
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];
290template <
typename T, std::
floating_po
int U>
296 auto V = u.function_space();
298 auto dofmap = V->dofmap();
300 auto mesh = V->mesh();
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,
309 bool need_padding =
false;
310 if (num_components < std::pow(3, rank))
312 num_components = std::pow(3, rank);
315 else if (num_components > std::pow(3, rank))
317 throw std::runtime_error(
318 "Fides does not support tensors larger than pow(3, rank)");
323 std::span<const T> data;
324 std::vector<T> _data;
325 auto eq_check = [](
auto x,
auto y) ->
bool
327 return x.extents() == y.extents()
328 and std::equal(x.data_handle(), x.data_handle() + x.size(),
332 if (!need_padding and eq_check(mesh->geometry().dofmap(), dofmap->map()))
333 data = u.x()->array();
336 _data = impl_fides::pack_function_data(u);
337 data = std::span<const T>(_data);
340 auto vertex_map = mesh->topology()->index_map(0);
342 std::uint32_t num_vertices
343 = vertex_map->size_local() + vertex_map->num_ghosts();
346 assert(data.size() % num_components == 0);
347 if constexpr (std::is_scalar_v<T>)
350 adios2::Variable local_output = impl_adios2::define_variable<T>(
351 io, u.name, {}, {}, {num_vertices, num_components});
354 engine.Put(local_output, data.data());
355 engine.PerformPuts();
360 using X =
typename T::value_type;
362 std::vector<X> data_real(data.size()), data_imag(data.size());
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());
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();
385template <std::
floating_po
int T>
390 auto topology = mesh.topology();
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());
405 int tdim = topology->dim();
406 std::int32_t num_cells = topology->index_map(tdim)->size_local();
407 int num_nodes = geometry.
cmap().dim();
409 topology->cell_type());
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();
421enum class FidesMeshPolicy
431template <std::
floating_po
int T>
432class FidesWriter :
public ADIOS2Writer
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)
451 auto topology = mesh->topology();
455 throw std::runtime_error(
"Fides only supports lowest-order meshes.");
456 impl_fides::initialize_mesh_attributes(*_io, type);
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),
480 throw std::runtime_error(
"FidesWriter fem::Function list is empty");
483 auto V0 = std::visit([](
auto& u) { return u->function_space().get(); },
486 auto mesh = V0->mesh().get();
490 auto element0 = V0->element().get();
494 if (element0->is_mixed())
496 throw std::runtime_error(
497 "Mixed functions are not supported by FidesWriter");
502 if (!element0->interpolation_ident())
504 throw std::runtime_error(
"Only Lagrange functions are supported. "
505 "Interpolate Functions before output.");
509 if (element0->space_dimension() / element0->block_size() == 1)
511 throw std::runtime_error(
512 "Piecewise constants are not (yet) supported by FidesWriter");
521 [V0, num_vertices_per_cell, element0](
auto&& u)
523 auto element = u->function_space()->element();
525 if (*element != *element0)
527 throw std::runtime_error(
"All functions in FidesWriter must have "
528 "the same element type");
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)
536 throw std::runtime_error(
"Only first order Lagrange spaces are "
537 "supported by FidesWriter");
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(),
548 throw std::runtime_error(
549 "All functions must have the same dofmap for FideWriter.");
556 auto topology = mesh->topology();
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);
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)
583 FidesWriter(
const FidesWriter&) =
delete;
586 FidesWriter(FidesWriter&& file) =
default;
589 ~FidesWriter() =
default;
592 FidesWriter& operator=(FidesWriter&&) =
default;
595 FidesWriter& operator=(
const FidesWriter&) =
delete;
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)
610 impl_fides::write_mesh(*_io, *_engine, *_mesh);
615 std::visit([
this](
auto&& u)
616 { impl_fides::write_data(*_io, *_engine, *u); }, v);
625 FidesMeshPolicy _mesh_reuse_policy;
627 std::shared_ptr<const mesh::Mesh<T>> _mesh;
628 adios2_writer::U<T> _u;
637 const std::vector<std::string>& cell_data);
640template <std::
floating_po
int T>
641std::vector<std::string>
644 std::vector<std::string> names;
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);
656 names.push_back(u->name + impl_adios2::field_ext[0]);
657 names.push_back(u->name + impl_adios2::field_ext[1]);
675template <
typename T, std::
floating_po
int X>
681 std::span<const T> u_vector = u.x()->array();
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,
689 if (num_comp < std::pow(3, rank))
690 num_comp = std::pow(3, rank);
692 std::shared_ptr<const fem::DofMap> dofmap = u.function_space()->dofmap();
694 std::shared_ptr<const common::IndexMap> index_map = dofmap->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())
701 if constexpr (std::is_scalar_v<T>)
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];
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);
716 using U =
typename T::value_type;
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]);
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);
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);
741template <std::
floating_po
int T>
746 auto topology = mesh.topology();
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());
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);
762 auto [vtkcells, shape]
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>(
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)
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()));
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());
792 adios2::Variable orig_id = impl_adios2::define_variable<std::int64_t>(
793 io,
"vtkOriginalPointIds", {}, {}, {num_vertices});
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();
811template <std::
floating_po
int T>
812std::pair<std::vector<std::int64_t>, std::vector<std::uint8_t>>
816 auto mesh = V.mesh();
818 auto topology = mesh->topology();
820 int tdim = topology->dim();
825 std::uint32_t num_dofs = xshape[0];
832 std::vector<std::int64_t> cells(vtkshape[0] * (vtkshape[1] + 1), vtkshape[1]);
835 for (std::size_t c = 0; c < vtkshape[0]; ++c)
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()));
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});
856 engine.Put<std::uint32_t>(vertices, num_dofs);
857 engine.Put<std::uint32_t>(elements, vtkshape[0]);
858 engine.Put<std::uint32_t>(
860 engine.Put(local_geometry, x.data());
861 engine.Put(local_topology, cells.data());
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());
871 engine.PerformPuts();
872 return {std::move(x_id), std::move(x_ghost)};
877enum class VTXMeshPolicy
889template <std::
floating_po
int T>
890class VTXWriter :
public ADIOS2Writer
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)
911 std::string vtk_scheme = impl_vtx::create_vtk_schema({}, {}).str();
912 impl_adios2::define_attribute<std::string>(*_io,
"vtk.xml", vtk_scheme);
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),
935 _mesh_reuse_policy(mesh_policy), _u(u), _is_piecewise_constant(false)
938 throw std::runtime_error(
"VTXWriter fem::Function list is empty.");
941 auto V0 = std::visit([](
auto& u) { return u->function_space().get(); },
944 auto element0 = V0->element().get();
948 if (element0->is_mixed())
950 throw std::runtime_error(
951 "Mixed functions are not supported by VTXWriter.");
957 if (!element0->interpolation_ident())
959 throw std::runtime_error(
960 "Only (discontinuous) Lagrange functions are "
961 "supported. Interpolate Functions before output.");
965 if (element0->space_dimension() / element0->block_size() == 1)
966 _is_piecewise_constant =
true;
974 auto element = u->function_space()->element();
976 if (*element != *V0->element().get())
978 throw std::runtime_error(
"All functions in VTXWriter must have "
979 "the same element type.");
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(),
989 throw std::runtime_error(
990 "All functions must have the same dofmap for VTXWriter.");
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();
1003 vtk_scheme = impl_vtx::create_vtk_schema(names, {}).str();
1005 impl_adios2::define_attribute<std::string>(*_io,
"vtk.xml", vtk_scheme);
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)
1030 VTXWriter(
const VTXWriter&) =
delete;
1033 VTXWriter(VTXWriter&& file) =
default;
1036 ~VTXWriter() =
default;
1039 VTXWriter& operator=(VTXWriter&&) =
default;
1042 VTXWriter& operator=(
const VTXWriter&) =
delete;
1046 void write(
double t)
1049 adios2::Variable var_step
1050 = impl_adios2::define_variable<double>(*_io,
"step");
1053 _engine->BeginStep();
1054 _engine->template Put<double>(var_step, t);
1057 if (_is_piecewise_constant or _u.empty())
1059 impl_vtx::vtx_write_mesh(*_io, *_engine, *_mesh);
1060 if (_is_piecewise_constant)
1064 std::visit([&](
auto& u)
1065 { impl_vtx::vtx_write_data(*_io, *_engine, *u); }, v);
1071 if (_mesh_reuse_policy == VTXMeshPolicy::update
1072 or !(_io->template InquireVariable<std::int64_t>(
"connectivity")))
1076 std::tie(_x_id, _x_ghost) = std::visit(
1079 return impl_vtx::vtx_write_mesh_from_space(*_io, *_engine,
1080 *u->function_space());
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();
1099 std::visit([&](
auto& u)
1100 { impl_vtx::vtx_write_data(*_io, *_engine, *u); }, v);
1108 std::shared_ptr<const mesh::Mesh<T>> _mesh;
1109 adios2_writer::U<T> _u;
1113 VTXMeshPolicy _mesh_reuse_policy;
1114 std::vector<std::int64_t> _x_id;
1115 std::vector<std::uint8_t> _x_ghost;
1118 bool _is_piecewise_constant;
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>;
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>;