resources.cpp algorithm cmath iomanip limits locale sstream typeinfo serif/backend/mfem/serialize/resources.hpp mfem/general/version.hpp serif/backend/mfem/geometry/deformation/reference_geometry.hpp serif serif::backend serif::backend::mfem serif::backend::mfem::serialize #include<algorithm> #include<cmath> #include<iomanip> #include<limits> #include<locale> #include<sstream> #include<typeinfo> #include"serif/backend/mfem/serialize/resources.hpp" #include"mfem/general/version.hpp" #ifdefined(MFEM_USE_MPI)&&__has_include(<HYPRE_config.h>) #include<HYPRE_config.h> #endif #ifdefined(MFEM_USE_METIS)&&__has_include(<metis.h>) #include<metis.h> #endif #ifdefined(MFEM_USE_ZLIB)&&__has_include(<zlib.h>) #include<zlib.h> #endif #include"serif/backend/mfem/geometry/deformation/reference_geometry.hpp" namespaceserif::backend::mfem::serialize{ namespace{ usingnamespaceutils::serialize; Nodeunsupported(std::stringmessage){ return{.type="unsupported",.value=std::move(message)}; } Nodelibraries(){ Noderesult=object("mfem.libraries",field("mfem_headers",std::string{MFEM_VERSION_STRING}), field("mfem_library",std::string{::mfem::GetVersionStr()}),field("mfem_git",std::string{::mfem::GetGitStr()}), field("mfem_features",std::string{::mfem::GetConfigStr()})); #ifdefHYPRE_RELEASE_VERSION result.children.emplace("hypre",encode(std::string{HYPRE_RELEASE_VERSION})); #endif #ifdefMETIS_VER_MAJOR result.children.emplace("metis",encode(std::to_string(METIS_VER_MAJOR)+"."+std::to_string(METIS_VER_MINOR)+"."+std::to_string(METIS_VER_SUBMINOR))); #endif #ifdefZLIB_VERSION result.children.emplace("zlib_headers",encode(std::string{ZLIB_VERSION})); result.children.emplace("zlib_library",encode(std::string{zlibVersion()})); #endif #ifdefMFEM_USE_MPI result.children.emplace("mpi_standard",encode(std::to_string(MPI_VERSION)+"."+std::to_string(MPI_SUBVERSION))); #ifdefOPEN_MPI result.children.emplace("mpi_implementation",encode(std::string{"OpenMPI"}+std::to_string(OMPI_MAJOR_VERSION)+"."+std::to_string(OMPI_MINOR_VERSION)+"."+std::to_string(OMPI_RELEASE_VERSION))); #elifdefined(MPICH_VERSION) result.children.emplace("mpi_implementation",encode(std::string{MPICH_VERSION})); #endif intinitialized=0; intfinalized=0; MPI_Initialized(&initialized); MPI_Finalized(&finalized); if(initialized&&!finalized){ charversion[MPI_MAX_LIBRARY_VERSION_STRING]; intlength=0; if(MPI_Get_library_version(version,&length)==MPI_SUCCESS){ result.children.emplace("mpi_library",encode(std::string{version,static_cast<std::size_t>(length)})); } } #endif returnresult; } std::vector<int>element_numbering(const::mfem::FiniteElementSpace&space){ std::vector<int>result; ::mfem::Array<int>dofs; for(intelement=0;element<space.GetNE();++element){ space.GetElementVDofs(element,dofs); result.push_back(dofs.Size()); result.insert(result.end(),dofs.begin(),dofs.end()); } returnresult; } Nodesave_collection(const::mfem::FiniteElementCollection&collection){ intorder=collection.GetOrder(); intclosed=-1; intopen=-1; std::stringfamily; if(typeid(collection)==typeid(::mfem::H1_FECollection)){ family="H1"; closed=static_cast<const::mfem::H1_FECollection&>(collection).GetBasisType(); }elseif(typeid(collection)==typeid(::mfem::L2_FECollection)){ family="L2"; open=static_cast<const::mfem::L2_FECollection&>(collection).GetBasisType(); }else{ returnunsupported("OnlystandardH1andL2collectionshaveanMFEMarchiveconstructor."); } if(std::string_view{collection.Name()}.contains("Pyr")||(family=="L2"&&collection.GetMapType(3)!=::mfem::FiniteElement::VALUE)){ returnunsupported("Nondefaultpyramidbasesandintegral-mappedscalarcollectionsneedanexplicitarchiveconstructor."); } returnobject("mfem.collection",field("family",family),field("order",order),field("closed_basis",closed), field("open_basis",open),field("name",std::string{collection.Name()})); } Result<std::unique_ptr<::mfem::FiniteElementCollection>>restore_collection(constNode&node){ autofields=read_object(node,"mfem.collection",[](std::stringfamily,intorder,intclosed,intopen,std::stringname){ returnstd::tuple{std::move(family),order,closed,open,std::move(name)}; },member<std::string>("family"),member<int>("order"),member<int>("closed_basis"),member<int>("open_basis"),member<std::string>("name")); if(!fields){ returnstd::unexpected(fields.error()); } constauto&[family,order,closed,open,name]=*fields; constautovalid=[](intbasis){returnbasis>=0&&basis<::mfem::BasisType::NumBasisTypes;}; if(order<0||(family=="H1"&&order==0)|| (family=="H1"&&!valid(closed))||(family=="L2"&&!valid(open))){ returnstd::unexpected(Error{"collection","Invalidfinite-elementorderorbasis."}); } std::unique_ptr<::mfem::FiniteElementCollection>result; if(family=="H1"){ result=std::make_unique<::mfem::H1_FECollection>(order,3,closed); }elseif(family=="L2"){ result=std::make_unique<::mfem::L2_FECollection>(order,3,open); }else{ returnstd::unexpected(Error{"collection/family","Unsupportedfinite-elementfamily."}); } if(name!=result->Name()){ returnstd::unexpected(Error{"collection/name","Thesavedcollectionrequiresadifferentbasis,maptypeorconstructorthanthisloadersupports."}); } returnresult; } boolsupported_mesh(const::mfem::Mesh&mesh){ if(mesh.Dimension()!=3||mesh.SpaceDimension()!=3||mesh.NURBSext!=nullptr||typeid(mesh)!=typeid(::mfem::Mesh)){ returnfalse; } if(constauto*nodes=mesh.GetNodes()){ for(intdof=0;dof<nodes->Size();++dof){ if(!std::isfinite((*nodes)[dof])){ returnfalse; } } }else{ for(intvertex=0;vertex<mesh.GetNV();++vertex){ for(intcomponent=0;component<mesh.SpaceDimension();++component){ if(!std::isfinite(mesh.GetVertex(vertex)[component])){ returnfalse; } } } } returntrue; } Result<std::unique_ptr<::mfem::Mesh>>restore_mesh(constNode&node,constMeshResolver&resolver){ autoidentity=read_field<std::string>(node,"identity"); if(node.type!="mfem.mesh"||!identity){ returnstd::unexpected(Error{"mesh","Missingsupportedmeshschemaorcontentidentity."}); } Result<std::string>text=std::unexpected(Error{"mesh","Thisarchiveomitsitsreferencemesh.Supplyameshresolverforitscontentidentity."}); if(node.children.contains("native")){ text=read_field<std::string>(node,"native"); }elseif(resolver){ text=resolver(*identity); } if(!text){ returnstd::unexpected(text.error()); } if(fingerprint(*text)!=*identity){ returnstd::unexpected(Error{"mesh/identity","Referencemeshcontentdiffersfromthesavedidentity."}); } std::istringstreamstream(*text); stream.imbue(std::locale::classic()); //Refinementmarkingandorientationrepaircanchangecoefficientnumbering. automesh=std::make_unique<::mfem::Mesh>(stream,1,0,false); if(!supported_mesh(*mesh)||mesh_text(*mesh)!=*text){ returnstd::unexpected(Error{"mesh","Nativemeshreconstructionchangedorderedtopologyorcoordinates,orusedunsupportedmeshcapabilities."}); } returnmesh; } } std::stringmesh_text(const::mfem::Mesh&mesh){ std::ostringstreamstream; stream.imbue(std::locale::classic()); stream<<std::setprecision(std::numeric_limits<double>::max_digits10); mesh.Print(stream); returnstream.str(); } ResourceWriter::ResourceWriter(SaveOptionsoptions):m_options(options),m_record{.type="mfem.resources"}{ usingnamespaceutils::serialize; m_record.children.emplace("mfem_version",encode(std::string{MFEM_VERSION_STRING})); m_record.children.emplace("libraries",libraries()); m_record.children.emplace("meshes",Node{.type="array"}); m_record.children.emplace("collections",Node{.type="array"}); m_record.children.emplace("spaces",Node{.type="array"}); } std::uint64_tResourceWriter::mesh(const::mfem::Mesh&value){ usingnamespaceutils::serialize; constautofound=std::find(m_meshes.begin(),m_meshes.end(),&value); if(found!=m_meshes.end()){ returnstatic_cast<std::uint64_t>(found-m_meshes.begin()); } conststd::uint64_tindex=m_meshes.size(); Noderecord; if(supported_mesh(value)){ conststd::stringtext=mesh_text(value); record=object("mfem.mesh",field("identity",fingerprint(text))); if(m_options.embed_mesh){ record.children.emplace("native",encode(text)); } }else{ record=unsupported("ExactMFEMmesharchivesrequirefiniteserialthree-dimensionalmesheswithoutNURBS."); } m_record.children.at("meshes").children.emplace(std::to_string(index),std::move(record)); m_meshes.push_back(&value); returnindex; } std::uint64_tResourceWriter::collection(const::mfem::FiniteElementCollection&value){ constautofound=std::find(m_collections.begin(),m_collections.end(),&value); if(found!=m_collections.end()){ returnstatic_cast<std::uint64_t>(found-m_collections.begin()); } conststd::uint64_tindex=m_collections.size(); m_record.children.at("collections").children.emplace(std::to_string(index),save_collection(value)); m_collections.push_back(&value); returnindex; } std::uint64_tResourceWriter::space(const::mfem::FiniteElementSpace&value){ usingnamespaceutils::serialize; constautofound=std::find(m_spaces.begin(),m_spaces.end(),&value); if(found!=m_spaces.end()){ returnstatic_cast<std::uint64_t>(found-m_spaces.begin()); } conststd::uint64_tindex=m_spaces.size(); Noderecord; if(value.GetMesh()==nullptr||value.FEColl()==nullptr||value.IsVariableOrder()||typeid(value)!=typeid(::mfem::FiniteElementSpace)){ record=unsupported("ExactMFEMspacearchivescurrentlyrequireinitializedfixed-orderserialspaces."); }else{ record=object("mfem.space",field("mesh",mesh(*value.GetMesh())),field("collection",collection(*value.FEColl())), field("vector_dimension",value.GetVDim()),field("ordering",static_cast<int>(value.GetOrdering())), field("size",value.GetVSize()),field("element_numbering",element_numbering(value))); } m_record.children.at("spaces").children.emplace(std::to_string(index),std::move(record)); m_spaces.push_back(&value); returnindex; } voidResourceWriter::mapping(const::mfem::GridFunction&compactification,constserif::mapping::DomainMapper&mapper){ usingnamespaceutils::serialize; if(compactification.FESpace()==nullptr){ m_record.children.emplace("compactification",unsupported("Thecompactificationfieldhasnofinite-elementspace.")); }else{ m_record.children.emplace("compactification",object("mfem.field",field("space",space(*compactification.FESpace())), field("values",std::vector<double>(compactification.HostRead(),compactification.HostRead()+compactification.Size())))); } constauto*kelvin=dynamic_cast<constserif::mapping::compactification::KelvinCompactification*>(&mapper.get_exterior_map()); if(kelvin==nullptr){ m_record.children.emplace("mapper",unsupported("Noserializerisregisteredforthisexterior-domainmap.")); }else{ m_record.children.emplace("mapper",object("mfem.kelvin_mapper",field("dimension",mapper.get_dimension()), field("vacuum_attribute",mapper.options().vacuum_element_attribute_ID),field("stellar_radius",kelvin->get_reference_stellar_radius()), field("infinity_radius",kelvin->get_reference_infinity_radius()),field("coordinate_tolerance",kelvin->get_coordinate_tolerance()))); } } utils::serialize::NodeResourceWriter::reference(constgeometry::deformation::backend::mfem::ReferenceDiscretization&value){ usingnamespaceutils::serialize; returnobject("mfem.deformation_reference",field("scalar_space",space(value.scalar_space)),field("displacement_space",space(value.displacement_space)), field("logical_mesh",mesh(value.logical_mesh)),field("stellar_attributes",value.stellar_attributes),field("vacuum_attributes",value.vacuum_attributes), field("surface_attribute",value.surface_attribute),field("infinity_attribute",value.infinity_attribute)); } utils::serialize::NodeResourceWriter::finish()&&{ returnstd::move(m_record); } utils::serialize::Result<Resources>restore_resources(constutils::serialize::Node&node,constMeshResolver&resolver){ usingnamespaceutils::serialize; if(node.type!="mfem.resources"||node.version!=1){ returnstd::unexpected(Error{"resources","UnsupportedMFEMresourceschema."}); } #ifndefMFEM_USE_EXCEPTIONS returnstd::unexpected(Error{"resources","SafenativeMFEMarchiveparsingrequiresMFEM_USE_EXCEPTIONS;rebuildMFEMwithexceptionsupport."}); #else if(::mfem::get_error_action()!=::mfem::MFEM_ERROR_THROW){ returnstd::unexpected(Error{"resources","SafenativeMFEMarchiveparsingrequiresMFEM_ERROR_THROWsoinvalidinputcanbereportedinsteadofterminatingtheapplication."}); } try{ autoversion=read_field<std::string>(node,"mfem_version"); automeshes=read_field<std::vector<Node>>(node,"meshes"); autocollections=read_field<std::vector<Node>>(node,"collections"); autospaces=read_field<std::vector<Node>>(node,"spaces"); if(!version||*version!=MFEM_VERSION_STRING||!meshes||!collections||!spaces){ returnstd::unexpected(Error{"resources","MissingresourcetablesoradifferentMFEMversion;exactnativecoefficientinterpretationisrequired."}); } Resourcesresult; for(constNode&record:*meshes){ autovalue=restore_mesh(record,resolver); if(!value){ returnstd::unexpected(value.error()); } result.meshes.push_back(std::move(*value)); } for(constNode&record:*collections){ autovalue=restore_collection(record); if(!value){ returnstd::unexpected(value.error()); } result.collections.push_back(std::move(*value)); } for(constNode&record:*spaces){ autofields=read_object(record,"mfem.space",[](std::uint64_tmesh,std::uint64_tcollection,intdimension,intordering,intsize,std::vector<int>numbering){ returnstd::tuple{mesh,collection,dimension,ordering,size,std::move(numbering)}; },member<std::uint64_t>("mesh"),member<std::uint64_t>("collection"),member<int>("vector_dimension"),member<int>("ordering"),member<int>("size"),member<std::vector<int>>("element_numbering")); if(!fields){ returnstd::unexpected(fields.error()); } constauto&[mesh,collection,dimension,ordering,size,numbering]=*fields; if(mesh>=result.meshes.size()||collection>=result.collections.size()||dimension<1|| (ordering!=::mfem::Ordering::byNODES&&ordering!=::mfem::Ordering::byVDIM)){ returnstd::unexpected(Error{"spaces","Invalidmesh/collectionassociation,vectordimensionorcoefficientordering."}); } autospace=std::make_unique<::mfem::FiniteElementSpace>(result.meshes[mesh].get(),result.collections[collection].get(),dimension,ordering); if(space->GetVSize()!=size||element_numbering(*space)!=numbering){ returnstd::unexpected(Error{"spaces","Reconstructedfinite-elementnumberingdiffersfromthearchivedcoefficientinterpretation."}); } result.spaces.push_back(std::move(space)); } autofield=read_field<Node>(node,"compactification"); automapper=read_field<Node>(node,"mapper"); if(!field||!mapper){ returnstd::unexpected(Error{"resources","Compactificationandmapperrecordsarerequired."}); } autocompactification=read_object(*field,"mfem.field",[](std::uint64_tspace,std::vector<double>values){ returnstd::pair{space,std::move(values)}; },member<std::uint64_t>("space"),member<std::vector<double>>("values")); if(!compactification){ returnstd::unexpected(compactification.error()); } constauto&[space,values]=*compactification; if(space>=result.spaces.size()||values.size()!=static_cast<std::size_t>(result.spaces[space]->GetVSize())|| !std::ranges::all_of(values,[](doublevalue){returnstd::isfinite(value);})){ returnstd::unexpected(Error{"compactification","Compactificationcoefficientsdonotmatchtheirspaceorarenotfinite."}); } result.compactification=std::make_unique<::mfem::GridFunction>(result.spaces[space].get()); std::copy(values.begin(),values.end(),result.compactification->HostWrite()); automapping=read_object(*mapper,"mfem.kelvin_mapper",[](intdimension,intvacuum,doublestellar,doubleinfinity,doubletolerance){ returnstd::make_unique<serif::mapping::DomainMapper>(utils::user_options::DomainMapperOptions{.dimension=dimension,.vacuum_element_attribute_ID=vacuum}, std::make_unique<serif::mapping::compactification::KelvinCompactification>(utils::user_options::KelvinCompactificationOptions{ .r_star_ref=stellar,.r_inf_ref=infinity,.coordinate_tolerance=tolerance})); },member<int>("dimension"),member<int>("vacuum_attribute"),member<double>("stellar_radius"),member<double>("infinity_radius"),member<double>("coordinate_tolerance")); if(!mapping){ returnstd::unexpected(mapping.error()); } result.mapper=std::move(*mapping); returnresult; }catch(conststd::exception&error){ returnstd::unexpected(Error{"resources","CannotreconstructsavedMFEMresources:"+std::string{error.what()}}); } #endif } utils::serialize::Result<geometry::deformation::backend::mfem::ReferenceDiscretization>restore_reference(constutils::serialize::Node&node,Resources&resources){ usingnamespaceutils::serialize; usingnamespacegeometry::deformation::backend::mfem; autofields=read_object(node,"mfem.deformation_reference",[](std::uint64_tscalar,std::uint64_tdisplacement,std::uint64_tlogical, std::vector<int>stellar,std::vector<int>vacuum,intsurface,intinfinity){ returnstd::tuple{scalar,displacement,logical,std::move(stellar),std::move(vacuum),surface,infinity}; },member<std::uint64_t>("scalar_space"),member<std::uint64_t>("displacement_space"),member<std::uint64_t>("logical_mesh"), member<std::vector<int>>("stellar_attributes"),member<std::vector<int>>("vacuum_attributes"),member<int>("surface_attribute"),member<int>("infinity_attribute")); if(!fields){ returnstd::unexpected(fields.error()); } constauto&[scalar,displacement,logical,stellar,vacuum,surface,infinity]=*fields; if(scalar>=resources.spaces.size()||displacement>=resources.spaces.size()||logical>=resources.meshes.size()){ returnstd::unexpected(Error{"deformation_reference","Referencemeshorspaceindexisoutsidetherestoredresourcetables."}); } constautostatus=validate_scalar_reference_space(*resources.spaces[scalar]); constautocompanions=validate_companion_numbering(*resources.spaces[scalar],*resources.spaces[displacement]); constautopositions=sample_reference_positions(*resources.spaces[scalar],*resources.meshes[logical]); if(!status||!companions||!positions||resources.spaces[displacement]->GetVDim()!=3){ returnstd::unexpected(Error{"deformation_reference","Restoredphysical/logicalmeshesandcompanionspacesdonotdescribethesameorderedreferencegeometry."}); } returnReferenceDiscretization{.scalar_space=*resources.spaces[scalar],.displacement_space=*resources.spaces[displacement], .logical_mesh=*resources.meshes[logical],.stellar_attributes=stellar,.vacuum_attributes=vacuum, .surface_attribute=surface,.infinity_attribute=infinity}; } utils::serialize::Nodebinding_coordinates(constdiscretization::backend::mfem::DofMap&binding){ usingnamespaceutils::serialize; binding.validate(); std::vector<int>support(binding.mesh().GetNE()); std::vector<int>reduction(binding.full_size(),-1); for(intelement=0;element<binding.mesh().GetNE();++element){ support[element]=binding.supports(element)?1:0; } for(intfull=0;full<binding.full_size();++full){ reduction[full]=binding.reduced_dof(full).value_or(-1); } Noderecord=object("mfem.binding_coordinates",field("support",support),field("full_to_reduced",reduction),field("size",binding.size())); if(binding.constrained()){ //Independentnumberingalonecannotidentifythemeaningofhangingcoefficients. std::vector<int>offsets{0}; std::vector<int>columns; std::vector<double>weights; for(intlocal=0;local<binding.full_size();++local){ binding.for_each_coefficient(local,[&](constintindex,constdoubleweight){ columns.push_back(index); weights.push_back(weight); }); offsets.push_back(static_cast<int>(columns.size())); } record.children.emplace("constraint_offsets",encode(offsets)); record.children.emplace("constraint_columns",encode(columns)); record.children.emplace("constraint_weights",encode(weights)); } returnrecord; } }