top.cpp serif/backend/mfem/serialize/top.hpp algorithm limits map optional tuple stdexcept utility vector mfem/fem/gridfunc.hpp serif/backend/mfem/discretization/field_values.hpp serif::backend::mfem::serialize::detail::TopModelSampler::Impl serif::backend::mfem::serialize::detail::Error serif serif::backend serif::backend::mfem serif::backend::mfem::serialize serif::backend::mfem::serialize::detail #include"serif/backend/mfem/serialize/top.hpp" #include<algorithm> #include<limits> #include<map> #include<optional> #include<tuple> #include<stdexcept> #include<utility> #include<vector> #include"mfem/fem/gridfunc.hpp" #include"serif/backend/mfem/discretization/field_values.hpp" namespaceserif::backend::mfem::serialize::detail{ namespace{ usingVec=std::array<double,3>; usingutils::serialize::Error; usingutils::serialize::Result; classDensityBindingfinal:publicdiscretization::backend::mfem::DofMap{ public: explicitDensityBinding(constDofMap&source): DofMap(source.space(),support(source),source.reconstruction()){} private: static::mfem::Array<int>support(constDofMap&source){ source.validate(); ::mfem::Array<int>result(source.mesh().GetNE()); for(intelement=0;element<result.Size();++element)result[element]=source.supports(element); returnresult; } }; ::mfem::Vectorcopy_density(std::span<constdouble>values){ ::mfem::Vectorresult(static_cast<int>(values.size())); std::copy(values.begin(),values.end(),result.GetData()); returnresult; } doubledot(constVec&a,constVec&b){ returna[0]*b[0]+a[1]*b[1]+a[2]*b[2]; } Veccross(constVec&a,constVec&b){ return{a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]}; } Vecnormalized(Vecvalue){ constdoublenorm=std::sqrt(dot(value,value)); if(!std::isfinite(norm)||norm<=0.0){throwstd::invalid_argument("TOPsamplingrequiresafinitenonzerodirection.");} for(double&component:value){component/=norm;} returnvalue; } std::pair<Vec,Vec>transverse_basis(constVec&axis){ constautohelper=std::abs(axis[2])<0.8?Vec{0.0,0.0,1.0}:Vec{0.0,1.0,0.0}; Vecfirst=normalized(cross(helper,axis)); return{first,cross(axis,first)}; } structBounds{ Veclower,upper; boolcontains(const::mfem::Vector&point)const{ for(intk=0;k<3;++k){ if(point[k]<lower[k]||point[k]>upper[k]){returnfalse;} } returntrue; } boolintersects_ray(constVec&center,constVec&direction)const{ doublenear=0.0,far=std::numeric_limits<double>::infinity(); for(intk=0;k<3;++k){ if(std::abs(direction[k])<1.0e-15){ if(center[k]<lower[k]||center[k]>upper[k]){returnfalse;} }else{ doublea=(lower[k]-center[k])/direction[k]; doubleb=(upper[k]-center[k])/direction[k]; if(a>b){std::swap(a,b);} near=std::max(near,a); far=std::min(far,b); if(near>far){returnfalse;} } } returnfar>0.0; } }; //Theseboxesacceleratethesearch,butarenottreatedasrigorouspolynomialbounds. //Everyfailedacceleratedsearchretriesallelements,socurvedovershootcannotcauseafalseexclusion. Boundsbounds(::mfem::ElementTransformation&transformation){ Boundsresult; result.lower.fill(std::numeric_limits<double>::infinity()); result.upper.fill(-std::numeric_limits<double>::infinity()); constauto&rule=::mfem::IntRules.Get(transformation.GetGeometryType(),2*transformation.Order()+4); constauto*vertices=::mfem::Geometries.GetVertices(transformation.GetGeometryType()); ::mfem::Vectorpoint(3); autovisit=[&](const::mfem::IntegrationPoint&ip){ transformation.Transform(ip,point); for(intk=0;k<3;++k){ if(!std::isfinite(point[k])){throwstd::invalid_argument("TOPsamplingfoundnonfinitedeformedgeometry.");} result.lower[k]=std::min(result.lower[k],point[k]); result.upper[k]=std::max(result.upper[k],point[k]); } }; for(intq=0;q<rule.GetNPoints();++q){visit(rule.IntPoint(q));} for(intq=0;q<vertices->GetNPoints();++q){visit(vertices->IntPoint(q));} doublediameter=0.0; for(intk=0;k<3;++k){diameter=std::max(diameter,result.upper[k]-result.lower[k]);} for(intk=0;k<3;++k){ result.lower[k]-=0.05*diameter; result.upper[k]+=0.05*diameter; } returnresult; } } structTopModelSampler::Impl{ ::mfem::Meshmesh; DensityBindingdensity_binding; ::mfem::Vectordensity_coefficients; discretization::backend::mfem::FieldValues<discretization::blocks::density::mass>density_values; Vecaxis,center,first,second; std::vector<int>material,boundary; std::vector<Bounds>material_bounds,boundary_bounds; std::map<std::pair<double,double>,double>radii; doubledensity_scale{0.0},total_mass{0.0},length_scale{0.0}; std::size_tclamps{0}; intlast_element{-1}; ::mfem::IntegrationPointlast_point; Impl(constdiscretization::backend::mfem::DofMap&binding,std::span<constdouble>density, constgeometry::deformation::backend::mfem::ReferenceDiscretization&reference, std::span<constdouble>displacement,constVec&supplied_axis,constVec&supplied_center): mesh(binding.mesh(),true),density_binding(binding),density_coefficients(copy_density(density)), density_values(density_binding,density_coefficients),axis(normalized(supplied_axis)),center(supplied_center){ binding.validate(); if(binding.space().GetVDim()!=1||binding.space().IsVariableOrder()|| reference.displacement_space.IsVariableOrder()||mesh.Dimension()!=3||mesh.SpaceDimension()!=3|| binding.space().GetMesh()!=reference.displacement_space.GetMesh()|| density.size()!=static_cast<std::size_t>(binding.size())){ throwstd::invalid_argument("TOPsamplingrequiresmatchingfixed-orderserialscalardensityandthree-dimensionaldisplacementspaces."); } for(doublevalue:center){ if(!std::isfinite(value)){throwstd::invalid_argument("TOPsamplingrequiresafiniterotationcenter.");} } std::tie(first,second)=transverse_basis(axis); ::mfem::GridFunctionmotion(const_cast<::mfem::FiniteElementSpace*>(&reference.displacement_space)); if(motion.Size()!=static_cast<int>(displacement.size())||motion.FESpace()->GetVDim()!=3){ throwstd::invalid_argument("TOPdisplacementcoefficientsdonotmatchthedeformationreferencespace."); } constintnodes=motion.FESpace()->GetNDofs(); for(intnode=0;node<nodes;++node){ for(intcomponent=0;component<3;++component){ constdoublevalue=displacement[node+component*nodes]; if(!std::isfinite(value)){throwstd::invalid_argument("TOPdisplacementcontainsanonfinitecoefficient.");} motion[motion.FESpace()->DofToVDof(node,component)]=value; } } //Preserveallreference-geometrymodesbeforeaddingthelower-orhigher-orderdisplacement. intorder=reference.displacement_space.GetMaxElementOrder(); if(mesh.GetNodalFESpace()){order=std::max(order,mesh.GetNodalFESpace()->GetMaxElementOrder());} mesh.SetCurvature(order,false,3,::mfem::Ordering::byNODES); ::mfem::GridFunctionnodal_motion(mesh.GetNodes()->FESpace()); nodal_motion.ProjectGridFunction(motion); *mesh.GetNodes()+=nodal_motion; mesh.NodesUpdated(); for(intelement=0;element<mesh.GetNE();++element){ if(binding.supports(element)){ material.push_back(element); material_bounds.push_back(bounds(*mesh.GetElementTransformation(element))); } } for(intface=0;face<mesh.GetNBE();++face){ if(mesh.GetBdrAttribute(face)==reference.surface_attribute){ boundary.push_back(face); boundary_bounds.push_back(bounds(*mesh.GetBdrElementTransformation(face))); } } if(material.empty()||boundary.empty()){throwstd::invalid_argument("TOPsamplingfoundnomaterialorstellarsurface.");} for(constauto&box:boundary_bounds){ for(intk=0;k<3;++k){ length_scale=std::max({length_scale,std::abs(box.lower[k]-center[k]),std::abs(box.upper[k]-center[k])}); } } if(!(length_scale>0.0)||!std::isfinite(length_scale)){throwstd::invalid_argument("InvalidstellarlengthscaleforTOPsampling.");} ::mfem::Vectororigin(3); for(intk=0;k<3;++k){origin[k]=center[k];} constautocentral=sample_raw(origin); if(!central||!std::isfinite(*central)||*central<=0.0){ throwstd::invalid_argument("TOPrequirespositivedensityatarotationcenterinsidethestellarmaterial."); } density_scale=*central; doublecorrection=0.0; ::mfem::Vectordensity_samples; for(intelement:material){ auto&transformation=*mesh.GetElementTransformation(element); //Fractionalradialpowersarenotpolynomials;overintegrateonlythereconstructedcollar. constintadditional_order=binding.reconstruction_element(element)==nullptr?0:binding.reconstruction()->integration_order_increment(); constintquadrature_order=binding.space().GetFE(element)->GetOrder()+3*order+3+additional_order; constauto&rule=::mfem::IntRules.Get(transformation.GetGeometryType(),quadrature_order); density_values.values(element,rule,density_samples); for(intq=0;q<rule.GetNPoints();++q){ constauto&ip=rule.IntPoint(q); transformation.SetIntPoint(&ip); constdoubledeterminant=transformation.Jacobian().Det(); if(!std::isfinite(determinant)||determinant<=0.0){ throwstd::invalid_argument("TOPsamplingrejectedanonpositivedeformedmaterialJacobian."); } autorho=checked_density(density_samples[q]); if(!rho){throwstd::invalid_argument(rho.error().message);} constdoubleterm=*rho*determinant*ip.weight-correction; constdoublenext=total_mass+term; correction=(next-total_mass)-term; total_mass=next; } } if(!std::isfinite(total_mass)||total_mass<=0.0){throwstd::invalid_argument("TOPrequirespositivefiniteintegratedstellarmass.");} } Vecdirection(doublemu,doublephi)const{ if(!std::isfinite(mu)||!std::isfinite(phi)||mu<-1.0||mu>1.0){ throwstd::invalid_argument("TOPsamplingrequiresfiniteangleswith-1<=mu<=1."); } constdoublesine=std::sqrt(std::max(0.0,1.0-mu*mu)); Vecresult; for(intk=0;k<3;++k){result[k]=mu*axis[k]+sine*(std::cos(phi)*first[k]+std::sin(phi)*second[k]);} returnresult; } Result<double>checked_density(doublevalue){ if(!std::isfinite(value)){returnstd::unexpected(Error{"top/state/density","ThesampledFEdensityisnonfinite."});} if(value<0.0){ constdoubletolerance=128.0*std::numeric_limits<double>::epsilon()*density_scale; if(-value>tolerance){ returnstd::unexpected(Error{"top/state/density","ThesampledFEdensityisnegativebeyond128machineepsilonstimesthecentraldensity;TOPexportdoesnotrepairthestellarsolution."}); } ++clamps; return0.0; } returnvalue; } Result<double>sample_raw(const::mfem::Vector&point){ autoattempt=[&](intelement,boolprevious,boolrobust)->bool{ auto&transformation=*mesh.GetElementTransformation(element); ::mfem::InverseElementTransformationinverse(&transformation); inverse.SetMaxIter(40); inverse.SetPhysicalRelTol(2.0e-13); inverse.SetReferenceTol(2.0e-13); inverse.SetPrintLevel(-1); if(previous){inverse.SetInitialGuess(last_point);} if(robust){inverse.SetInitialGuessType(::mfem::InverseElementTransformation::ClosestPhysNode);} ::mfem::IntegrationPointip; if(inverse.Transform(point,ip)!=::mfem::InverseElementTransformation::Inside){returnfalse;} ::mfem::Vectorreconstructed(3); transformation.Transform(ip,reconstructed); reconstructed-=point; if(reconstructed.Norml2()>2.0e-11*length_scale){returnfalse;} last_element=element; last_point=ip; returntrue; }; if(last_element>=0&&attempt(last_element,true,false)){ returndensity_values.value(last_element,last_point); } for(std::size_ti=0;i<material.size();++i){ if(material_bounds[i].contains(point)&&attempt(material[i],false,false)){ returndensity_values.value(last_element,last_point); } } for(intelement:material){ if(attempt(element,false,true)){returndensity_values.value(last_element,last_point);} } returnstd::unexpected(Error{"top/sampling","Arequestedphysicalpointcouldnotbelocatedinsidethedeformedstellarmaterial;nofieldextrapolationwasperformed."}); } std::optional<double>intersect(intface,constVec&ray,constVec&a,constVec&b,boolrobust){ auto&transformation=*mesh.GetBdrElementTransformation(face); autosolve=[&](::mfem::IntegrationPointip)->std::optional<double>{ ::mfem::Vectorpoint(3); for(intiteration=0;iteration<35;++iteration){ transformation.Transform(ip,point); Vecrelative{point[0]-center[0],point[1]-center[1],point[2]-center[2]}; constdoublef=dot(relative,a),g=dot(relative,b); if(std::hypot(f,g)<=5.0e-13*length_scale){ constdoubleradius=dot(relative,ray); if(radius>0.0&&::mfem::Geometry::CheckPoint(transformation.GetGeometryType(),ip,2.0e-10)){returnradius;} return{}; } transformation.SetIntPoint(&ip); constauto&jacobian=transformation.Jacobian(); doublej00=0.0,j01=0.0,j10=0.0,j11=0.0; for(intk=0;k<3;++k){ j00+=a[k]*jacobian(k,0);j01+=a[k]*jacobian(k,1); j10+=b[k]*jacobian(k,0);j11+=b[k]*jacobian(k,1); } constdoubledeterminant=j00*j11-j01*j10; if(!std::isfinite(determinant)||std::abs(determinant)<1.0e-16*length_scale*length_scale){return{};} doubledu=(j11*f-j01*g)/determinant; doubledv=(j00*g-j10*f)/determinant; constdoublescale=std::max({1.0,std::abs(du),std::abs(dv)}); ip.x-=du/scale; ip.y-=dv/scale; if(!std::isfinite(ip.x)||!std::isfinite(ip.y)||ip.x<-0.5||ip.x>1.5||ip.y<-0.5||ip.y>1.5){return{};} } return{}; }; if(autoresult=solve(::mfem::Geometries.GetCenter(transformation.GetGeometryType()))){returnresult;} if(robust){ constauto&rule=::mfem::IntRules.Get(transformation.GetGeometryType(),4); for(intq=0;q<rule.GetNPoints();++q){if(autoresult=solve(rule.IntPoint(q))){returnresult;}} } return{}; } Result<double>surface(doublemu,doublephi){ constautokey=std::pair{mu,phi}; if(constautofound=radii.find(key);found!=radii.end()){returnfound->second;} constVecray=direction(mu,phi); constauto[a,b]=transverse_basis(ray); std::optional<double>result; for(std::size_ti=0;i<boundary.size();++i){ if(!boundary_bounds[i].intersects_ray(center,ray)){continue;} if(constautoradius=intersect(boundary[i],ray,a,b,true)){ if(result&&std::abs(*result-*radius)>2.0e-9*length_scale){ returnstd::unexpected(Error{"top/geometry/surface","ThestellarboundaryhasmultipleradialintersectionsandcannotberepresentedasaTOPstar-shapedsurface."}); } result=radius; } } if(!result){ for(intface:boundary){ if(constautoradius=intersect(face,ray,a,b,true)){ if(result&&std::abs(*result-*radius)>2.0e-9*length_scale){ returnstd::unexpected(Error{"top/geometry/surface","Thestellarboundaryisnotsingle-valuedinradius."}); } result=radius; } } } if(!result){returnstd::unexpected(Error{"top/geometry/surface","CouldnotintersecttherequestedraywiththeactualdeformedFEstellarsurface."});} radii.emplace(key,*result); return*result; } }; TopModelSampler::TopModelSampler(constdiscretization::backend::mfem::DofMap&binding, std::span<constdouble>density,constgeometry::deformation::backend::mfem::ReferenceDiscretization&reference, std::span<constdouble>displacement,constVec&axis,constVec&center): m_impl(std::make_unique<Impl>(binding,density,reference,displacement,axis,center)){} TopModelSampler::~TopModelSampler()=default; Result<double>TopModelSampler::surface_radius(doublemu,doublephi){returnm_impl->surface(mu,phi);} Result<double>TopModelSampler::density(doubleradius,doublemu,doublephi){ if(!std::isfinite(radius)||radius<0.0){returnstd::unexpected(Error{"top/sampling/radius","TOPsamplingrequiresafinitenonnegativephysicalradius."});} if(radius==0.0){returnm_impl->density_scale;} constVecdirection=m_impl->direction(mu,phi); ::mfem::Vectorpoint(3); for(intk=0;k<3;++k){point[k]=m_impl->center[k]+radius*direction[k];} constautovalue=m_impl->sample_raw(point); if(!value){returnstd::unexpected(value.error());} returnm_impl->checked_density(*value); } doubleTopModelSampler::mass()constnoexcept{returnm_impl->total_mass;} std::size_tTopModelSampler::roundoff_density_clamps()constnoexcept{returnm_impl->clamps;} }