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

[binding] QPInverseProblemSolver: adds python binding #24

Merged
merged 7 commits into from
Apr 18, 2024

Conversation

EulalieCoevoet
Copy link
Member

@EulalieCoevoet EulalieCoevoet commented Apr 3, 2024

Usage example:

import Sofa.SoftRobotsInverse

class MyQPInverseProblemSolver(Sofa.SoftRobotsInverse.QPInverseProblemSolver):

    def __init__(self, *args, **kwargs):
        Sofa.SoftRobotsInverse.QPInverseProblemSolver.__init__(self, *args, **kwargs)

    def solveSystem(self):
        W = self.W()
        dfree = self.dfree()
        forces = self.lambda_force()  # pointer on lambda
        [...] # solve the problem and store the result in forces
        return True

def createScene(rootnode):
    rootnode.addObject(MyQPInverseProblemSolver)
    [...]

@damienmarchal
Copy link
Member

I gave it a quick look and seems ok.

Comment on lines 121 to 140
s.def("W", [](QPInverseProblemSolver& self) -> EigenMatrixMap
{
assert(self.getConstraintProblem());
auto& W_matrix = self.getConstraintProblem()->W;
return { W_matrix.ptr(), W_matrix.rows(), W_matrix.cols()};
});

s.def("lambda_force", [](QPInverseProblemSolver& self) -> Eigen::Map<Eigen::Matrix<SReal, Eigen::Dynamic, 1> >
{
assert(self.getConstraintProblem());
auto& lambda = self.getConstraintProblem()->f;
return { lambda.ptr(), lambda.size()};
});

s.def("dfree", [](QPInverseProblemSolver& self) -> Eigen::Map<Eigen::Matrix<SReal, Eigen::Dynamic, 1> >
{
assert(self.getConstraintProblem());
auto& dfree = self.getConstraintProblem()->dFree;
return { dfree.ptr(), dfree.size()};
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why you need this whereas there is already these bindings for ConstraintSolverImpl.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the same, but I didn't find why it does not work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate a bit ? What is not working ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When used like that:

import Sofa.SoftRobotsInverse
import Sofa.SofaConstraintSolver


class MyQPInverseProblemSolver(Sofa.SoftRobotsInverse.QPInverseProblemSolver):

    def __init__(self, emio, *args, **kwargs):
        Sofa.SoftRobotsInverse.QPInverseProblemSolver.__init__(self, *args, **kwargs)

    def solveSystem(self):
        W = self.W()
        dfree = self.dfree()
        torques = self.lambda_force()
        return True

The calls to W(), dfree(), and lambda_force() do not work, meaning I get :

terminate called after throwing an instance of 'pybind11::error_already_set'
what():  AttributeError: Unable to find attribute: W

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the expected behavior :)

What is the result of:

q =  MyQPInverseProblemSolver(name="q")
print(dir(q))      # What is the content of q
print(type(q))   # And its type

# Adding the python object 'q' to c++ sofa... 
root.addObject(q)
  
# And retrieve it from the rootNode (so converting it back to python)
q = rootNode.q                
print(dir(q))      # What is the content of q
print(type(q))   # And its type

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

['actuatorsOnly', 'allowSliding', 'bbox', 'componentState', 'countdown', 'displayTime', 'epsilon', 'info', 'listening', 'maxContactForces', 'maxIterations', 'minContactForces', 'multithreading', 'name', 'objective', 'printLog', 'responseFriction', 'reverseAccumulateOrder', 'saveMatrices', 'tags', 'tolerance']
<class 'lab2-inversekinematics.MyQPInverseProblemSolver'>
['actuatorsOnly', 'allowSliding', 'bbox', 'componentState', 'countdown', 'displayTime', 'epsilon', 'info', 'listening', 'maxContactForces', 'maxIterations', 'minContactForces', 'multithreading', 'name', 'objective', 'printLog', 'responseFriction', 'reverseAccumulateOrder', 'saveMatrices', 'tags', 'tolerance']
<class 'lab2-inversekinematics.MyQPInverseProblemSolver'>

@alxbilger
Copy link
Member

The unit test https://github.com/alxbilger/SofaPython3/blob/d16750f7fa6fe1075876877fc831f4d225ec9f90/bindings/Modules/tests/SofaConstraintSolver/matrix_access.py works for me with the following changes (without your PR):

                                                      "Sofa.Component.Mapping.MappedMatrix",
                                                      "Sofa.Component.Mass",
                                                      "Sofa.Component.ODESolver.Backward",
-                                                     "Sofa.Component.Topology.Container.Dynamic"])
+                                                     "Sofa.Component.Topology.Container.Dynamic",
+                                                     "SoftRobots.Inverse"])
 
         root.addObject("FreeMotionAnimationLoop", solveVelocityConstraintFirst=True)
-        root.addObject("GenericConstraintSolver", name="constraint_solver", tolerance=1e-9, maxIterations=1000)
+        root.addObject("QPInverseProblemSolver", name="constraint_solver", tolerance=1e-9, maxIterations=1000)
         root.addObject("StringMeshCreator", name="loader", resolution="20")
 
         root.addObject("EulerImplicitSolver")

Could you check that you have the same result?

@EulalieCoevoet
Copy link
Member Author

The unit test https://github.com/alxbilger/SofaPython3/blob/d16750f7fa6fe1075876877fc831f4d225ec9f90/bindings/Modules/tests/SofaConstraintSolver/matrix_access.py works for me with the following changes (without your PR).
Could you check that you have the same result?

Yes it works. What does not work is when used like that (without duplicating what you did in the module SofaConstraintSolver):

import Sofa.SoftRobotsInverse
import Sofa.SofaConstraintSolver


class MyQPInverseProblemSolver(Sofa.SoftRobotsInverse.QPInverseProblemSolver):

    def __init__(self, emio, *args, **kwargs):
        Sofa.SoftRobotsInverse.QPInverseProblemSolver.__init__(self, *args, **kwargs)

    def solveSystem(self):
        W = self.W()
        dfree = self.dfree()
        torques = self.lambda_force()
        return True

@alxbilger
Copy link
Member

I am considering adding a trampoline class for ConstraintSolverImpl, not only for QPInverseProblemSolver. I see that your version needs specific data from QPInverseProblemSolver in storeResults. Would it be required to write its own storeResults for ConstraintSolverImpl?

@alxbilger alxbilger merged commit 772d658 into SofaDefrost:main Apr 18, 2024
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants