// Copyright (c) 2010-2023, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-806117. // // This file is part of the MFEM library. For more information and source code // availability visit https://mfem.org. // // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. // // Created on: Jan 22, 2019 // Author: William F Godoy godoywf@ornl.gov // adios2: Adaptable Input/Output System https://github.com/ornladios/ADIOS2 #include "adios2stream.hpp" #ifdef MFEM_USE_ADIOS2 #include "../fem/geom.hpp" #include "../general/array.hpp" #include "../mesh/element.hpp" #include "../mesh/mesh.hpp" #include "../fem/gridfunc.hpp" #include namespace mfem { namespace { // these functions might be included in adios2 upstream next release template adios2::Variable SafeDefineVariable(adios2::IO io, const std::string& variable_name, const adios2::Dims& shape = adios2::Dims(), const adios2::Dims& start = adios2::Dims(), const adios2::Dims& count = adios2::Dims()) { adios2::Variable variable = io.InquireVariable(variable_name); if (variable) { if (variable.Count() != count && variable.ShapeID() == adios2::ShapeID::LocalArray) { variable.SetSelection({start, count}); } } else { variable = io.DefineVariable(variable_name, shape, start, count); } return variable; } template adios2::Attribute SafeDefineAttribute(adios2::IO io, const std::string& attribute_name, const T& value, const std::string& variable_name = "", const std::string separator = "/") { adios2::Attribute attribute = io.InquireAttribute(attribute_name); if (attribute) { return attribute; } return io.DefineAttribute(attribute_name, value, variable_name, separator ); } template adios2::Attribute SafeDefineAttribute(adios2::IO io, const std::string& attribute_name, const T* values, const size_t size, const std::string& variable_name = "", const std::string separator = "/") { adios2::Attribute attribute = io.InquireAttribute(attribute_name); if (attribute) { return attribute; } return io.DefineAttribute(attribute_name, values, size, variable_name, separator ); } bool SetBoolParameter(const std::string key, const std::map& parameters, const bool default_value) noexcept { auto it = parameters.find(key); if (it != parameters.end()) { std::string value = it->second; std::transform(value.begin(), value.end(), value.begin(), ::tolower); if (value == "on" || value == "true") { return true; } else if ( value == "off" || value == "false") { return false; } } return default_value; } } //end empty namespace // PUBLIC #ifdef MFEM_USE_MPI adios2stream::adios2stream(const std::string& name, const openmode mode, MPI_Comm comm, const std::string engineType) : name(name), adios2_openmode(mode), adios(new adios2::ADIOS(comm)), io(adios->DeclareIO(name)) { io.SetEngine(engineType); } #else adios2stream::adios2stream(const std::string& name, const openmode mode, const std::string engineType) : name(name), adios2_openmode(mode), adios(new adios2::ADIOS()), io(adios->DeclareIO(name)) { io.SetEngine(engineType); } #endif adios2stream::~adios2stream() { if (engine) { SafeDefineAttribute(io, "vtk.xml", VTKSchema() ); engine.Close(); } } void adios2stream::SetParameters( const std::map& parameters) { io.SetParameters(parameters); refine = SetBoolParameter("RefinedData", parameters, true); } void adios2stream::SetParameter(const std::string key, const std::string value) noexcept { io.SetParameter(key, value); if (key == "RefinedData") { refine = SetBoolParameter("RefinedData", io.Parameters(), true); } } void adios2stream::BeginStep() { if (!engine) { engine = io.Open(name, adios2::Mode::Write); } engine.BeginStep(); active_step = true; } void adios2stream::EndStep() { if (!engine || !active_step) { const std::string message = "MFEM adios2stream error: calling EndStep " "on uninitialized step (need BeginStep)"; mfem_error(message.c_str()); } SafeDefineAttribute(io, "vtk.xml", VTKSchema() ); engine.EndStep(); active_step = false; } void adios2stream::SetTime(const double time) { adios2::Variable var_time = SafeDefineVariable(io, "TIME"); engine.Put(var_time, time); transient = true; } void adios2stream::SetCycle(const int cycle) { adios2::Variable var_cycle = SafeDefineVariable(io,"CYCLE"); engine.Put(var_cycle, cycle); } void adios2stream::SetRefinementLevel(const int level) noexcept { refinement_level = level; } size_t adios2stream::CurrentStep() const { return engine.CurrentStep(); } void adios2stream::Close() { if (engine) { if (!active_step) { SafeDefineAttribute(io, "vtk.xml", VTKSchema() ); } engine.Close(); } if (adios) { adios.reset(); } } // PROTECTED (accessible by friend class Mesh) void adios2stream::Print(const Mesh& mesh, const mode print_mode) { auto lf_DefineMeshMetadata = [this](Mesh& mesh) { // check types are constant if (!IsConstantElementType(mesh.elements)) { throw std::invalid_argument("MFEM::adios2stream ERROR: non-constant " " element types not yet implemented\n"); } // format info SafeDefineAttribute(io, "format", "MFEM ADIOS2 BP v0.2" ); SafeDefineAttribute(io, "format/version", "0.2" ); std::string mesh_type = "Unknown"; std::vector viz_tools; viz_tools.reserve(2); //for now if (mesh.NURBSext) { mesh_type = "MFEM NURBS"; viz_tools.push_back("NONE"); } else if (mesh.ncmesh) { mesh_type = "MFEM mesh v1.1"; viz_tools.push_back("NONE"); } else { mesh_type = "MFEM mesh v1.0"; viz_tools.push_back("Paraview: ADIOS2VTXReader"); viz_tools.push_back("VTK: vtkADIOS2VTXReader.h"); } SafeDefineAttribute(io, "format/mfem_mesh", mesh_type ); SafeDefineAttribute(io, "format/viz_tools", viz_tools.data(), viz_tools.size() ); // elements const uint32_t dimension = static_cast(mesh.Dimension()); SafeDefineAttribute(io, "dimension", dimension); SafeDefineVariable(io,"NumOfElements", {adios2::LocalValueDim}); SafeDefineVariable(io, "types"); size_t nelements = 0; size_t element_nvertices = 0; size_t nvertices = 0; if (refine) { for (int i = 0; i < mesh.GetNE(); ++i) { const Geometry::Type type = mesh.GetElementBaseGeometry(i); RefinedGeometry* refined_geometry = GlobGeometryRefiner.Refine(type, refinement_level, 1); if (refined_geometry == nullptr) { mfem_error("ERROR: could not refine geometry in call to Save with adios2stream \n"); } element_nvertices = static_cast(Geometries.GetVertices( type)->GetNPoints()); nelements += refined_geometry->RefGeoms.Size() / element_nvertices; nvertices += refined_geometry->RefPts.GetNPoints(); } refined_mesh_nelements = nelements; refined_mesh_nvertices = nvertices; } else { nelements = static_cast(mesh.GetNE()); element_nvertices = static_cast(mesh.elements[0]->GetNVertices()); } SafeDefineVariable(io, "connectivity", {}, {}, {nelements, element_nvertices+1}); SafeDefineVariable(io, "attribute", {}, {}, {nelements}); // vertices SafeDefineVariable(io,"NumOfVertices", {adios2::LocalValueDim}); if (refine) { SafeDefineVariable( io, "vertices", {}, {}, {nvertices, static_cast(dimension)}); } else { const GridFunction* grid_function = mesh.GetNodes(); if (grid_function == nullptr) { const size_t nVertices = static_cast(mesh.GetNV()); const size_t spaceDim = static_cast(mesh.SpaceDimension()); // similar to Ordering::byVDIM SafeDefineVariable( io, "vertices", {}, {}, {nVertices, spaceDim}); } else { const size_t size = static_cast(grid_function->Size()); const FiniteElementSpace* fes = grid_function->FESpace(); const size_t components = static_cast(fes->GetVDim()); const size_t tuples = size /components; SafeDefineVariable(io, "vertices", {}, {}, {tuples, components} ); if (fes->GetOrdering() == Ordering::byNODES) { ordering_by_node = true; } } } }; auto lf_PrintRefinedMeshData = [this](Mesh& mesh) { // elements and vertices engine.Put("NumOfElements", static_cast(refined_mesh_nelements)); engine.Put("NumOfVertices", static_cast(refined_mesh_nvertices)); const uint32_t vtkType = GLVISToVTKType(static_cast(mesh.elements[0]->GetGeometryType())); engine.Put("types", vtkType); adios2::Variable var_vertices = io.InquireVariable("vertices"); adios2::Variable::Span span_vertices = engine.Put(var_vertices); adios2::Variable var_connectivity = io.InquireVariable("connectivity"); adios2::Variable::Span span_connectivity = engine.Put (var_connectivity); adios2::Variable var_element_attribute = io.InquireVariable("attribute"); adios2::Variable::Span span_element_attribute = engine.Put (var_element_attribute); size_t span_vertices_offset = 0; size_t span_connectivity_offset = 0; size_t span_element_attribute_offset = 0; // use for setting absolute node id for each element size_t point_id = 0; DenseMatrix pmatrix; for (int e = 0; e < mesh.GetNE(); ++e) { const Geometry::Type type = mesh.GetElementBaseGeometry(e); RefinedGeometry* refined_geometry = GlobGeometryRefiner.Refine(type, refinement_level, 1); // vertices mesh.GetElementTransformation(e)->Transform(refined_geometry->RefPts, pmatrix); for (int i = 0; i < pmatrix.Width(); ++i) { for (int j = 0; j < pmatrix.Height(); ++j) { span_vertices[span_vertices_offset + i*pmatrix.Height() + j] = pmatrix(j, i); } } span_vertices_offset += static_cast(pmatrix.Width()*pmatrix.Height()); // element attribute const int element_attribute = mesh.GetAttribute(e); // connectivity const int nv = Geometries.GetVertices(type)->GetNPoints(); const Array &element_vertices = refined_geometry->RefGeoms; for (int v = 0; v < element_vertices.Size();) { span_connectivity[span_connectivity_offset] = static_cast(nv); ++span_connectivity_offset; span_element_attribute[span_element_attribute_offset] = static_cast (element_attribute); ++span_element_attribute_offset; for (int k =0; k < nv; k++, v++ ) { span_connectivity[span_connectivity_offset] = static_cast (point_id + element_vertices[v]); ++span_connectivity_offset; } } point_id += static_cast(refined_geometry->RefPts.GetNPoints()); } for (int e = 0; e < mesh.GetNE(); ++e) { const Geometry::Type type = mesh.GetElementBaseGeometry(e); RefinedGeometry* refined_geometry = GlobGeometryRefiner.Refine(type, refinement_level, 1); } }; auto lf_PrintMeshData = [&](Mesh& mesh) { if (refine) { lf_PrintRefinedMeshData(mesh); return; } // elements engine.Put("NumOfElements", static_cast(mesh.GetNE())); const uint32_t vtkType = GLVISToVTKType(static_cast(mesh.elements[0]->GetGeometryType())); engine.Put("types", vtkType); adios2::Variable varConnectivity = io.InquireVariable("connectivity"); // zero-copy access to adios2 buffer to put non-contiguous to contiguous memory adios2::Variable::Span spanConnectivity = engine.Put(varConnectivity); adios2::Variable varElementAttribute = io.InquireVariable("attribute"); // zero-copy access to adios2 buffer to put non-contiguous to contiguous memory adios2::Variable::Span spanElementAttribute = engine.Put(varElementAttribute); size_t elementPosition = 0; for (int e = 0; e < mesh.GetNE(); ++e) { spanElementAttribute[e] = static_cast(mesh.GetAttribute(e)); const int nVertices = mesh.elements[e]->GetNVertices(); spanConnectivity[elementPosition] = nVertices; for (int v = 0; v < nVertices; ++v) { spanConnectivity[elementPosition + v + 1] = mesh.elements[e]->GetVertices()[v]; } elementPosition += nVertices + 1; } // vertices engine.Put("NumOfVertices", static_cast(mesh.GetNV())); if (mesh.GetNodes() == nullptr) { adios2::Variable varVertices = io.InquireVariable("vertices"); // zero-copy access to adios2 buffer to put non-contiguous to contiguous memory adios2::Variable::Span spanVertices = engine.Put(varVertices); for (int v = 0; v < mesh.GetNV(); ++v) { const int space_dim = mesh.SpaceDimension(); for (int coord = 0; coord < space_dim; ++coord) { spanVertices[v * space_dim + coord] = mesh.vertices[v](coord); } } } else { const GridFunction* grid_function = mesh.GetNodes(); if (ordering_by_node) { adios2::Variable varVertices = io.InquireVariable("vertices"); // zero-copy access to adios2 buffer to put non-contiguous to contiguous memory adios2::Variable::Span spanVertices = engine.Put(varVertices); const size_t size = static_cast(grid_function->Size()); const FiniteElementSpace* fes = grid_function->FESpace(); const size_t components = static_cast(fes->GetVDim()); const size_t tuples = size /components; const double* data = grid_function->GetData(); for (size_t i = 0; i < tuples; ++i) { for (size_t j = 0; j < components; ++j) { spanVertices[i*components + j] = data[j*tuples + i]; } } } else { grid_function->Print(*this, "vertices"); } } }; // BODY OF FUNCTION STARTS HERE try { Mesh ref_mesh(mesh); lf_DefineMeshMetadata(ref_mesh); if (!engine) // if Engine is closed { engine = io.Open(name, adios2::Mode::Write); } lf_PrintMeshData(ref_mesh); if (print_mode == mode::sync) { engine.PerformPuts(); } } catch (std::exception& e) { const std::string warning = "MFEM: adios2stream exception caught, invalid bp dataset: " + name + "," + e.what(); mfem_warning( warning.c_str()); } } void adios2stream::Save(const GridFunction& grid_function, const std::string& variable_name, const data_type type) { auto lf_SafeDefine = [&](const std::string& variable_name, const size_t tuples, const size_t components, const Ordering::Type ordering, const std::string& fespace_name) { adios2::Variable var = io.InquireVariable(variable_name); if (!var) { if (components == 1 && type == adios2stream::data_type::point_data) { io.DefineVariable(variable_name, {}, {}, {tuples*components}); } else { const adios2::Dims count = (ordering == Ordering::byNODES) ? adios2::Dims{components, tuples} : adios2::Dims{tuples, components}; io.DefineVariable(variable_name, {}, {}, count); } SafeDefineAttribute(io, "FiniteElementSpace", fespace_name, variable_name); } }; // BODY OF FUNCTION STARTS HERE const std::map parameters = io.Parameters(); const bool full_data = SetBoolParameter("FullData", parameters, false); if (!full_data && !refine) { return; } const FiniteElementSpace* fes = grid_function.FESpace(); if (refine) { const Mesh *mesh = fes->GetMesh(); const size_t components = static_cast(grid_function.VectorDim()); // const size_t tuples = static_cast(mesh->GetNV()); const size_t tuples = refined_mesh_nvertices; lf_SafeDefine(variable_name, tuples, components, Ordering::byVDIM, std::string(fes->FEColl()->Name())); if (type == adios2stream::data_type::point_data) { point_data_variables.insert(variable_name); } RefinedGeometry* refined_geometry; DenseMatrix transform; // zero-copy access to adios2 buffer to put non-contiguous to contiguous memory adios2::Variable variable = io.InquireVariable(variable_name); adios2::Variable::Span span = engine.Put(variable); size_t offset = 0; if (components == 1) { Vector scalar; const int nelements = mesh->GetNE(); for (int e = 0; e < nelements; ++e) { refined_geometry = GlobGeometryRefiner.Refine( mesh->GetElementBaseGeometry(e), refinement_level, 1); grid_function.GetValues(e, refined_geometry->RefPts, scalar, transform); const int size = scalar.Size(); for (int i = 0; i < size; ++i) { const double value = scalar(i); span.at(offset+i) = value; } offset += static_cast(size); } } else { DenseMatrix vector; for (int e = 0; e < mesh->GetNE(); ++e) { refined_geometry = GlobGeometryRefiner.Refine( mesh->GetElementBaseGeometry(e), refinement_level, 1); grid_function.GetVectorValues(e, refined_geometry->RefPts, vector, transform); for (int i = 0; i < vector.Width(); ++i) { for (int j = 0; j < vector.Height(); ++j) { span[offset + i*vector.Height() + j] = vector(j, i); } } offset += static_cast(vector.Width()*vector.Height()); } } } if (full_data) { const size_t size = static_cast(grid_function.Size()); const size_t components = static_cast(fes->GetVDim()); const size_t tuples = size /components; lf_SafeDefine(variable_name +"/full", tuples, components, fes->GetOrdering(), std::string(fes->FEColl()->Name()) ); // calls Vector::Print grid_function.Print(*this, variable_name+"/full"); if (!refine && type == adios2stream::data_type::point_data) { point_data_variables.insert(variable_name+"/full"); } } } // PRIVATE int32_t adios2stream::GLVISToVTKType( const int glvisType) const noexcept { uint32_t vtkType = 0; switch (glvisType) { case Geometry::Type::POINT: vtkType = 1; break; case Geometry::Type::SEGMENT: vtkType = 3; break; case Geometry::Type::TRIANGLE: vtkType = 5; break; case Geometry::Type::SQUARE: // vtkType = 8; vtkType = 9; break; case Geometry::Type::TETRAHEDRON: vtkType = 10; break; case Geometry::Type::CUBE: // vtkType = 11; vtkType = 12; break; case Geometry::Type::PRISM: vtkType = 13; break; default: vtkType = 0; break; } return vtkType; } bool adios2stream::IsConstantElementType(const Array& elements ) const noexcept { bool isConstType = true; const Geometry::Type type = elements[0]->GetGeometryType(); for (int e = 1; e < elements.Size(); ++e) { if (type != elements[e]->GetGeometryType()) { isConstType = false; break; } } return isConstType; } std::string adios2stream::VTKSchema() const noexcept { std::string vtkSchema = R"( )"; vtkSchema += R"( )"; if (point_data_variables.empty()) { vtkSchema += "\n"; } else { for (const std::string& point_datum : point_data_variables ) { vtkSchema += " \n"; } } if (transient) { vtkSchema += " \n"; vtkSchema += " TIME\n"; vtkSchema += " \n"; } vtkSchema += R"( )"; return vtkSchema; } adios2::Mode adios2stream::ToADIOS2Mode(const adios2stream::openmode mode) const noexcept { adios2::Mode adios2Mode = adios2::Mode::Undefined; switch (mode) { case adios2stream::openmode::out: adios2Mode = adios2::Mode::Write; break; case adios2stream::openmode::in: adios2Mode = adios2::Mode::Read; break; default: const std::string message = "MFEM adios2stream ERROR: only " "openmode::out and openmode::in " " are valid, in call to adios2stream constructor"; mfem_error(message.c_str()); } return adios2Mode; } } // end namespace mfem #endif // MFEM_USE_ADIOS2