surface_potential.cpp serif/backend/mfem/model/surface_potential.hpp cmath algorithm array limits memory vector mfem/fem/intrules.hpp serif/backend/mfem/shared/boundary_elements.hpp serif/backend/mfem/shared/parallel.hpp serif/backend/mfem/geometry/deformation/reference_geometry.hpp serif serif::model serif::model::backend serif::model::backend::mfem #include"serif/backend/mfem/model/surface_potential.hpp" #include<cmath> #include<algorithm> #include<array> #include<limits> #include<memory> #include<vector> #include"mfem/fem/intrules.hpp" #include"serif/backend/mfem/shared/boundary_elements.hpp" #include"serif/backend/mfem/shared/parallel.hpp" #include"serif/backend/mfem/geometry/deformation/reference_geometry.hpp" namespaceserif::model::backend::mfem{ namespace{ classOrderedAccumulator{ public: OrderedAccumulator(constintheight,constintwidth): m_height(height),m_width(width),m_values(static_cast<std::size_t>(height)*static_cast<std::size_t>(width),0.0), m_present(m_values.size(),0),m_order(static_cast<std::size_t>(height)){} voidadd(constintrow,constintcolumn,constdoublevalue){ conststd::size_tentry=static_cast<std::size_t>(row)*static_cast<std::size_t>(m_width)+static_cast<std::size_t>(column); if(m_present[entry]==0){ m_present[entry]=1; m_order[static_cast<std::size_t>(row)].push_back(column); } m_values[entry]+=value; } [[nodiscard]]std::shared_ptr<::mfem::SparseMatrix>finalize()const{ int*I=newint[static_cast<std::size_t>(m_height)+1]; I[0]=0; for(introw=0;row<m_height;++row){ intkept=0; for(constintcolumn:m_order[static_cast<std::size_t>(row)]){kept+=value(row,column)!=0.0;} I[row+1]=I[row]+kept; } int*J=newint[static_cast<std::size_t>(I[m_height])]; double*A=newdouble[static_cast<std::size_t>(I[m_height])]; boolsorted=true; for(introw=0,next=0;row<m_height;++row){ intlast_column=-1; constauto&order=m_order[static_cast<std::size_t>(row)]; for(autocolumn=order.rbegin();column!=order.rend();++column){ if(value(row,*column)==0.0){continue;} J[next]=*column; A[next]=value(row,*column); if(last_column>*column){sorted=false;} last_column=*column; ++next; } } returnstd::make_shared<::mfem::SparseMatrix>(I,J,A,m_height,m_width,true,true,sorted); } private: [[nodiscard]]doublevalue(constintrow,constintcolumn)const{ returnm_values[static_cast<std::size_t>(row)*static_cast<std::size_t>(m_width)+static_cast<std::size_t>(column)]; } intm_height; intm_width; std::vector<double>m_values; std::vector<unsignedchar>m_present; std::vector<std::vector<int>>m_order; }; } SurfacePotentialLinearizationassemble_surface_potential( constequations::SurfacePotentialBalance&equation, constgeometry::deformation::backend::mfem::SurfaceDiscretization&discretization, constgeometry::deformation::ReferenceSurface&reference, constgeometry::deformation::SurfaceEmbedding&embedding, conststd::span<constdouble>displacement, constdiscretization::backend::mfem::DofMap&potential_binding, const::mfem::Vector&potential,constdoublelevel,constdoubleangular_velocity){ usingnamespaceutils::errors; namespacedeformation=geometry::deformation::backend::mfem; namespaceboundary=::serif::backend::mfem::shared; usingEquation=equations::SurfacePotentialBalance; potential_binding.validate(); constautotrace_status=deformation::validate_surface_trace(discretization.scalar_space, discretization.surface_attribute,reference,embedding); serif_ensure<SERiFErrorCode::INVALID_ARGUMENT>(trace_status.has_value(), "Surfacepotentialassemblyrequiresacompatiblereferencesurfaceandradialembedding:{}", trace_status?"":trace_status.error().message); serif_ensure<SERiFErrorCode::INVALID_DOF_COUNT>(embedding.input_size()<=static_cast<std::size_t>(std::numeric_limits<int>::max())&& displacement.size()==embedding.output_size()&&potential.Size()==potential_binding.size(), "Surfacepotentialassemblyreceivedincompatiblecoefficientcounts."); serif_ensure<SERiFErrorCode::CACHE_FE_BINDING_ERROR>(potential_binding.space().GetMesh()==discretization.scalar_space.GetMesh()&& potential_binding.space().GetVDim()==1, "Surfacepotentialandreferencegeometrymustusescalartracesonthesamemesh."); serif_ensure<SERiFErrorCode::NON_FINITE_VALUE_ERROR>(std::isfinite(level)&&std::isfinite(angular_velocity), "Surfacepotentiallevelandangularvelocitymustbefinite."); constintcount=static_cast<int>(embedding.input_size()); SurfacePotentialLinearizationresult{ .residual=std::vector<double>(count,0.0), .potential=nullptr, .shape=nullptr, .level=std::make_shared<::mfem::DenseMatrix>(count,1), .angular_velocity=std::make_shared<::mfem::DenseMatrix>(count,1)}; *result.level=0.0; *result.angular_velocity=0.0; OrderedAccumulatorpotential_matrix(count,potential_binding.size()),shape_matrix(count,count); doublereference_area=0.0; constautostatus=boundary::for_each_boundary_element(discretization.scalar_space, discretization.surface_attribute,reference.scalar_dofs, [&](constboundary::BoundaryElement&element)->boundary::BoundaryStatus{ //MFEMidentifiesaboundarytransformationbyitsboundary-elementindex. constintboundary_index=element.transformation.ElementNo; constauto&potential_element=*potential_binding.space().GetBE(boundary_index); ::mfem::Array<int>potential_dofs; if(potential_binding.space().GetBdrElementDofs(boundary_index,potential_dofs)!=nullptr|| potential_dofs.Size()!=potential_element.GetDof()){ returnstd::unexpected(boundary::BoundaryFailure{.message="Potentialsurfaceassemblyrequiresacanonicalscalarnodaltrace."}); } constintorder=3*element.finite_element.GetOrder()+potential_element.GetOrder()+element.transformation.OrderW(); constauto&rule=::mfem::IntRules.Get(element.finite_element.GetGeomType(),order); //Nodalshapefunctionsarelocal.Harmonicmodeshaveglobalsupport. std::vector<int>parameters; if(embedding.has_nodal_parameters()){ parameters.assign(element.selected_nodes.begin(),element.selected_nodes.end()); }else{ for(intparameter=0;parameter<count;++parameter){parameters.push_back(parameter);} } constintactive=static_cast<int>(parameters.size()); constintpoints=rule.GetNPoints(); constintgeometry_count=element.finite_element.GetDof(),potential_count=potential_element.GetDof(); constboolnodal_parameters=embedding.has_nodal_parameters(); constautonodal_basis=embedding.nodal_basis(); constautoradial_directions=embedding.radial_directions(); //1.Theboundarytransformationissharedstate,soitssamplesaretakenserially. std::vector<double>weights(static_cast<std::size_t>(points)); std::vector<double>geometry_shapes(static_cast<std::size_t>(points*geometry_count)); std::vector<double>potential_shapes(static_cast<std::size_t>(points*potential_count)); std::vector<double>positions(static_cast<std::size_t>(points*3)); { ::mfem::Vectorgeometry_shape(geometry_count),potential_shape(potential_count),position(3); for(intquadrature=0;quadrature<points;++quadrature){ constauto&point=rule.IntPoint(quadrature); element.transformation.SetIntPoint(&point); weights[static_cast<std::size_t>(quadrature)]=point.weight*element.transformation.Weight(); element.finite_element.CalcShape(point,geometry_shape); potential_element.CalcShape(point,potential_shape); element.transformation.Transform(point,position); std::copy_n(geometry_shape.GetData(),geometry_count,geometry_shapes.begin()+quadrature*geometry_count); std::copy_n(potential_shape.GetData(),potential_count,potential_shapes.begin()+quadrature*potential_count); std::copy_n(position.GetData(),3,positions.begin()+quadrature*3); } } //2.Eachpoint'stestfunctions,surfacemotionsandbalance,independently. structPointBalance{ doubleresidual,level,angular_velocity,potential; std::array<double,3>position_derivative; }; std::vector<double>tests(static_cast<std::size_t>(points)*static_cast<std::size_t>(active)); std::vector<discretization::forms::VectorValue>motions(static_cast<std::size_t>(points)*static_cast<std::size_t>(active)); std::vector<PointBalance>balances(static_cast<std::size_t>(points)); utils::parallel::for_each_index(points,[&](constintquadrature,int){ constdouble*geometry_shape=geometry_shapes.data()+quadrature*geometry_count; constdouble*potential_shape=potential_shapes.data()+quadrature*potential_count; double*test=tests.data()+static_cast<std::size_t>(quadrature)*static_cast<std::size_t>(active); auto*motion=motions.data()+static_cast<std::size_t>(quadrature)*static_cast<std::size_t>(active); equations::EquationPoint<Equation::Operands>physical; for(intaxis=0;axis<3;++axis){physical.position[axis]=positions[static_cast<std::size_t>(quadrature*3+axis)];} for(intlocal=0;local<geometry_count;++local){ constintnode=element.selected_nodes[local]; constdoublevalue=geometry_shape[local]*(element.encoded_dofs[local]<0?-1.0:1.0); for(intaxis=0;axis<3;++axis){physical.position[axis]+=value*displacement[3*node+axis];} for(intparameter=0;parameter<active;++parameter){ doublebasis=0.0; if(nodal_parameters){ basis=parameters[parameter]==node?1.0:0.0; }else{ basis=nodal_basis[static_cast<std::size_t>(node)*count+parameters[parameter]]; } test[parameter]+=value*basis; for(intaxis=0;axis<3;++axis){ motion[parameter][axis]+=value*basis*radial_directions[node][axis]; } } } for(intlocal=0;local<potential_count;++local){ constdoublevalue=potential_shape[local]*(potential_dofs[local]<0?-1.0:1.0); boolsupported=false; potential_binding.for_each_coefficient(::mfem::FiniteElementSpace::DecodeDof(potential_dofs[local]),[&](constintindex,constdoubleweight){ supported=true; physical.fields.get<Equation::Potential>()+=value*weight*potential[index]; }); serif_ensure<SERiFErrorCode::INVALID_DOF_COUNT>(supported,"Apotentialtracenodehasnosupportedcoefficient."); } physical.fields.get<Equation::SurfaceLevel>()=level; physical.fields.get<Equation::AngularVelocity>()=angular_velocity; constautobalance=equation.evaluate(physical); constdoubleweight=weights[static_cast<std::size_t>(quadrature)]; serif_ensure<SERiFErrorCode::NON_FINITE_VALUE_ERROR>(std::isfinite(weight)&&weight>0.0&&std::isfinite(balance.residual), "Surfacepotentialintegrationrequiresfinitegeometryandfields."); balances[static_cast<std::size_t>(quadrature)]={balance.residual,balance.field_derivatives.get<Equation::SurfaceLevel>(), balance.field_derivatives.get<Equation::AngularVelocity>(),balance.field_derivatives.get<Equation::Potential>(), {balance.position_derivative[0],balance.position_derivative[1],balance.position_derivative[2]}}; }); for(constdoubleweight:weights){reference_area+=weight;} //3.Accumulatebyrow:eachrowstillsumsitspointsinorder,soeveryvalueandinsertionorderisunchanged. utils::parallel::for_each_index(active,[&](constintrow,int){ constinttarget=parameters[row]; for(intquadrature=0;quadrature<points;++quadrature){ constauto&balance=balances[static_cast<std::size_t>(quadrature)]; constdouble*potential_shape=potential_shapes.data()+quadrature*potential_count; constauto*motion=motions.data()+static_cast<std::size_t>(quadrature)*static_cast<std::size_t>(active); constdoubletested_weight=weights[static_cast<std::size_t>(quadrature)]* tests[static_cast<std::size_t>(quadrature)*static_cast<std::size_t>(active)+static_cast<std::size_t>(row)]; result.residual[target]+=tested_weight*balance.residual; (*result.level)(target,0)+=tested_weight*balance.level; (*result.angular_velocity)(target,0)+=tested_weight*balance.angular_velocity; for(intcolumn=0;column<active;++column){ doublederivative=0.0; for(intaxis=0;axis<3;++axis){ derivative+=tested_weight*balance.position_derivative[axis]*motion[column][axis]; } if(derivative!=0.0){shape_matrix.add(target,parameters[column],derivative);} } for(intlocal=0;local<potential_count;++local){ potential_binding.for_each_coefficient(::mfem::FiniteElementSpace::DecodeDof(potential_dofs[local]),[&](constintindex,constdoubleweight){ //Aseparatestatementkeepstheproductunfused,aswhenitwaspassedtoSparseMatrix::Add. constdoublecontribution=tested_weight*balance.potential* potential_shape[local]*(potential_dofs[local]<0?-1.0:1.0)*weight; potential_matrix.add(target,index,contribution); }); } } }); return{}; }); serif_ensure<SERiFErrorCode::INVALID_ARGUMENT>(status.has_value(), "Surfacepotentialintegrationrejectedboundaryelement{}:{}", status?-1:status.error().element,status?"":status.error().message); serif_ensure<SERiFErrorCode::INVALID_ARGUMENT>(std::isfinite(reference_area)&&reference_area>0.0, "Surfacepotentialintegrationrequiresanonemptysurfaceofpositivefinitereferencearea."); result.potential=potential_matrix.finalize(); result.shape=shape_matrix.finalize(); *result.potential*=1.0/reference_area; *result.shape*=1.0/reference_area; *result.level*=1.0/reference_area; *result.angular_velocity*=1.0/reference_area; for(double&value:result.residual){value/=reference_area;} returnresult; } }