Skip to content

Commit 88d0a2b

Browse files
authored
Merge pull request #289 from alxbilger/accesscompliance
Binding to access to the compliance matrix in constraint space
2 parents 14019be + b9d122d commit 88d0a2b

11 files changed

Lines changed: 357 additions & 4 deletions

File tree

bindings/Modules/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
project(Bindings.Modules)
22

3-
set(MODULEBINDINGS_MODULE_LIST SofaBaseTopology SofaDeformable SofaLinearSolver)
3+
set(MODULEBINDINGS_MODULE_LIST SofaBaseTopology SofaDeformable SofaLinearSolver SofaConstraintSolver)
44

55
find_package(Sofa.GL QUIET)
66
if(Sofa.GL_FOUND)
@@ -11,6 +11,7 @@ endif()
1111

1212

1313
foreach(modulebindings_module ${MODULEBINDINGS_MODULE_LIST})
14+
message(STATUS "Adding binding module ${modulebindings_module}")
1415
add_subdirectory(src/SofaPython3/${modulebindings_module})
1516
endforeach()
1617

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/******************************************************************************
2+
* SOFA, Simulation Open-Framework Architecture *
3+
* (c) 2021 INRIA, USTL, UJF, CNRS, MGH *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify it *
6+
* under the terms of the GNU Lesser General Public License as published by *
7+
* the Free Software Foundation; either version 2.1 of the License, or (at *
8+
* your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, but WITHOUT *
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
13+
* for more details. *
14+
* *
15+
* You should have received a copy of the GNU Lesser General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17+
*******************************************************************************
18+
* Contact information: contact@sofa-framework.org *
19+
******************************************************************************/
20+
#include <SofaPython3/SofaConstraintSolver/Binding_ConstraintSolver.h>
21+
#include <SofaPython3/SofaConstraintSolver/Binding_ConstraintSolver_doc.h>
22+
#include <pybind11/pybind11.h>
23+
#include <pybind11/eigen.h>
24+
25+
#include <SofaPython3/Sofa/Core/Binding_Base.h>
26+
#include <SofaPython3/PythonFactory.h>
27+
28+
#include <sofa/component/constraint/lagrangian/solver/ConstraintSolverImpl.h>
29+
30+
namespace py { using namespace pybind11; }
31+
32+
namespace sofapython3 {
33+
34+
using sofa::component::constraint::lagrangian::solver::ConstraintSolverImpl;
35+
using EigenDenseMatrix = Eigen::Matrix<SReal, Eigen::Dynamic, Eigen::Dynamic>;
36+
using EigenMatrixMap = Eigen::Map<EigenDenseMatrix>;
37+
38+
void moduleAddConstraintSolver(py::module &m)
39+
{
40+
const auto typeName = ConstraintSolverImpl::GetClass()->className;
41+
py::class_<ConstraintSolverImpl,
42+
sofa::core::objectmodel::BaseObject,
43+
sofapython3::py_shared_ptr<ConstraintSolverImpl> > c(m, typeName.c_str(), sofapython3::doc::constraintsolver::constraintSolverClass);
44+
45+
c.def("W", [](ConstraintSolverImpl& self) -> EigenMatrixMap
46+
{
47+
assert(self.getConstraintProblem());
48+
auto& W_matrix = self.getConstraintProblem()->W;
49+
return { W_matrix.ptr(), W_matrix.rows(), W_matrix.cols()};
50+
}, sofapython3::doc::constraintsolver::constraintSolver_W);
51+
52+
c.def("lambda_force", [](ConstraintSolverImpl& self) -> Eigen::Map<Eigen::Matrix<SReal, Eigen::Dynamic, 1> >
53+
{
54+
assert(self.getConstraintProblem());
55+
auto& lambda = self.getConstraintProblem()->f;
56+
return { lambda.ptr(), lambda.size()};
57+
}, sofapython3::doc::constraintsolver::constraintSolver_lambda);
58+
59+
c.def("dfree", [](ConstraintSolverImpl& self) -> Eigen::Map<Eigen::Matrix<SReal, Eigen::Dynamic, 1> >
60+
{
61+
assert(self.getConstraintProblem());
62+
auto& dfree = self.getConstraintProblem()->dFree;
63+
return { dfree.ptr(), dfree.size()};
64+
}, sofapython3::doc::constraintsolver::constraintSolver_dfree);
65+
66+
/// register the binding in the downcasting subsystem
67+
PythonFactory::registerType<ConstraintSolverImpl>([](sofa::core::objectmodel::Base* object)
68+
{
69+
return py::cast(dynamic_cast<ConstraintSolverImpl*>(object));
70+
});
71+
}
72+
73+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/******************************************************************************
2+
* SOFA, Simulation Open-Framework Architecture *
3+
* (c) 2021 INRIA, USTL, UJF, CNRS, MGH *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify it *
6+
* under the terms of the GNU Lesser General Public License as published by *
7+
* the Free Software Foundation; either version 2.1 of the License, or (at *
8+
* your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, but WITHOUT *
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
13+
* for more details. *
14+
* *
15+
* You should have received a copy of the GNU Lesser General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17+
*******************************************************************************
18+
* Contact information: contact@sofa-framework.org *
19+
******************************************************************************/
20+
21+
#pragma once
22+
23+
#include <pybind11/pybind11.h>
24+
25+
namespace sofapython3
26+
{
27+
28+
void moduleAddConstraintSolver(pybind11::module &m);
29+
30+
} /// namespace sofapython3
31+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/******************************************************************************
2+
* SOFA, Simulation Open-Framework Architecture *
3+
* (c) 2021 INRIA, USTL, UJF, CNRS, MGH *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify it *
6+
* under the terms of the GNU Lesser General Public License as published by *
7+
* the Free Software Foundation; either version 2.1 of the License, or (at *
8+
* your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, but WITHOUT *
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
13+
* for more details. *
14+
* *
15+
* You should have received a copy of the GNU Lesser General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17+
*******************************************************************************
18+
* Contact information: contact@sofa-framework.org *
19+
******************************************************************************/
20+
21+
#pragma once
22+
23+
namespace sofapython3::doc::constraintsolver {
24+
25+
static auto constraintSolverClass =
26+
R"(
27+
Constraint solver.
28+
)";
29+
30+
static auto constraintSolver_W =
31+
R"(
32+
Returns the compliance matrix projected in constraint space, built in the constraint solver
33+
34+
example:
35+
------------
36+
37+
constraint_solver = root.addObject("GenericConstraintSolver", tolerance=1e-9, maxIterations=1000)
38+
matrix = constraint_solver.W()
39+
)";
40+
41+
static auto constraintSolver_lambda =
42+
R"(
43+
Returns the force resulting from the constraints
44+
45+
example:
46+
------------
47+
48+
constraint_solver = root.addObject("GenericConstraintSolver", tolerance=1e-9, maxIterations=1000)
49+
lambda = constraint_solver.lambda_force()
50+
)";
51+
52+
static auto constraintSolver_dfree =
53+
R"(
54+
Returns the displacement computed without any constraint
55+
56+
example:
57+
------------
58+
59+
constraint_solver = root.addObject("GenericConstraintSolver", tolerance=1e-9, maxIterations=1000)
60+
dfree = constraint_solver.dfree()
61+
)";
62+
63+
} // namespace sofapython3::doc::constraintsolver
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
project(Bindings.Modules.SofaConstraintSolver)
2+
3+
set(HEADER_FILES
4+
${CMAKE_CURRENT_SOURCE_DIR}/Binding_ConstraintSolver.h
5+
${CMAKE_CURRENT_SOURCE_DIR}/Binding_ConstraintSolver_doc.h
6+
)
7+
8+
set(SOURCE_FILES
9+
${CMAKE_CURRENT_SOURCE_DIR}/Binding_ConstraintSolver.cpp
10+
${CMAKE_CURRENT_SOURCE_DIR}/Module_SofaConstraintSolver.cpp
11+
)
12+
13+
if (NOT TARGET SofaPython3::Plugin)
14+
find_package(SofaPython3 REQUIRED COMPONENTS Plugin Bindings.Sofa)
15+
endif()
16+
17+
find_package(Sofa.Component.Constraint.Lagrangian.Solver REQUIRED)
18+
19+
SP3_add_python_module(
20+
TARGET ${PROJECT_NAME}
21+
PACKAGE Bindings.Modules
22+
MODULE SofaConstraintSolver
23+
DESTINATION Sofa
24+
SOURCES ${SOURCE_FILES}
25+
HEADERS ${HEADER_FILES}
26+
DEPENDS Sofa.Component.Constraint.Lagrangian.Solver SofaPython3::Plugin SofaPython3::Bindings.Sofa.Core
27+
)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/******************************************************************************
2+
* SofaPython3 plugin *
3+
* (c) 2021 CNRS, University of Lille, INRIA *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify it *
6+
* under the terms of the GNU Lesser General Public License as published by *
7+
* the Free Software Foundation; either version 2.1 of the License, or (at *
8+
* your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, but WITHOUT *
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
13+
* for more details. *
14+
* *
15+
* You should have received a copy of the GNU Lesser General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17+
*******************************************************************************
18+
* Contact information: contact@sofa-framework.org *
19+
******************************************************************************/
20+
21+
#include <pybind11/pybind11.h>
22+
#include <SofaPython3/SofaConstraintSolver/Binding_ConstraintSolver.h>
23+
24+
namespace sofapython3
25+
{
26+
27+
PYBIND11_MODULE(SofaConstraintSolver, m)
28+
{
29+
moduleAddConstraintSolver(m);
30+
}
31+
32+
} // namespace sofapython3
33+

bindings/Modules/tests/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ set(PYTHON_FILES
99
${CMAKE_CURRENT_SOURCE_DIR}/SofaDeformable/SpringForceField.py
1010

1111
${CMAKE_CURRENT_SOURCE_DIR}/SofaLinearSolver/matrix_access.py
12+
13+
${CMAKE_CURRENT_SOURCE_DIR}/SofaConstraintSolver/matrix_access.py
1214
)
1315

1416
find_package(Sofa.Testing REQUIRED)
@@ -27,7 +29,7 @@ sofa_auto_set_target_rpath(
2729

2830
add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME})
2931

30-
set(DIR_BINDING_LIST SofaBaseTopology SofaDeformable SofaLinearSolver)
32+
set(DIR_BINDING_LIST SofaBaseTopology SofaDeformable SofaLinearSolver SofaConstraintSolver)
3133
get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
3234
foreach(dir_binding ${DIR_BINDING_LIST})
3335
if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${dir_binding}")
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import unittest
2+
import Sofa.Core
3+
import Sofa.Components
4+
from Sofa import SofaConstraintSolver
5+
6+
class Test(unittest.TestCase):
7+
8+
def simulate_pendulum(self):
9+
root = Sofa.Core.Node("rootNode")
10+
11+
root.addObject("RequiredPlugin", pluginName=["Sofa.Component.AnimationLoop",
12+
"Sofa.Component.Constraint.Lagrangian.Correction",
13+
"Sofa.Component.Constraint.Lagrangian.Model",
14+
"Sofa.Component.Constraint.Lagrangian.Solver",
15+
"Sofa.Component.Constraint.Projective",
16+
"Sofa.Component.IO.Mesh",
17+
"Sofa.Component.LinearSolver.Direct",
18+
"Sofa.Component.Mapping.MappedMatrix",
19+
"Sofa.Component.Mass",
20+
"Sofa.Component.ODESolver.Backward",
21+
"Sofa.Component.Topology.Container.Dynamic"])
22+
23+
root.addObject("FreeMotionAnimationLoop", solveVelocityConstraintFirst=True)
24+
root.addObject("GenericConstraintSolver", name="constraint_solver", tolerance=1e-9, maxIterations=1000)
25+
root.addObject("StringMeshCreator", name="loader", resolution="20")
26+
27+
root.addObject("EulerImplicitSolver")
28+
root.addObject("SparseCholeskySolver")
29+
root.addObject("GenericConstraintCorrection")
30+
31+
root.addObject("EdgeSetTopologyContainer", position="@loader.position", edges="@loader.edges")
32+
root.addObject("MechanicalObject", name="defoDOF", template="Vec3d")
33+
root.addObject("EdgeSetGeometryAlgorithms", drawEdges=True)
34+
root.addObject("FixedConstraint", indices=[0])
35+
root.addObject("DiagonalMass", name="mass", totalMass="1e-3")
36+
root.addObject("MappingGeometricStiffnessForceField", mapping="@./extensionsNode/distanceMapping")
37+
38+
ext = root.addChild("extensionsNode")
39+
ext.addObject("MechanicalObject", template="Vec1d", name="extensionsDOF")
40+
ext.addObject("DistanceMapping", name="distanceMapping")
41+
ext.addObject("UniformConstraint", template="Vec1d", iterative=True)
42+
43+
Sofa.Simulation.init(root)
44+
Sofa.Simulation.animate(root, 0.0001)
45+
46+
return root
47+
48+
def test_matrix_access(self):
49+
root = self.simulate_pendulum()
50+
W = root.constraint_solver.W()
51+
52+
self.assertEqual(W.ndim, 2)
53+
self.assertEqual(W.shape, (38, 38))

bindings/Modules/tests/SofaLinearSolver/matrix_access.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ def test_matrix_access_blocks_scalar(self):
3838

3939
self.assertEqual(A.ndim, 2)
4040
self.assertEqual(A.shape, (960, 960))
41-
self.assertGreater(A.nnz, 960) // Should be Equal to one specific value but a different between OS has been reported here: https://github.com/sofa-framework/sofa/issues/3036
41+
self.assertGreater(A.nnz, 960) # Should be Equal to one specific value but a different between OS has been reported here: https://github.com/sofa-framework/sofa/issues/3036
4242

4343
def test_matrix_access_blocks3x3(self):
4444
root = self.simulate_beam("CompressedRowSparseMatrixMat3x3d")
4545
A = root.linear_solver.A()
4646

4747
self.assertEqual(A.ndim, 2)
4848
self.assertEqual(A.shape, (960, 960))
49-
self.assertGreater(A.nnz, 960) // Should be Equal to one specific value but a different between OS has been reported here: https://github.com/sofa-framework/sofa/issues/3036
49+
self.assertGreater(A.nnz, 960) # Should be Equal to one specific value but a different between OS has been reported here: https://github.com/sofa-framework/sofa/issues/3036

bindings/Modules/tests/main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ static struct Tests : public sofapython3::PythonTestExtractor
4040
const std::string executable_directory = sofa::helper::Utils::getExecutableDirectory();
4141
addTestDirectory(executable_directory+"/Bindings.Modules.Tests.d/SofaDeformable", "SofaDeformable_");
4242
addTestDirectory(executable_directory+"/Bindings.Modules.Tests.d/SofaLinearSolver", "SofaLinearSolver_");
43+
addTestDirectory(executable_directory+"/Bindings.Modules.Tests.d/SofaConstraintSolver", "SofaConstraintSolver_");
4344
}
4445
} python_tests;
4546

0 commit comments

Comments
 (0)