volume_field_block.hpp algorithm concepts functional memory vector serif/backend/mfem/discretization/element_coordinates.hpp serif/backend/mfem/operators/linearized_block.hpp serif/backend/mfem/operators/volume_application.hpp serif/backend/mfem/shared/parallel.hpp src/include/serif/backend/mfem/model/computations/interior.hpp src/lib/serif/backend/mfem/model/system_selection.cpp serif::operators::backend::mfem::detail::VolumeFieldOperator serif::operators::backend::mfem::detail::VolumeFieldOperator::Scratch serif::operators::backend::mfem::detail::ProbedElementBlock serif::operators::backend::mfem::detail::ProbeScratch serif::operators::backend::mfem::detail::CoefficientExpansion serif::operators::backend::mfem::detail::ConstrainedElementBlock serif serif::operators serif::operators::backend serif::operators::backend::mfem serif::operators::backend::mfem::detail #pragmaonce #include<algorithm> #include<concepts> #include<functional> #include<memory> #include<vector> #include"serif/backend/mfem/discretization/element_coordinates.hpp" #include"serif/backend/mfem/operators/linearized_block.hpp" #include"serif/backend/mfem/operators/volume_application.hpp" #include"serif/backend/mfem/shared/parallel.hpp" namespaceserif::operators::backend::mfem{ namespacedetail{ template<typenameField,typenameEquation,typenameSchema,typenameVisit> voidfor_each_field_element(constVolumeData<Equation, Schema>&data,Visit&&visit){ for_each_volume_element(data,[&]<typenameContribution>(constauto&element){ constexprboolreads_field=[]<typename...Operands>(utils::types::TypeList<Operands...>){ return(std::same_as<Field,typenameOperands::term_type>||...); }(typenameContribution::Operands{}); ifconstexpr(reads_field){ visit.templateoperator()<Contribution>(element); } }); } template<typenameField,typenameEquation,typenameContribution> voidfield_integrand(constElementData<EquationPlan<Equation>::operand_count>&element,const::mfem::Vector&direction,::mfem::Vector&integrand,::mfem::Vector&samples,BasisWorkspace&workspace){ usingPlan=EquationPlan<Equation>; usingTest=OperandEvaluator<equations::TestOperandOf<Contribution>>; integrand.SetSize(element.basis->rule->GetNPoints()*Test::components); integrand=0.0; Plan::templatefor_each_input<Contribution>([&]<typenameOperand>(Operand,conststd::size_tindex){ ifconstexpr(std::same_as<typenameOperand::term_type,Field>){ usingEvaluator=OperandEvaluator<Operand>; Evaluator::evaluate(element.geometry->operand_basis[index],direction,samples,workspace); for(intpoint=0;point<element.basis->rule->GetNPoints();++point){ for(intoutput=0;output<Test::components;++output){ constintrow=point*Test::components+output; for(intinput=0;input<Evaluator::components;++input){ integrand[row]+=element.weighted_field_derivatives(row,Plan::templatecomponent_offset<Operand>()+input)*samples[point*Evaluator::components+input]; } } } } }); } template<typenameField,typenameEquation> [[nodiscard]]constevalstd::size_tfield_column(){ returnequations::OperandValues<typename EquationPlan<Equation>::Columns>::templateindex<Field>(); } template<typenameField,VolumeEquationEquation,typenameSchema> classVolumeFieldOperatorfinal:public::mfem::Operator{ public: explicitVolumeFieldOperator(std::shared_ptr<constVolumeData<Equation, Schema>>data):::mfem::Operator(volume_height(*data),data->bindings.columns[field_column<Field, Equation>()]->size()),m_data(std::move(data)){ m_input.SetSize(width); m_full.SetSize(m_data->configuration->discretization.residual_binding.full_size()); } voidMult(const::mfem::Vector&input,::mfem::Vector&output)constoverride{ usingnamespaceutils::errors; validate_volume_data(*m_data); serif_ensure<SERiFErrorCode::INVALID_DOF_COUNT>(input.Size()==width,"Aphysicalfieldblockreceived{}coefficients;expected{}.",input.Size(),width); m_input=input; m_full=0.0; constauto&row=m_data->configuration->discretization.residual_binding; constauto&column=*m_data->bindings.columns[field_column<Field, Equation>()]; utils::CompensatedSumglobal_sum; constautorow_elements=row.validated_elements(); constautocolumn_elements=column.validated_elements(); m_actions.clear(); m_elements.clear(); detail::for_each_field_element<Field>(*m_data,[&]<typenameContribution>(constauto&element){ usingTest=OperandEvaluator<equations::TestOperandOf<Contribution>>; m_elements.push_back(element.element); m_actions.emplace_back([&element,&column_elements,this](Scratch&local,::mfem::Vector&result){ column_elements.element_values(element.element,m_input,local.direction); field_integrand<Field, Equation, Contribution>(element,local.direction,local.integrand,local.samples,local.workspace); Test::integrate(element.geometry->test_basis,local.integrand,result,local.workspace); }); }); run_actions(); for(std::size_tindex=0;index<m_actions.size();++index){ ifconstexpr(discretization::blocks::GlobalScalarTerm<typename Equation::Test>){ global_sum.add(m_results[index][0]); }else{ row_elements.add_element(m_elements[index],m_results[index],m_full); } } ifconstexpr(discretization::blocks::GlobalScalarTerm<typename Equation::Test>){ m_full[0]=global_sum.value(); } row.accumulate_dual(m_full,output); row.validate_values(output); } voidMultTranspose(const::mfem::Vector&input,::mfem::Vector&output)constoverride{ usingnamespaceutils::errors; usingPlan=EquationPlan<Equation>; validate_volume_data(*m_data); serif_ensure<SERiFErrorCode::INVALID_DOF_COUNT>(input.Size()==height,"Aphysicalfieldtransposereceived{}coefficients;expected{}.",input.Size(),height); m_dual=input; constauto&row=m_data->configuration->discretization.residual_binding; constauto&column=*m_data->bindings.columns[field_column<Field, Equation>()]; m_full_transpose.SetSize(column.full_size()); m_full_transpose=0.0; utils::CompensatedSumglobal_sum; constautorow_elements=row.validated_elements(); constautocolumn_elements=column.validated_elements(); m_actions.clear(); m_elements.clear(); detail::for_each_field_element<Field>(*m_data,[&]<typenameContribution>(constauto&element){ usingTest=OperandEvaluator<equations::TestOperandOf<Contribution>>; //Oneactionperfieldoperand,inoperandorder,eachrecomputingtheelement'stestsamples. Plan::templatefor_each_input<Contribution>([&]<typenameOperand>(Operand,conststd::size_tindex){ ifconstexpr(std::same_as<typenameOperand::term_type,Field>){ m_elements.push_back(element.element); m_actions.emplace_back([&element,&row_elements,index,this](Scratch&local,::mfem::Vector&result){ usingEvaluator=OperandEvaluator<Operand>; row_elements.element_values(element.element,m_dual,local.direction); element.geometry->test_basis.evaluate(local.direction,local.test_samples,local.workspace); local.integrand.SetSize(element.basis->rule->GetNPoints()*Evaluator::components); local.integrand=0.0; for(intpoint=0;point<element.basis->rule->GetNPoints();++point){ for(intfield_component=0;field_component<Evaluator::components;++field_component){ for(inttest_component=0;test_component<Test::components;++test_component){ constinttest_index=point*Test::components+test_component; local.integrand[point*Evaluator::components+field_component]+=element.weighted_field_derivatives(test_index, Plan::templatecomponent_offset<Operand>()+field_component)* local.test_samples[test_index]; } } } Evaluator::integrate(element.geometry->operand_basis[index],local.integrand,result,local.workspace); }); } }); }); run_actions(); for(std::size_tindex=0;index<m_actions.size();++index){ ifconstexpr(discretization::blocks::GlobalScalarTerm<Field>){ global_sum.add(m_results[index][0]); }else{ column_elements.add_element(m_elements[index],m_results[index],m_full_transpose); } } ifconstexpr(discretization::blocks::GlobalScalarTerm<Field>){ m_full_transpose[0]=global_sum.value(); } column.accumulate_dual(m_full_transpose,output); column.validate_values(output); } private: structScratch{ ::mfem::Vectordirection,integrand,samples,test_samples; BasisWorkspaceworkspace; }; voidrun_actions()const{ m_results.resize(m_actions.size()); m_scratch.resize(static_cast<std::size_t>(std::max(1,utils::parallel::worker_count()))); utils::parallel::for_each_index(static_cast<int>(m_actions.size()),[&](constintindex,constintworker){ m_actions[static_cast<std::size_t>(index)](m_scratch[static_cast<std::size_t>(worker)],m_results[static_cast<std::size_t>(index)]); }); } std::shared_ptr<constVolumeData<Equation,Schema>>m_data; mutable::mfem::Vectorm_input,m_full; mutable::mfem::Vectorm_dual,m_full_transpose; mutablestd::vector<std::function<void(Scratch&,::mfem::Vector&)>>m_actions; mutablestd::vector<int>m_elements; mutablestd::vector<::mfem::Vector>m_results; mutablestd::vector<Scratch>m_scratch; }; }//namespacedetail namespacedetail{ structProbedElementBlock{ std::vector<int>rows; std::vector<int>columns; std::vector<double>values; }; structProbeScratch{ ::mfem::Vectordirection,integrand,samples,values; BasisWorkspaceworkspace; }; structCoefficientExpansion{ std::vector<int>offsets{0}; std::vector<int>indices; std::vector<double>weights; }; structConstrainedElementBlock{ introw_count{0}; std::vector<double>values; CoefficientExpansionrows; CoefficientExpansioncolumns; }; [[nodiscard]]inlinestd::shared_ptr<const::mfem::SparseMatrix>make_csr( conststd::vector<std::vector<int>>&row_columns,conststd::vector<std::vector<double>>&row_values,constintheight,constintwidth ){ int*I=newint[static_cast<std::size_t>(height)+1]; I[0]=0; for(introw=0;row<height;++row){I[row+1]=I[row]+static_cast<int>(row_columns[static_cast<std::size_t>(row)].size());} int*J=newint[static_cast<std::size_t>(I[height])]; double*A=newdouble[static_cast<std::size_t>(I[height])]; boolsorted=true; for(introw=0;row<height;++row){ intlast_column=-1; constauto&columns=row_columns[static_cast<std::size_t>(row)]; constauto&values=row_values[static_cast<std::size_t>(row)]; for(std::size_tk=0;k<columns.size();++k){ J[I[row]+static_cast<int>(k)]=columns[k]; A[I[row]+static_cast<int>(k)]=values[k]; if(last_column>columns[k]){sorted=false;} last_column=columns[k]; } } returnstd::make_shared<::mfem::SparseMatrix>(I,J,A,height,width,true,true,sorted); } [[nodiscard]]inlinestd::shared_ptr<const::mfem::SparseMatrix>finalize_probed_blocks( conststd::vector<ProbedElementBlock>&blocks,constintheight,constintwidth ){ //Row->(block,localrow)ininsertionorder:blocksascending,thenlocalrowsascending. std::vector<int>offsets(static_cast<std::size_t>(height)+1,0); for(constauto&block:blocks){ for(constintrow:block.rows){ if(row>=0){++offsets[static_cast<std::size_t>(row)+1];} } } for(introw=0;row<height;++row){offsets[row+1]+=offsets[row];} std::vector<int>incidence_block(static_cast<std::size_t>(offsets.back())); std::vector<int>incidence_row(static_cast<std::size_t>(offsets.back())); { std::vector<int>cursor(offsets.begin(),offsets.end()-1); for(intblock=0;block<static_cast<int>(blocks.size());++block){ constauto&rows=blocks[static_cast<std::size_t>(block)].rows; for(intlocal=0;local<static_cast<int>(rows.size());++local){ if(rows[local]>=0){ constintslot=cursor[static_cast<std::size_t>(rows[local])]++; incidence_block[static_cast<std::size_t>(slot)]=block; incidence_row[static_cast<std::size_t>(slot)]=local; } } } } structRowScratch{ std::vector<int>slot_of_column; std::vector<int>columns; std::vector<double>values; }; std::vector<std::vector<int>>row_columns(static_cast<std::size_t>(height)); std::vector<std::vector<double>>row_values(static_cast<std::size_t>(height)); std::vector<RowScratch>scratch(static_cast<std::size_t>(utils::parallel::worker_count())); utils::parallel::for_each_index(height,[&](constintrow,constintworker){ RowScratch&local=scratch[static_cast<std::size_t>(worker)]; local.slot_of_column.resize(static_cast<std::size_t>(width),-1); local.columns.clear(); local.values.clear(); for(intentry=offsets[row];entry<offsets[row+1];){ //Insertionsforoneblockcomecolumnbycolumn,eachcolumnvisitingitslocalrowsinorder. constintblock_index=incidence_block[static_cast<std::size_t>(entry)]; intend=entry; while(end<offsets[row+1]&&incidence_block[static_cast<std::size_t>(end)]==block_index){++end;} constauto&block=blocks[static_cast<std::size_t>(block_index)]; constintblock_rows=static_cast<int>(block.rows.size()); for(intcolumn=0;column<static_cast<int>(block.columns.size());++column){ constintcolumn_index=block.columns[static_cast<std::size_t>(column)]; if(column_index<0){continue;} int&slot=local.slot_of_column[static_cast<std::size_t>(column_index)]; for(intk=entry;k<end;++k){ if(slot<0){ slot=static_cast<int>(local.columns.size()); local.columns.push_back(column_index); local.values.push_back(0.0); } local.values[static_cast<std::size_t>(slot)]+=block.values[static_cast<std::size_t>(column*block_rows+incidence_row[static_cast<std::size_t>(k)])]; } } entry=end; } auto&columns=row_columns[static_cast<std::size_t>(row)]; auto&values=row_values[static_cast<std::size_t>(row)]; for(std::size_tslot=local.columns.size();slot-->0;){ local.slot_of_column[static_cast<std::size_t>(local.columns[slot])]=-1; if(local.values[slot]==0.0){continue;} columns.push_back(local.columns[slot]); values.push_back(local.values[slot]); } }); returnmake_csr(row_columns,row_values,height,width); } [[nodiscard]]inlinestd::shared_ptr<const::mfem::SparseMatrix>finalize_constrained_blocks( conststd::vector<ConstrainedElementBlock>&blocks,constintheight,constintwidth ){ //Row->(block,localrow,weight)ininsertionorder:blocks,thenlocalrows,thentheircoefficients. std::vector<int>offsets(static_cast<std::size_t>(height)+1,0); for(constauto&block:blocks){ for(constintrow:block.rows.indices){++offsets[static_cast<std::size_t>(row)+1];} } for(introw=0;row<height;++row){offsets[row+1]+=offsets[row];} std::vector<int>incidence_block(static_cast<std::size_t>(offsets.back())); std::vector<int>incidence_row(static_cast<std::size_t>(offsets.back())); std::vector<double>incidence_weight(static_cast<std::size_t>(offsets.back())); { std::vector<int>cursor(offsets.begin(),offsets.end()-1); for(intblock=0;block<static_cast<int>(blocks.size());++block){ constauto&rows=blocks[static_cast<std::size_t>(block)].rows; for(intlocal=0;local+1<static_cast<int>(rows.offsets.size());++local){ for(intk=rows.offsets[static_cast<std::size_t>(local)];k<rows.offsets[static_cast<std::size_t>(local)+1];++k){ constintslot=cursor[static_cast<std::size_t>(rows.indices[static_cast<std::size_t>(k)])]++; incidence_block[static_cast<std::size_t>(slot)]=block; incidence_row[static_cast<std::size_t>(slot)]=local; incidence_weight[static_cast<std::size_t>(slot)]=rows.weights[static_cast<std::size_t>(k)]; } } } } structRowScratch{ std::vector<int>slot_of_column; std::vector<int>columns; std::vector<double>values; }; std::vector<std::vector<int>>row_columns(static_cast<std::size_t>(height)); std::vector<std::vector<double>>row_values(static_cast<std::size_t>(height)); std::vector<RowScratch>scratch(static_cast<std::size_t>(utils::parallel::worker_count())); utils::parallel::for_each_index(height,[&](constintrow,constintworker){ RowScratch&local=scratch[static_cast<std::size_t>(worker)]; local.slot_of_column.resize(static_cast<std::size_t>(width),-1); local.columns.clear(); local.values.clear(); for(intentry=offsets[row];entry<offsets[row+1];){ constintblock_index=incidence_block[static_cast<std::size_t>(entry)]; intend=entry; while(end<offsets[row+1]&&incidence_block[static_cast<std::size_t>(end)]==block_index){++end;} constauto&block=blocks[static_cast<std::size_t>(block_index)]; constauto&columns=block.columns; for(intcolumn=0;column+1<static_cast<int>(columns.offsets.size());++column){ for(intk=entry;k<end;++k){ constdoublevalue=block.values[static_cast<std::size_t>(column)*static_cast<std::size_t>(block.row_count)+ static_cast<std::size_t>(incidence_row[static_cast<std::size_t>(k)])]; constdoublerow_weight=incidence_weight[static_cast<std::size_t>(k)]; for(intc=columns.offsets[static_cast<std::size_t>(column)];c<columns.offsets[static_cast<std::size_t>(column)+1];++c){ int&slot=local.slot_of_column[static_cast<std::size_t>(columns.indices[static_cast<std::size_t>(c)])]; if(slot<0){ slot=static_cast<int>(local.columns.size()); local.columns.push_back(columns.indices[static_cast<std::size_t>(c)]); local.values.push_back(0.0); } //Aseparatestatementkeepstheproductunfused,aswhenitwaspassedtoSparseMatrix::Add. constdoublecontribution=row_weight*value*columns.weights[static_cast<std::size_t>(c)]; local.values[static_cast<std::size_t>(slot)]+=contribution; } } } entry=end; } auto&columns=row_columns[static_cast<std::size_t>(row)]; auto&values=row_values[static_cast<std::size_t>(row)]; for(std::size_tslot=local.columns.size();slot-->0;){ local.slot_of_column[static_cast<std::size_t>(local.columns[slot])]=-1; if(local.values[slot]==0.0){continue;} columns.push_back(local.columns[slot]); values.push_back(local.values[slot]); } }); returnmake_csr(row_columns,row_values,height,width); } }//namespacedetail template<typenameField,VolumeEquationEquation,typenameSchema> [[nodiscard]]std::shared_ptr<const::mfem::SparseMatrix>assemble_volume_field(constVolumeData<Equation, Schema>&data){ validate_volume_data(data); constauto&row_binding=data.configuration->discretization.residual_binding; constauto&column_binding=*data.bindings.columns[detail::field_column<Field, Equation>()]; if(!row_binding.constrained()&&!column_binding.constrained()){ //Probeelementsconcurrently(thesameoperationspercolumnasbelow),thenbuildtherows. constautorow_elements=row_binding.validated_elements(); constautocolumn_elements=column_binding.validated_elements(); std::vector<std::function<void(detail::ProbedElementBlock&,detail::ProbeScratch&)>>probes; detail::for_each_field_element<Field>(data,[&]<typenameContribution>(constauto&element){ probes.emplace_back([&row_elements,&column_elements,&element](detail::ProbedElementBlock&block,detail::ProbeScratch&scratch){ usingTest=OperandEvaluator<equations::TestOperandOf<Contribution>>; constautorows=row_elements.coordinates(element.element); constautocolumns=column_elements.coordinates(element.element); block.rows.resize(static_cast<std::size_t>(rows.size())); block.columns.resize(static_cast<std::size_t>(columns.size())); block.values.assign(static_cast<std::size_t>(rows.size())*static_cast<std::size_t>(columns.size()),0.0); for(introw=0;row<rows.size();++row){ block.rows[static_cast<std::size_t>(row)]=rows.reduced_dof(row)>=0?rows.reduced_dof(row):-1; } for(intcolumn=0;column<columns.size();++column){ block.columns[static_cast<std::size_t>(column)]=columns.reduced_dof(column)>=0?columns.reduced_dof(column):-1; if(columns.reduced_dof(column)<0){ continue; } columns.unit_direction(column,scratch.direction); detail::field_integrand<Field, Equation, Contribution>(element,scratch.direction,scratch.integrand,scratch.samples,scratch.workspace); Test::integrate(element.geometry->test_basis,scratch.integrand,scratch.values,scratch.workspace); rows.orient_dual(scratch.values); for(introw=0;row<rows.size();++row){ block.values[static_cast<std::size_t>(column)*static_cast<std::size_t>(rows.size())+static_cast<std::size_t>(row)]=scratch.values[row]; } } }); }); std::vector<detail::ProbedElementBlock>blocks(probes.size()); std::vector<detail::ProbeScratch>scratch(static_cast<std::size_t>(utils::parallel::worker_count())); utils::parallel::for_each_index(static_cast<int>(probes.size()),[&](constintindex,constintworker){ probes[static_cast<std::size_t>(index)](blocks[static_cast<std::size_t>(index)],scratch[static_cast<std::size_t>(worker)]); }); returndetail::finalize_probed_blocks(blocks,row_binding.size(),column_binding.size()); } //Hanging-nodeconstraintsdistributeeachlocalentryoverweightedreducedcoordinates.Probeelements //concurrently,thenreplaytheconstrainedinsertionsrowbyrowintheiroriginalorder. constautorow_elements=row_binding.validated_elements(); constautocolumn_elements=column_binding.validated_elements(); constboolskip_unsupported_columns=!column_binding.constrained(); std::vector<std::function<void(detail::ConstrainedElementBlock&,detail::ProbeScratch&)>>probes; detail::for_each_field_element<Field>(data,[&]<typenameContribution>(constauto&element){ probes.emplace_back([&row_elements,&column_elements,&element,skip_unsupported_columns](detail::ConstrainedElementBlock&block,detail::ProbeScratch&scratch){ usingTest=OperandEvaluator<equations::TestOperandOf<Contribution>>; constautorows=row_elements.coordinates(element.element); constautocolumns=column_elements.coordinates(element.element); constautoexpand=[](constauto&coordinates,constintlocal,detail::CoefficientExpansion&expansion){ coordinates.for_each_coefficient(local,[&](constintindex,constdoubleweight){ expansion.indices.push_back(index); expansion.weights.push_back(weight); }); expansion.offsets.push_back(static_cast<int>(expansion.indices.size())); }; block.row_count=rows.size(); block.values.assign(static_cast<std::size_t>(rows.size())*static_cast<std::size_t>(columns.size()),0.0); for(introw=0;row<rows.size();++row){ expand(rows,row,block.rows); } for(intcolumn=0;column<columns.size();++column){ if(skip_unsupported_columns&&columns.reduced_dof(column)<0){ block.columns.offsets.push_back(static_cast<int>(block.columns.indices.size())); continue; } expand(columns,column,block.columns); columns.unit_direction(column,scratch.direction); detail::field_integrand<Field, Equation, Contribution>(element,scratch.direction,scratch.integrand,scratch.samples,scratch.workspace); Test::integrate(element.geometry->test_basis,scratch.integrand,scratch.values,scratch.workspace); rows.orient_dual(scratch.values); for(introw=0;row<rows.size();++row){ block.values[static_cast<std::size_t>(column)*static_cast<std::size_t>(rows.size())+static_cast<std::size_t>(row)]=scratch.values[row]; } } }); }); std::vector<detail::ConstrainedElementBlock>blocks(probes.size()); std::vector<detail::ProbeScratch>scratch(static_cast<std::size_t>(utils::parallel::worker_count())); utils::parallel::for_each_index(static_cast<int>(probes.size()),[&](constintindex,constintworker){ probes[static_cast<std::size_t>(index)](blocks[static_cast<std::size_t>(index)],scratch[static_cast<std::size_t>(worker)]); }); returndetail::finalize_constrained_blocks(blocks,row_binding.size(),column_binding.size()); } template<typenameField,VolumeEquationEquation,typenameSchema> [[nodiscard]]std::vector<double>volume_field_diagonal(constVolumeData<Equation, Schema>&data){ usingnamespaceutils::errors; validate_volume_data(data); constauto&row_binding=data.configuration->discretization.residual_binding; constauto&column_binding=*data.bindings.columns[detail::field_column<Field, Equation>()]; serif_ensure<SERiFErrorCode::MATRIX_SHAPE_ERROR>(row_binding.size()==column_binding.size(),"Physicaldiagonalextractionrequiresasquareselectedblock."); if(row_binding.constrained()||column_binding.constrained()){ //Off-diagonallocalentriesalsocontributetodiag(P^TAP). constautomatrix=assemble_volume_field<Field>(data); ::mfem::Vectordiagonal; matrix->GetDiag(diagonal); returnstd::vector<double>(diagonal.GetData(),diagonal.GetData()+diagonal.Size()); } std::vector<double>diagonal(row_binding.size(),0.0); ::mfem::Vectordirection,row_direction,integrand,samples,test_samples; BasisWorkspaceworkspace; constautorow_elements=row_binding.validated_elements(); constautocolumn_elements=column_binding.validated_elements(); detail::for_each_field_element<Field>(data,[&]<typenameContribution>(constauto&element){ constautorows=row_elements.coordinates(element.element); constautocolumns=column_elements.coordinates(element.element); for(intcolumn=0;column<columns.size();++column){ constintindex=columns.reduced_dof(column); if(index<0){ continue; } constintrow=rows.local_dof(index); if(row<0){ continue; } columns.unit_direction(column,direction); rows.unit_direction(row,row_direction); detail::field_integrand<Field, Equation, Contribution>(element,direction,integrand,samples,workspace); element.geometry->test_basis.evaluate(row_direction,test_samples,workspace); diagonal[index]+=test_samples*integrand; } }); returndiagonal; } template<typenameField,VolumeEquationEquation,typenameSchema> [[nodiscard]]LinearizedBlockvolume_field_block(std::shared_ptr<constVolumeData<Equation, Schema>>data){ utils::errors::serif_ensure<utils::errors::SERiFErrorCode::INVALID_ARGUMENT>(data!=nullptr,"Aphysicalblockneedsanacceptedvolumelinearization."); autoaction=std::make_shared<detail::VolumeFieldOperator<Field,Equation,Schema>>(data); returnLinearizedBlock(std::move(action),[data](){ returnvolume_field_diagonal<Field>(*data); },[data](){ returnassemble_volume_field<Field>(*data); }); } }//namespaceserif::operators::backend::mfem