Bipedal-locomotion-framework: Introduce Python and Matlab Bindings

Created on 22 Jun 2020  路  18Comments  路  Source: dic-iit/bipedal-locomotion-framework

I think it may be useful to generate Python and Matlab bindings for the libraries developed in this repository.

The prerequisite to generate the Matlab bindings is an experimental version of Swig. This can be found in the repository: https://github.com/robotology-dependencies/swig/

This task does not have a high priority but we should keep it in mind

Epic

Most helpful comment

With a brief investigation to understand if we can try to call the python bindings generated by bipedal-locomotion-framework from Matlab, I tried to replicate the test_schmitt_trigger_detector.py in Matlab and it seems to work.

It was one monolithic ctrl+c, ctrl+v topped with few python-matlab data mapping specific modifications (Necessary documentation in https://it.mathworks.com/help/matlab/call-python-libraries.html).

Matlab version

>> version

ans =

    '9.9.0.1495850 (R2020b) Update 1'

Ensure to have the bindings in pythonpath.
Firstly, for the current matlab session, I set pyenv('Version', '/usr/bin/python3'). Then I run the script below.


Matlab code

py.importlib.import_module('bipedal_locomotion_framework.bindings');
ph = py.bipedal_locomotion_framework.bindings.StdParametersHandler();
ph.set_parameter_vector_string("contacts", py.list({'right'}));
ph.set_parameter_vector_float("contact_make_thresholds", py.list({100}));
ph.set_parameter_vector_float("contact_break_thresholds", py.list({10}));
ph.set_parameter_vector_float("contact_make_switch_times", py.list({0.2}));
ph.set_parameter_vector_float("contact_break_switch_times", py.list({0.2, 0.2}));

detector = py.bipedal_locomotion_framework.bindings.SchmittTriggerDetector();
assert(detector.initialize(ph) == false)

ph.set_parameter_vector_float("contact_break_switch_times", py.list({0.2}));
assert(detector.initialize(ph))

assert(detector.reset_contacts())

% rise signal
assert(detector.set_timed_trigger_input("right", 0.1, 120.0))
assert(detector.advance())
assert(detector.set_timed_trigger_input("right", 0.2, 120.0))
assert(detector.advance())
assert(detector.set_timed_trigger_input("right", 0.3, 120.0))
assert(detector.advance())

% contact state should turn true
right_contact = detector.get("right");
assert(right_contact.is_active)
assert(right_contact.switch_time == 0.3)

% fall signal
assert(detector.set_timed_trigger_input("right", 0.4, 7.0))
assert(detector.advance())
assert(detector.set_timed_trigger_input("right", 0.5, 7.0))
assert(detector.advance())
assert(detector.set_timed_trigger_input("right", 0.6, 7.0))
assert(detector.advance())

%contact state should turn false
right_contact = detector.get("right");
assert(right_contact.is_active == false)
assert(right_contact.switch_time == 0.6)

%add a new contact
params = py.bipedal_locomotion_framework.bindings.SchmittTriggerParams();
params.off_threshold = 10;
params.on_threshold = 100;
params.switch_off_after = 0.2;
params.switch_on_after = 0.2;
detector.add_contact("left", false, params, 0.6);
contacts = detector.get();
assert(double(py.len(keys(contacts))) == 2);
assert(contacts{'right'}.is_active == false);

% test multiple measurement updates
right_input = py.bipedal_locomotion_framework.bindings.SchmittTriggerInput();
right_input.time = 0.7;
right_input.value = 120;
left_input = py.bipedal_locomotion_framework.bindings.SchmittTriggerInput();
left_input.time = 0.7;
left_input.value = 120;
timed_inputs = py.dict(pyargs('right',right_input,'left',left_input));
assert(detector.set_timed_trigger_inputs(timed_inputs))


% test removing a contact
assert(detector.remove_contact("left"));
contacts = detector.get();
assert(double(py.len(keys(contacts))) == 1);

%test resetting a contact
assert(detector.reset_contact("right", true, params));
right_contact = detector.get("right");
assert(right_contact.is_active == true);

All 18 comments

Nice idea @GiulioRomualdi, probably this can be also useful to the @dic-iit/reinforcement-learning guys

Probably looking into iDynTree can provide good insight on how to set up this. A tricky aspect may be how to properly generate bindings for data structures that are actually wrapped in iDynTree bindings, for example iDynTree::VectorDynSize. In theory it is possible to import the iDynTree.i of iDynTree to permit to do so, but in practice I never tried.

@DanielePucci Indeed! I had a super quick look to the code and it seems that the public APIs use quite a lot non-STL types. In my experience it could be tricky to bind them with swig.

@DanielePucci

Nice idea @GiulioRomualdi, probably this can be also useful to the @dic-iit/reinforcement-learning guys

I was thinking also to the @dic-iit/ironcub team. At least for the Simulator #46

Another interesting tool for generating python bindings is pybind11.
In Drake they replaced SWIG bindings with Pybind11 https://github.com/RobotLocomotion/drake/pull/4949

It's a powerful alternative if you just need Python. The beauty of SWIG is that it can be adapted with small effort also for other languages, and we always relied on it. I remember a benchmark that also showed that SWIG has fairly good performances wrt to the other solutions. The trade off is the steep learning curve of its .i files.

Note that pybind11 is not directly a replacement for SWIG, as it is basically a replacement for some lower level aspects of SWIG, but while for SWIG the code that wrap every class/function/method is automatically generate by the parsing of the .h file, with pybind11 you need to manually write which class/function/method you want to wrap, see for example all the files present in https://github.com/RobotLocomotion/drake/tree/master/bindings/pydrake .

There are indeed a few tools that work on the top of pybind11 and that combined with pybind11 are able to provide the functionality similar to the one of SWIG , you can find some of them listed in https://github.com/jslee02/awesome-cpp-python-binding-generator#pybind11 and https://github.com/RobotLocomotion/drake/issues/7889 .

I will convert this issue into an epic and I will open the associated issue for python and matlab

I did not know that latest version of MATLAB come with a C++ FFI (Foreign Function Interface) named clibgen. It seems to be still quite limited to C++98 and a few C++11 support (see https://www.mathworks.com/help/matlab/matlab_external/limitations-to-cpp-support-in-matlab-cpp-library-interface.html) but it could be a nice option, perhaps for simplified APIs for interoperability. An introductory blog post is https://blogs.mathworks.com/developer/2019/07/11/cpp-interface/ .

Given https://github.com/dic-iit/bipedal-locomotion-framework/pull/131, if the goal is always to get MATLAB bindings as well a possible strategy is to provide nice Python bindings and then access the resulting Python library from MATLAB (instead of directly accessing the C++ library), by using MATLAB support to call Python, see https://www.mathworks.com/help/matlab/call-python-libraries.html . I have no clue of the performance of this method.

Back in time I think this was the strategy used by Drake (see https://github.com/RobotLocomotion/drake/issues/5981) but I guess that at some point then they just dropped MATLAB support, not sure if for limitation of this approach or just because the lack of interest in MATLAB.

Given #131, if the goal is always to get MATLAB bindings as well a possible strategy is to provide nice Python bindings and then access the resulting Python library from MATLAB (instead of directly accessing the C++ library), by using MATLAB support to call Python, see https://www.mathworks.com/help/matlab/call-python-libraries.html . I have no clue of the performance of this method.

I thought the same few days ago, but then I thought that since the passage from bindings to C++ code is usually slow, having two of these in the pipeline would have been a performance killer, but I agree it would need to be tested.

Example of use of a Python library in MATLAB: https://github.com/CATIA-Systems/FMPy/issues/228 .

Example of use of a Python library in MATLAB: CATIA-Systems/FMPy#228 .

Thanks for posting this, besides calling existing Python libraries from Matlab, I am also interested in finding a way to call C++ libraries in Matlab and Python. For Python, it seems that pybind11 is quite convenient, if the resulting Python module is callable from Matlab, that would be very convenient.
Matlab introduced a feature to auto generate bindings for C++ dlls, I wonder if something similar is available for pybind11? I.e. some code to parse a C++ header file and auto resolve the macros for pybind.

Thanks for your input!

Matlab introduced a feature to auto generate bindings for C++ dlls, I wonder if something similar is available for pybind11? I.e. some code to parse a C++ header file and auto resolve the macros for pybind.

There are a few such projects, that are similar to SWIG permit to start directly from header files and generate bindings, but that differently from SWIG build on the top of pybind11. Unfortunately, we still did not explored their use, but you can find a list of them in:

Matlab introduced a feature to auto generate bindings for C++ dlls, I wonder if something similar is available for pybind11? I.e. some code to parse a C++ header file and auto resolve the macros for pybind.

There are a few such projects, that are similar to SWIG permit to start directly from header files and generate bindings, but that differently from SWIG build on the top of pybind11. Unfortunately, we still did not explored their use, but you can find a list of them in:

Thanks, this is very usefull!

For the time being, we decided to support only python bindings

With a brief investigation to understand if we can try to call the python bindings generated by bipedal-locomotion-framework from Matlab, I tried to replicate the test_schmitt_trigger_detector.py in Matlab and it seems to work.

It was one monolithic ctrl+c, ctrl+v topped with few python-matlab data mapping specific modifications (Necessary documentation in https://it.mathworks.com/help/matlab/call-python-libraries.html).

Matlab version

>> version

ans =

    '9.9.0.1495850 (R2020b) Update 1'

Ensure to have the bindings in pythonpath.
Firstly, for the current matlab session, I set pyenv('Version', '/usr/bin/python3'). Then I run the script below.


Matlab code

py.importlib.import_module('bipedal_locomotion_framework.bindings');
ph = py.bipedal_locomotion_framework.bindings.StdParametersHandler();
ph.set_parameter_vector_string("contacts", py.list({'right'}));
ph.set_parameter_vector_float("contact_make_thresholds", py.list({100}));
ph.set_parameter_vector_float("contact_break_thresholds", py.list({10}));
ph.set_parameter_vector_float("contact_make_switch_times", py.list({0.2}));
ph.set_parameter_vector_float("contact_break_switch_times", py.list({0.2, 0.2}));

detector = py.bipedal_locomotion_framework.bindings.SchmittTriggerDetector();
assert(detector.initialize(ph) == false)

ph.set_parameter_vector_float("contact_break_switch_times", py.list({0.2}));
assert(detector.initialize(ph))

assert(detector.reset_contacts())

% rise signal
assert(detector.set_timed_trigger_input("right", 0.1, 120.0))
assert(detector.advance())
assert(detector.set_timed_trigger_input("right", 0.2, 120.0))
assert(detector.advance())
assert(detector.set_timed_trigger_input("right", 0.3, 120.0))
assert(detector.advance())

% contact state should turn true
right_contact = detector.get("right");
assert(right_contact.is_active)
assert(right_contact.switch_time == 0.3)

% fall signal
assert(detector.set_timed_trigger_input("right", 0.4, 7.0))
assert(detector.advance())
assert(detector.set_timed_trigger_input("right", 0.5, 7.0))
assert(detector.advance())
assert(detector.set_timed_trigger_input("right", 0.6, 7.0))
assert(detector.advance())

%contact state should turn false
right_contact = detector.get("right");
assert(right_contact.is_active == false)
assert(right_contact.switch_time == 0.6)

%add a new contact
params = py.bipedal_locomotion_framework.bindings.SchmittTriggerParams();
params.off_threshold = 10;
params.on_threshold = 100;
params.switch_off_after = 0.2;
params.switch_on_after = 0.2;
detector.add_contact("left", false, params, 0.6);
contacts = detector.get();
assert(double(py.len(keys(contacts))) == 2);
assert(contacts{'right'}.is_active == false);

% test multiple measurement updates
right_input = py.bipedal_locomotion_framework.bindings.SchmittTriggerInput();
right_input.time = 0.7;
right_input.value = 120;
left_input = py.bipedal_locomotion_framework.bindings.SchmittTriggerInput();
left_input.time = 0.7;
left_input.value = 120;
timed_inputs = py.dict(pyargs('right',right_input,'left',left_input));
assert(detector.set_timed_trigger_inputs(timed_inputs))


% test removing a contact
assert(detector.remove_contact("left"));
contacts = detector.get();
assert(double(py.len(keys(contacts))) == 1);

%test resetting a contact
assert(detector.reset_contact("right", true, params));
right_contact = detector.get("right");
assert(right_contact.is_active == true);

Was this page helpful?
0 / 5 - 0 ratings