From 87a7660cd2427fd858daa5707f1da6f23f489655 Mon Sep 17 00:00:00 2001 From: Themis Skamagkis Date: Tue, 21 Jul 2026 14:41:34 +0200 Subject: [PATCH 1/2] [SolidMechanics] Add FEMSourceTerm component --- .../SolidMechanics/FEM/Elastic/CMakeLists.txt | 3 + .../fem/elastic/FEMSourceTerm.cpp | 58 ++++++ .../fem/elastic/FEMSourceTerm.h | 158 ++++++++++++++++ .../fem/elastic/FEMSourceTerm.inl | 169 ++++++++++++++++++ .../solidmechanics/fem/elastic/init.cpp | 2 + 5 files changed, 390 insertions(+) create mode 100644 Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.cpp create mode 100644 Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.h create mode 100644 Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.inl diff --git a/Sofa/Component/SolidMechanics/FEM/Elastic/CMakeLists.txt b/Sofa/Component/SolidMechanics/FEM/Elastic/CMakeLists.txt index fddd6033a0d..829fcc8475a 100644 --- a/Sofa/Component/SolidMechanics/FEM/Elastic/CMakeLists.txt +++ b/Sofa/Component/SolidMechanics/FEM/Elastic/CMakeLists.txt @@ -15,6 +15,8 @@ set(HEADER_FILES ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/BeamFEMForceField.inl ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/CorotationalFEMForceField.h ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/CorotationalFEMForceField.inl + ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/FEMSourceTerm.h + ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/FEMSourceTerm.inl ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/LinearSmallStrainFEMForceField.h ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/LinearSmallStrainFEMForceField.inl ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/FastTetrahedralCorotationalForceField.h @@ -69,6 +71,7 @@ set(SOURCE_FILES ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/BaseLinearElasticityFEMForceField.cpp ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/BeamFEMForceField.cpp ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/CorotationalFEMForceField.cpp + ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/FEMSourceTerm.cpp ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/LinearSmallStrainFEMForceField.cpp ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/FastTetrahedralCorotationalForceField.cpp ${SOFACOMPONENTSOLIDMECHANICSFEMELASTIC_SOURCE_DIR}/FEMForceField.cpp diff --git a/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.cpp b/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.cpp new file mode 100644 index 00000000000..31935713335 --- /dev/null +++ b/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.cpp @@ -0,0 +1,58 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2006 INRIA, USTL, UJF, CNRS, MGH * +* * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU Lesser General Public License as published by * +* the Free Software Foundation; either version 2.1 of the License, or (at * +* your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, but WITHOUT * +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * +* for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this program. If not, see . * +******************************************************************************* +* Authors: The SOFA Team and external contributors (see Authors.txt) * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#define SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_FEM_SOURCE_TERM_CPP + +#include + +#include +#include +#include + +namespace sofa::component::solidmechanics::fem::elastic +{ + +void registerFEMSourceTerm(sofa::core::ObjectFactory* factory) +{ + factory->registerObjects(sofa::core::ObjectRegistrationData("Consistent nodal load of a source term, integrated with the finite-element quadrature from a nodal source field") + .add< FEMSourceTerm >() + .add< FEMSourceTerm >() + .add< FEMSourceTerm >() + .add< FEMSourceTerm >() + .add< FEMSourceTerm >() + .add< FEMSourceTerm >() + .add< FEMSourceTerm >() + .add< FEMSourceTerm >() + .add< FEMSourceTerm >() + ); +} + +template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; + +} // namespace sofa::component::solidmechanics::fem::elastic diff --git a/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.h b/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.h new file mode 100644 index 00000000000..b99a9c42173 --- /dev/null +++ b/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.h @@ -0,0 +1,158 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2006 INRIA, USTL, UJF, CNRS, MGH * +* * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU Lesser General Public License as published by * +* the Free Software Foundation; either version 2.1 of the License, or (at * +* your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, but WITHOUT * +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * +* for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this program. If not, see . * +******************************************************************************* +* Authors: The SOFA Team and external contributors (see Authors.txt) * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include + +#if !defined(SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_FEM_SOURCE_TERM_CPP) +#include +#endif + +namespace sofa::component::solidmechanics::fem::elastic +{ + +/** + * @class FEMSourceTerm + * @brief Computes nodal source terms by integrating a given source density field stored at the nodes. + * + * This class assembles and stores a geometric matrix M using a quadrature rule over the element domain. + * The matrix is then used to multiply the source density b to compute a nodal source term F that + * contributes to the RHS of the weak form through the addForce function. + * + * @tparam TDataTypes The data types used for positions, velocities, etc. (e.g., Vec3Types). + * @tparam TElementType The type of finite element (e.g., sofa::geometry::Tetrahedron). + */ +template +class FEMSourceTerm : + public sofa::core::behavior::ForceField, + public virtual sofa::core::behavior::TopologyAccessor +{ +public: + using DataTypes = TDataTypes; + using ElementType = TElementType; + SOFA_CLASS2(SOFA_TEMPLATE2(FEMSourceTerm, DataTypes, ElementType), + sofa::core::behavior::ForceField, + sofa::core::behavior::TopologyAccessor); + +protected: + using FiniteElement = sofa::fem::FiniteElement; + + static constexpr sofa::Size NumberOfNodesInElement = ElementType::NumberOfNodes; + + using GlobalMatrix = sofa::linearalgebra::CompressedRowSparseMatrixMechanical>; + +public: + + /** + * @brief Initializes the component. + * + * This method performs several initialization steps: + * 1. Initializes the base force field. + * 2. Initializes the topology accessor. + * 3. Resizes the nodal source density. + * 4. Assembles the global matrix M. + */ + void init() override; + + /** + * @brief Adds the force (f = M * b) to the RHS vector. + * + * This method computes the product of the geometric matrix and the source density vector, + * adding the result to the force vector `f`. + * + * @param mparams Mechanical parameters for the computation. + * @param f The force vector to which the source term will be added. + * @param x The current positions (unused in this implementation). + * @param v The current velocities (unused in this implementation). + */ + void addForce( + const sofa::core::MechanicalParams* mparams, + sofa::DataVecDeriv_t& f, + const sofa::DataVecCoord_t& x, + const sofa::DataVecDeriv_t& v) override; + + /** + * @brief A no-op as the load is prescribed on the rest configuration + */ + void addDForce(const sofa::core::MechanicalParams* mparams, + sofa::DataVecDeriv_t& df, + const sofa::DataVecDeriv_t& dx) override; + + /** + * @brief A no-op as the load is prescribed on the rest configuration + */ + void buildStiffnessMatrix(sofa::core::behavior::StiffnessMatrix* matrix) override; + + using sofa::core::behavior::ForceField::getPotentialEnergy; + /** + * @brief Not implemented, returns 0. + */ + SReal getPotentialEnergy(const sofa::core::MechanicalParams* mparams, + const sofa::DataVecCoord_t& x) const override; + + /** + * @brief Source term (per unit volume) sampled at each node. + */ + sofa::Data > d_nodalSourceDensity; + +protected: + + /** + * @brief Default constructor. + */ + FEMSourceTerm(); + + /** + * @brief Resizes the source density to the size of the mechanical state + */ + void resizeNodalSourceDensity(const std::size_t size); + + /** + * @brief Assembles and stores the geometry-only matrix \f$ M_{ij} = \int_{\Omega} N_i N_j \, d\Omega \f$ over each element on the rest configuration. + */ + void assembleGlobalMatrix(); + + /** + * @brief Geometry-only matrix \f$ M_{ij} = \int_{\Omega} N_i N_j \, d\Omega \f$ of the system. + * + * Stored in compressed sparse row format. Assembled once in init on the rest configuration. + */ + GlobalMatrix m_globalMatrix; +}; + +#if !defined(SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_FEM_SOURCE_TERM_CPP) +extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +extern template class SOFA_COMPONENT_SOLIDMECHANICS_FEM_ELASTIC_API FEMSourceTerm; +#endif + +} // namespace sofa::component::solidmechanics::fem::elastic diff --git a/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.inl b/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.inl new file mode 100644 index 00000000000..d55cf8bdf7e --- /dev/null +++ b/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.inl @@ -0,0 +1,169 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2006 INRIA, USTL, UJF, CNRS, MGH * +* * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU Lesser General Public License as published by * +* the Free Software Foundation; either version 2.1 of the License, or (at * +* your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, but WITHOUT * +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * +* for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this program. If not, see . * +******************************************************************************* +* Authors: The SOFA Team and external contributors (see Authors.txt) * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#pragma once +#include +#include + +namespace sofa::component::solidmechanics::fem::elastic +{ + +template +FEMSourceTerm::FEMSourceTerm() + : d_nodalSourceDensity(initData(&d_nodalSourceDensity, "nodalSourceDensity", + "Source term (per unit volume) sampled at each node. Interpolated inside the " + "element with the shape functions and integrated on the reference configuration.")) +{ +} + +template +void FEMSourceTerm::init() +{ + sofa::core::behavior::ForceField::init(); + + if (!this->isComponentStateInvalid()) + { + sofa::core::behavior::TopologyAccessor::init(); + } + + if (!this->isComponentStateInvalid() && this->mstate) + { + this->resizeNodalSourceDensity(this->mstate->getSize()); + } + + if (!this->isComponentStateInvalid() && this->l_topology && this->mstate) + { + this->assembleGlobalMatrix(); + } + + if (!this->isComponentStateInvalid()) + { + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid); + } +} + +template +void FEMSourceTerm::resizeNodalSourceDensity(const std::size_t size) +{ + sofa::helper::WriteAccessor nodalSourceDensity = sofa::helper::getWriteAccessor(d_nodalSourceDensity); + + if (nodalSourceDensity.size() < size) + { + nodalSourceDensity.resize(size, sofa::Deriv_t{}); + } +} + +template +void FEMSourceTerm::assembleGlobalMatrix() +{ + // M_ij = integral of N_i N_j dV, evaluated on the rest configuration (geometry only). + const auto restPositionsAccessor = this->mstate->readRestPositions(); + const auto& elements = FiniteElement::getElementSequence(*this->l_topology); + + m_globalMatrix.clear(); + const auto size = this->mstate->getSize(); + m_globalMatrix.resize(size, size); + + for (const auto& element : elements) + { + const std::array, NumberOfNodesInElement> elementNodesRestCoordinates = + extractNodesVectorFromGlobalVector(element, restPositionsAccessor.ref()); + + sofa::type::Mat> elementMatrix; + + for (const auto& [quadraturePoint, weight] : FiniteElement::quadraturePoints()) + { + const auto N = FiniteElement::shapeFunctions(quadraturePoint); + const auto dN_dq_ref = FiniteElement::gradientShapeFunctions(quadraturePoint); + + const auto jacobian = FiniteElement::Helper::jacobianFromReferenceToPhysical( + elementNodesRestCoordinates, dN_dq_ref); + const auto detJ = sofa::type::absGeneralizedDeterminant(jacobian); + + elementMatrix += (static_cast>(weight) * static_cast>(detJ)) * sofa::type::dyad(N, N); + } + + for (sofa::Size i = 0; i < NumberOfNodesInElement; ++i) + { + for (sofa::Size j = 0; j < NumberOfNodesInElement; ++j) + { + m_globalMatrix.add(element[i], element[j], elementMatrix(i, j)); + } + } + } + + m_globalMatrix.compress(); +} + +template +void FEMSourceTerm::addForce(const sofa::core::MechanicalParams* mparams, + sofa::DataVecDeriv_t& f, + const sofa::DataVecCoord_t& x, + const sofa::DataVecDeriv_t& v) +{ + SOFA_UNUSED(mparams); + SOFA_UNUSED(x); + SOFA_UNUSED(v); + + const sofa::helper::ReadAccessor nodalSourceDensity = sofa::helper::getReadAccessor(d_nodalSourceDensity); + auto forceAccessor = sofa::helper::getWriteAccessor(f); + + // f_i = sum_j M_ij b_j : apply the cached matrix to the nodal source density. + for (std::size_t xi = 0; xi < m_globalMatrix.rowIndex.size(); ++xi) + { + const auto rowId = m_globalMatrix.rowIndex[xi]; + typename GlobalMatrix::Range rowRange(m_globalMatrix.rowBegin[xi], m_globalMatrix.rowBegin[xi + 1]); + for (typename GlobalMatrix::Index xj = rowRange.begin(); xj < rowRange.end(); ++xj) + { + const auto columnId = m_globalMatrix.colsIndex[xj]; + const auto& value = m_globalMatrix.colsValue[xj]; + + forceAccessor[rowId] += nodalSourceDensity[columnId] * value; + } + } +} + +template +void FEMSourceTerm::addDForce(const sofa::core::MechanicalParams* mparams, + sofa::DataVecDeriv_t& df, + const sofa::DataVecDeriv_t& dx) +{ + SOFA_UNUSED(mparams); + SOFA_UNUSED(df); + SOFA_UNUSED(dx); +} + +template +void FEMSourceTerm::buildStiffnessMatrix(sofa::core::behavior::StiffnessMatrix* matrix) +{ + SOFA_UNUSED(matrix); +} + +template +SReal FEMSourceTerm::getPotentialEnergy(const sofa::core::MechanicalParams* mparams, + const sofa::DataVecCoord_t& x) const +{ + SOFA_UNUSED(mparams); + SOFA_UNUSED(x); + return 0.0; +} + +} // namespace sofa::component::solidmechanics::fem::elastic diff --git a/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/init.cpp b/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/init.cpp index 2c37e45f155..95e84fb5d1b 100644 --- a/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/init.cpp +++ b/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/init.cpp @@ -28,6 +28,7 @@ namespace sofa::component::solidmechanics::fem::elastic extern void registerBeamFEMForceField(sofa::core::ObjectFactory* factory); extern void registerCorotationalFEMForceField(sofa::core::ObjectFactory* factory); +extern void registerFEMSourceTerm(sofa::core::ObjectFactory* factory); extern void registerFastTetrahedralCorotationalForceField(sofa::core::ObjectFactory* factory); extern void registerHexahedralFEMForceField(sofa::core::ObjectFactory* factory); extern void registerHexahedralFEMForceFieldAndMass(sofa::core::ObjectFactory* factory); @@ -68,6 +69,7 @@ void registerObjects(sofa::core::ObjectFactory* factory) { registerBeamFEMForceField(factory); registerCorotationalFEMForceField(factory); + registerFEMSourceTerm(factory); registerFastTetrahedralCorotationalForceField(factory); registerHexahedralFEMForceField(factory); registerHexahedralFEMForceFieldAndMass(factory); From fd66f5bebc8d531247c2bcb0844ddd67e46fc128 Mon Sep 17 00:00:00 2001 From: Themis Skamagkis Date: Tue, 21 Jul 2026 18:19:01 +0200 Subject: [PATCH 2/2] Split assembly in two steps --- .../fem/elastic/FEMSourceTerm.h | 11 +++++ .../fem/elastic/FEMSourceTerm.inl | 48 +++++++++++++++---- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.h b/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.h index b99a9c42173..724881a4bd2 100644 --- a/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.h +++ b/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.h @@ -62,6 +62,7 @@ class FEMSourceTerm : static constexpr sofa::Size NumberOfNodesInElement = ElementType::NumberOfNodes; + using ElementMatrix = sofa::type::Mat>; using GlobalMatrix = sofa::linearalgebra::CompressedRowSparseMatrixMechanical>; public: @@ -135,6 +136,16 @@ class FEMSourceTerm : */ void assembleGlobalMatrix(); + /** + * @brief Computes the geometry-only matrix of each element. + */ + void calculateElementMatrix(const auto& elements, sofa::type::vector& elementMatrices); + + /** + * @brief Scatters the element matrices into the global matrix. + */ + void initializeGlobalMatrix(const auto& elements, const sofa::type::vector& elementMatrices); + /** * @brief Geometry-only matrix \f$ M_{ij} = \int_{\Omega} N_i N_j \, d\Omega \f$ of the system. * diff --git a/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.inl b/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.inl index d55cf8bdf7e..528e0079750 100644 --- a/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.inl +++ b/Sofa/Component/SolidMechanics/FEM/Elastic/src/sofa/component/solidmechanics/fem/elastic/FEMSourceTerm.inl @@ -74,21 +74,32 @@ void FEMSourceTerm::resizeNodalSourceDensity(const std:: template void FEMSourceTerm::assembleGlobalMatrix() { - // M_ij = integral of N_i N_j dV, evaluated on the rest configuration (geometry only). - const auto restPositionsAccessor = this->mstate->readRestPositions(); const auto& elements = FiniteElement::getElementSequence(*this->l_topology); + sofa::type::vector elementMatrices; - m_globalMatrix.clear(); - const auto size = this->mstate->getSize(); - m_globalMatrix.resize(size, size); + // 1. compute the geometry-only matrix of each element + calculateElementMatrix(elements, elementMatrices); + + // 2. scatter the element matrices into the global matrix + initializeGlobalMatrix(elements, elementMatrices); +} - for (const auto& element : elements) +template +void FEMSourceTerm::calculateElementMatrix( + const auto& elements, sofa::type::vector& elementMatrices) +{ + const auto restPositionsAccessor = this->mstate->readRestPositions(); + elementMatrices.resize(elements.size()); + + for (std::size_t elementId = 0; elementId < elements.size(); ++elementId) { + const auto& element = elements[elementId]; + auto& elementMatrix = elementMatrices[elementId]; + const std::array, NumberOfNodesInElement> elementNodesRestCoordinates = extractNodesVectorFromGlobalVector(element, restPositionsAccessor.ref()); - sofa::type::Mat> elementMatrix; - + // M_ij = integral of N_i N_j dV, evaluated on the rest configuration (geometry only). for (const auto& [quadraturePoint, weight] : FiniteElement::quadraturePoints()) { const auto N = FiniteElement::shapeFunctions(quadraturePoint); @@ -98,8 +109,25 @@ void FEMSourceTerm::assembleGlobalMatrix() elementNodesRestCoordinates, dN_dq_ref); const auto detJ = sofa::type::absGeneralizedDeterminant(jacobian); - elementMatrix += (static_cast>(weight) * static_cast>(detJ)) * sofa::type::dyad(N, N); + const auto NT_N = sofa::type::dyad(N, N); + + elementMatrix += (weight * detJ) * NT_N; } + } +} + +template +void FEMSourceTerm::initializeGlobalMatrix( + const auto& elements, const sofa::type::vector& elementMatrices) +{ + m_globalMatrix.clear(); + const auto size = this->mstate->getSize(); + m_globalMatrix.resize(size, size); + + for (std::size_t elementId = 0; elementId < elements.size(); ++elementId) + { + const auto& element = elements[elementId]; + const auto& elementMatrix = elementMatrices[elementId]; for (sofa::Size i = 0; i < NumberOfNodesInElement; ++i) { @@ -126,7 +154,7 @@ void FEMSourceTerm::addForce(const sofa::core::Mechanica const sofa::helper::ReadAccessor nodalSourceDensity = sofa::helper::getReadAccessor(d_nodalSourceDensity); auto forceAccessor = sofa::helper::getWriteAccessor(f); - // f_i = sum_j M_ij b_j : apply the cached matrix to the nodal source density. + // f_i = sum_j M_ij b_j : apply the global matrix to the nodal source density. for (std::size_t xi = 0; xi < m_globalMatrix.rowIndex.size(); ++xi) { const auto rowId = m_globalMatrix.rowIndex[xi];