Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ODESolver] Introduction of generic Newton-Raphson algorithm #5173

Draft
wants to merge 46 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
61ca6a5
introduce files for NewtonRaphsonSolver
alxbilger Dec 12, 2024
750f376
start implementation
alxbilger Dec 12, 2024
ccf1096
move into Core
alxbilger Dec 12, 2024
807b5cb
create module for integration methods
alxbilger Dec 12, 2024
ac41553
files for backward euler
alxbilger Dec 12, 2024
cf2647b
register objects
alxbilger Dec 12, 2024
8cb2c04
function to get matrices factors
alxbilger Dec 12, 2024
ec2c460
start computation of right-hand side
alxbilger Dec 12, 2024
911a1d8
matrix factors
alxbilger Dec 13, 2024
0570e72
start manipulation of vectors
alxbilger Dec 13, 2024
656054e
some comments
alxbilger Dec 13, 2024
e551873
rhs input
alxbilger Dec 13, 2024
6cacdd9
apply projective
alxbilger Dec 13, 2024
9253a0b
compute error
alxbilger Dec 13, 2024
a2fbe3a
introduce first convergence criterion
alxbilger Dec 13, 2024
e208206
introduce example
alxbilger Dec 13, 2024
4c71b68
start the loop
alxbilger Dec 13, 2024
6c7df17
fix convergence criterion
alxbilger Dec 13, 2024
d6e6964
solve system
alxbilger Dec 13, 2024
e17854c
update state
alxbilger Dec 13, 2024
fbcc3b2
some comments
alxbilger Dec 13, 2024
d7725a1
another simpler example
alxbilger Dec 13, 2024
79ba86e
fix intermediateVelocity
alxbilger Dec 13, 2024
62124c8
check for convergence
alxbilger Dec 13, 2024
f43c900
fix force computation
alxbilger Dec 13, 2024
3db33c9
fix some vector operations
alxbilger Dec 16, 2024
dcab1d7
fix mass factor
alxbilger Dec 16, 2024
403c846
adjust parameters
alxbilger Dec 16, 2024
c82f224
clear debug stuff
alxbilger Dec 16, 2024
c99f45a
cleaning
alxbilger Dec 16, 2024
ff843b7
show iteration count in the failing case
alxbilger Dec 16, 2024
9d551c9
implement line search
alxbilger Dec 16, 2024
5db5f97
support no line search with nb iter = 0
alxbilger Dec 18, 2024
f6534c1
Rayleigh damping
alxbilger Dec 18, 2024
52f6f0d
extract force from the input because it's not an input
alxbilger Dec 19, 2024
430095a
update the scenes
alxbilger Dec 19, 2024
6e16029
compute true residual
alxbilger Dec 19, 2024
e4b9dbd
implementation of relative stopping criteria
alxbilger Dec 20, 2024
477e68f
rename
alxbilger Dec 20, 2024
35e9f27
implement relative successive stopping criteria
alxbilger Dec 20, 2024
34e194e
compare to initial guess (not the first iteration)
alxbilger Dec 20, 2024
9682391
implement absolute estimate difference criterion
alxbilger Dec 20, 2024
8c6731e
implement relative estimate difference criterion
alxbilger Dec 20, 2024
7ebebe5
update scenes
alxbilger Dec 20, 2024
16d3e6e
test and feedback on convergence
alxbilger Dec 20, 2024
2e47ddd
fix
alxbilger Dec 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Sofa/Component/ODESolver/Backward/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ set(HEADER_FILES
${SOFACOMPONENTODESOLVERBACKWARD_SOURCE_DIR}/StaticSolver.h
${SOFACOMPONENTODESOLVERBACKWARD_SOURCE_DIR}/VariationalSymplecticSolver.h
${SOFACOMPONENTODESOLVERBACKWARD_SOURCE_DIR}/NewmarkImplicitSolver.h
${SOFACOMPONENTODESOLVERBACKWARD_SOURCE_DIR}/NewtonRaphsonSolver.h
)

set(SOURCE_FILES
Expand All @@ -18,6 +19,7 @@ set(SOURCE_FILES
${SOFACOMPONENTODESOLVERBACKWARD_SOURCE_DIR}/StaticSolver.cpp
${SOFACOMPONENTODESOLVERBACKWARD_SOURCE_DIR}/VariationalSymplecticSolver.cpp
${SOFACOMPONENTODESOLVERBACKWARD_SOURCE_DIR}/NewmarkImplicitSolver.cpp
${SOFACOMPONENTODESOLVERBACKWARD_SOURCE_DIR}/NewtonRaphsonSolver.cpp
)

sofa_find_package(Sofa.Simulation.Core REQUIRED)
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>. *
*******************************************************************************
* Authors: The SOFA Team and external contributors (see Authors.txt) *
* *
* Contact information: [email protected] *
******************************************************************************/
#pragma once
#include <sofa/component/odesolver/backward/config.h>

#include <sofa/core/behavior/LinearSolverAccessor.h>
#include <sofa/core/behavior/OdeSolver.h>
#include <sofa/core/behavior/BaseIntegrationMethod.h>
#include <sofa/simulation/MechanicalOperations.h>


namespace sofa::component::odesolver::backward
{

class SOFA_COMPONENT_ODESOLVER_BACKWARD_API NewtonRaphsonSolver
: public sofa::core::behavior::OdeSolver
, public sofa::core::behavior::LinearSolverAccessor
{
public:
SOFA_CLASS2(NewtonRaphsonSolver, sofa::core::behavior::OdeSolver, sofa::core::behavior::LinearSolverAccessor);

void init() override;
void reset() override;
void solve(const core::ExecParams* params, SReal dt, sofa::core::MultiVecCoordId xResult, sofa::core::MultiVecDerivId vResult) override;

SingleLink<NewtonRaphsonSolver, core::behavior::BaseIntegrationMethod, BaseLink::FLAG_STRONGLINK> l_integrationMethod;

Data<unsigned int> d_maxNbIterationsNewton;
Data<SReal> d_relativeSuccessiveStoppingThreshold;
Data<SReal> d_relativeInitialStoppingThreshold;
Data<SReal> d_absoluteResidualStoppingThreshold;
Data<SReal> d_relativeEstimateDifferenceThreshold;
Data<SReal> d_absoluteEstimateDifferenceThreshold;
Data<unsigned int> d_maxNbIterationsLineSearch;
Data<SReal> d_lineSearchCoefficient;

MAKE_SELECTABLE_ITEMS(NewtonStatus,
sofa::helper::Item{"Undefined", "The solver has not been called yet"},
sofa::helper::Item{"Running", "The solver is still running and/or did not finish"},
sofa::helper::Item{"ConvergedEquilibrium", "Converged: the iterations did not start because the system is already at equilibrium"},
sofa::helper::Item{"DivergedLineSearch", "Diverged: line search failed"},
sofa::helper::Item{"DivergedMaxIterations", "Diverged: Reached the maximum number of iterations"},
sofa::helper::Item{"ConvergedResidualSuccessiveRatio", "Converged: Residual successive ratio is smaller than the threshold"},
sofa::helper::Item{"ConvergedResidualInitialRatio", "Converged: Residual initial ratio is smaller than the threshold"},
sofa::helper::Item{"ConvergedAbsoluteResidual", "Converged: Absolute residual is smaller than the threshold"},
sofa::helper::Item{"ConvergedRelativeEstimateDifference", "Converged: Relative estimate difference is smaller than the threshold"},
sofa::helper::Item{"ConvergedAbsoluteEstimateDifference", "Converged: Absolute estimate difference is smaller than the threshold"});

Data<NewtonStatus> d_status;

protected:
NewtonRaphsonSolver();

core::behavior::MultiVecDeriv m_linearSystemSolution;

void computeRightHandSide(const core::ExecParams* params, SReal dt,
core::MultiVecDerivId force,
core::MultiVecDerivId b,
core::MultiVecDerivId velocity_i,
core::MultiVecCoordId position_i) const;


SReal computeResidual(const core::ExecParams* params, sofa::simulation::common::MechanicalOperations& mop, SReal dt,
core::MultiVecDerivId force,
core::MultiVecDerivId oldVelocity, core::MultiVecDerivId newVelocity);
};

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extern void registerEulerImplicitSolver(sofa::core::ObjectFactory* factory);
extern void registerNewmarkImplicitSolver(sofa::core::ObjectFactory* factory);
extern void registerStaticSolver(sofa::core::ObjectFactory* factory);
extern void registerVariationalSymplecticSolver(sofa::core::ObjectFactory* factory);
extern void registerNewtonRaphsonSolver(sofa::core::ObjectFactory* factory);

extern "C" {
SOFA_EXPORT_DYNAMIC_LIBRARY void initExternalModule();
Expand Down Expand Up @@ -59,6 +60,7 @@ void registerObjects(sofa::core::ObjectFactory* factory)
registerNewmarkImplicitSolver(factory);
registerStaticSolver(factory);
registerVariationalSymplecticSolver(factory);
registerNewtonRaphsonSolver(factory);
}

void init()
Expand Down
1 change: 1 addition & 0 deletions Sofa/Component/ODESolver/Backward/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ set(SOURCE_FILES
EulerImplicitSolverStatic_test.cpp
EulerImplicitSolver_withDamping_test.cpp
NewmarkImplicitSolverDynamic_test.cpp
NewtonRaphsonSolver_test.cpp
StaticSolver_test.cpp
SpringSolverDynamic_test.cpp
VariationalSymplecticExplicitSolverDynamic_test.cpp
Expand Down
212 changes: 212 additions & 0 deletions Sofa/Component/ODESolver/Backward/tests/NewtonRaphsonSolver_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/******************************************************************************
* 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 <http://www.gnu.org/licenses/>. *
*******************************************************************************
* Authors: The SOFA Team and external contributors (see Authors.txt) *
* *
* Contact information: [email protected] *
******************************************************************************/
#include <sofa/component/odesolver/backward/NewtonRaphsonSolver.h>
#include <sofa/component/statecontainer/MechanicalObject.h>
#include <sofa/simpleapi/SimpleApi.h>
#include <sofa/testing/BaseSimulationTest.h>
#include <sofa/testing/NumericTest.h>


namespace sofa
{

struct NewtonRaphsonParameters
{
SReal relativeSuccessiveStoppingThreshold {};
SReal relativeInitialStoppingThreshold {};
SReal absoluteResidualStoppingThreshold {};
SReal relativeEstimateDifferenceThreshold {};
SReal absoluteEstimateDifferenceThreshold {};
unsigned int maxNbIterationsLineSearch = 1;
unsigned int maxNbIterationsNewton = 1;

void apply(component::odesolver::backward::NewtonRaphsonSolver* solver) const
{
solver->d_relativeSuccessiveStoppingThreshold.setValue(relativeSuccessiveStoppingThreshold);
solver->d_relativeInitialStoppingThreshold.setValue(relativeInitialStoppingThreshold);
solver->d_absoluteResidualStoppingThreshold.setValue(absoluteResidualStoppingThreshold);
solver->d_relativeEstimateDifferenceThreshold.setValue(relativeEstimateDifferenceThreshold);
solver->d_absoluteEstimateDifferenceThreshold.setValue(absoluteEstimateDifferenceThreshold);
solver->d_maxNbIterationsLineSearch.setValue(maxNbIterationsLineSearch);
solver->d_maxNbIterationsNewton.setValue(maxNbIterationsNewton);
}
};

struct NewtonRaphsonTest : public testing::BaseSimulationTest
{
SceneInstance m_scene{};

component::odesolver::backward::NewtonRaphsonSolver* m_solver { nullptr };
component::statecontainer::MechanicalObject<defaulttype::Vec1Types>* m_state { nullptr };

void onSetUp() override
{
sofa::simpleapi::importPlugin("Sofa.Component.LinearSolver.Direct");
sofa::simpleapi::importPlugin("Sofa.Component.ODESolver.Backward");
sofa::simpleapi::importPlugin("Sofa.Component.ODESolver.Integration");
sofa::simpleapi::importPlugin("Sofa.Component.StateContainer");
sofa::simpleapi::importPlugin("Sofa.Component.Topology.Container.Dynamic");

sofa::simpleapi::createObject(m_scene.root, "DefaultAnimationLoop");
sofa::simpleapi::createObject(m_scene.root, "BDF1");
auto solverObject = sofa::simpleapi::createObject(m_scene.root, "NewtonRaphsonSolver", {{"printLog", "true"}});
m_solver = dynamic_cast<component::odesolver::backward::NewtonRaphsonSolver*>(solverObject.get());
sofa::simpleapi::createObject(m_scene.root, "EigenSimplicialLDLT", {{"template", "CompressedRowSparseMatrix"}});
auto stateObject = sofa::simpleapi::createObject(m_scene.root, "MechanicalObject", {{"template", "Vec1"}, {"position", "1"}, {"rest_position", "0"}});
m_state = dynamic_cast<component::statecontainer::MechanicalObject<defaulttype::Vec1Types>*>(stateObject.get());
sofa::simpleapi::createObject(m_scene.root, "PointSetTopologyContainer", {{"name", "topology"}, {"position", "1"}});

ASSERT_NE(nullptr, m_solver);
ASSERT_NE(nullptr, m_state);
}

void noForce()
{
m_scene.initScene();
EXPECT_EQ(m_solver->d_status.getValue(), component::odesolver::backward::NewtonRaphsonSolver::NewtonStatus("Undefined"));

m_scene.simulate(0.01_sreal);
EXPECT_EQ(m_solver->d_status.getValue(), component::odesolver::backward::NewtonRaphsonSolver::NewtonStatus("ConvergedEquilibrium"));
}

void gravity(const NewtonRaphsonParameters& params, const component::odesolver::backward::NewtonRaphsonSolver::NewtonStatus& expectedStatus)
{
static constexpr SReal gravity = -1;
m_scene.root->setGravity({gravity, 0, 0});

sofa::simpleapi::importPlugin("Sofa.Component.Mass");
sofa::simpleapi::createObject(m_scene.root, "UniformMass", {{"totalMass", "1"}, {"topology", "@topology"}, {"printLog", "true"}});
params.apply(m_solver);

m_scene.initScene();
EXPECT_EQ(m_solver->d_status.getValue(), component::odesolver::backward::NewtonRaphsonSolver::NewtonStatus("Undefined"));

auto previousVelocity = m_state->read(sofa::core::vec_id::read_access::velocity)->getValue();
auto previousPosition = m_state->read(sofa::core::vec_id::read_access::position)->getValue();

static constexpr SReal dt = 0.1_sreal;
m_scene.simulate(dt);
EXPECT_EQ(m_solver->d_status.getValue(), expectedStatus);

const auto& velocity = m_state->read(sofa::core::vec_id::read_access::velocity)->getValue();
const auto& position = m_state->read(sofa::core::vec_id::read_access::position)->getValue();

EXPECT_FLOATINGPOINT_EQ(velocity[0].x(), gravity * dt)
EXPECT_FLOATINGPOINT_EQ(position[0].x(), previousPosition[0].x() + velocity[0].x() * dt);

for (unsigned int i = 1; i < 10; ++i)
{
previousVelocity = m_state->read(sofa::core::vec_id::read_access::velocity)->getValue();
previousPosition = m_state->read(sofa::core::vec_id::read_access::position)->getValue();

m_scene.simulate(dt);
EXPECT_FLOATINGPOINT_EQ(velocity[0].x(), previousVelocity[0].x() + gravity * dt);

EXPECT_FLOATINGPOINT_EQ(position[0].x(), previousPosition[0].x() + velocity[0].x() * dt);
}
}

void spring()
{
sofa::simpleapi::importPlugin("Sofa.Component.SolidMechanics.Spring");
sofa::simpleapi::createObject(m_scene.root, "RestShapeSpringsForceField", {{"points", "0"}});

m_scene.initScene();
EXPECT_EQ(m_solver->d_status.getValue(), component::odesolver::backward::NewtonRaphsonSolver::NewtonStatus("Undefined"));

m_scene.simulate(0.01_sreal);
}

};

TEST_F(NewtonRaphsonTest, noForce)
{
this->noForce();
}

TEST_F(NewtonRaphsonTest, gravity_noStopping)
{
// no stopping criteria is defined so it cannot converge
this->gravity(
NewtonRaphsonParameters{},
component::odesolver::backward::NewtonRaphsonSolver::NewtonStatus("DivergedMaxIterations"));
}

TEST_F(NewtonRaphsonTest, gravity_relativeSuccessiveStopping)
{
NewtonRaphsonParameters params;
params.relativeSuccessiveStoppingThreshold = 1e-15_sreal;

this->gravity(
params,
component::odesolver::backward::NewtonRaphsonSolver::NewtonStatus("ConvergedResidualSuccessiveRatio"));
}

TEST_F(NewtonRaphsonTest, gravity_relativeInitialStopping)
{
NewtonRaphsonParameters params;
params.relativeInitialStoppingThreshold = 1e-15_sreal;

this->gravity(
params,
component::odesolver::backward::NewtonRaphsonSolver::NewtonStatus("ConvergedResidualInitialRatio"));
}

TEST_F(NewtonRaphsonTest, gravity_absoluteResidualStopping)
{
NewtonRaphsonParameters params;
params.absoluteResidualStoppingThreshold = 1e-15_sreal;

this->gravity(
params,
component::odesolver::backward::NewtonRaphsonSolver::NewtonStatus("ConvergedAbsoluteResidual"));
}

TEST_F(NewtonRaphsonTest, gravity_relativeEstimateDifferenceStopping)
{
NewtonRaphsonParameters params;
params.relativeEstimateDifferenceThreshold = 1e-15_sreal;

// considered as diverged because this threshold requires more than one iteration
this->gravity(
params,
component::odesolver::backward::NewtonRaphsonSolver::NewtonStatus("DivergedMaxIterations"));
}

TEST_F(NewtonRaphsonTest, gravity_absoluteEstimateDifferenceStopping)
{
NewtonRaphsonParameters params;
params.absoluteEstimateDifferenceThreshold = 1e-15_sreal;

// considered as diverged because this threshold requires more than one iteration
this->gravity(
params,
component::odesolver::backward::NewtonRaphsonSolver::NewtonStatus("DivergedMaxIterations"));
}

TEST_F(NewtonRaphsonTest, spring)
{
this->spring();
}


}
2 changes: 1 addition & 1 deletion Sofa/Component/ODESolver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ project(Sofa.Component.ODESolver LANGUAGES CXX)
set(SOFACOMPONENTODESOLVER_SOURCE_DIR "src/sofa/component/odesolver")

sofa_add_subdirectory_modules(SOFACOMPONENTODESOLVER_TARGETS
DIRECTORIES Forward Backward
DIRECTORIES Forward Backward Integration
)

set(HEADER_FILES
Expand Down
35 changes: 35 additions & 0 deletions Sofa/Component/ODESolver/Integration/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.22)
project(Sofa.Component.ODESolver.Integration LANGUAGES CXX)

set(SOFACOMPONENTODESOLVERINTEGRATION_SOURCE_DIR "src/sofa/component/odesolver/integration")

set(HEADER_FILES
${SOFACOMPONENTODESOLVERINTEGRATION_SOURCE_DIR}/config.h.in
${SOFACOMPONENTODESOLVERINTEGRATION_SOURCE_DIR}/init.h

${SOFACOMPONENTODESOLVERINTEGRATION_SOURCE_DIR}/BDF1.h
)

set(SOURCE_FILES
${SOFACOMPONENTODESOLVERINTEGRATION_SOURCE_DIR}/init.cpp

${SOFACOMPONENTODESOLVERINTEGRATION_SOURCE_DIR}/BDF1.cpp
)

sofa_find_package(Sofa.Simulation.Core REQUIRED)

add_library(${PROJECT_NAME} SHARED ${HEADER_FILES} ${SOURCE_FILES})
target_link_libraries(${PROJECT_NAME} PUBLIC Sofa.Simulation.Core)

sofa_create_package_with_targets(
PACKAGE_NAME ${PROJECT_NAME}
PACKAGE_VERSION ${Sofa_VERSION}
TARGETS ${PROJECT_NAME} AUTO_SET_TARGET_PROPERTIES
INCLUDE_SOURCE_DIR "src"
INCLUDE_INSTALL_DIR "${PROJECT_NAME}"
)

#cmake_dependent_option(SOFA_COMPONENT_ODESOLVER_INTEGRATION_BUILD_TESTS "Compile the automatic tests" ON "SOFA_BUILD_TESTS OR NOT DEFINED SOFA_BUILD_TESTS" OFF)
#if(SOFA_COMPONENT_ODESOLVER_INTEGRATION_BUILD_TESTS)
# add_subdirectory(tests)
#endif()
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# CMake package configuration file for the @PROJECT_NAME@ module

@PACKAGE_GUARD@
@PACKAGE_INIT@

find_package(Sofa.Simulation.Core QUIET REQUIRED)

if(NOT TARGET @PROJECT_NAME@)
include("${CMAKE_CURRENT_LIST_DIR}/@[email protected]")
endif()

check_required_components(@PROJECT_NAME@)
Loading
Loading