From 7d18c3e29930b9f0c45c08c7a06a8bd82714a074 Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Tue, 8 Sep 2026 22:54:52 +0200 Subject: [PATCH 1/5] differential operations --- Sofa/framework/Simulation/Core/CMakeLists.txt | 2 + .../simulation/DifferentialOperations.cpp | 57 +++++++++++++++++++ .../sofa/simulation/DifferentialOperations.h | 50 ++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.cpp create mode 100644 Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h diff --git a/Sofa/framework/Simulation/Core/CMakeLists.txt b/Sofa/framework/Simulation/Core/CMakeLists.txt index cf32c217c79..b18c01d2992 100644 --- a/Sofa/framework/Simulation/Core/CMakeLists.txt +++ b/Sofa/framework/Simulation/Core/CMakeLists.txt @@ -17,6 +17,7 @@ set(HEADER_FILES ${SRC_ROOT}/DefaultAnimationLoop.h ${SRC_ROOT}/DefaultVisualManagerLoop.h ${SRC_ROOT}/DeleteVisitor.h + ${SRC_ROOT}/DifferentialOperations.h ${SRC_ROOT}/ExportDotVisitor.h ${SRC_ROOT}/ExportGnuplotVisitor.h ${SRC_ROOT}/ExportVisualModelOBJVisitor.h @@ -169,6 +170,7 @@ set(SOURCE_FILES ${SRC_ROOT}/DefaultAnimationLoop.cpp ${SRC_ROOT}/DefaultVisualManagerLoop.cpp ${SRC_ROOT}/DeleteVisitor.cpp + ${SRC_ROOT}/DifferentialOperations.cpp ${SRC_ROOT}/ExportDotVisitor.cpp ${SRC_ROOT}/ExportGnuplotVisitor.cpp ${SRC_ROOT}/ExportVisualModelOBJVisitor.cpp diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.cpp b/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.cpp new file mode 100644 index 00000000000..5e987037198 --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.cpp @@ -0,0 +1,57 @@ +/****************************************************************************** +* 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 * +******************************************************************************/ +#include + +namespace sofa::simulation::common +{ + +void DifferentialOperations::pushforwardCoord( + const MappingGraph& mappingGraph, const core::MechanicalParams& mparams, + core::MultiVecCoordId coordVectorId) +{ + mappingGraph.algorithms.traverseTopDown_([&](core::BaseMapping& mapping) + { + mapping.apply(&mparams, coordVectorId, coordVectorId); + }); +} + +void DifferentialOperations::pushforwardTangent( + const MappingGraph& mappingGraph, const core::MechanicalParams& mparams, + core::MultiVecDerivId tangentVectorId) +{ + mappingGraph.algorithms.traverseTopDown_([&](core::BaseMapping& mapping) + { + mapping.applyJ(&mparams, tangentVectorId, tangentVectorId); + }); +} + +void DifferentialOperations::pullbackCotangent( + const MappingGraph& mappingGraph, const core::MechanicalParams& mparams, + core::MultiVecDerivId cotangentVectorId) +{ + mappingGraph.algorithms.traverseBottomUp_([&](core::BaseMapping& mapping) + { + mapping.applyJ(&mparams, cotangentVectorId, cotangentVectorId); + }); +} + +} // namespace sofa::simulation::common diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h new file mode 100644 index 00000000000..18cdf193311 --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h @@ -0,0 +1,50 @@ +/****************************************************************************** +* 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 + +namespace sofa::simulation::common +{ + +class SOFA_SIMULATION_CORE_API DifferentialOperations +{ +public: + + static void pushforwardCoord( + const MappingGraph& mappingGraph, const core::MechanicalParams& mparams, + core::MultiVecCoordId coordVectorId); + + static void pushforwardTangent( + const MappingGraph& mappingGraph, const core::MechanicalParams& mparams, + core::MultiVecDerivId tangentVectorId); + + static void pullbackCotangent( + const MappingGraph& mappingGraph, const core::MechanicalParams& mparams, + core::MultiVecDerivId cotangentVectorId); + +}; + +} From 7e5fba8fd563acfbf80393fccbde498a91490ab9 Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Tue, 8 Sep 2026 23:00:33 +0200 Subject: [PATCH 2/5] ignore flags --- .../Core/src/sofa/simulation/DifferentialOperations.cpp | 7 +++++-- .../Core/src/sofa/simulation/DifferentialOperations.h | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.cpp b/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.cpp index 5e987037198..7d5521a9e79 100644 --- a/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.cpp +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.cpp @@ -46,11 +46,14 @@ void DifferentialOperations::pushforwardTangent( void DifferentialOperations::pullbackCotangent( const MappingGraph& mappingGraph, const core::MechanicalParams& mparams, - core::MultiVecDerivId cotangentVectorId) + core::MultiVecDerivId cotangentVectorId, bool ignoreMappingFlag) { mappingGraph.algorithms.traverseBottomUp_([&](core::BaseMapping& mapping) { - mapping.applyJ(&mparams, cotangentVectorId, cotangentVectorId); + if (mapping.areForcesMapped() || ignoreMappingFlag) + { + mapping.applyJT(&mparams, cotangentVectorId, cotangentVectorId); + } }); } diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h index 18cdf193311..6542ad990d7 100644 --- a/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h @@ -43,7 +43,7 @@ class SOFA_SIMULATION_CORE_API DifferentialOperations static void pullbackCotangent( const MappingGraph& mappingGraph, const core::MechanicalParams& mparams, - core::MultiVecDerivId cotangentVectorId); + core::MultiVecDerivId cotangentVectorId, bool ignoreMappingFlag = true); }; From 8065102683936c3cc3237a9c6b8feab28e7f2ea0 Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Wed, 9 Sep 2026 07:21:43 +0200 Subject: [PATCH 3/5] documentation --- .../sofa/simulation/DifferentialOperations.h | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h index 6542ad990d7..569ffd6d050 100644 --- a/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h @@ -29,21 +29,68 @@ namespace sofa::simulation::common { +/** + * @brief Differential and geometric operations over a MappingGraph. + * + * Provides static operations for pushing forward and pulling back multi-vectors + * through the hierarchy of mappings defined within a MappingGraph. + * + * In physical simulations, primal quantities (coordinates, tangent vectors such as velocities + * or displacements) are propagated top-to-bottom through pushforward operations (applying + * the mapping function or its Jacobian $\mathbf{J}$). Dual quantities (cotangent vectors such + * as forces) are transformed bottom-to-top through pullback operations (using the transpose + * Jacobian $\mathbf{J}^T$). + */ class SOFA_SIMULATION_CORE_API DifferentialOperations { public: + /** + * @brief Push forward primal coordinates top-down through the mapping graph. + * + * Traverses the mapping graph from top to bottom and applies each mapping function + * to transform primal configuration coordinates (e.g. positions) across coordinate frames. + * + * @param mappingGraph The mapping graph containing the topology and mappings to traverse. + * @param mparams Mechanical parameters associated with the current operation. + * @param coordVectorId Identifier of the coordinate multi-vector to push forward. + */ static void pushforwardCoord( const MappingGraph& mappingGraph, const core::MechanicalParams& mparams, core::MultiVecCoordId coordVectorId); + /** + * @brief Push forward tangent vectors top-down via the mapping Jacobian. + * + * Traverses the mapping graph from top to bottom and applies the Jacobian matrix $\mathbf{J}$ + * of each mapping to propagate tangent quantities (e.g. velocities, displacements $dx$) + * via Jacobian-vector products ($\mathbf{J}v$). + * + * @param mappingGraph The mapping graph containing the topology and mappings to traverse. + * @param mparams Mechanical parameters associated with the current operation. + * @param tangentVectorId Identifier of the derivative multi-vector representing tangent quantities to push forward. + */ static void pushforwardTangent( const MappingGraph& mappingGraph, const core::MechanicalParams& mparams, core::MultiVecDerivId tangentVectorId); + /** + * @brief Pull back cotangent vectors bottom-up via the transpose of the mapping Jacobian. + * + * Traverses the mapping graph from bottom to top and applies the transposed Jacobian $\mathbf{J}^T$ + * of each mapping to pull back dual quantities (e.g. forces, momentum) via + * vector-Jacobian products ($v^T\mathbf{J}$). + * + * @param mappingGraph The mapping graph containing the topology and mappings to traverse. + * @param mparams Mechanical parameters associated with the current operation. + * @param cotangentVectorId Identifier of the derivative multi-vector representing cotangent quantities to pull back. + * @param ignoreMappingFlag If true, forces are pulled back through all mappings regardless of + * whether their internal force-mapping flag (`areForcesMapped()`) is enabled. + */ static void pullbackCotangent( const MappingGraph& mappingGraph, const core::MechanicalParams& mparams, - core::MultiVecDerivId cotangentVectorId, bool ignoreMappingFlag = true); + core::MultiVecDerivId cotangentVectorId, + bool ignoreMappingFlag = true); }; From d88c01d37fc9f215447453748aeae4926c4af7d4 Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Wed, 9 Sep 2026 07:22:38 +0200 Subject: [PATCH 4/5] use pullbackCotangent --- .../MappingGraphMechanicalOperations.cpp | 15 +++++++-------- .../simulation/MappingGraphMechanicalOperations.h | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.cpp b/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.cpp index 51ca30266b2..b09314529d1 100644 --- a/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.cpp +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.cpp @@ -20,6 +20,7 @@ * Contact information: contact@sofa-framework.org * ******************************************************************************/ #include +#include namespace sofa::simulation::common { @@ -41,7 +42,7 @@ void MappingGraphMechanicalOperations::projectResponse(const MappingGraph& mappi void MappingGraphMechanicalOperations::computeForce(const MappingGraph& mappingGraph, core::MultiVecDerivId result, bool clearForceBefore, - bool accumulateForcesFromMappedStates, + bool pullbackForces, TaskScheduler* taskScheduler) { //assumes the mapping graph is valid and properly initialized @@ -79,16 +80,14 @@ void MappingGraphMechanicalOperations::computeForce(const MappingGraph& mappingG forceField.addForce(&mparams, result); }, sofa::simulation::VisitorApplication::ALL_NODES, taskScheduler); - if (accumulateForcesFromMappedStates) + if (pullbackForces) { /** - * Compute f_in += J^T * f_out using mappings in the mapping graph. This operation must be - * performed in a bottom-up order to ensure correct force accumulation. + * Pull back force f_in += J^T * f_out using the mappings in the mapping graph. Force is a + * dual quantity (a cotangent vector). That is why this operation must be performed in a + * bottom-up order. */ - mappingGraph.algorithms.traverseBottomUp_([&](core::BaseMapping& mapping) - { - mapping.applyJT(&mparams, result, result); - }); + DifferentialOperations::pullbackCotangent(mappingGraph, mparams, result); } } void MappingGraphMechanicalOperations::addMBKv(const MappingGraph& mappingGraph, diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.h index 80a12a60945..8ebccdb2367 100644 --- a/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.h +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.h @@ -53,7 +53,7 @@ class SOFA_SIMULATION_CORE_API MappingGraphMechanicalOperations : public Mechani using MechanicalOperations::projectResponse; /// Compute the current force (given the latest propagated position and velocity) - void computeForce(const MappingGraph& mappingGraph, core::MultiVecDerivId result, bool clearForceBefore, bool accumulateForcesFromMappedStates, TaskScheduler* taskScheduler); + void computeForce(const MappingGraph& mappingGraph, core::MultiVecDerivId result, bool clearForceBefore, bool pullbackForces, TaskScheduler* taskScheduler); using MechanicalOperations::computeForce; /// accumulate $ df += (m M + b B + k K) velocity $ From e2b2f4f833155749ecc3f377b28c760f96df531c Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Wed, 9 Sep 2026 10:05:52 +0200 Subject: [PATCH 5/5] pullbackCotangentTangent --- .../simulation/DifferentialOperations.cpp | 17 +++++++++++++ .../sofa/simulation/DifferentialOperations.h | 24 +++++++++++++++++++ .../MappingGraphMechanicalOperations.cpp | 9 +------ 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.cpp b/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.cpp index 7d5521a9e79..38ea8847b19 100644 --- a/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.cpp +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.cpp @@ -20,6 +20,7 @@ * Contact information: contact@sofa-framework.org * ******************************************************************************/ #include +#include namespace sofa::simulation::common { @@ -57,4 +58,20 @@ void DifferentialOperations::pullbackCotangent( }); } +void DifferentialOperations::pullbackCotangentTangent( + const MappingGraph& mappingGraph, const core::MechanicalParams& mparams, + core::MultiVecDerivId cotangentTangentVectorId) +{ + mappingGraph.algorithms.traverseBottomUp_( + [&](core::BaseMapping& mapping) + { + mapping.applyJT(&mparams, cotangentTangentVectorId, cotangentTangentVectorId); + if (mparams.kFactor() != 0) + { + // ideally, the child cotangent vector must be provided here, instead of implicitly getting it in the mapping + mapping.applyDJT(&mparams, cotangentTangentVectorId, cotangentTangentVectorId); + } + }); +} + } // namespace sofa::simulation::common diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h index 569ffd6d050..775a1517bca 100644 --- a/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/DifferentialOperations.h @@ -92,6 +92,30 @@ class SOFA_SIMULATION_CORE_API DifferentialOperations core::MultiVecDerivId cotangentVectorId, bool ignoreMappingFlag = true); + /** + * @brief Pull back the differential (tangent variation) of a cotangent vector bottom-up. + * + * Computes the total differential of a vector-Jacobian product (VJP), corresponding to + * the variation of a pulled-back cotangent quantity (such as force variation @f$ df_x = d(\mathbf{J}^T f_y) @f$): + * @f[ + * df_x = \mathbf{J}^T df_y + d(\mathbf{J}^T) f_y + * @f] + * + * Traverses the mapping graph from bottom to top and accumulates: + * 1. The first-order pullback of the cotangent differential via the transpose Jacobian: + * @f$ \mathbf{J}^T df_y @f$ (`applyJT()`). + * 2. When the stiffness factor @c kFactor is non-zero, the geometric stiffness contribution + * arising from the non-linear variation of the mapping's Jacobian (a Hessian-vector contraction): + * @f$ d(\mathbf{J}^T) f_y @f$ (`applyDJT()`). + * + * @param mappingGraph The mapping graph containing the topology and mappings to traverse. + * @param mparams Mechanical parameters associated with the current operation (including @c kFactor). + * @param cotangentTangentVectorId Identifier of the derivative multi-vector storing the cotangent + * tangent variations (e.g. @f$ df @f$) to pull back. + */ + static void pullbackCotangentTangent( + const MappingGraph& mappingGraph, const core::MechanicalParams& mparams, + core::MultiVecDerivId cotangentTangentVectorId); }; } diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.cpp b/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.cpp index b09314529d1..c167fc7404d 100644 --- a/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.cpp +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.cpp @@ -123,14 +123,7 @@ void MappingGraphMechanicalOperations::addMBKv(const MappingGraph& mappingGraph, if (accumulate) { - mappingGraph.algorithms.traverseBottomUp_([&](core::BaseMapping& mapping) - { - mapping.applyJT(&mparams, df, df); - if( mparams.kFactor() != 0 ) - { - mapping.applyDJT(&mparams, df, df); - } - }); + DifferentialOperations::pullbackCotangentTangent(mappingGraph, mparams, df); } mparams.setDx(dx);