-
Notifications
You must be signed in to change notification settings - Fork 38
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
Add Python bindings based on pybind11 #134
Add Python bindings based on pybind11 #134
Conversation
726a005
to
0f06adb
Compare
src/ParametersHandler/include/BipedalLocomotion/ParametersHandler/IParametersHandler.h
Outdated
Show resolved
Hide resolved
src/ParametersHandler/include/BipedalLocomotion/ParametersHandler/IParametersHandler.h
Outdated
Show resolved
Hide resolved
src/ParametersHandler/YarpImplementation/src/YarpImplementation.cpp
Outdated
Show resolved
Hide resolved
Btw, thanks a lot @diegoferigo for this. The whole bindings code is a bit Aramaic for me 😁 |
bindings/python/CMakeLists.txt
Outdated
# |-- __init__.py (generated from main bindings CMake file) | ||
# `-- bindings.<cpython_extension> | ||
# | ||
install(TARGETS pybind11_BipedalLocomotion DESTINATION ${PYTHON_INSTDIR}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not part of the review, just a brainstorming not blocking the code to be merged in any way: I wonder if there is a way to install pybind11 bindings in a way that a library that depends on this one is able to write its own bindings that takes in input and return arguments defined in the pybind11 library of blf.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good point, maybe an advanced and rather uncommon use-case but I think that pybind11 to some extent would allow that. Let me think, in a05407e I changed the structure of the project and it could come handy for this use case. Maybe by making bipedal_locomotion.h
public and installing it, I think that downstream projects could use our bindings::Create*
functions to add blf types in their modules.
I would leave this out from this first implementation, but thanks for making me think about this possibility.
0f06adb
to
f9ffcd8
Compare
...andler/YarpImplementation/include/BipedalLocomotion/ParametersHandler/YarpImplementation.tpp
Outdated
Show resolved
Hide resolved
...sHandler/YarpImplementation/include/BipedalLocomotion/ParametersHandler/YarpImplementation.h
Outdated
Show resolved
Hide resolved
5eeb3ef
to
4865a32
Compare
CC @dic-iit/dynamic-interaction-control |
b48b5f2
to
098c28f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@diegoferigo this PR is starting to have two features that are pretty much perpendicular. On one side we have the bindings, and I love that.
On the other hand, we have the modifications on the parameters handler, where I have more concerns and that is enough for a second PR, IMHO.
The biggest concern is the handling of the groups.
src/ParametersHandler/YarpImplementation/src/YarpImplementation.cpp
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@diegoferigo, thank you for the effort. You can find some comments through the code.
I completely understand the reason why you decided to use pybind11 instead of swig, however I'm afraid of the amount of code that has to be written if a new class/method will be implemented in the future. For instance, if I'm going to implement new methods in the TimeVarying DCM planner before having them in python the bindings/python/TimeVaryingDCMPlanner.cpp
has to be changed (and I'm too lazy to do the same modification twice, also because it may be not trivial. E.g. definition of lambda functions.)
bindings/python/QuinticSpline.cpp
Outdated
.def("evaluate_point", | ||
py::overload_cast<const double&, | ||
Eigen::Ref<Eigen::VectorXd>, | ||
Eigen::Ref<Eigen::VectorXd>, | ||
Eigen::Ref<Eigen::VectorXd>>( | ||
&QuinticSpline::evaluatePoint)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably here we can change the python signature to something like
pos, vel, acc = spline.evaluate_point(t)
I think it can be easily done with a lambda function, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't edit the implementation of this class after you propose it in the previous pull request (now that I see it, I just used the py::overload_cast
instead of the ugly pre-C++14 syntax for overloads). I'll check if it is possible returning a std::tuple<double, double, double>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 69fa094. Just to understand, why 4 elements?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just realized that the dimension of pos
is not always 4. It depends on the size of the knots that should be equal to the size of the initial/final velocity/acceleration.
As far as I remember there is no way to retrieve the size of the points, we may think to add a method. in this case, the code may become
Eigen::VectorXd pos(qs.size());
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you suggest to proceed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can revert the previous implementation and that I can figure out how to change it.
I can do it just after this PR get merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've rebased this PR to remove 69fa094.
bindings/python/SwingFootPlaner.cpp
Outdated
.def_readwrite("is_in_contact", &SwingFootPlannerState::isInContact) | ||
.def_property( | ||
"transform", | ||
[](const SwingFootPlannerState& s) { return s.transform.coeffs(); }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this might be error-prone. Indeed s.tranform
is a mainf::SE3d
object
https://github.com/dic-iit/bipedal-locomotion-framework/blob/be1e0a2ea0077e3ed745ec6891aac3e71d35824a/src/Planners/include/BipedalLocomotion/Planners/SwingFootPlanner.h#L31
the method coeffs()
allow you to access the row coefficient of the SE3d
element (7 numbers that represent quaternion + position) The first three-elements represent the translation and the others the quaternion (and actually I don't remember if the conversion is [w x y z] or [x y z w]).
As pointed by @artivis (the maintainer of manif
), manually setting the values of the quaternion may lead to a non-unitary quaternion and consequentially to bugs that are complex to spot artivis/manif#160. (cc @prashanthr05 )
Back in time, I implemented a set of setters for the SE3d object that automatically handled all these problems: artivis/manif#166
I think we should continue in this direction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your comment @GiulioRomualdi, I never used manif
and I'm not familiar with it.
I addressed your comment in 114225a by first exposing what we need of manif::SE3d
and then in 278891f by updating the pybind configuration of the methods that were using that type. Now, since Python is aware of that, we don't have to use lambdas to convert exposed eigen types (supported by pybind) to manif. You can see the result in the updated Python tests of 597b78e.
...andler/YarpImplementation/include/BipedalLocomotion/ParametersHandler/YarpImplementation.tpp
Outdated
Show resolved
Hide resolved
src/ParametersHandler/include/BipedalLocomotion/ParametersHandler/IParametersHandler.h
Outdated
Show resolved
Hide resolved
Thanks @GiulioRomualdi for the comments
I guess this is the price to pay for the decision to use modern standards. To my knowledge there are no valid / widely used / thoroughly documented alternatives to pybind11 for our case. Keep in mind that new contributions to C++ do not strictly need to include also the Python counterpart. If you want to do it, perfect, otherwise the next person that will need the method from Python will implement the missing method. The lambda function is required only if the method uses non-common data types like those from |
Sorry guys but the situation for the I'll open a PR with the current implementation to keep track of the current code (with the corner case) in case anyone wants to get it back. Feel free to close the new PR right after I open it. |
ebaf487
to
f52478a
Compare
41c4270
to
5f65237
Compare
I think after the last commits this PR is finalized. Let me know if you have any other comment to address. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After these two small things, we are good to merge for me!
597b78e
to
e3e37a5
Compare
Hi @diegoferigo I try to replicate the CI error on my laptop. Here I compile in release
Indeed if I compile it in
|
This seems like we are mixing the numpy of pip and the one from apt. |
I investigated on #134 (comment) and I discovered this. Last but not least Finally, since manif is a template-only library it is compiled with blf so if blf is compiled in release Long story short that exception will never raise in release. |
Thanks @GiulioRomualdi for the feedback, brilliant! I initially though something similar too, but it could not explain why also the test that compiles BLF in Debug fails with the same error. Do you have any idea? |
@@ -0,0 +1,107 @@ | |||
import pytest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in the file name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ouch :) good catch
}) | ||
.def_property( | ||
"quaternion", | ||
[](const manif::SE3d& se3) -> Eigen::Vector4d { return se3.coeffs().segment<4>(3); }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember that manif
stores the quaternion like eigen.
https://eigen.tuxfamily.org/dox/classEigen_1_1Quaternion.html#a4e6800985f1205f94c2e83260ebe90cf
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I inspected manif
code, I saw it takes the quaternion and position and serializes them to the coeffs
vector. So, when we extract the quaternion, I take the raw data.
Pybind11 does not know the type Eigen::Quaternion
so we have in any case to map to to a Eigen::Vector4d
if we want automatic conversion to a numpy type.
Actually checking the ci.yml file there seems that |
Now everything starts to make sense, thanks @GiulioRomualdi. I saw you merged here Then, I hope I can squash all those commits, I never tried after a merge commit :) I'm used to push and squash on fork branches, and if needed, rebase. |
Debug is indeed working, thanks @GiulioRomualdi! I'll fix the PR when I can get back to it. |
The assertion is raised only if the project was compiled in Debug. Therefore, testing it would fail in other modes.
70d563d
to
ba9232f
Compare
@GiulioRomualdi in the latest force-push I merged (again) master and squashed all the test commits I did few days ago. Then, I fixed the failing test and disabled Windows (note that all the dependencies are there in case someone wants to finalize its status, only the CMake feature is disables for this platform). I also moved the Edit: I found some related documentation in the pybind11 website. |
d6d200c fixes also the last problem on macOS. The binary of |
Perfect @diegoferigo . Just to recap. On Ubuntu and macOS the bindigs are compiled and the test run without error. On windows the bindigs and the test are disabled, right? |
Correct. |
I think we can merge. I then open an issue to track the compilation of the bindings on windows. @diegoferigo @traversaro @S-Dafarra please give me the 👍 if you agree |
For the records, compilation is successful, but the bindings do not work as intended. This workflow is the latest on windows that failed. I don't have experience with Python on Windows and I have no clue why only there things differ. |
Today it's an important day! We have the first working python bindings for the project. Thank you a lot guys! And thank you @diegoferigo for keeping iterating |
This PR build upon #131 and provides a first working implementation of Python bindings for the following classes:
Contact
ContactList
ContactPhase
ContactPhaseList
QuinticSpline
IParameterHandler
StdImplementation
SwingFootPlanner
DCMPlanner
TimeVaryingDCMPlanner
Each of these classes are accompanied by some unit testing developed with pytest. I updated the previous example that was using unittest, I believe that pytest is superior in features and integration with other Python tools.
This is the first time I develop Python bindings with pybind11, I'm more used with SWIG. This means that the current implementation is not optimal, I'm still in the process of learning tricks for pybind11. All in all, I can say that coming from SWIG, there are many things that are much easier with pybind11.
Here below some caveats I found:
IParametersHandler
APIs logic to select the correct method based on the type cannot work (reliably). I had to define a different overloaded method for each of the supported parameter type.manif
types is still rudimentary, I tried to expose to Python the underlying Eigen container since pybind11 automatically performs the conversion from/to numpy.ContactPhase::activeContacts
is r/o, and some of theContactList
methods have not been exposed to Python. Be aware that the usage theContactList
class, particularly, could differ than the C++ counterpart.Advanceable
andIParametersHandler
, but it's not possible creating Python classes that inherit from them. This is not a big limitation unless we're interested in extending those classes in Python, and it could be addressed if and when we'll going to have this need.Note: I removed the
GenericContainer
bindings initially added by @GiulioRomualdi since they're not currently needed and I still have to figure out how to deal with them.cc @GiulioRomualdi @S-Dafarra @traversaro