tensor_basis.hpp algorithm array cmath limits memory vector utility mfem/fem/fe/fe_h1.hpp mfem/fem/fe/fe_l2.hpp serif/utils/error/errors.hpp serif/backend/mfem/discretization/reconstruction.hpp src/include/serif/backend/mfem/discretization/basis_evaluation.hpp src/lib/serif/backend/mfem/discretization/basis_evaluation.cpp serif::discretization::backend::mfem::TensorWorkspace serif::discretization::backend::mfem::TensorBasis serif::discretization::backend::mfem::TensorBasis::Product serif::discretization::backend::mfem::TensorBasisCache serif::discretization::backend::mfem::TensorBasisCache::Entry serif serif::discretization serif::discretization::backend serif::discretization::backend::mfem Implementation of fast tensor evaluation functions. These do not contribute physics but are used to accelerate evaluations. Emily M. Boudreaux #pragmaonce #include<algorithm> #include<array> #include<cmath> #include<limits> #include<memory> #include<vector> #include<utility> #include"mfem/fem/fe/fe_h1.hpp" #include"mfem/fem/fe/fe_l2.hpp" #include"serif/utils/error/errors.hpp" #include"serif/backend/mfem/discretization/reconstruction.hpp" namespaceserif::discretization::backend::mfem{ enumclassReferenceOperation{ value, gradient, value_and_gradient }; enumclassContractionMode{ automatic, dense }; structTensorWorkspace{ ::mfem::Vectorcoefficients; ::mfem::Vectorfirst; ::mfem::Vectorsecond; }; classTensorBasisfinal{ structProduct{ std::array<::mfem::DenseMatrix,3>factors; ::mfem::Array<int>dofs; intoutput; }; public: [[nodiscard]]staticstd::shared_ptr<constTensorBasis>make( const::mfem::FiniteElement&element, const::mfem::IntegrationRule&rule, constReferenceOperationoperation, constintcopies, constElementReconstruction*reconstruction=nullptr ){ constauto*scalar=dynamic_cast<const::mfem::NodalTensorFiniteElement*>(&element); constboolscalar_hex=dynamic_cast<const::mfem::H1_HexahedronElement*>(&element)||dynamic_cast<const::mfem::L2_HexahedronElement*>(&element); //TODO:Thisisanawfullotofchecks,compresstheseintosomesortof"isthisavalidtensorbasis"functionthatcanbeusedinthetestsaswell. if(copies<=0||!scalar_hex||rule.GetNPoints()==0||scalar==nullptr||element.GetMapType()!=::mfem::FiniteElement::VALUE){ return{}; } constintq=static_cast<int>(std::lround(std::cbrt(rule.GetNPoints()))); if(q*q*q!=rule.GetNPoints()){ return{}; } for(intz=0;z<q;++z){ for(inty=0;y<q;++y){ for(intx=0;x<q;++x){ constauto&point=rule.IntPoint(x+q*(y+q*z)); if(point.x!=rule.IntPoint(x).x||point.y!=rule.IntPoint(q*y).y||point.z!=rule.IntPoint(q*q*z).z){ return{}; } } } } autoresult=std::make_shared<TensorBasis>(); result->m_points=rule.GetNPoints(); result->m_dofs=element.GetDof()*copies; constintsamples=operation==ReferenceOperation::gradient?3:operation==ReferenceOperation::value_and_gradient?4:1; result->m_components=copies*samples; constintclosed=element.GetOrder()+1; constintblock=element.GetDof(); constauto&native_map=scalar->GetDofMap(); if(native_map.Size()!=0&&native_map.Size()!=element.GetDof()){ return{}; } //TODO:Ionceagainmakeuseofavector<bool>sinceitisthemostobviousconceptualdatatype.However,thestandardbackedimplementationof //vector<bool>isnotactuallyavectorofbools;rather,itisabitset.IdontthinkthismattersforwhatIamdoingbutisisprobably //worthbeingawareofthis std::vector<bool>seen(element.GetDof(),false); for(intdof=0;dof<element.GetDof();++dof){ constintencoded=native_map.Size()?native_map[dof]:dof; constintnative=encoded<0?-1-encoded:encoded; if(encoded<0||native<0||native>=element.GetDof()||seen[native]){ return{}; } seen[native]=true; } for(intcomponent=0;component<copies;++component){ for(intsample=0;sample<samples;++sample){ auto&[factors,dofs,output]=result->m_products.emplace_back(); output=component*samples+sample; constintderivative=operation==ReferenceOperation::gradient?sample:operation==ReferenceOperation::value_and_gradient?sample-1:-1; inttensor_dofs=1; for(intaxis=0;axis<3;++axis){ constintcount=closed; tensor_dofs*=count; ::mfem::DenseMatrix&factor=factors[axis]; factor.SetSize(q,count); ::mfem::Vectorvalues(count); ::mfem::Vectorgradients(count); constauto&basis=scalar->GetBasis1D(); for(intpoint=0;point<q;++point){ constdoublecoordinate=axis==0?rule.IntPoint(point).x:axis==1?rule.IntPoint(q*point).y:rule.IntPoint(q*q*point).z; //NotethatherethisisjusttwooverloadsofEvalthatmfem::Poly_1Dprovides,thefirstoneevaluatesthegradientandthelatterevaluatesthevalue. if(reconstruction!=nullptr&&axis==reconstruction->inward_axis){ reconstruction->radial(element,coordinate,values,gradients); } elseif(axis==derivative){ basis.Eval(coordinate,values,gradients); } else{ basis.Eval(coordinate,values); } for(intdof=0;dof<count;++dof){ factor(point,dof)=axis==derivative?gradients[dof]:values[dof]; } } } if(tensor_dofs!=block){ return{}; } dofs.SetSize(block); for(intlex=0;lex<block;++lex){ constintencoded=native_map.Size()?native_map[lex]:lex; dofs[lex]=encoded+component*element.GetDof(); } } } returnresult->agrees_with(element,rule,operation,reconstruction)?result:nullptr; } [[nodiscard]]intpoints()constnoexcept{ returnm_points; } [[nodiscard]]intdofs()constnoexcept{ returnm_dofs; } [[nodiscard]]intcomponents()constnoexcept{ returnm_components; } voidapply(const::mfem::Vector&input,::mfem::Vector&output,TensorWorkspace&workspace,constbooltranspose=false)const{ usingnamespaceutils::errors; serif_ensure<SERiFErrorCode::INVALID_DOF_COUNT,std::invalid_argument>(input.Size()==(transpose?m_points*m_components:m_dofs),"Tensorcontractioninputhasthewrongcoefficientorquadraturesize."); output.SetSize(transpose?m_dofs:m_points*m_components); output=0.0; for(constauto&product:m_products){ auto&coefficients=workspace.coefficients; coefficients.SetSize(transpose?m_points:product.dofs.Size()); for(inti=0;i<coefficients.Size();++i){ constintencoded=transpose?0:product.dofs[i]; coefficients[i]=transpose?input[i*m_components+product.output]:encoded<0?-input[-1-encoded]:input[encoded]; } std::array<int,3>shape; for(intaxis=0;axis<3;++axis){ shape[axis]=transpose?product.factors[axis].Height():product.factors[axis].Width(); } const::mfem::Vector*source=&coefficients; for(intstep=0;step<3;++step){ constintaxis=transpose?2-step:step; auto&destination=step%2==0?workspace.first:workspace.second; contract_axis(product.factors[axis],axis,shape,*source,destination,transpose); shape[axis]=transpose?product.factors[axis].Width():product.factors[axis].Height(); source=&destination; } for(inti=0;i<source->Size();++i){ if(transpose){ constintencoded=product.dofs[i]; output[encoded<0?-1-encoded:encoded]+=(encoded<0?-1.0:1.0)*(*source)[i]; }else{ output[i*m_components+product.output]+=(*source)[i]; } } } } voidapply_many(const::mfem::Vector&input,::mfem::Vector&output,constintlanes,TensorWorkspace&workspace,constbooltranspose=false)const{ usingnamespaceutils::errors; serif_ensure<SERiFErrorCode::INVALID_DOF_COUNT,std::invalid_argument>(lanes>0&&input.Size()==lanes*(transpose?m_points*m_components:m_dofs),"Batchedtensorcontractioninputhasthewrongcoefficientorquadraturesize."); output.SetSize(lanes*(transpose?m_dofs:m_points*m_components)); output=0.0; for(constauto&product:m_products){ auto&coefficients=workspace.coefficients; constintentries=transpose?m_points:product.dofs.Size(); coefficients.SetSize(entries*lanes); for(inti=0;i<entries;++i){ constintencoded=transpose?0:product.dofs[i]; for(intlane=0;lane<lanes;++lane){ coefficients[i*lanes+lane]=transpose?input[(i*m_components+product.output)*lanes+lane]:encoded<0?-input[(-1-encoded)*lanes+lane]:input[encoded*lanes+lane]; } } std::array<int,3>shape; for(intaxis=0;axis<3;++axis){ shape[axis]=transpose?product.factors[axis].Height():product.factors[axis].Width(); } const::mfem::Vector*source=&coefficients; for(intstep=0;step<3;++step){ constintaxis=transpose?2-step:step; auto&destination=step%2==0?workspace.first:workspace.second; contract_axis(product.factors[axis],axis,shape,*source,destination,transpose,lanes); shape[axis]=transpose?product.factors[axis].Width():product.factors[axis].Height(); source=&destination; } constintresults=source->Size()/lanes; for(inti=0;i<results;++i){ for(intlane=0;lane<lanes;++lane){ if(transpose){ constintencoded=product.dofs[i]; output[(encoded<0?-1-encoded:encoded)*lanes+lane]+=(encoded<0?-1.0:1.0)*(*source)[i*lanes+lane]; }else{ output[(i*m_components+product.output)*lanes+lane]+=(*source)[i*lanes+lane]; } } } } } private: staticvoidcontract_axis( const::mfem::DenseMatrix&factor, constintaxis, conststd::array<int,3>&shape, const::mfem::Vector&input, ::mfem::Vector&output, constbooltranspose, constintlanes=1 ){ constintinput_extent=shape[axis]; constintoutput_extent=transpose?factor.Width():factor.Height(); intinner=lanes,outer=1; for(intdimension=0;dimension<axis;++dimension){ inner*=shape[dimension]; } for(intdimension=axis+1;dimension<3;++dimension){ outer*=shape[dimension]; } output.SetSize(outer*output_extent*inner); output=0.0; constintfactor_height=factor.Height(); constdouble*factors=factor.GetData(); constdouble*source=input.GetData(); double*destination=output.GetData(); //Eachtensorsliceiscontiguous.Everyoutputstillsumscolumnsinascendingorder. for(intblock=0;block<outer;++block){ for(introw=0;row<output_extent;++row){ double*output_slice=destination+(block*output_extent+row)*inner; for(intcolumn=0;column<input_extent;++column){ constdoublecoefficient=transpose?factors[row*factor_height+column]:factors[column*factor_height+row]; constdouble*input_slice=source+(block*input_extent+column)*inner; for(intentry=0;entry<inner;++entry){ output_slice[entry]+=coefficient*input_slice[entry]; } } } } } [[nodiscard]]boolagrees_with( const::mfem::FiniteElement&element, const::mfem::IntegrationRule&rule, constReferenceOperationoperation, constElementReconstruction*reconstruction )const{ constintsamples=operation==ReferenceOperation::gradient?3:operation==ReferenceOperation::value_and_gradient?4:1; ::mfem::Vectorvalues(element.GetDof()); ::mfem::DenseMatrixderivatives(element.GetDof(),3); for(intpoint=0;point<m_points;++point){ if(reconstruction!=nullptr){ reconstruction->sample(element,rule.IntPoint(point),values,derivatives); }else{ element.CalcShape(rule.IntPoint(point),values); if(samples>1){ element.CalcDShape(rule.IntPoint(point),derivatives); } } for(constauto&[factors,dofs,output]:m_products){ constintq=factors[0].Height(),nx=factors[0].Width(),ny=factors[1].Width(); for(intdofID=0;dofID<dofs.Size();++dofID){ constintencoded=dofs[dofID],native=(encoded<0?-1-encoded:encoded)%element.GetDof(); constintderivative=operation==ReferenceOperation::gradient?output%samples:output%samples-1; doubleexpected=samples==1||derivative<0?values[native]:derivatives(native,derivative); expected*=encoded<0?-1.0:1.0; constdoubleactual=factors[0](point%q,dofID%nx)*factors[1]((point/q)%q,(dofID/nx)%ny)*factors[2](point/(q*q),dofID/(nx*ny)); //Similartoaboveinthisfile,thisisalongseriesofchecks,mayberefactorintoafunction? if(!std::isfinite(actual)||!std::isfinite(expected)||std::abs(actual-expected)>64.0*std::numeric_limits<double>::epsilon()*std::max(1.0,std::abs(expected))){ returnfalse; } } } } returntrue; } intm_points{0}; intm_dofs{0}; intm_components{0}; std::vector<Product>m_products; }; classTensorBasisCachefinal{ structEntry{ const::mfem::FiniteElement*element; ReferenceOperationoperation; intcopies; ElementReconstructionreconstruction; std::vector<std::array<double,4>>points; std::shared_ptr<constTensorBasis>basis; }; public: [[nodiscard]]std::shared_ptr<constTensorBasis>get( const::mfem::FiniteElement&element, const::mfem::IntegrationRule&rule, ReferenceOperationoperation, constintcopies, constElementReconstruction*reconstruction=nullptr ){ std::vector<std::array<double,4>>points; for(intq=0;q<rule.GetNPoints();++q){ constauto&point=rule.IntPoint(q); points.push_back({point.x,point.y,point.z,point.weight}); } for(constauto&[el,op,cps,representation,pts,bs]:m_entries){ if(el==&element&&op==operation&&cps==copies&&representation==(reconstruction==nullptr?ElementReconstruction{}:*reconstruction)&&pts==points){ returnbs; } } constautobasis=TensorBasis::make(element,rule,operation,copies,reconstruction); m_entries.push_back({ .element=&element, .operation=operation, .copies=copies, .reconstruction=reconstruction==nullptr?ElementReconstruction{}:*reconstruction, .points=std::move(points), .basis=basis }); returnbasis; } [[nodiscard]]std::size_tsize()constnoexcept{ returnm_entries.size(); } voidclear(){ m_entries.clear(); } private: std::vector<Entry>m_entries; }; }//namespaceserif::discretization::backend::mfem