diff --git a/Sofa/Component/LinearSystem/src/sofa/component/linearsystem/TypedMatrixLinearSystem.inl b/Sofa/Component/LinearSystem/src/sofa/component/linearsystem/TypedMatrixLinearSystem.inl index 291532a7306..1f11234f9bd 100644 --- a/Sofa/Component/LinearSystem/src/sofa/component/linearsystem/TypedMatrixLinearSystem.inl +++ b/Sofa/Component/LinearSystem/src/sofa/component/linearsystem/TypedMatrixLinearSystem.inl @@ -71,7 +71,7 @@ void TypedMatrixLinearSystem::preAssembleSystem(const core::Me { SCOPED_TIMER_VARNAME(mappingGraphTimer, "buildMappingGraph"); // build the mapping graph: this is used to know the relationship between the mechanical states and their associated components - m_mappingGraph.build(mparams, getSolveContext()); + m_mappingGraph.build(getSolveContext()); } associateLocalMatrixToComponents(mparams); @@ -191,7 +191,7 @@ void TypedMatrixLinearSystem::setRHS(core::MultiVecDerivId v) { if (!m_mappingGraph.isBuilt()) //note: this check does not make sure the scene graph is different from when the mapping graph has been built { - m_mappingGraph.build(core::execparams::defaultInstance(), getSolveContext()); + m_mappingGraph.build(getSolveContext()); } copyLocalVectorToGlobalVector(v, getRHSVector()); @@ -202,7 +202,7 @@ void TypedMatrixLinearSystem::setSystemSolution(core::MultiVec { if (!m_mappingGraph.isBuilt()) //note: this check does not guarantee the scene graph is not different from when the mapping graph has been built { - m_mappingGraph.build(core::execparams::defaultInstance(), getSolveContext()); + m_mappingGraph.build(getSolveContext()); } if (!v.isNull()) diff --git a/Sofa/Component/ODESolver/Backward/src/sofa/component/odesolver/backward/EulerImplicitSolver.cpp b/Sofa/Component/ODESolver/Backward/src/sofa/component/odesolver/backward/EulerImplicitSolver.cpp index 796ff4e4e7d..d58a5a71f72 100644 --- a/Sofa/Component/ODESolver/Backward/src/sofa/component/odesolver/backward/EulerImplicitSolver.cpp +++ b/Sofa/Component/ODESolver/Backward/src/sofa/component/odesolver/backward/EulerImplicitSolver.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include #include @@ -82,11 +82,13 @@ void EulerImplicitSolver::cleanup() void EulerImplicitSolver::solve(const core::ExecParams* params, SReal dt, sofa::core::MultiVecCoordId xResult, sofa::core::MultiVecDerivId vResult) { + m_mappingGraph.build(this->getContext()); + #ifdef SOFA_DUMP_VISITOR_INFO sofa::simulation::Visitor::printNode("SolverVectorAllocation"); #endif sofa::simulation::common::VectorOperations vop( params, this->getContext() ); - sofa::simulation::common::MechanicalOperations mop( params, this->getContext() ); + sofa::simulation::common::MappingGraphMechanicalOperations mop( params, this->getContext() ); MultiVecCoord pos(&vop, core::vec_id::write_access::position ); MultiVecDeriv vel(&vop, core::vec_id::write_access::velocity ); MultiVecDeriv f(&vop, core::vec_id::write_access::force ); @@ -126,7 +128,7 @@ void EulerImplicitSolver::solve(const core::ExecParams* params, SReal dt, sofa:: SCOPED_TIMER("ComputeForce"); mop->setImplicit(true); // this solver is implicit // compute the net forces at the beginning of the time step - mop.computeForce(f); //f = Kx + Bv + mop.computeForce(m_mappingGraph, f, true, true, nullptr); msg_info() << "initial f = " << f; } @@ -147,7 +149,7 @@ void EulerImplicitSolver::solve(const core::ExecParams* params, SReal dt, sofa:: msg_info() << "f = " << f; // add the change of force due to stiffness + Rayleigh damping - mop.addMBKv(b, core::MatricesFactors::M(-d_rayleighMass.getValue()), + mop.addMBKv(m_mappingGraph, b, core::MatricesFactors::M(-d_rayleighMass.getValue()), core::MatricesFactors::B(0), core::MatricesFactors::K(h * tr + d_rayleighStiffness.getValue())); // b = f + ( rm M + (h+rs) K ) v @@ -157,7 +159,7 @@ void EulerImplicitSolver::solve(const core::ExecParams* params, SReal dt, sofa:: msg_info() << "b = " << b; - mop.projectResponse(b); // b is projected to the constrained space + mop.projectResponse(m_mappingGraph, b); // b is projected to the constrained space msg_info() << "projected b = " << b; } diff --git a/Sofa/Component/ODESolver/Backward/src/sofa/component/odesolver/backward/EulerImplicitSolver.h b/Sofa/Component/ODESolver/Backward/src/sofa/component/odesolver/backward/EulerImplicitSolver.h index aba8e9fe33d..a42f7685b79 100644 --- a/Sofa/Component/ODESolver/Backward/src/sofa/component/odesolver/backward/EulerImplicitSolver.h +++ b/Sofa/Component/ODESolver/Backward/src/sofa/component/odesolver/backward/EulerImplicitSolver.h @@ -22,8 +22,8 @@ #pragma once #include #include - #include +#include namespace sofa::simulation::common { @@ -178,6 +178,8 @@ class SOFA_COMPONENT_ODESOLVER_BACKWARD_API EulerImplicitSolver : void reallocSolutionVector(sofa::simulation::common::VectorOperations* vop); void reallocRightHandSideVector(sofa::simulation::common::VectorOperations* vop); void reallocResidualVector(sofa::simulation::common::VectorOperations* vop); + + sofa::simulation::MappingGraph m_mappingGraph; }; } // namespace sofa::component::odesolver::backward diff --git a/Sofa/Component/ODESolver/Forward/src/sofa/component/odesolver/forward/EulerExplicitSolver.cpp b/Sofa/Component/ODESolver/Forward/src/sofa/component/odesolver/forward/EulerExplicitSolver.cpp index 2d3b4fe7e13..3e2b4179048 100644 --- a/Sofa/Component/ODESolver/Forward/src/sofa/component/odesolver/forward/EulerExplicitSolver.cpp +++ b/Sofa/Component/ODESolver/Forward/src/sofa/component/odesolver/forward/EulerExplicitSolver.cpp @@ -27,10 +27,8 @@ #include #include #include -#include +#include #include -#include -using sofa::simulation::mechanicalvisitor::MechanicalGetNonDiagonalMassesCountVisitor; //#define SOFA_NO_VMULTIOP @@ -66,10 +64,12 @@ void EulerExplicitSolver::solve(const core::ExecParams* params, SCOPED_TIMER("EulerExplicitSolve"); + m_mappingGraph.build(this->getContext()); + // Create the vector and mechanical operations tools. These are used to execute special operations (multiplication, // additions, etc.) on multi-vectors (a vector that is stored in different buffers inside the mechanical objects) sofa::simulation::common::VectorOperations vop( params, this->getContext() ); - sofa::simulation::common::MechanicalOperations mop( params, this->getContext() ); + sofa::simulation::common::MappingGraphMechanicalOperations mop( params, this->getContext() ); // Let the mechanical operations know that the current solver is explicit. This will be propagated back to the // force fields during the addForce and addKToMatrix phase. Force fields use this information to avoid @@ -121,7 +121,7 @@ void EulerExplicitSolver::solve(const core::ExecParams* params, } void EulerExplicitSolver::updateState(sofa::simulation::common::VectorOperations* vop, - sofa::simulation::common::MechanicalOperations* mop, + sofa::simulation::common::MappingGraphMechanicalOperations* mop, sofa::core::MultiVecCoordId xResult, sofa::core::MultiVecDerivId vResult, const sofa::core::behavior::MultiVecDeriv& acc, @@ -267,7 +267,7 @@ void EulerExplicitSolver::init() d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid); } -void EulerExplicitSolver::addSeparateGravity(sofa::simulation::common::MechanicalOperations* mop, SReal dt, core::MultiVecDerivId v) +void EulerExplicitSolver::addSeparateGravity(sofa::simulation::common::MappingGraphMechanicalOperations* mop, SReal dt, core::MultiVecDerivId v) { SCOPED_TIMER("addSeparateGravity"); @@ -277,17 +277,17 @@ void EulerExplicitSolver::addSeparateGravity(sofa::simulation::common::Mechanica mop->addSeparateGravity(dt, v); } -void EulerExplicitSolver::computeForce(sofa::simulation::common::MechanicalOperations* mop, core::MultiVecDerivId f) +void EulerExplicitSolver::computeForce(sofa::simulation::common::MappingGraphMechanicalOperations* mop, core::MultiVecDerivId f) const { SCOPED_TIMER("ComputeForce"); // 1. Clear the force vector (F := 0) // 2. Go down in the current context tree calling addForce on every forcefields // 3. Go up from the current context tree leaves calling applyJT on every mechanical mappings - mop->computeForce(f); + mop->computeForce(m_mappingGraph, f, true, true, nullptr); } -void EulerExplicitSolver::computeAcceleration(sofa::simulation::common::MechanicalOperations* mop, core::MultiVecDerivId acc, core::ConstMultiVecDerivId f) +void EulerExplicitSolver::computeAcceleration(sofa::simulation::common::MappingGraphMechanicalOperations* mop, core::MultiVecDerivId acc, core::ConstMultiVecDerivId f) { SCOPED_TIMER("AccFromF"); @@ -299,17 +299,17 @@ void EulerExplicitSolver::computeAcceleration(sofa::simulation::common::Mechanic mop->accFromF(acc, f); } -void EulerExplicitSolver::projectResponse(sofa::simulation::common::MechanicalOperations* mop, core::MultiVecDerivId vecId) +void EulerExplicitSolver::projectResponse(sofa::simulation::common::MappingGraphMechanicalOperations* mop, core::MultiVecDerivId vecId) const { SCOPED_TIMER("projectResponse"); // Calls the "projectResponse" method of every BaseProjectiveConstraintSet objects found in the // current context tree. An example of such constraint set is the FixedProjectiveConstraint. In this case, // it will set to 0 every row (i, _) of the input vector for the ith degree of freedom. - mop->projectResponse(vecId); + mop->projectResponse(m_mappingGraph, vecId); } -void EulerExplicitSolver::solveConstraints(sofa::simulation::common::MechanicalOperations* mop, core::MultiVecDerivId acc) +void EulerExplicitSolver::solveConstraints(sofa::simulation::common::MappingGraphMechanicalOperations* mop, core::MultiVecDerivId acc) { SCOPED_TIMER("solveConstraint"); @@ -317,7 +317,7 @@ void EulerExplicitSolver::solveConstraints(sofa::simulation::common::MechanicalO mop->solveConstraint(acc, core::ConstraintOrder::ACC); } -void EulerExplicitSolver::assembleSystemMatrix(sofa::simulation::common::MechanicalOperations* mop) const +void EulerExplicitSolver::assembleSystemMatrix(sofa::simulation::common::MappingGraphMechanicalOperations* mop) const { SCOPED_TIMER("MBKBuild"); @@ -342,52 +342,9 @@ void EulerExplicitSolver::solveSystem(core::MultiVecDerivId solution, core::Mult l_linearSolver->getLinearSystem()->dispatchSystemSolution(solution); } -class MappedMassVisitor final : public simulation::BaseMechanicalVisitor -{ -public: - MappedMassVisitor(const sofa::core::ExecParams* params, sofa::simulation::MappingGraph* mappingGraph) - : BaseMechanicalVisitor(params), m_mappingGraph(mappingGraph) - {} - - Result fwdMass(simulation::Node*, sofa::core::behavior::BaseMass* mass) override - { - if (mass && m_mappingGraph) - { - m_hasMappedMass |= m_mappingGraph->hasAnyMappingInput(mass); - } - return Result::RESULT_CONTINUE; - } - - [[nodiscard]] bool hasMappedMass() const { return m_hasMappedMass; } - -private: - sofa::simulation::MappingGraph* m_mappingGraph { nullptr }; - bool m_hasMappedMass { false }; -}; - -class AllOfMassesAreDiagonalVisitor final : public simulation::BaseMechanicalVisitor -{ -public: - using simulation::BaseMechanicalVisitor::BaseMechanicalVisitor; - Result fwdMass(simulation::Node*, sofa::core::behavior::BaseMass* mass) override - { - if (mass) - { - m_allMassesAreDiagonal &= mass->isDiagonal(); - } - return Result::RESULT_CONTINUE; - } - - [[nodiscard]] bool areAllMassesAreDiagonal() const { return m_allMassesAreDiagonal; } - -private: - bool m_allMassesAreDiagonal { true }; -}; - -bool EulerExplicitSolver::isMassMatrixTriviallyInvertible(const core::ExecParams* params) +bool EulerExplicitSolver::isMassMatrixTriviallyInvertible(const core::ExecParams* params) const { - sofa::simulation::MappingGraph mappingGraph; - mappingGraph.build(params, this->getContext()); + SOFA_UNUSED(params); // To achieve a diagonal global mass matrix in this system: // 1) Each individual mass matrix must itself be diagonal. @@ -397,15 +354,23 @@ bool EulerExplicitSolver::isMassMatrixTriviallyInvertible(const core::ExecParams // we identify a mapped mass, we cannot guarantee the global mass matrix will remain diagonal. // Moreover, computing the inverse of a mapped mass would require a complex API. Therefore, this // case is not supported without assembling the global mass matrix. - MappedMassVisitor mappedMassVisitor(params, &mappingGraph); - mappedMassVisitor.execute(this->getContext()); - if (mappedMassVisitor.hasMappedMass()) + bool hasMappedMass = false; + m_mappingGraph.algorithms.traverseComponentGroups_([&hasMappedMass](const sofa::core::behavior::BaseMass&) + { + hasMappedMass = true; + }, simulation::VisitorApplication::ONLY_MAPPED_NODES); + if (hasMappedMass) + { return false; + } // At this stage, we know that we don't have any mapped mass. We can check if they are all diagonal. - AllOfMassesAreDiagonalVisitor allOfMassesAreDiagonalVisitor(params); - allOfMassesAreDiagonalVisitor.execute(this->getContext()); - return allOfMassesAreDiagonalVisitor.areAllMassesAreDiagonal(); + bool areAllMassesDiagonal = true; + m_mappingGraph.algorithms.traverseComponentGroups_([&areAllMassesDiagonal](const sofa::core::behavior::BaseMass& mass) + { + areAllMassesDiagonal &= mass.isDiagonal(); + }); + return areAllMassesDiagonal; } } // namespace sofa::component::odesolver::forward diff --git a/Sofa/Component/ODESolver/Forward/src/sofa/component/odesolver/forward/EulerExplicitSolver.h b/Sofa/Component/ODESolver/Forward/src/sofa/component/odesolver/forward/EulerExplicitSolver.h index 72173e477a9..66cd610d1e7 100644 --- a/Sofa/Component/ODESolver/Forward/src/sofa/component/odesolver/forward/EulerExplicitSolver.h +++ b/Sofa/Component/ODESolver/Forward/src/sofa/component/odesolver/forward/EulerExplicitSolver.h @@ -25,10 +25,11 @@ #include #include #include +#include namespace sofa::simulation::common { -class MechanicalOperations; +class MappingGraphMechanicalOperations; class VectorOperations; } @@ -96,7 +97,7 @@ class SOFA_COMPONENT_ODESOLVER_FORWARD_API EulerExplicitSolver : public sofa::co /// Update state variable (new position and velocity) based on the computed acceleration /// The update takes constraints into account void updateState(sofa::simulation::common::VectorOperations* vop, - sofa::simulation::common::MechanicalOperations* mop, + sofa::simulation::common::MappingGraphMechanicalOperations* mop, sofa::core::MultiVecCoordId xResult, sofa::core::MultiVecDerivId vResult, const sofa::core::behavior::MultiVecDeriv& acc, @@ -104,27 +105,30 @@ class SOFA_COMPONENT_ODESOLVER_FORWARD_API EulerExplicitSolver : public sofa::co /// Gravity times time step size is added to the velocity for some masses /// v += g * dt - static void addSeparateGravity(sofa::simulation::common::MechanicalOperations* mop, SReal dt, core::MultiVecDerivId v); + static void addSeparateGravity(sofa::simulation::common::MappingGraphMechanicalOperations* mop, SReal dt, core::MultiVecDerivId v); /// Assemble the force vector (right-hand side of the equation) - static void computeForce(sofa::simulation::common::MechanicalOperations* mop, core::MultiVecDerivId f); + void computeForce(sofa::simulation::common::MappingGraphMechanicalOperations* mop, core::MultiVecDerivId f) const; /// Compute the acceleration from the force and the inverse of the mass /// acc = M^-1 * f - static void computeAcceleration(sofa::simulation::common::MechanicalOperations* mop, + static void computeAcceleration(sofa::simulation::common::MappingGraphMechanicalOperations* mop, core::MultiVecDerivId acc, core::ConstMultiVecDerivId f); /// Apply projective constraints, such as FixedProjectiveConstraint - static void projectResponse(sofa::simulation::common::MechanicalOperations* mop, core::MultiVecDerivId vecId); + void projectResponse(sofa::simulation::common::MappingGraphMechanicalOperations* mop, core::MultiVecDerivId vecId) const; - static void solveConstraints(sofa::simulation::common::MechanicalOperations* mop, core::MultiVecDerivId acc); + static void solveConstraints(sofa::simulation::common::MappingGraphMechanicalOperations* mop, core::MultiVecDerivId acc); - void assembleSystemMatrix(sofa::simulation::common::MechanicalOperations* mop) const; + void assembleSystemMatrix(sofa::simulation::common::MappingGraphMechanicalOperations* mop) const; void solveSystem(core::MultiVecDerivId solution, core::MultiVecDerivId rhs) const; - bool isMassMatrixTriviallyInvertible(const core::ExecParams* params); + bool isMassMatrixTriviallyInvertible(const core::ExecParams* params) const; + + + simulation::MappingGraph m_mappingGraph; }; } // namespace sofa::component::odesolver::forward diff --git a/Sofa/framework/Simulation/Core/CMakeLists.txt b/Sofa/framework/Simulation/Core/CMakeLists.txt index e16319d1d4e..006cecd588c 100644 --- a/Sofa/framework/Simulation/Core/CMakeLists.txt +++ b/Sofa/framework/Simulation/Core/CMakeLists.txt @@ -67,6 +67,7 @@ set(HEADER_FILES ${SRC_ROOT}/SceneCheckRegistry.h ${SRC_ROOT}/SceneCheckMainRegistry.h ${SRC_ROOT}/MappingGraph.h + ${SRC_ROOT}/MappingGraphMechanicalOperations.h ${SRC_ROOT}/events/BuildConstraintSystemEndEvent.h ${SRC_ROOT}/events/SimulationInitDoneEvent.h @@ -76,6 +77,15 @@ set(HEADER_FILES ${SRC_ROOT}/events/SimulationStopEvent.h ${SRC_ROOT}/events/SolveConstraintSystemEndEvent.h + ${SRC_ROOT}/mappinggraph/BaseMappingGraphNode.h + ${SRC_ROOT}/mappinggraph/CallableVisitor.h + ${SRC_ROOT}/mappinggraph/ComponentGroupMappingGraphNode.h + ${SRC_ROOT}/mappinggraph/ExportDot.h + ${SRC_ROOT}/mappinggraph/MappingGraphAlgorithms.h + ${SRC_ROOT}/mappinggraph/MappingGraphNode.h + ${SRC_ROOT}/mappinggraph/MappingGraphVisitor.h + ${SRC_ROOT}/mappinggraph/VisitorApplication.h + ${SRC_ROOT}/mechanicalvisitor/MechanicalAccFromFVisitor.h ${SRC_ROOT}/mechanicalvisitor/MechanicalAccumulateJacobian.h ${SRC_ROOT}/mechanicalvisitor/MechanicalAccumulateJacobian.h @@ -170,6 +180,7 @@ set(SOURCE_FILES ${SRC_ROOT}/IntegrateBeginEvent.cpp ${SRC_ROOT}/IntegrateEndEvent.cpp ${SRC_ROOT}/MappingGraph.cpp + ${SRC_ROOT}/MappingGraphMechanicalOperations.cpp ${SRC_ROOT}/MechanicalOperations.cpp ${SRC_ROOT}/MechanicalVPrintVisitor.cpp ${SRC_ROOT}/MechanicalVisitor.cpp @@ -214,6 +225,11 @@ set(SOURCE_FILES ${SRC_ROOT}/events/SimulationStopEvent.cpp ${SRC_ROOT}/events/SolveConstraintSystemEndEvent.cpp + ${SRC_ROOT}/mappinggraph/BaseMappingGraphNode.cpp + ${SRC_ROOT}/mappinggraph/ExportDot.cpp + ${SRC_ROOT}/mappinggraph/MappingGraphAlgorithms.cpp + ${SRC_ROOT}/mappinggraph/MappingGraphVisitor.cpp + ${SRC_ROOT}/mechanicalvisitor/MechanicalAccFromFVisitor.cpp ${SRC_ROOT}/mechanicalvisitor/MechanicalAccumulateJacobian.cpp ${SRC_ROOT}/mechanicalvisitor/MechanicalAccumulateJacobian.cpp diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraph.cpp b/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraph.cpp index b9735b9e37f..e990dc40a33 100644 --- a/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraph.cpp +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraph.cpp @@ -1,159 +1,134 @@ -/****************************************************************************** -* 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 +#include -#include -#include -#include -#include -#include -#include -#include -#include +#include + +#include namespace sofa::simulation { -core::objectmodel::BaseContext* MappingGraph::getRootNode() const +MappingGraph::InputLists MappingGraph::InputLists::makeFromNode(core::objectmodel::BaseContext* node) { - return m_rootNode; + InputLists inputLists; + if (node) + { + node->getObjects(inputLists.mechanicalStates, core::objectmodel::BaseContext::SearchDirection::SearchDown); + node->getObjects(inputLists.mappings, core::objectmodel::BaseContext::SearchDirection::SearchDown); + node->getObjects(inputLists.forceFields, core::objectmodel::BaseContext::SearchDirection::SearchDown); + node->getObjects(inputLists.masses, core::objectmodel::BaseContext::SearchDirection::SearchDown); + node->getObjects(inputLists.projectedConstraints, core::objectmodel::BaseContext::SearchDirection::SearchDown); + } + return inputLists; } -const sofa::type::vector& MappingGraph::getMainMechanicalStates() const +MappingGraph::MappingGraph(const InputLists& input) { - return m_mainMechanicalStates; + build(input); } -auto MappingGraph::getTopMostMechanicalStates(core::behavior::BaseMechanicalState* mstate) const -> MappingInputs +MappingGraph::MappingGraph(core::objectmodel::BaseContext* node) { - if (m_rootNode == nullptr) - { - msg_error("MappingGraph") << "Graph is not built yet"; - } - - if (mstate == nullptr) - { - dmsg_error("MappingGraph") << "Requested mechanical state is invalid"; - return {}; - } - - if (const auto it = m_topMostInputsMechanicalStates.find(mstate); it != m_topMostInputsMechanicalStates.end()) - return it->second; - - return {}; + build(node); } -auto MappingGraph::getTopMostMechanicalStates(core::behavior::BaseForceField* forceField) const -> MappingInputs +void MappingGraph::clear() { - if (forceField == nullptr) - { - dmsg_error("MappingGraph") << "Requested force field is invalid"; - return {}; - } - - const auto& associatedMechanicalStates = forceField->getMechanicalStates(); - MappingInputs topMostMechanicalStates; - for (auto* mstate : associatedMechanicalStates) - { - const auto mstates = getTopMostMechanicalStates(mstate); - topMostMechanicalStates.insert(topMostMechanicalStates.end(), mstates.begin(), mstates.end()); - } - return topMostMechanicalStates; + m_rootNode = nullptr; + m_isBuilt = false; + m_hasAnyMapping = false; + m_totalNbMainDofs = 0; + m_positionInGlobalMatrix.clear(); + m_allNodes.clear(); + m_rootStates.clear(); + m_stateIndex.clear(); + m_groupIndex.clear(); } -auto MappingGraph::getTopMostMechanicalStates(core::behavior::BaseMass* mass) const -> MappingInputs +core::objectmodel::BaseContext* MappingGraph::getRootNode() const { - if (mass == nullptr) - { - dmsg_error("MappingGraph") << "Requested mass is invalid"; - return {}; - } - - const auto& associatedMechanicalStates = mass->getMechanicalStates(); - MappingInputs topMostMechanicalStates; - for (auto* mstate : associatedMechanicalStates) - { - const auto mstates = getTopMostMechanicalStates(mstate); - topMostMechanicalStates.insert(topMostMechanicalStates.end(), mstates.begin(), mstates.end()); - } - return topMostMechanicalStates; + return m_rootNode; } -class ComponentGroupsVisitor final : public simulation::BaseMechanicalVisitor +const sofa::type::vector& +MappingGraph::getMainMechanicalStates() const { -public: - ComponentGroupsVisitor(const sofa::core::ExecParams* params, MappingGraph::ComponentGroups& groups) - : simulation::BaseMechanicalVisitor(params) - , m_groups(groups) - {} - - Result fwdMass(simulation::Node*, sofa::core::behavior::BaseMass* mass) override + return m_rootStates; +} +MappingGraph::MappingInputs MappingGraph::getTopMostMechanicalStates( + core::behavior::BaseMechanicalState* state) const +{ + auto* sn = findStateNode(state); + if (sn) { - if (mass) + struct CollectInput : public MappingGraphVisitor { - for (auto mstate : mass->getMechanicalStates()) + void visit(core::behavior::BaseMechanicalState& state) override { - if (mstate) - { - m_groups[mstate].masses.push_back(mass); - } + inputs.push_back(&state); } + + MappingInputs inputs; + } visitor; + + for (auto& node : m_allNodes) + { + node->m_pendingCount = 0; } - return Result::RESULT_CONTINUE; - } - Result fwdForceField(simulation::Node*, sofa::core::behavior::BaseForceField* ff) override - { - if (ff) + + std::queue nodes; + nodes.push(sn); + + while (!nodes.empty()) { - for (auto mstate : ff->getMechanicalStates()) + BaseMappingGraphNode* current = nodes.front(); + nodes.pop(); + + ++(current->m_pendingCount); + + if (current->m_parents.empty()) + { + current->accept(visitor); + } + + for (auto& parent : current->m_parents) { - if (mstate) + if (parent->m_pendingCount == 0) { - m_groups[mstate].forceFields.push_back(ff); + nodes.push(parent.get()); } } } - return Result::RESULT_CONTINUE; + + return visitor.inputs; } -private: - MappingGraph::ComponentGroups& m_groups; -}; + return {}; +} -MappingGraph::ComponentGroups MappingGraph::makeComponentGroups(const sofa::core::ExecParams* params) const +MappingGraph::MappingInputs MappingGraph::getTopMostMechanicalStates( + core::behavior::StateAccessor* stateAccessor) const { - ComponentGroups groups; - if (m_rootNode) + if (stateAccessor == nullptr) + { + dmsg_error("MappingGraph") << "Requested mass is invalid"; + return {}; + } + + const auto& associatedMechanicalStates = stateAccessor->getMechanicalStates(); + MappingInputs topMostMechanicalStates; + for (auto* mstate : associatedMechanicalStates) { - ComponentGroupsVisitor(params, groups).execute(m_rootNode); + const auto mstates = getTopMostMechanicalStates(mstate); + topMostMechanicalStates.insert(topMostMechanicalStates.end(), mstates.begin(), mstates.end()); } - return groups; + return topMostMechanicalStates; } bool MappingGraph::hasAnyMapping() const { return m_hasAnyMapping; } - bool MappingGraph::hasAnyMappingInput(core::behavior::BaseMechanicalState* mstate) const { if (m_rootNode == nullptr) @@ -172,9 +147,9 @@ bool MappingGraph::hasAnyMappingInput(core::behavior::BaseMechanicalState* mstat return !m_positionInGlobalMatrix.contains(mstate); } -bool MappingGraph::hasAnyMappingInput(core::behavior::BaseForceField* forceField) const +bool MappingGraph::hasAnyMappingInput(core::behavior::StateAccessor* stateAccessor) const { - for (auto* mstate : forceField->getMechanicalStates()) + for (auto* mstate : stateAccessor->getMechanicalStates()) { if (mstate) { @@ -187,95 +162,9 @@ bool MappingGraph::hasAnyMappingInput(core::behavior::BaseForceField* forceField return false; } -bool MappingGraph::hasAnyMappingInput(core::behavior::BaseMass* mass) const +sofa::Size MappingGraph::getTotalNbMainDofs() const { - for (auto* mstate : mass->getMechanicalStates()) - { - if (mstate) - { - if (hasAnyMappingInput(mstate)) - { - return true; - } - } - } - return false; -} - -bool MappingGraph::isMechanicalStateInContext(core::behavior::BaseMechanicalState* mstate) const -{ - return std::find(m_mechanicalStates.begin(), m_mechanicalStates.end(), mstate) != m_mechanicalStates.end(); -} - -bool MappingGraph::isMappingInput(BaseMechanicalState* mappingInput, BaseMechanicalState* mappingOutput) const -{ - const auto it = m_adjacencyList.find(mappingOutput); - if (it != m_adjacencyList.end()) - { - const auto& inputs = it->second; - if (std::find(inputs.begin(), inputs.end(), mappingInput) != inputs.end()) - { - return true; - } - - for (auto* directInput : inputs) - { - if (isMappingInput(mappingInput, directInput)) - { - return true; - } - } - } - return false; -} - -sofa::type::vector MappingGraph::getMappingInputs(BaseMechanicalState* mstate) const -{ - const auto it = m_adjacencyList.find(mstate); - if (it != m_adjacencyList.end()) - { - return it->second; - } - return {}; -} - -sofa::type::vector MappingGraph::getBottomUpMappingsFrom( - BaseMechanicalState* mstate) const -{ - if (mstate) - { - sofa::type::vector allMappings; - - sofa::type::vector connectedMappings; - for (auto* mapping : m_mappings) - { - if (mapping) - { - for (const auto* child : mapping->getMechTo()) - { - if (child != nullptr && child == mstate) - { - connectedMappings.push_back(mapping); - break; - } - } - } - } - - allMappings.insert(allMappings.end(), connectedMappings.begin(), connectedMappings.end()); - for (auto* mapping : connectedMappings) - { - for (auto* parent : mapping->getMechFrom()) - { - const auto mappings = getBottomUpMappingsFrom(parent); - allMappings.insert(allMappings.end(), mappings.begin(), mappings.end()); - } - } - - return allMappings; - } - - return {}; + return m_totalNbMainDofs; } type::Vec2u MappingGraph::getPositionInGlobalMatrix(core::behavior::BaseMechanicalState* mstate) const @@ -301,134 +190,219 @@ type::Vec2u MappingGraph::getPositionInGlobalMatrix(core::behavior::BaseMechanic } type::Vec2u MappingGraph::getPositionInGlobalMatrix(core::behavior::BaseMechanicalState* a, - core::behavior::BaseMechanicalState* b) const + core::behavior::BaseMechanicalState* b) const { const auto pos_a = getPositionInGlobalMatrix(a); const auto pos_b = getPositionInGlobalMatrix(b); return {pos_a[0], pos_b[1]}; } -bool MappingGraph::isBuilt() const -{ - return m_rootNode != nullptr; -} - -void MappingGraph::build(const sofa::core::ExecParams* params, core::objectmodel::BaseContext* rootNode) +sofa::type::vector MappingGraph::getBottomUpMappingsFrom( + core::behavior::BaseMechanicalState* state) const { - SOFA_UNUSED(params); - - m_rootNode = rootNode; - - m_mainMechanicalStates.clear(); - m_mappings.clear(); - m_adjacencyList.clear(); - m_topMostInputsMechanicalStates.clear(); - m_positionInGlobalMatrix.clear(); - - m_totalNbMainDofs = 0; - m_hasAnyMapping = false; - - if (m_rootNode) + auto* sn = findStateNode(state); + if (sn) { - m_rootNode->getObjects(m_mappings, core::objectmodel::BaseContext::SearchDirection::SearchDown); - } + struct CollectMapping final : public MappingGraphVisitor + { + void visit(core::BaseMapping& mapping) override + { + mappings.push_back(&mapping); + } - buildAjacencyList(); + sofa::type::vector mappings; + } visitor; - m_mechanicalStates.clear(); - if (m_rootNode) - { - m_rootNode->getObjects(m_mechanicalStates, core::objectmodel::BaseContext::SearchDirection::SearchDown); - } + for (auto& node : m_allNodes) + { + node->m_pendingCount = 0; + } - buildMStateRelationships(); -} + std::queue nodes; + nodes.push(sn); -void MappingGraph::buildAjacencyList() -{ - for (auto* mapping : m_mappings) - { - if (mapping) + while (!nodes.empty()) { - m_hasAnyMapping = true; + BaseMappingGraphNode* current = nodes.front(); + nodes.pop(); + + ++(current->m_pendingCount); - // The mechanical states which are parents of another mechanical state through a mapping are stored in a map for later use - for (auto* child : mapping->getMechTo()) + current->accept(visitor); + + for (auto& parent : current->m_parents) { - if (child != nullptr) + if (parent->m_pendingCount == 0) { - for (auto* parent : mapping->getMechFrom()) - { - if (parent != nullptr) - { - m_adjacencyList[child].push_back(parent); - } - } + nodes.push(parent.get()); } } } + + return visitor.mappings; } + + return {}; +} + +bool MappingGraph::isBuilt() const { return m_isBuilt; } + +template +typename MappingGraphNode::SPtr makeMappingGraphNode(typename TComponent::SPtr s) +{ + return typename MappingGraphNode::SPtr( new MappingGraphNode(s) ); } -void MappingGraph::buildMStateRelationships() +void MappingGraph::build(const InputLists& input) { - for (auto* mstate : m_mechanicalStates) + clear(); + + m_hasAnyMapping = input.mappings.size() > 0; + + // 1. Create one wrapper node per object; index state nodes by raw ptr. + std::vector*> mechanicalStateNodes; + for (auto& s : input.mechanicalStates) { - if (mstate == nullptr) - { - return; - } + auto node = makeMappingGraphNode(s); + mechanicalStateNodes.push_back(node.get()); + m_stateIndex[s] = node.get(); + m_allNodes.push_back(std::move(node)); + } - auto it = m_adjacencyList.find(mstate); + // Collect (leafNode*, connected states) for every leaf component type. + std::vector>> + leafConnections; - if (it == m_adjacencyList.end()) + const auto processComponents = [&leafConnections, this](const sofa::type::vector& components) + { + for (const auto& component : components) { - //mstate has not been found in the map: it's not an output of any mapping - const auto matrixSize = mstate->getMatrixSize(); + auto node = makeMappingGraphNode(component); + + const auto& states = component->getMechanicalStates(); + std::vector statesVector{states.begin(), + states.end()}; + leafConnections.emplace_back(node.get(), statesVector); - m_mainMechanicalStates.push_back(mstate); - m_positionInGlobalMatrix[mstate] = sofa::type::Vec2u(m_totalNbMainDofs, m_totalNbMainDofs); + m_allNodes.push_back(std::move(node)); + } + }; - m_totalNbMainDofs += matrixSize; + processComponents(input.forceFields); + processComponents(input.masses); + processComponents(input.projectedConstraints); - m_topMostInputsMechanicalStates[mstate].push_back(mstate); + // 2. Wire leaf component edges: connectedState → leafComponent + for (auto& [leafNode, states] : leafConnections) + { + auto sn = findGroupNode(states); + if (sn) + { + addEdge(sn.get(), leafNode); } - else + } + + std::vector mappingNodePtrs; + for (auto& m : input.mappings) + { + auto node = makeMappingGraphNode(m); + mappingNodePtrs.push_back(node.get()); + m_allNodes.push_back(std::move(node)); + } + + // 3. Wire mapping edges: inputState → mapping → outputState + for (size_t i = 0; i < input.mappings.size(); ++i) + { + BaseMappingGraphNode* mappingNode = mappingNodePtrs[i]; + for (auto& s : input.mappings[i]->getMechFrom()) { - //mstate is the output of at least one mapping and has at least one mechanical state as an input - MappingGraph::MappingInputs inputs = it->second; - if (inputs.empty()) + if (auto groupNode = findInGroupNodes(s)) { - msg_error("MappingGraph") << "Mechanical state " << mstate->getPathName() << " is involved in a mapping, but does not have any valid input mechanical states"; + addEdge(groupNode.get(), mappingNode); } - else + else if (auto* sn = findStateNode(s)) { - // continue to check that the input of the mstate is not itself an output of another mstate, and so on - while(!inputs.empty()) - { - auto* visitedMState = inputs.back(); - inputs.pop_back(); - it = m_adjacencyList.find(visitedMState); - if (it != m_adjacencyList.end()) - { - for (auto* p : it->second) - inputs.push_back(p); - } - else - { - if (isMechanicalStateInContext(visitedMState)) - { - m_topMostInputsMechanicalStates[mstate].push_back(visitedMState); - } - else - { - //the top most input is not in the current context - } - } - } + addEdge(sn, mappingNode); } + } + + for (auto& s : input.mappings[i]->getMechTo()) + { + if (auto* sn = findStateNode(s)) + { + addEdge(mappingNode, sn); + } + } + } + + // 4. Roots: MechanicalState nodes with no parents. + m_totalNbMainDofs = 0; + for (const auto& node : mechanicalStateNodes) + { + if (node->m_parents.empty()) + { + m_rootStates.push_back(node->m_component.get()); + m_positionInGlobalMatrix[node->m_component.get()] = type::Vec2u(m_totalNbMainDofs, m_totalNbMainDofs); + m_totalNbMainDofs += node->m_component->getMatrixSize(); + } + } + + m_isBuilt = true; +} + +void MappingGraph::build(core::objectmodel::BaseContext* rootNode) +{ + if (rootNode) + { + build(InputLists::makeFromNode(rootNode)); + } + m_rootNode = rootNode; +} + +ComponentGroupMappingGraphNode::SPtr MappingGraph::findGroupNode( + const std::vector& states) +{ + auto it = std::find_if(m_groupIndex.begin(), m_groupIndex.end(), + [&states](auto& group){ return group.first == states; }); + if (it != m_groupIndex.end()) + return it->second; + auto group = ComponentGroupMappingGraphNode::SPtr{new ComponentGroupMappingGraphNode}; + for (const auto& state : states) + { + if (auto* sn = findStateNode(state.get())) + { + addEdge(sn, group.get()); } } + m_groupIndex.emplace_back(states, group); + m_allNodes.push_back(group); + return group; } + +ComponentGroupMappingGraphNode::SPtr MappingGraph::findInGroupNodes( + const core::behavior::BaseMechanicalState::SPtr state) +{ + auto it = std::find_if(m_groupIndex.begin(), m_groupIndex.end(), + [&state](auto& group) + { + return std::find(group.first.begin(), group.first.end(), state) != group.first.end(); + }); + if (it != m_groupIndex.end()) + return it->second; + return nullptr; } + +BaseMappingGraphNode* MappingGraph::findStateNode(core::behavior::BaseMechanicalState* raw) const +{ + auto it = m_stateIndex.find(raw); + return (it != m_stateIndex.end()) ? it->second : nullptr; +} + +void MappingGraph::addEdge(BaseMappingGraphNode* from, BaseMappingGraphNode* to) +{ + from->m_children.push_back(to->shared_from_this()); + to->m_parents.push_back(from->shared_from_this()); +} + +} // namespace sofa::simulation diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraph.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraph.h index 75d0aac75c0..b0c6e67f383 100644 --- a/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraph.h +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraph.h @@ -19,152 +19,309 @@ * * * Contact information: contact@sofa-framework.org * ******************************************************************************/ -#pragma once -#include -#include +/** + * @file MappingGraph.h + * @brief Implements a mapping graph structure for simulating mechanical systems in SOFA. + * + * This header defines the core structures necessary to represent how various + * physical components and behavioral states are linked together during simulation. + * It allows for top-down (prerequisite check) and bottom-up (dependency accumulation) + * traversals of a component hierarchy, ensuring that inputs are calculated correctly + * before they are needed by dependent nodes or mappings. + */ + +#pragma once #include +#include +#include +#include namespace sofa::simulation { - -using core::behavior::BaseMechanicalState; +class TaskScheduler; /** - * Connexions between objects through mappings - * - * Graph must be built with the build() function. + * @brief Represents the overall mechanical simulation graph structure (Mapping Graph). + * + * This class builds and manages a dependency graph connecting all major components + * (MechanicalStates, Mappings, ForceFields, Masses) within an SOFA scene. + * It allows for systematic traversal (Top-down/Bottom-up) to determine the correct + * order of calculation required during simulation initialization or execution. */ class SOFA_SIMULATION_CORE_API MappingGraph { - public: - using MappingInputs = type::vector; + using MappingInputs = type::vector; - /// Return the node used to start the exploration of the scene graph in order to build the mapping graph - [[nodiscard]] core::objectmodel::BaseContext* getRootNode() const; - /// Return the list of all mechanical states which are not mapped - [[nodiscard]] const sofa::type::vector& getMainMechanicalStates() const; - - /// Return the list of mechanical states which are: - /// 1) non-mapped - /// 2) input of a mapping involving the provided mechanical state as an output. - /// The search is recursive (more than one level of mapping) and is done during mapping graph construction. - MappingInputs getTopMostMechanicalStates(BaseMechanicalState*) const; - - /// Return the list of mechanical states which are: - /// 1) non-mapped - /// 2) input of a mapping involving the mechanical states associated to the provided force field as an output. - /// The search is recursive (more than one level of mapping) and is done during mapping graph construction. - MappingInputs getTopMostMechanicalStates(core::behavior::BaseForceField*) const; - - /// Return the list of mechanical states which are: - /// 1) non-mapped - /// 2) input of a mapping involving the mechanical states associated to the provided mass as an output. - /// The search is recursive (more than one level of mapping) and is done during mapping graph construction. - MappingInputs getTopMostMechanicalStates(core::behavior::BaseMass*) const; - - struct SameGroupComponents + /** + * @brief Container struct holding lists of all potential input components + * collected from a scene context. + */ + struct SOFA_SIMULATION_CORE_API InputLists { + sofa::type::vector mechanicalStates; + sofa::type::vector mappings; sofa::type::vector forceFields; sofa::type::vector masses; + sofa::type::vector projectedConstraints; + + /** + * @brief Creates InputLists from a context pointer. + * @param node The SOFA object model base context associated with the component list. + * @return A populated InputLists structure. + */ + static InputLists makeFromNode(core::objectmodel::BaseContext* node); + + /** + * @brief Creates InputLists from a shared pointer context. + * @param node The SOFA object model base context smart pointer. + * @return A populated InputLists structure. + */ + static InputLists makeFromNode(core::objectmodel::BaseContext::SPtr node) { return makeFromNode(node.get()); } }; - using ComponentGroups = std::map; - - /// Create groups of components associated to the same mechanical state - ComponentGroups makeComponentGroups(const sofa::core::ExecParams* params) const; - - [[nodiscard]] - bool hasAnyMapping() const; - - /// Return true if the provided mechanical state is an output of a mapping - bool hasAnyMappingInput(BaseMechanicalState*) const; - /// Return true if the mechanical states associated to the provided force field is an output of a mapping - bool hasAnyMappingInput(core::behavior::BaseForceField*) const; - /// Return true if the mechanical states associated to the provided mass is an output of a mapping - bool hasAnyMappingInput(core::behavior::BaseMass*) const; - - /// Return true if the provided mechanical state has been visited when building the mapping graph - bool isMechanicalStateInContext(BaseMechanicalState*) const; - - /// Return true if @input is a mapping input of @output. Multiple intermediate mappings are supported - /// In term of graph connectivity, return true if the two nodes of the directed graph are connected - bool isMappingInput(BaseMechanicalState* mappingInput, BaseMechanicalState* mappingOutput) const; - - /// Returns all mechanical states which are input of a mapping where the mechanical state in - /// parameter is an output - MappingInputs getMappingInputs(BaseMechanicalState*) const; - - sofa::type::vector getBottomUpMappingsFrom(BaseMechanicalState*) const; + /** + * @brief Default constructor initializes an empty graph. + */ + MappingGraph() = default; - /// Return the sum of the degrees of freedom of all main mechanical states - [[nodiscard]] - sofa::Size getTotalNbMainDofs() const { return m_totalNbMainDofs; } + /** + * @brief Constructs the graph using pre-collected input lists. + * @param input The list of all components found in the scene. + */ + explicit MappingGraph(const InputLists& input); - /// Return where in the global matrix the provided mechanical state writes its contribution - type::Vec2u getPositionInGlobalMatrix(BaseMechanicalState*) const; - /// Return where in the global matrix the provided mechanical states writes its contribution - type::Vec2u getPositionInGlobalMatrix(BaseMechanicalState* a, BaseMechanicalState* b) const; + /** + * @brief Constructs the graph by traversing a starting node in the SOFA object model context. + * @param node The root context node from which to build the graph. + */ + explicit MappingGraph(core::objectmodel::BaseContext* node); - MappingGraph() = default; + void clear(); - bool isBuilt() const; + /** + * @brief Returns the root node used during the initial construction of the graph. + * @return A pointer to the root object model context. + */ + [[nodiscard]] core::objectmodel::BaseContext* getRootNode() const; - /// Build the graph: mandatory to get valid data from the functions that use the graph - void build(const sofa::core::ExecParams* params, core::objectmodel::BaseContext* rootNode); + friend struct MappingGraphAlgorithms; + MappingGraphAlgorithms algorithms { this }; + + /** + * @brief Gets the list of all main mechanical states that are not used as outputs + * in any mapping (i.e., they are root inputs). + * @return Const reference to the vector of non-mapped mechanical state pointers. + */ + [[nodiscard]] const sofa::type::vector& getMainMechanicalStates() const; + + /** + * @brief Recursively finds top-most mechanical states that are unmapped but serve as + * inputs to a mapping involving the provided state. + * + * This search is recursive, handling multiple levels of dependencies. + * + * @param state The starting mechanical state for dependency checking. + * @return A list of top-most unmapped mechanical states required by `state`. + */ + MappingInputs getTopMostMechanicalStates(core::behavior::BaseMechanicalState* state) const; + + /** + * @brief Recursively finds top-most mechanical states that are unmapped but serve as + * inputs to a mapping involving the mechanical states associated with a given accessor. + * + * This search is recursive, handling multiple levels of dependencies. + * + * @param stateAccessor The starting state accessor for dependency checking. + * @return A list of top-most unmapped mechanical states required by `stateAccessor`. + */ + MappingInputs getTopMostMechanicalStates(core::behavior::StateAccessor* stateAccessor) const; + + /** + * @brief Checks if any mapping exists anywhere in the graph structure. + * @return True if at least one mapping is present, false otherwise. + */ + [[nodiscard]] bool hasAnyMapping() const; + + /** + * @brief Determines if a specific mechanical state is an output of any mapping node + * connected to the graph. + * @param mstate The mechanical state to check. + * @return True if `mstate` is an output, false otherwise. + */ + bool hasAnyMappingInput(core::behavior::BaseMechanicalState* mstate) const; + + /** + * @brief Determines if the mechanical states associated with a component are outputs + * of any mapping node connected to the graph. + * @param stateAccessor The state accessor for the component to check. + * @return True if the associated states are mapped output, false otherwise. + */ + bool hasAnyMappingInput(core::behavior::StateAccessor* stateAccessor) const; + + /** + * @brief Calculates the total number of degrees of freedom (DoF) contributed + * by all main mechanical states in the graph. + * @return The sum of DoFs across all primary states. + */ + [[nodiscard]] sofa::Size getTotalNbMainDofs() const; + + /** + * @brief Finds the global matrix indices (row/column) where a specific state + * contributes its degrees of freedom. + * @param mstate The mechanical state. + * @return A pair representing (global row index, global column index). + */ + type::Vec2u getPositionInGlobalMatrix(core::behavior::BaseMechanicalState* mstate) const; + + /** + * @brief Finds the global matrix indices where two specified states contribute + * their degrees of freedom. (Used for cross-axis checks). + * @param a The first mechanical state. + * @param b The second mechanical state. + * @return A pair representing (global row index, global column index) for the combined contribution. + */ + type::Vec2u getPositionInGlobalMatrix(core::behavior::BaseMechanicalState* a, + core::behavior::BaseMechanicalState* b) const; + + /** + * @brief Retrieves all mapping components that depend on (receive input from) + * the provided mechanical state. This is used for bottom-up dependency checks. + * @param mstate The mechanical state acting as an input source. + * @return A vector of shared pointers to dependent BaseMapping nodes. + */ + sofa::type::vector getBottomUpMappingsFrom( + core::behavior::BaseMechanicalState*) const; + + /** + * @brief Checks if the graph has been successfully built and analyzed. + * @return True if building is complete, false otherwise. + */ + [[nodiscard]] bool isBuilt() const; + + // ------------------------------------------------------------------ + // Graph construction methods: + // ------------------------------------------------------------------ + + /** + * @brief Builds the mapping graph using a provided set of input components. + * @param input The collected list of all potential SOFA components. + */ + void build(const InputLists& input); + + /** + * @brief Builds the mapping graph by traversing and collecting components + * starting from a specific root node in the object model hierarchy. + * @param rootNode The starting context node. + */ + void build(core::objectmodel::BaseContext* rootNode); + + const sofa::type::vector& getAllNodes() const { return m_allNodes; } private: - - /// node used to start the exploration of the scene graph in order to build the mapping graph + ///< Root node used to start graph exploration during construction. core::objectmodel::BaseContext* m_rootNode { nullptr }; - /// List of all mechanical states in the root context. - std::vector m_mechanicalStates; - - /// List of all mappings in the root context. - sofa::type::vector m_mappings; - - /// Key: any mechanical state - /// Value: The list of mapping inputs - std::map m_adjacencyList; - - /// List of mechanical states that are non-mapped. They can be involved as a mapping input, but not as an output. - sofa::type::vector m_mainMechanicalStates; - - /// Association between a mechanical state (the key) and a list of mapping input which are non-mapped. In this list, - /// the mechanical states are involved as an input, but not as an output. The mechanical state in the key is an - /// output of mappings (even over multiple levels). - std::map< BaseMechanicalState*, MappingInputs> m_topMostInputsMechanicalStates; - - /// for each main mechanical states, gives the position of its contribution in the global matrix - std::map< BaseMechanicalState*, type::Vec2u > m_positionInGlobalMatrix; - - sofa::Size m_totalNbMainDofs {}; - bool m_hasAnyMapping = false; - - void buildAjacencyList(); - void buildMStateRelationships(); - + bool m_isBuilt = false; ///< Flag indicating if the graph structure is finalized. + bool m_hasAnyMapping = false; ///< Flag indicating if any mapping exists in the graph. + + sofa::Size m_totalNbMainDofs {}; ///< Total number of primary degrees of freedom managed by the system. + + /** + * @brief Map storing the global indices (row, column) for each main mechanical state's contribution matrix block. + */ + std::map m_positionInGlobalMatrix; + + // Graph ownership structures: + sofa::type::vector m_allNodes; ///< All nodes in the graph. + sofa::type::vector m_rootStates {}; ///< List of initial, unmapped mechanical states (graph roots). + std::unordered_map m_stateIndex; ///< Quick lookup for a state's node. + std::vector, + ComponentGroupMappingGraphNode::SPtr>> m_groupIndex; ///< Indexing mechanism for group nodes. + + // ------------------------------------------------------------------ + + /** + * @brief Locates or creates a component group node encompassing the given set of states. + * @param states The mechanical states belonging to the group. + * @return A shared pointer to the found/created ComponentGroupMappingGraphNode. + */ + ComponentGroupMappingGraphNode::SPtr findGroupNode(const std::vector& states); + + /** + * @brief Locates or creates a component group node for a single state's context. + * @param state The mechanical state defining the scope of the group. + * @return A shared pointer to the found/created ComponentGroupMappingGraphNode. + */ + ComponentGroupMappingGraphNode::SPtr findInGroupNodes(const core::behavior::BaseMechanicalState::SPtr state); + + /** + * @brief Finds the graph node corresponding to a raw mechanical state pointer. + * @param raw The mechanical state raw pointer. + * @return Pointer to the associated BaseMappingGraphNode, or nullptr if not found. + */ + BaseMappingGraphNode* findStateNode(core::behavior::BaseMechanicalState* raw) const; + + /** + * @brief Adds a directed edge between two nodes in the graph structure (from -> to). + * + * Both 'from' and 'to' pointers are added to the respective parent/child lists, + * ensuring that the graph manages ownership of all nodes via `SPtr`. + * @param from The starting node. + * @param to The ending node. + */ + static void addEdge(BaseMappingGraphNode* from, BaseMappingGraphNode* to); }; + +/** + * @brief Manages Jacobian matrix contributions for a single mechanical state. + * + * This class holds and retrieves the Jacobian matrices calculated during + * graph construction. It maps input states (which require Jacobians) to their + * corresponding Jacobian calculation object. + * + * @tparam JacobianMatrixType The concrete type used for the Jacobian matrix structure. + */ template class MappingJacobians { - const BaseMechanicalState& m_mappedState; + const core::behavior::BaseMechanicalState& m_mappedState; ///< The mechanical state whose Jacobians are being managed. + /** + * @brief Map from an input mechanical state to its calculated Jacobian matrix. + */ std::map< core::behavior::BaseMechanicalState*, std::shared_ptr > m_map; public: - + /** + * @brief Deleted constructor enforces usage via the parameterized constructor. + */ MappingJacobians() = delete; - MappingJacobians(const BaseMechanicalState& mappedState) : m_mappedState(mappedState) {} + /** + * @brief Constructs the Jacobian manager for a specific mechanical state. + * @param mappedState Reference to the mechanical state being managed. + */ + MappingJacobians(const core::behavior::BaseMechanicalState& mappedState) : m_mappedState(mappedState) {} + + /** + * @brief Associates a calculated Jacobian matrix with a top-most parent state. + * @param jacobian The shared pointer to the Jacobian matrix. + * @param topMostParent The mechanical state that uses this Jacobian as input. + */ void addJacobianToTopMostParent(std::shared_ptr jacobian, core::behavior::BaseMechanicalState* topMostParent) { m_map[topMostParent] = jacobian; } + /** + * @brief Retrieves the Jacobian matrix associated with a given mechanical state. + * @param mstate The state whose Jacobian is requested. + * @return A shared pointer to the Jacobian, or nullptr if not found. + */ std::shared_ptr getJacobianFrom(core::behavior::BaseMechanicalState* mstate) const { const auto it = m_map.find(mstate); @@ -174,4 +331,4 @@ class MappingJacobians } }; -} //namespace sofa::component::linearsolver +} diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.cpp b/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.cpp new file mode 100644 index 00000000000..51ca30266b2 --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.cpp @@ -0,0 +1,139 @@ +/****************************************************************************** +* 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 MappingGraphMechanicalOperations::projectResponse(const MappingGraph& mappingGraph, + core::MultiVecDerivId dx, double** W) +{ + setDx(dx); + mappingGraph.algorithms.traverse_([&](core::behavior::BaseProjectiveConstraintSet& constraint) + { + constraint.projectResponse(&mparams, dx); + if (W != nullptr) + { + constraint.projectResponse(&mparams, W); + } + }); +} + +void MappingGraphMechanicalOperations::computeForce(const MappingGraph& mappingGraph, + core::MultiVecDerivId result, + bool clearForceBefore, + bool accumulateForcesFromMappedStates, + TaskScheduler* taskScheduler) +{ + //assumes the mapping graph is valid and properly initialized + + setF(result); + if (clearForceBefore) + { + /** + * Reset forces on all mechanical states in the mapping graph. This operation can be performed + * in any order on all states in the graph. + */ + mappingGraph.algorithms.traverse_([&](core::behavior::BaseMechanicalState& state) + { + const core::VecDerivId& stateForce = result.getId(&state); + state.resetForce(&mparams, stateForce); + }); + } + + /** + * Compute f += externalForce on all mechanical states in the mapping graph. This operation can + * be performed in any order on all states in the graph. + */ + mappingGraph.algorithms.traverse_([&](core::behavior::BaseMechanicalState& state) + { + const core::VecDerivId& stateForce = result.getId(&state); + state.accumulateForce(&mparams, stateForce); + }); + + /** + * Compute f += f(x) on all force fields in the mapping graph. This operation can be performed + * in any order on all states in the graph, and can be parallelized among component groups. + */ + mappingGraph.algorithms.traverseComponentGroups_([&](core::behavior::BaseForceField& forceField) + { + forceField.addForce(&mparams, result); + }, sofa::simulation::VisitorApplication::ALL_NODES, taskScheduler); + + if (accumulateForcesFromMappedStates) + { + /** + * 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. + */ + mappingGraph.algorithms.traverseBottomUp_([&](core::BaseMapping& mapping) + { + mapping.applyJT(&mparams, result, result); + }); + } +} +void MappingGraphMechanicalOperations::addMBKv(const MappingGraph& mappingGraph, + core::MultiVecDerivId df, core::MatricesFactors::M m, + core::MatricesFactors::B b, + core::MatricesFactors::K k, bool clear, + bool accumulate) +{ + const core::ConstMultiVecDerivId dx = mparams.dx(); + mparams.setDx(mparams.v()); + setDf(df); + if (clear) + { + /** + * Reset forces on all mapped mechanical states in the mapping graph. This operation can be performed + * in any order on all mapped states in the graph. + */ + mappingGraph.algorithms.traverse_([&](core::behavior::BaseMechanicalState& state) + { + const core::VecDerivId& stateForce = df.getId(&state); + state.resetForce(&mparams, stateForce); + }, VisitorApplication::ONLY_MAPPED_NODES); + } + mparams.setBFactor(b.get()); + mparams.setKFactor(k.get()); + mparams.setMFactor(m.get()); + /* useV = true */ + + mappingGraph.algorithms.traverseComponentGroups_([&](core::behavior::BaseForceField& forceField) + { + forceField.addMBKdx(&mparams, df); + }); + + if (accumulate) + { + mappingGraph.algorithms.traverseBottomUp_([&](core::BaseMapping& mapping) + { + mapping.applyJT(&mparams, df, df); + if( mparams.kFactor() != 0 ) + { + mapping.applyDJT(&mparams, df, df); + } + }); + } + + mparams.setDx(dx); +} +} // namespace sofa::simulation::common diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.h new file mode 100644 index 00000000000..80a12a60945 --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/MappingGraphMechanicalOperations.h @@ -0,0 +1,64 @@ +/****************************************************************************** +* 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::simulation::common +{ + +/** + * @brief Provides mechanical operations functionality using a MappingGraph. + * + * This class extends {@link MechanicalOperations}, providing methods that adapt core + * mechanical computations to use the topological structure provided by a {@link MappingGraph}. + * + * The standard {@link MechanicalOperations} typically relies on scene graph visitors, which may + * fail or yield incorrect results for complex mappings where the required execution order + * follows the specific dependencies defined in a mapping graph. Therefore, mechanical operations, + * especially those involving kinematic mappings, must be performed by traversing and respecting + * the structure of the MappingGraph to ensure computational consistency and correctness. + * + * Note: The use of {@link MappingGraphMechanicalOperations} is strongly recommended. In future + * versions, reliance on the scene graph will be deprecated, and this class will become the + * primary and exclusive mechanism for performing such operations. + */ +class SOFA_SIMULATION_CORE_API MappingGraphMechanicalOperations : public MechanicalOperations +{ +public: + using MechanicalOperations::MechanicalOperations; + + /// Apply projective constraints to the given vector + void projectResponse(const MappingGraph& mappingGraph, core::MultiVecDerivId dx, double** W = nullptr); + 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); + using MechanicalOperations::computeForce; + + /// accumulate $ df += (m M + b B + k K) velocity $ + void addMBKv(const MappingGraph& mappingGraph, core::MultiVecDerivId df, core::MatricesFactors::M m, core::MatricesFactors::B b, core::MatricesFactors::K k, bool clear = true, bool accumulate = true); + using MechanicalOperations::addMBKv; +}; + +} diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/BaseMappingGraphNode.cpp b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/BaseMappingGraphNode.cpp new file mode 100644 index 00000000000..49f5fce593e --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/BaseMappingGraphNode.cpp @@ -0,0 +1,43 @@ +/****************************************************************************** +* 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 +{ + +bool BaseMappingGraphNode::isMapped() const +{ + if (m_isMapped.has_value()) + { + return m_isMapped.value(); + } + + const auto isMapped = std::any_of(m_parents.begin(), m_parents.end(), [](const SPtr& node) + { + return node->getType() == NodeType::Mapping || node->isMapped(); + }); + + m_isMapped = isMapped; + return isMapped; +} + +} diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/BaseMappingGraphNode.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/BaseMappingGraphNode.h new file mode 100644 index 00000000000..a76a8b6897f --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/BaseMappingGraphNode.h @@ -0,0 +1,87 @@ +/****************************************************************************** +* 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 + +namespace sofa::simulation +{ + + +/** + * @brief Abstract base class for nodes in the mapping graph. + * + * Provides common functionality for tracking prerequisites (parents) and dependencies + * (children), forming a Directed Acyclic Graph (DAG). It implements the Visitor pattern. + */ +class SOFA_SIMULATION_CORE_API BaseMappingGraphNode : public std::enable_shared_from_this +{ +public: + using SPtr = std::shared_ptr; + friend class MappingGraph; + friend struct MappingGraphAlgorithms; + + virtual ~BaseMappingGraphNode() = default; + + /** + * @brief Accepts a visitor, allowing the graph to be processed by an external algorithm. + * @param visitor The concrete visitor implementation. + */ + virtual void accept(MappingGraphVisitor& visitor) const = 0; + + /** + * @brief Gets the name of the component represented by this node. + * @return The name string. + */ + virtual std::string getName() const { return {}; } + + enum class NodeType { + MechanicalState, + Mapping, + Component, + Group + }; + + virtual NodeType getType() const = 0; + + /** + * @return True if the node has an ancestor which is a mapping node + */ + bool isMapped() const; + + const sofa::type::vector& getParents() const { return m_parents; } + const sofa::type::vector& getChildren() const { return m_children; } + +private: + sofa::type::vector m_parents; ///< prerequisite nodes (nodes pointing to this one) + sofa::type::vector m_children; ///< dependent nodes (nodes pointed from this one) + + // Mutable counter used during traversal (reset before each traversal). + mutable int m_pendingCount = 0; + + mutable std::optional m_isMapped; +}; + + +} diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/CallableVisitor.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/CallableVisitor.h new file mode 100644 index 00000000000..bb9656a648f --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/CallableVisitor.h @@ -0,0 +1,81 @@ +/****************************************************************************** +* 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 + +namespace sofa::simulation +{ + +template +struct BaseCallableVisitor : public MappingGraphVisitor +{ + explicit BaseCallableVisitor(const Callable& callable) + : m_callable(callable) + {} + + void visit(Component& component) override + { + this->m_callable(component); + } + +protected: + const Callable& m_callable; +}; + +template +struct GetComponentFromCallable; + +template requires std::is_invocable_v +struct GetComponentFromCallable +{ + using type = core::behavior::BaseForceField; +}; + +template requires std::is_invocable_v +struct GetComponentFromCallable +{ + using type = core::behavior::BaseMass; +}; + +template requires std::is_invocable_v +struct GetComponentFromCallable +{ + using type = core::behavior::BaseMechanicalState; +}; + +template requires std::is_invocable_v +struct GetComponentFromCallable +{ + using type = core::BaseMapping; +}; + +template requires std::is_invocable_v +struct GetComponentFromCallable +{ + using type = core::behavior::BaseProjectiveConstraintSet; +}; + +template +using CallableVisitor = BaseCallableVisitor::type>; + +} diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/ComponentGroupMappingGraphNode.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/ComponentGroupMappingGraphNode.h new file mode 100644 index 00000000000..eb16c413d93 --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/ComponentGroupMappingGraphNode.h @@ -0,0 +1,54 @@ +/****************************************************************************** +* 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 + +namespace sofa::simulation +{ + +/** + * @brief A node wrapper used for representing groups of components or abstract groupings. + */ +class SOFA_SIMULATION_CORE_API ComponentGroupMappingGraphNode : public BaseMappingGraphNode +{ +public: + using SPtr = std::shared_ptr; + + void accept(MappingGraphVisitor& visitor) const override { SOFA_UNUSED(visitor); } + + /** + * @brief Returns the fixed name "group" for this type of node. + * @return The string "group". + */ + std::string getName() const override + { + return "group"; + } + + NodeType getType() const override + { + return NodeType::Group; + } +}; + +} diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/ExportDot.cpp b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/ExportDot.cpp new file mode 100644 index 00000000000..addd0d95ef6 --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/ExportDot.cpp @@ -0,0 +1,110 @@ +/****************************************************************************** +* 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 +#include + +#include + +namespace sofa::simulation +{ + +std::string getType(BaseMappingGraphNode* node) +{ + const auto type = node->getType(); + if (type == BaseMappingGraphNode::NodeType::Mapping) + return "Mapping"; + else if (type == BaseMappingGraphNode::NodeType::Component) + return "Component"; + else if (type == BaseMappingGraphNode::NodeType::MechanicalState) + return "State"; + else if (type == BaseMappingGraphNode::NodeType::Group) + return "Group"; + else + return "Unknown"; +} + +std::string getColor(BaseMappingGraphNode* node) +{ + const auto type = node->getType(); + if (type == BaseMappingGraphNode::NodeType::Mapping) + return "red1"; + else if (type == BaseMappingGraphNode::NodeType::Component) + return "royalblue1"; + else if (type == BaseMappingGraphNode::NodeType::MechanicalState) + return "seagreen1"; + else if (type == BaseMappingGraphNode::NodeType::Group) + return "slateblue1"; + else + return "webgray"; +} + +std::string getShape(BaseMappingGraphNode* node) +{ + const auto type = node->getType(); + if (type == BaseMappingGraphNode::NodeType::Mapping) + return "diamond"; + else if (type == BaseMappingGraphNode::NodeType::Component) + return "ellipse"; + else if (type == BaseMappingGraphNode::NodeType::MechanicalState) + return "box"; + else if (type == BaseMappingGraphNode::NodeType::Group) + return "component"; + else + return "note"; +} + +std::string exportToDotFormat(const MappingGraph& graph) +{ + // Use a string stream to build the DOT content + std::stringstream ss; + ss << "digraph MappingGraph {\n"; + ss << " rankdir=TB;\n"; // Top to Bottom layout is common for dependency graphs + + // 1. Add all nodes (vertices) + for (const auto& node : graph.getAllNodes()) + { + const std::string label = "[" + getType(node.get()) + "]" + node->getName(); + + // Assuming a unique name or ID can be generated for the node in DOT format + ss << " \"Node_" << std::to_string(reinterpret_cast(node.get())) + << "\" [label=\""<< label << "\", color=\"" << getColor(node.get()) << "\", shape=\"" + << getShape(node.get()) << "\"];\n"; + } + + // 2. Add all edges (dependencies) + for (const auto& node : graph.getAllNodes()) + { + const std::string sourceName = "Node_" + std::to_string(reinterpret_cast(node.get())); + for (const auto& child : node->getChildren()) + { + const std::string targetName = "Node_" + std::to_string(reinterpret_cast(child.get())); + + // Assuming edges represent dependencies (Source -> Target) + ss << " " << sourceName << " -> " << targetName << ";\n"; + } + } + + ss << "}\n"; + return ss.str(); +} + +} // namespace sofa::simulation diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/ExportDot.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/ExportDot.h new file mode 100644 index 00000000000..da25f198d16 --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/ExportDot.h @@ -0,0 +1,33 @@ +/****************************************************************************** +* 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::simulation +{ + +class MappingGraph; + +std::string SOFA_SIMULATION_CORE_API exportToDotFormat(const MappingGraph& graph); + +} // namespace sofa::simulation diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphAlgorithms.cpp b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphAlgorithms.cpp new file mode 100644 index 00000000000..d436aa2abce --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphAlgorithms.cpp @@ -0,0 +1,235 @@ +/****************************************************************************** +* 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 +#include +#include + +namespace sofa::simulation +{ + +namespace +{ +bool shouldVisit(const BaseMappingGraphNode* node, VisitorApplication scope) +{ + if (node) + { + switch (scope) + { + case VisitorApplication::ONLY_MAPPED_NODES: + return node->isMapped(); + case VisitorApplication::ONLY_MAIN_NODES: + return !node->isMapped(); + case VisitorApplication::ALL_NODES: + default: + return true; // Visit all nodes. + } + } + return false; +} +} + +std::queue MappingGraphAlgorithms::prepareRootForTraversal() const +{ + std::queue ready; + for (auto& node : m_mappingGraph->m_allNodes) + { + node->m_pendingCount = static_cast(node->m_parents.size()); + if (node->m_pendingCount == 0) //node without any parent -> a root + { + ready.push(node.get()); + } + } + + return ready; +} + + +/** + * @brief Performs a breadth-first search (BFS) traversal, processing nodes in dependency order. + * + * This static helper method is used for both top-down and bottom-up traversals. + * + * @param ready The queue of nodes that are currently ready to be visited/processed. + */ +template +void MappingGraphAlgorithms::processQueue(std::queue& ready, const Callable& f) +{ + while (!ready.empty()) + { + BaseMappingGraphNode* current = ready.front(); + ready.pop(); + + f(current); + + for (auto& child : current->m_children) + { + --(child->m_pendingCount); + if (child->m_pendingCount == 0) + { + ready.push(child.get()); + } + } + } +} + + +void MappingGraphAlgorithms::traverse(MappingGraphVisitor& visitor, VisitorApplication scope) const +{ + for (auto& node : m_mappingGraph->m_allNodes) + { + if (shouldVisit(node.get(), scope)) + { + node->accept(visitor); + } + } +} + +void MappingGraphAlgorithms::traverse(MappingGraphVisitor& visitor, + VisitorApplication scope, TaskScheduler* taskScheduler) const +{ + if (taskScheduler) + { + sofa::type::vector visitableNodes; + for (const auto& node : m_mappingGraph->m_allNodes) + { + if (shouldVisit(node.get(), scope)) + { + visitableNodes.push_back(node); + } + } + + sofa::simulation::parallelForEach(*taskScheduler, + visitableNodes.begin(), visitableNodes.end(), + [&visitor](const auto& node) + { + node->accept(visitor); + }); + } + else + { + traverse(visitor); + } +} + +void MappingGraphAlgorithms::traverseTopDown(MappingGraphVisitor& visitor, + VisitorApplication scope) const +{ + std::queue ready = prepareRootForTraversal(); + processQueue(ready, [&visitor, scope](const BaseMappingGraphNode* node) + { + if (shouldVisit(node, scope)) + { + node->accept(visitor); + } + }); +} + +void MappingGraphAlgorithms::traverseBottomUp(MappingGraphVisitor& visitor, + VisitorApplication scope) const +{ + //the strategy consists in traversing the graph from top to bottom and + //register the traversed nodes in a list. The bottom-up traversal corresponds to the + //reversed list. + + sofa::type::vector nodes; + nodes.reserve(m_mappingGraph->m_allNodes.size()); + { + std::queue ready = prepareRootForTraversal(); + processQueue(ready, [&nodes, scope](BaseMappingGraphNode* node) + { + if (shouldVisit(node, scope)) + { + nodes.push_back(node); + } + }); + } + + for (auto it = nodes.crbegin(); it != nodes.crend(); ++it) + { + (*it)->accept(visitor); + } +} + +void MappingGraphAlgorithms::traverseComponentGroups( + MappingGraphVisitor& visitor, VisitorApplication scope) const +{ + for (auto& [states, node] : m_mappingGraph->m_groupIndex) + { + if (shouldVisit(node.get(), scope)) + { + for (auto& child : node->m_children) + { + child->accept(visitor); + } + } + } +} + +void MappingGraphAlgorithms::traverseComponentGroups(MappingGraphVisitor& visitor, + VisitorApplication scope, + TaskScheduler* taskScheduler) const +{ + if (taskScheduler) + { + sofa::type::vector parallelNodes, sequentialNodes; + for (const auto& [states, node] : m_mappingGraph->m_groupIndex) + { + if (shouldVisit(node.get(), scope)) + { + //with a size of 1, we are sure that they are all different, preventing data races + if ( states.size() == 1) + { + parallelNodes.push_back(node); + } + else + { + sequentialNodes.push_back(node); + } + } + } + + sofa::simulation::parallelForEach(*taskScheduler, + parallelNodes.begin(), parallelNodes.end(), + [&visitor, &scope](const auto& node) + { + for (auto& child : node->m_children) + { + child->accept(visitor); + } + }); + + for (const auto& node : sequentialNodes) + { + for (auto& child : node->m_children) + { + child->accept(visitor); + } + } + } + else + { + traverseComponentGroups(visitor); + } +} + + +} // namespace sofa::simulation diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphAlgorithms.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphAlgorithms.h new file mode 100644 index 00000000000..ba8c70473bd --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphAlgorithms.h @@ -0,0 +1,179 @@ +/****************************************************************************** +* 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 + +namespace sofa::simulation +{ + +class TaskScheduler; +class BaseMappingGraphNode; +class MappingGraph; + +/** + * @brief Provides graph traversal algorithms for the mapping graph. + * + * This class contains static and instance methods that implement various ways + * to traverse the nodes and component groups defined in a MappingGraph, + * such as top-down, bottom-up, or arbitrary order. It uses the Visitor pattern + * to process components during traversal. + */ +struct SOFA_SIMULATION_CORE_API MappingGraphAlgorithms +{ + /** + * @brief Constructor for MappingGraphAlgorithms. + * @param mappingGraph Pointer to the mapping graph to be traversed. + */ + explicit MappingGraphAlgorithms(MappingGraph* mappingGraph) + : m_mappingGraph(mappingGraph) + { + } + + // ------------------------------------------------------------------ + // Traverse without any specific order + // ------------------------------------------------------------------ + + /** + * @brief Traverses the entire mapping graph nodes in an arbitrary order. + * @param visitor The concrete visitor implementation to process each node. + * @param scope Specifies which types of nodes should be visited (e.g., all, or only component groups). + */ + void traverse(MappingGraphVisitor& visitor, VisitorApplication scope = VisitorApplication::ALL_NODES) const; + + void traverse(MappingGraphVisitor& visitor, VisitorApplication scope, TaskScheduler* taskScheduler) const; + + /** + * @brief Traverses the entire mapping graph nodes using a callable function in an arbitrary order. + * @tparam Callable The type of callable object used for visitation. + * @param callable The callable object containing the logic to execute during traversal. + * @param scope Specifies which types of nodes should be visited. + */ + template + void traverse_(const Callable& callable, VisitorApplication scope = VisitorApplication::ALL_NODES) const + { + CallableVisitor visitor{callable}; + traverse(visitor, scope); + } + + template + void traverse_(const Callable& callable, VisitorApplication scope, TaskScheduler* taskScheduler) const + { + CallableVisitor visitor{callable}; + traverse(visitor, scope, taskScheduler); + } + + // ------------------------------------------------------------------ + // Top-Down traversal + // ------------------------------------------------------------------ + + /** + * @brief Traverses the mapping graph in a top-down order. + * @param visitor The concrete visitor implementation to process each node. + * @param scope Specifies which types of nodes should be visited. + */ + void traverseTopDown(MappingGraphVisitor& visitor, VisitorApplication scope = VisitorApplication::ALL_NODES) const; + + /** + * @brief Traverses the mapping graph in a top-down order using a callable function. + * @tparam Callable The type of callable object used for visitation. + * @param callable The callable object containing the logic to execute during traversal. + * @param scope Specifies which types of nodes should be visited. + */ + template + void traverseTopDown_(const Callable& callable, VisitorApplication scope = VisitorApplication::ALL_NODES) const + { + CallableVisitor visitor{callable}; + traverseTopDown(visitor, scope); + } + + // ------------------------------------------------------------------ + // Bottom-Up traversal + // ------------------------------------------------------------------ + + /** + * @brief Traverses the mapping graph in a bottom-up order. + * @param visitor The concrete visitor implementation to process each node. + * @param scope Specifies which types of nodes should be visited. + */ + void traverseBottomUp(MappingGraphVisitor& visitor, VisitorApplication scope = VisitorApplication::ALL_NODES) const; + + /** + * @brief Traverses the mapping graph in a bottom-up order using a callable function. + * @tparam Callable The type of callable object used for visitation. + * @param callable The callable object containing the logic to execute during traversal. + * @param scope Specifies which types of nodes should be visited. + */ + template + void traverseBottomUp_(const Callable& callable, VisitorApplication scope = VisitorApplication::ALL_NODES) const + { + CallableVisitor visitor{callable}; + traverseBottomUp(visitor, scope); + } + + // ------------------------------------------------------------------ + // Traverse only component groups without any specific order + // ------------------------------------------------------------------ + + /** + * @brief Visit and process component groups without any specific order. + * @param visitor The concrete visitor implementation. + * @param scope Specifies which types of nodes should be visited. Defaults to ALL_NODES. + */ + void traverseComponentGroups(MappingGraphVisitor& visitor, VisitorApplication scope = VisitorApplication::ALL_NODES) const; + + void traverseComponentGroups(MappingGraphVisitor& visitor, VisitorApplication scope, TaskScheduler* taskScheduler) const; + + /** + * @brief Visit and process component groups without any specific order using a callable function. + * @tparam Callable The type of callable object used for visitation. + * @param callable The callable object containing the logic to execute during traversal. + * @param scope Specifies which types of nodes should be visited. Defaults to ALL_NODES. + */ + template + void traverseComponentGroups_(const Callable& callable, VisitorApplication scope = VisitorApplication::ALL_NODES) const + { + CallableVisitor visitor{callable}; + traverseComponentGroups(visitor, scope); + } + + template + void traverseComponentGroups_(const Callable& callable, VisitorApplication scope, TaskScheduler* taskScheduler) const + { + CallableVisitor visitor{callable}; + traverseComponentGroups(visitor, scope, taskScheduler); + } + +private: + MappingGraph* m_mappingGraph { nullptr }; + + std::queue prepareRootForTraversal() const; + + template + static void processQueue(std::queue& ready, const Callable& f); +}; +} diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphNode.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphNode.h new file mode 100644 index 00000000000..dc1d74e4779 --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphNode.h @@ -0,0 +1,88 @@ +/****************************************************************************** +* 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 + +namespace sofa::simulation +{ + +/** + * @brief Template class representing a graph node associated with any SOFA component. + * + * This wrapper allows the `MappingGraph` to manage nodes for various types of + * components (e.g., MechanicalState, ForceField) polymorphically while maintaining + * type safety and providing standard graph node interfaces. + * + * @tparam TComponent The actual SOFA component class pointer type. + */ +template +class MappingGraphNode : public BaseMappingGraphNode +{ +public: + using SPtr = std::shared_ptr; + friend class MappingGraph; + + /** + * @brief Constructs a node wrapper for the given component. + * @param s A shared pointer to the component instance. + */ + explicit MappingGraphNode(typename TComponent::SPtr s) + : m_component(std::move(s)) + {} + + /** + * @brief Implements accept by calling visit on the wrapped component. + * @param visitor The concrete visitor implementation. + */ + void accept(MappingGraphVisitor& visitor) const override + { + if (m_component) + { + visitor.visit(*m_component); + } + } + + /** + * @brief Returns the name of the wrapped component. + * @return The name string from the component. + */ + std::string getName() const override + { + return m_component->getName(); + } + + NodeType getType() const override + { + if constexpr (std::is_base_of_v) + return NodeType::MechanicalState; + else if constexpr (std::is_base_of_v) + return NodeType::Mapping; + else + return NodeType::Component; + } + +private: + typename TComponent::SPtr m_component; ///< The actual SOFA component instance pointer. +}; + +} diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphVisitor.cpp b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphVisitor.cpp new file mode 100644 index 00000000000..425b7e5aa4c --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphVisitor.cpp @@ -0,0 +1,27 @@ +/****************************************************************************** +* 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 +{ + +} diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphVisitor.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphVisitor.h new file mode 100644 index 00000000000..c627279ca1c --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/MappingGraphVisitor.h @@ -0,0 +1,78 @@ +/****************************************************************************** +* 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 +#include + +namespace sofa::simulation +{ + +/** + * @brief Visitor interface for traversing the mapping graph. + * + * This visitor pattern is used to allow algorithms to process nodes in a + * structured way without modifying their structure or coupling traversal logic + * with specific component types. Implementations must override visit methods + * corresponding to the behaviors they intend to handle. + */ +class MappingGraphVisitor +{ +public: + virtual ~MappingGraphVisitor() = default; + + /** + * @brief Visits a mechanical state node. + * @param mstate The mechanical state component to visit. + */ + virtual void visit(core::behavior::BaseMechanicalState&) {} + + /** + * @brief Visits a base mapping node. + * @param mapping The mapping component to visit. + */ + virtual void visit(core::BaseMapping&) {} + + /** + * @brief Visits a force field behavior node. + * @param ff The force field component to visit. + */ + virtual void visit(core::behavior::BaseForceField&) {} + + /** + * @brief Visits a mass behavior node. + * @param m The mass component to visit. + */ + virtual void visit(core::behavior::BaseMass&) {} + + /** + * @brief Visits a projective constraint node. + * @param pcs The projective constraint component to visit. + */ + virtual void visit(sofa::core::behavior::BaseProjectiveConstraintSet&) {} +}; + +} diff --git a/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/VisitorApplication.h b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/VisitorApplication.h new file mode 100644 index 00000000000..6e9c4b085ca --- /dev/null +++ b/Sofa/framework/Simulation/Core/src/sofa/simulation/mappinggraph/VisitorApplication.h @@ -0,0 +1,35 @@ +/****************************************************************************** +* 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 + +namespace sofa::simulation +{ + +enum class SOFA_SIMULATION_CORE_API VisitorApplication +{ + ALL_NODES, + ONLY_MAPPED_NODES, + ONLY_MAIN_NODES +}; + +} diff --git a/Sofa/framework/Simulation/Core/test/CMakeLists.txt b/Sofa/framework/Simulation/Core/test/CMakeLists.txt index 3f58b89edc9..3f3bc5cd26f 100644 --- a/Sofa/framework/Simulation/Core/test/CMakeLists.txt +++ b/Sofa/framework/Simulation/Core/test/CMakeLists.txt @@ -5,6 +5,7 @@ project(Sofa.Simulation.Core_test) set(SOURCE_FILES Colors_test.cpp MappingGraph_test.cpp + MappingGraph2_test.cpp ParallelForEach_test.cpp RequiredPlugin_test.cpp SceneCheckRegistry_test.cpp diff --git a/Sofa/framework/Simulation/Core/test/MappingGraph2_test.cpp b/Sofa/framework/Simulation/Core/test/MappingGraph2_test.cpp new file mode 100644 index 00000000000..243187ce5b2 --- /dev/null +++ b/Sofa/framework/Simulation/Core/test/MappingGraph2_test.cpp @@ -0,0 +1,422 @@ +/****************************************************************************** +* 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 +#include +#include +#include +#include + +namespace sofa +{ + +TEST(MappingGraph, DefaultConstructor) +{ + sofa::simulation::MappingGraph mappingGraph; + EXPECT_FALSE(mappingGraph.isBuilt()); +} + +TEST(MappingGraph, Build) +{ + sofa::simulation::MappingGraph mappingGraph; + sofa::simulation::MappingGraph::InputLists input; + mappingGraph.build(input); + EXPECT_TRUE(mappingGraph.isBuilt()); +} + +struct CollectNamesVisitor : public sofa::simulation::MappingGraphVisitor +{ + void visit(core::BaseMapping& mapping) override + { + names.push_back("[MAPPING]" + mapping.getName()); + } + + void visit(core::behavior::BaseMechanicalState& state) override + { + names.push_back("[STATE]" + state.getName()); + } + + void visit(core::behavior::BaseForceField& ff) override + { + names.push_back("[FORCEFIELD]" + ff.getName()); + } + + void visit(core::behavior::BaseMass& mass) override + { + names.push_back("[MASS]" + mass.getName()); + } + + std::vector names; +}; + +TEST(MappingGraph, SingleState) +{ + const sofa::simulation::Node::SPtr root = sofa::simpleapi::createRootNode(sofa::simulation::getSimulation(), "root"); + + sofa::simpleapi::importPlugin(Sofa.Component.StateContainer); + sofa::simpleapi::createObject(root, "MechanicalObject", {{"name", "state"}}); + + auto inputs = sofa::simulation::MappingGraph::InputLists::makeFromNode(root.get()); + ASSERT_EQ(inputs.mechanicalStates.size(), 1); + sofa::simulation::MappingGraph mappingGraph(inputs); + ASSERT_TRUE(mappingGraph.isBuilt()); + + CollectNamesVisitor visitor; + + mappingGraph.algorithms.traverseTopDown(visitor); + ASSERT_EQ(visitor.names.size(), 1); + EXPECT_EQ(visitor.names[0], "[STATE]state"); +} + +TEST(MappingGraph, SingleMappingInSingleNode) +{ + const sofa::simulation::Node::SPtr root = sofa::simpleapi::createRootNode(sofa::simulation::getSimulation(), "root"); + + sofa::simpleapi::importPlugin(Sofa.Component.Mapping.Linear); + sofa::simpleapi::importPlugin(Sofa.Component.StateContainer); + + sofa::simpleapi::createObject(root, "MechanicalObject", {{"name", "state1"}}); + sofa::simpleapi::createObject(root, "MechanicalObject", {{"name", "state2"}}); + sofa::simpleapi::createObject(root, "IdentityMapping", {{"name", "mapping"}, {"input", "@state1"}, {"output", "@state2"}}); + + auto inputs = sofa::simulation::MappingGraph::InputLists::makeFromNode(root.get()); + ASSERT_EQ(inputs.mappings.size(), 1); + ASSERT_EQ(inputs.mechanicalStates.size(), 2); + sofa::simulation::MappingGraph mappingGraph(inputs); + ASSERT_TRUE(mappingGraph.isBuilt()); + + CollectNamesVisitor visitor; + + mappingGraph.algorithms.traverseTopDown(visitor); + ASSERT_EQ(visitor.names.size(), 3); + EXPECT_EQ(visitor.names[0], "[STATE]state1"); + EXPECT_EQ(visitor.names[1], "[MAPPING]mapping"); + EXPECT_EQ(visitor.names[2], "[STATE]state2"); + + visitor.names.clear(); + mappingGraph.algorithms.traverseBottomUp(visitor); + ASSERT_EQ(visitor.names.size(), 3); + EXPECT_EQ(visitor.names[0], "[STATE]state2"); + EXPECT_EQ(visitor.names[1], "[MAPPING]mapping"); + EXPECT_EQ(visitor.names[2], "[STATE]state1"); +} + +TEST(MappingGraph, SingleMappingWithIntermediateNode) +{ + const sofa::simulation::Node::SPtr root = sofa::simpleapi::createRootNode(sofa::simulation::getSimulation(), "root"); + + sofa::simpleapi::importPlugin(Sofa.Component.Mapping.Linear); + sofa::simpleapi::importPlugin(Sofa.Component.StateContainer); + + sofa::simpleapi::createObject(root, "MechanicalObject", {{"name", "state1"}}); + const auto node1 = root->createChild("node1"); + sofa::simpleapi::createObject(node1, "MechanicalObject", {{"name", "state2"}}); + sofa::simpleapi::createObject(node1, "IdentityMapping", {{"name", "mapping"}, {"input", "@state1"}, {"output", "@state2"}}); + + auto inputs = sofa::simulation::MappingGraph::InputLists::makeFromNode(root); + ASSERT_EQ(inputs.mappings.size(), 1); + ASSERT_EQ(inputs.mechanicalStates.size(), 2); + sofa::simulation::MappingGraph mappingGraph(inputs); + ASSERT_TRUE(mappingGraph.isBuilt()); + + CollectNamesVisitor visitor; + + mappingGraph.algorithms.traverseTopDown(visitor); + ASSERT_EQ(visitor.names.size(), 3); + EXPECT_EQ(visitor.names[0], "[STATE]state1"); + EXPECT_EQ(visitor.names[1], "[MAPPING]mapping"); + EXPECT_EQ(visitor.names[2], "[STATE]state2"); + + visitor.names.clear(); + mappingGraph.algorithms.traverseBottomUp(visitor); + ASSERT_EQ(visitor.names.size(), 3); + EXPECT_EQ(visitor.names[0], "[STATE]state2"); + EXPECT_EQ(visitor.names[1], "[MAPPING]mapping"); + EXPECT_EQ(visitor.names[2], "[STATE]state1"); +} + +TEST(MappingGraph, SingleMappingWithIntermediateNodeInverseInputOutput) +{ + const sofa::simulation::Node::SPtr root = sofa::simpleapi::createRootNode(sofa::simulation::getSimulation(), "root"); + + sofa::simpleapi::importPlugin(Sofa.Component.Mapping.Linear); + sofa::simpleapi::importPlugin(Sofa.Component.StateContainer); + + sofa::simpleapi::createObject(root, "MechanicalObject", {{"name", "state1"}}); + const auto node1 = root->createChild("node1"); + sofa::simpleapi::createObject(node1, "MechanicalObject", {{"name", "state2"}}); + sofa::simpleapi::createObject(node1, "IdentityMapping", {{"name", "mapping"}, {"input", "@state2"}, {"output", "@state1"}}); + + auto inputs = sofa::simulation::MappingGraph::InputLists::makeFromNode(root); + ASSERT_EQ(inputs.mappings.size(), 1); + ASSERT_EQ(inputs.mechanicalStates.size(), 2); + sofa::simulation::MappingGraph mappingGraph(inputs); + ASSERT_TRUE(mappingGraph.isBuilt()); + + CollectNamesVisitor visitor; + + mappingGraph.algorithms.traverseTopDown(visitor); + ASSERT_EQ(visitor.names.size(), 3); + EXPECT_EQ(visitor.names[0], "[STATE]state2"); + EXPECT_EQ(visitor.names[1], "[MAPPING]mapping"); + EXPECT_EQ(visitor.names[2], "[STATE]state1"); + + visitor.names.clear(); + mappingGraph.algorithms.traverseBottomUp(visitor); + ASSERT_EQ(visitor.names.size(), 3); + EXPECT_EQ(visitor.names[0], "[STATE]state1"); + EXPECT_EQ(visitor.names[1], "[MAPPING]mapping"); + EXPECT_EQ(visitor.names[2], "[STATE]state2"); +} + +/** + * @brief Sets up the complex graph environment for testing, creating nodes and components. + * + * @return std::tuple A tuple containing the root node pointer and collected input lists. + */ +auto setupComplexGraphEnvironment() -> std::pair +{ + // Setup common plugins required for both complex graph tests + sofa::simpleapi::importPlugin(Sofa.Component.Mapping.Linear); + sofa::simpleapi::importPlugin(Sofa.Component.StateContainer); + sofa::simpleapi::importPlugin(Sofa.Component.MechanicalLoad); + sofa::simpleapi::importPlugin(Sofa.Component.Mass); + + const sofa::simulation::Node::SPtr root = sofa::simpleapi::createRootNode(sofa::simulation::getSimulation(), "root"); + + // Components on the root node (state1) + sofa::simpleapi::createObject(root, "MechanicalObject", {{"name", "state1"}}); + sofa::simpleapi::createObject(root, "ConstantForceField", {{"name", "ff1"}, {"state", "@state1"}, {"forces", "1 0 0"}}); + sofa::simpleapi::createObject(root, "UniformMass", {{"name", "mass1"}}); + + // Components on the child node (state2) + const auto node1 = root->createChild("node1"); + sofa::simpleapi::createObject(node1, "MechanicalObject", {{"name", "state2"}}); + sofa::simpleapi::createObject(node1, "ConstantForceField", {{"name", "ff2"}, {"state", "@state2"}, {"forces", "1 0 0"}}); + sofa::simpleapi::createObject(node1, "UniformMass", {{"name", "mass2"}}); + + // Mapping connecting state1 to state2 + sofa::simpleapi::createObject(node1, "IdentityMapping", {{"name", "mapping"}, {"input", "@state1"}, {"output", "@state2"}}); + + // Initialize the root node structure + sofa::simulation::node::initRoot(root.get()); + + auto inputs = sofa::simulation::MappingGraph::InputLists::makeFromNode(root); + return {root, inputs}; +} + + +TEST(MappingGraph, ComplexGraph) +{ + // Setup environment using helper function + auto [root, inputs] = setupComplexGraphEnvironment(); + + ASSERT_EQ(inputs.mappings.size(), 1); + ASSERT_EQ(inputs.mechanicalStates.size(), 2); + ASSERT_EQ(inputs.forceFields.size(), 4); + sofa::simulation::MappingGraph mappingGraph(inputs); + ASSERT_TRUE(mappingGraph.isBuilt()); + + CollectNamesVisitor visitor; + + // Top Down Traversal Check + mappingGraph.algorithms.traverseTopDown(visitor); + ASSERT_EQ(visitor.names.size(), 9); // 9 and not 7 because a UniformMass is a BaseMass and also a BaseForceField + + EXPECT_EQ(visitor.names[0], "[STATE]state1"); + EXPECT_EQ(visitor.names[1], "[FORCEFIELD]ff1"); + EXPECT_EQ(visitor.names[2], "[FORCEFIELD]mass1"); + EXPECT_EQ(visitor.names[3], "[MASS]mass1"); + + EXPECT_EQ(visitor.names[4], "[MAPPING]mapping"); + EXPECT_EQ(visitor.names[5], "[STATE]state2"); + EXPECT_EQ(visitor.names[6], "[FORCEFIELD]ff2"); + EXPECT_EQ(visitor.names[7], "[FORCEFIELD]mass2"); + EXPECT_EQ(visitor.names[8], "[MASS]mass2"); + + visitor.names.clear(); + // Bottom Up Traversal Check + mappingGraph.algorithms.traverseBottomUp(visitor); + ASSERT_EQ(visitor.names.size(), 9); + + EXPECT_EQ(visitor.names[0], "[MASS]mass2"); + EXPECT_EQ(visitor.names[1], "[FORCEFIELD]mass2"); + EXPECT_EQ(visitor.names[2], "[FORCEFIELD]ff2"); + EXPECT_EQ(visitor.names[3], "[STATE]state2"); + + EXPECT_EQ(visitor.names[4], "[MAPPING]mapping"); + EXPECT_EQ(visitor.names[5], "[MASS]mass1"); + EXPECT_EQ(visitor.names[6], "[FORCEFIELD]mass1"); + EXPECT_EQ(visitor.names[7], "[FORCEFIELD]ff1"); + EXPECT_EQ(visitor.names[8], "[STATE]state1"); +} + +/** + * @brief Tests scoped traversal using different VisitorApplication scopes, limiting results to mapped nodes only. + */ +TEST(MappingGraph, ComplexGraph_OnlyMappedNodes) +{ + // Setup environment using helper function + auto [root, inputs] = setupComplexGraphEnvironment(); + + ASSERT_EQ(inputs.mappings.size(), 1); + ASSERT_EQ(inputs.mechanicalStates.size(), 2); + ASSERT_EQ(inputs.forceFields.size(), 4); + sofa::simulation::MappingGraph mappingGraph(inputs); + ASSERT_TRUE(mappingGraph.isBuilt()); + + CollectNamesVisitor visitor; + + // Test ONLY_MAPPED_NODES scope + mappingGraph.algorithms.traverseTopDown(visitor, sofa::simulation::VisitorApplication::ONLY_MAPPED_NODES); + ASSERT_EQ(visitor.names.size(), 4); + EXPECT_EQ(visitor.names[0], "[STATE]state2"); + EXPECT_EQ(visitor.names[1], "[FORCEFIELD]ff2"); + EXPECT_EQ(visitor.names[2], "[FORCEFIELD]mass2"); + EXPECT_EQ(visitor.names[3], "[MASS]mass2"); + + visitor.names.clear(); + // Bottom Up Traversal Check + mappingGraph.algorithms.traverseBottomUp(visitor, sofa::simulation::VisitorApplication::ONLY_MAPPED_NODES); + ASSERT_EQ(visitor.names.size(), 4); + + EXPECT_EQ(visitor.names[0], "[MASS]mass2"); + EXPECT_EQ(visitor.names[1], "[FORCEFIELD]mass2"); + EXPECT_EQ(visitor.names[2], "[FORCEFIELD]ff2"); + EXPECT_EQ(visitor.names[3], "[STATE]state2"); + +} + +/** + * @brief Tests scoped traversal using different VisitorApplication scopes, limiting results to main nodes only. + */ +TEST(MappingGraph, ComplexGraph_OnlyMainNodes) +{ + // Setup environment using helper function + auto [root, inputs] = setupComplexGraphEnvironment(); + + ASSERT_EQ(inputs.mappings.size(), 1); + ASSERT_EQ(inputs.mechanicalStates.size(), 2); + ASSERT_EQ(inputs.forceFields.size(), 4); + sofa::simulation::MappingGraph mappingGraph(inputs); + ASSERT_TRUE(mappingGraph.isBuilt()); + + CollectNamesVisitor visitor; + + // Test ONLY_MAIN_NODES scope + mappingGraph.algorithms.traverseTopDown(visitor, sofa::simulation::VisitorApplication::ONLY_MAIN_NODES); + ASSERT_EQ(visitor.names.size(), 5); + + EXPECT_EQ(visitor.names[0], "[STATE]state1"); + EXPECT_EQ(visitor.names[1], "[FORCEFIELD]ff1"); + EXPECT_EQ(visitor.names[2], "[FORCEFIELD]mass1"); + EXPECT_EQ(visitor.names[3], "[MASS]mass1"); + EXPECT_EQ(visitor.names[4], "[MAPPING]mapping"); + + visitor.names.clear(); + // Bottom Up Traversal Check + mappingGraph.algorithms.traverseBottomUp(visitor, sofa::simulation::VisitorApplication::ONLY_MAIN_NODES); + ASSERT_EQ(visitor.names.size(), 5); + + EXPECT_EQ(visitor.names[0], "[MAPPING]mapping"); + EXPECT_EQ(visitor.names[1], "[MASS]mass1"); + EXPECT_EQ(visitor.names[2], "[FORCEFIELD]mass1"); + EXPECT_EQ(visitor.names[3], "[FORCEFIELD]ff1"); + EXPECT_EQ(visitor.names[4], "[STATE]state1"); +} + +TEST(MappingGraph, ComplexGraphUsingLambdas) +{ + // Setup environment using helper function + auto [root, inputs] = setupComplexGraphEnvironment(); + + ASSERT_EQ(inputs.mappings.size(), 1); + ASSERT_EQ(inputs.mechanicalStates.size(), 2); + ASSERT_EQ(inputs.forceFields.size(), 4); + sofa::simulation::MappingGraph mappingGraph(inputs); + ASSERT_TRUE(mappingGraph.isBuilt()); + + sofa::type::vector visited; + + mappingGraph.algorithms.traverseTopDown_([&visited](const core::behavior::BaseMechanicalState& state) + { + visited.push_back(state.getName()); + }); + ASSERT_EQ(visited.size(), 2); + + EXPECT_EQ(visited[0], "state1"); + EXPECT_EQ(visited[1], "state2"); + + visited.clear(); + mappingGraph.algorithms.traverseTopDown_([&visited](const core::BaseMapping& mapping) + { + visited.push_back(mapping.getName()); + }); + ASSERT_EQ(visited.size(), 1); + + EXPECT_EQ(visited[0], "mapping"); +} + + +TEST(MappingGraph, ComplexGraphInteractionForceField) +{ + // Setup common plugins required for both complex graph tests + sofa::simpleapi::importPlugin(Sofa.Component.Mapping.Linear); + sofa::simpleapi::importPlugin(Sofa.Component.StateContainer); + sofa::simpleapi::importPlugin(Sofa.Component.MechanicalLoad); + sofa::simpleapi::importPlugin(Sofa.Component.Mass); + sofa::simpleapi::importPlugin(Sofa.Component.SolidMechanics.Spring); + + const sofa::simulation::Node::SPtr root = sofa::simpleapi::createRootNode(sofa::simulation::getSimulation(), "root"); + + const auto node1 = root->createChild("node1"); + sofa::simpleapi::createObject(node1, "MechanicalObject", {{"name", "state1"}}); + sofa::simpleapi::createObject(node1, "ConstantForceField", {{"name", "ff1"}, {"state", "@state1"}, {"forces", "1 0 0"}}); + sofa::simpleapi::createObject(node1, "UniformMass", {{"name", "mass1"}}); + + const auto node2 = root->createChild("node2"); + sofa::simpleapi::createObject(node2, "MechanicalObject", {{"name", "state2"}}); + sofa::simpleapi::createObject(node2, "ConstantForceField", {{"name", "ff2"}, {"state", "@state2"}, {"forces", "1 0 0"}}); + sofa::simpleapi::createObject(node2, "UniformMass", {{"name", "mass2"}}); + + // const auto node3 = root->createChild("node3"); + // sofa::simpleapi::createObject(node3, "MechanicalObject", {{"name", "state3"}}); + + sofa::simpleapi::createObject(root, "SpringForceField", {{"name", "spring"}, {"object1", "@node1/state1"}, {"object2", "@node2/state2"}}); + + // Initialize the root node structure + sofa::simulation::node::initRoot(root.get()); + + sofa::simulation::MappingGraph mappingGraph(root.get()); + ASSERT_TRUE(mappingGraph.isBuilt()); + + CollectNamesVisitor visitor; + + // Top Down Traversal Check + mappingGraph.algorithms.traverseTopDown(visitor); + // ASSERT_EQ(visitor.names.size(), 9); // 9 and not 7 because a UniformMass is a BaseMass and also a BaseForceField + + EXPECT_EQ(visitor.names[0], "[STATE]state1"); +} + + + +} diff --git a/Sofa/framework/Simulation/Core/test/MappingGraph_test.cpp b/Sofa/framework/Simulation/Core/test/MappingGraph_test.cpp index 0d6dc401b5b..9ca599c1a67 100644 --- a/Sofa/framework/Simulation/Core/test/MappingGraph_test.cpp +++ b/Sofa/framework/Simulation/Core/test/MappingGraph_test.cpp @@ -21,6 +21,7 @@ ******************************************************************************/ #include #include +#include #include #include #include @@ -40,7 +41,6 @@ TEST(MappingGraph, noBuild) EXPECT_TRUE(graph.getTopMostMechanicalStates((sofa::core::behavior::BaseForceField*)nullptr).empty()); EXPECT_TRUE(graph.getTopMostMechanicalStates((sofa::core::behavior::BaseMass*)nullptr).empty()); - EXPECT_TRUE(graph.makeComponentGroups(sofa::core::MechanicalParams::defaultInstance()).empty()); EXPECT_FALSE(graph.hasAnyMapping()); EXPECT_EQ(graph.getTotalNbMainDofs(), 0); } @@ -48,7 +48,7 @@ TEST(MappingGraph, noBuild) TEST(MappingGraph, nullRootNode) { sofa::simulation::MappingGraph graph; - graph.build(sofa::core::MechanicalParams::defaultInstance(), nullptr); + graph.build(nullptr); EXPECT_FALSE(graph.isBuilt()); EXPECT_EQ(graph.getRootNode(), nullptr); @@ -58,7 +58,6 @@ TEST(MappingGraph, nullRootNode) EXPECT_TRUE(graph.getTopMostMechanicalStates((sofa::core::behavior::BaseForceField*)nullptr).empty()); EXPECT_TRUE(graph.getTopMostMechanicalStates((sofa::core::behavior::BaseMass*)nullptr).empty()); - EXPECT_TRUE(graph.makeComponentGroups(sofa::core::MechanicalParams::defaultInstance()).empty()); EXPECT_FALSE(graph.hasAnyMapping()); EXPECT_EQ(graph.getTotalNbMainDofs(), 0); } @@ -68,7 +67,7 @@ TEST(MappingGraph, emptyRootNode) const sofa::simulation::Node::SPtr root = sofa::core::objectmodel::New(); sofa::simulation::MappingGraph graph; - graph.build(sofa::core::MechanicalParams::defaultInstance(), root.get()); + graph.build(root.get()); EXPECT_TRUE(graph.isBuilt()); EXPECT_EQ(graph.getRootNode(), root.get()); @@ -78,7 +77,6 @@ TEST(MappingGraph, emptyRootNode) EXPECT_TRUE(graph.getTopMostMechanicalStates((sofa::core::behavior::BaseForceField*)nullptr).empty()); EXPECT_TRUE(graph.getTopMostMechanicalStates((sofa::core::behavior::BaseMass*)nullptr).empty()); - EXPECT_TRUE(graph.makeComponentGroups(sofa::core::MechanicalParams::defaultInstance()).empty()); EXPECT_FALSE(graph.hasAnyMapping()); EXPECT_EQ(graph.getTotalNbMainDofs(), 0); } @@ -92,7 +90,7 @@ TEST(MappingGraph, oneMechanicalObject) mstate->resize(10); sofa::simulation::MappingGraph graph; - graph.build(sofa::core::MechanicalParams::defaultInstance(), root.get()); + graph.build(root.get()); EXPECT_TRUE(graph.isBuilt()); EXPECT_EQ(graph.getRootNode(), root.get()); @@ -100,7 +98,6 @@ TEST(MappingGraph, oneMechanicalObject) EXPECT_EQ(graph.getTopMostMechanicalStates(mstate.get()), sofa::type::vector{mstate.get()}); - EXPECT_TRUE(graph.makeComponentGroups(sofa::core::MechanicalParams::defaultInstance()).empty()); EXPECT_FALSE(graph.hasAnyMapping()); EXPECT_EQ(graph.getTotalNbMainDofs(), 30); } @@ -118,7 +115,7 @@ TEST(MappingGraph, twoMechanicalObject) mstate2->resize(2); sofa::simulation::MappingGraph graph; - graph.build(sofa::core::MechanicalParams::defaultInstance(), root.get()); + graph.build(root.get()); EXPECT_TRUE(graph.isBuilt()); EXPECT_EQ(graph.getRootNode(), root.get()); @@ -128,7 +125,6 @@ TEST(MappingGraph, twoMechanicalObject) EXPECT_EQ(graph.getTopMostMechanicalStates(mstate1.get()), sofa::type::vector{mstate1.get()}); EXPECT_EQ(graph.getTopMostMechanicalStates(mstate2.get()), sofa::type::vector{mstate2.get()}); - EXPECT_TRUE(graph.makeComponentGroups(sofa::core::MechanicalParams::defaultInstance()).empty()); EXPECT_FALSE(graph.hasAnyMapping()); EXPECT_EQ(graph.getTotalNbMainDofs(), 36); } @@ -152,7 +148,7 @@ TEST(MappingGraph, oneMapping) mapping->setTo(mstate2.get()); sofa::simulation::MappingGraph graph; - graph.build(sofa::core::MechanicalParams::defaultInstance(), root.get()); + graph.build(root.get()); EXPECT_TRUE(graph.isBuilt()); EXPECT_EQ(graph.getRootNode(), root.get()); @@ -161,7 +157,6 @@ TEST(MappingGraph, oneMapping) EXPECT_EQ(graph.getTopMostMechanicalStates(mstate1.get()), sofa::type::vector{mstate1.get()}); EXPECT_EQ(graph.getTopMostMechanicalStates(mstate2.get()), sofa::type::vector{mstate1.get()}); - EXPECT_TRUE(graph.makeComponentGroups(sofa::core::MechanicalParams::defaultInstance()).empty()); EXPECT_TRUE(graph.hasAnyMapping()); EXPECT_EQ(graph.getTotalNbMainDofs(), 30); } @@ -214,7 +209,7 @@ TEST(MappingGraph, diamondMapping) }); sofa::simulation::MappingGraph graph; - graph.build(sofa::core::MechanicalParams::defaultInstance(), root.get()); + graph.build(root.get()); EXPECT_TRUE(graph.isBuilt()); EXPECT_EQ(graph.getRootNode(), root.get()); @@ -225,7 +220,6 @@ TEST(MappingGraph, diamondMapping) const sofa::type::vector expectedList {top.get(), top.get()}; EXPECT_EQ(graph.getTopMostMechanicalStates(bottom.get()), expectedList); - EXPECT_TRUE(graph.makeComponentGroups(sofa::core::MechanicalParams::defaultInstance()).empty()); EXPECT_TRUE(graph.hasAnyMapping()); EXPECT_EQ(graph.getTotalNbMainDofs(), 3 * 3);