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

Enable Python bindings for Dubins path segments #1261

Merged

Conversation

srmainwaring
Copy link
Contributor

@srmainwaring srmainwaring commented Apr 2, 2025

Provide a binding generator for std::optional<T> and change the storage type of the static array of DubinsPathSegmentType from c-style arrays to std::vector. This is to allow the Python binding generator to generate interfaces for the DubinsPath returned by the getPath function in the DubinsStateSpace.

This change is more intrusive than I would have liked (ideally the C++ code would not need to change to provide bindings). The preferred solution would be to add a generator for c-style arrays of fixed size, however attempts at this were not successful (see below).

Alternatives

Looked into providing bindings for the c-array types directly and also using std::array<T, N>. For some reason neither of these approaches was successful. The bindings appear to have been generated by the compiler but the following errors are reported at runtime when loading the module (i.e. >>> from ompl import base as ob):

TypeError: No to_python (by-value) converter found for C++ type: ompl::base::DubinsStateSpace::DubinsPathSegmentType [3]

or in the case of std::array:

TypeError: No Python class registered for C++ class std::__1::array<std::__1::array<ompl::base::DubinsStateSpace::DubinsPathSegmentType, 3ul>, 6ul>

This branch (https://github.com/srmainwaring/ompl/tree/prs/pr-fix-path-segment-array-binding) contains a version of the DubinsStateSpace using std::array.

Testing

The DubinsAirplane.py demo is updated to display details of the Dubins curve segments from the generated path.

Debug:   RRTstar: Planner range detected to be 7.242362
Warning: RRTstar requires a state space with symmetric distance and symmetric interpolation.
         at line 101 in /Users/rhys/Code/ompl/ompl/src/ompl/geometric/planners/rrt/src/RRTstar.cpp
...
Info:    RRTstar: Started planning with 1 states. Seeking a solution better than 0.00000.
Info:    RRTstar: Initial k-nearest value of 83
Info:    RRTstar: Found an initial solution with a cost of 18.60 in 115 iterations (23 vertices in the graph)
Info:    RRTstar: Created 669 new states. Checked 224115 rewire options. 1 goal states in tree. Final solution cost 15.544
Info:    Solution found in 10.113416 seconds
-3.25318 6.34438 -2.35268 -0.225403 
-3.22259 6.33686 -2.36956 -0.256906 
-3.19225 6.32838 -2.38643 -0.288409 
-3.16219 6.31894 -2.40331 -0.319912 
-3.13245 6.30857 -2.42018 -0.351416 
-3.10305 6.29726 -2.43706 -0.382919 
-3.07402 6.28503 -2.45393 -0.414422 
...
Path length is  15.543926579752133
from_state: -3.3, 6.3, -2.4, -0.2
to_state:   -3.2, 6.3, -2.4, -0.3
path_type.phi: 0.00
path_type.deltaZ: -0.0169
path_type.numTurns: 0
path_type.path: DubinsPath[ type=RSL, length=0.0315032+0+0=0.0315032, reverse=0 ]
path_type.path.type: DUBINS_RIGHT, DUBINS_STRAIGHT. DUBINS_LEFT
path_type.path.length: 0.0315, 0.0000, 0.0000
path_type.path.reverse: False
from_state: -3.2, 6.3, -2.4, -0.3
to_state:   -3.2, 6.3, -2.4, -0.3
path_type.phi: 0.00
path_type.deltaZ: -0.0169
path_type.numTurns: 0
path_type.path: DubinsPath[ type=LSR, length=0+0+0.0315032=0.0315032, reverse=0 ]
path_type.path.type: DUBINS_LEFT, DUBINS_STRAIGHT. DUBINS_RIGHT
path_type.path.length: 0.0000, 0.0000, 0.0315
path_type.path.reverse: False
...

@mamoll
Copy link
Member

mamoll commented Apr 2, 2025

First off, thanks for creating these python bindings. I thought about it and decided it was too much effort.

I can't reproduce your results. The code compiles, but when I run DubinsAirplane.py I get this error:

Traceback (most recent call last):
  File "/usr/share/ompl/demos/DubinsAirplane.py", line 170, in <module>
    plan(args.space, args.planner)
  File "/usr/share/ompl/demos/DubinsAirplane.py", line 137, in plan
    path_type = space.getPath(from_state, to_state)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: No to_python (by-value) converter found for C++ type: std::optional<ompl::base::VanaOwenStateSpace::PathType>

I built your branch inside a Docker container using this Dockerfile. Can you add more details on your setup (versions of OS, compiler, boost, castxml, pyplusplus, pygccxml, ...)? You can attach your CMakeCache.txt, which should have all the relevant info.

@mamoll
Copy link
Member

mamoll commented Apr 2, 2025

Nvm, I see now that you only added the bindings for OwenStateSpace and not the others:

        self.add_optional_wrapper('ompl::base::OwenStateSpace::PathType') 

Let me try again by adding similar lines for the other PathTypes.

@srmainwaring
Copy link
Contributor Author

srmainwaring commented Apr 2, 2025

Yes, only added the optional wrapper for OwenStateSpace::PathType. It does not give rise to compilation errors in my environment:

  • System: macOS Sequoia 15.2, Xcode 16.2
  • Pkgmgr: brew
  • Python: brew installed Python 3.13.1 running in a venv.
castxml --version
castxml version 0.6.10

CastXML project maintained and supported by Kitware (kitware.com).

Homebrew clang version 19.1.6
Target: x86_64-apple-darwin24.2.0
Thread model: posix

pygccxml and pyplusplus are installed into the VM from source. A small patch is required to pygccxml for this version of macOS / Xcode as the default location for the C++ stdlib is not automatically resolved. It can probably be placed in a config file somewhere but I have yet not figured that out.

I tried applying similar changes to the ReedSheppStateSpace which also uses static c-array storage for the various path combinations, there are a number of compilation issues to resolve there however. I suspect it has to do with copy and move constructors for the ReedsSheppPath that the Boost Python internals are expecting. The DubinsStateSpace and ReedSheppStateSpace differ slightly in their use of the nested Path class (use of temporaries etc) and I expect this is the reason one compiles and not the other.

CMakeCache.txt

@mamoll
Copy link
Member

mamoll commented Apr 2, 2025

This might be helpful for creating bindings for raw pointers or std::array<T,N>:

https://pyplusplus.readthedocs.io/en/latest/functions/call_policies/as_tuple.html

@srmainwaring
Copy link
Contributor Author

srmainwaring commented Apr 2, 2025

The branch I linked above (https://github.com/srmainwaring/ompl/tree/prs/pr-fix-path-segment-array-binding) uses that code adapted to std::array<T, 3>. It all builds but for some reason at run time the bindings are not found and the library fails to load. I expect the same code could also be adapted for c-style arrays.

It may be an ordering issue, but even when I moved the wrapper, in this case

self.add_c_array_3_wrapper('ompl::base::DubinsStateSpace::DubinsPathSegmentType')

to the top of the ompl_base_generator_t I could not get the binding to resolve. It's odd because the pattern for registering follows that for std::optional, which is found.

Note: I may have messed the naming of the registration method up in this branch while squashing and rebasing.

@mamoll
Copy link
Member

mamoll commented Apr 3, 2025

I checked out your std::array version and can't figure out why it doesn't work. It seems like it should. I did some benchmarking with this PR. Although the use of std::vectors for fixed-size arrays is not ideal, it doesn't seem to negatively impact performance, so I think we can let that slide.

path_type = space.getPath(from_state, to_state)
db_path = path_type.path_

print(f"from_state: {from_state[0]:.1f}, {from_state[1]:.1f}, "
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 a nice demonstration that the Python bindings work, but it is only correct for OwenStateSpace. It doesn't work for the other types. I'd leave the whole if path.getStateCount() > 1: ... block out

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated in 19523f6



# add wrappers for std::optional types
self.add_optional_wrapper('ompl::base::OwenStateSpace::PathType')
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
self.add_optional_wrapper('ompl::base::OwenStateSpace::PathType')
self.add_optional_wrapper('ompl::base::OwenStateSpace::PathType')
self.add_optional_wrapper('ompl::base::VanaStateSpace::PathType')
self.add_optional_wrapper('ompl::base::VanaOwenStateSpace::PathType')

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated in d24b283

@srmainwaring
Copy link
Contributor Author

I checked out your std::array version and can't figure out why it doesn't work. It seems like it should.

It has me confused as well. The registration does not seem materially different from that for std::optional, so I cannot see why it is not being picked up by the generator. I think the only thing for it is to create a simple standalone pyplusplus example and build up cases step by step to understand what is going on. I plan to do that, as it is bugging me that it will not work.

Thanks for the feedback. I'll update the PR with the changes. Do you prefer separate commits to follow the feedback, or rebased and squashed (I'm used to ArduPilot which prefers the latter, but either is good by me).

For some context: I'm using the existing OMPL bindings in a Python port of the ETHZ terrain_navigation library. The port is here: terrain_nav_py, and the intention is to initially have it integrated into a MAVProxy module for mission generation (ArduPilot/MAVProxy#1533). It's all working fairly well, but using the OwenStateSpace rather than the Python implemented DubinsAirplaneStateSpace will give a significant performance uptick, hence the desire for the additional bindings.

If you'd prefer to hold off a merge until I can resolve getting the bindings for std::array and c-arrays working, that's good by me, as I can continue testing the OwenStateSpace with a local version, and having the bindings in main is not currently holding anything up.

- Add a binding generator for std::optional<T>.
- Apply to OwenStateSpace::getPath.
- Apply to remaining state space types requiring them.

Signed-off-by: Rhys Mainwaring <[email protected]>
- Replace c-style arrays with std::vector, which has a binding generator.

Signed-off-by: Rhys Mainwaring <[email protected]>
@srmainwaring srmainwaring force-pushed the prs/pr-fix-path-segment-vector-binding branch from 3562725 to 19523f6 Compare April 3, 2025 18:36
@mamoll
Copy link
Member

mamoll commented Apr 4, 2025

Let's merge this PR and consider a possible refactor to use std::array in a later PR. This PR works and doesn't negatively affect performance of non-Python code, so it's just a stylistic issue. We're about to release OMPL 1.7, so it'd be nice to have this in the release.

@mamoll mamoll merged commit 2e953fe into ompl:main Apr 4, 2025
4 checks passed
@srmainwaring
Copy link
Contributor Author

srmainwaring commented Apr 4, 2025

@mamoll thanks! Is there a window prior to the 1.7 for a quick addition that exposes the length() member functions in the PathType. These are not generated automatically by pyplusplus for some reason, but I have a patch that forces them to be included and this would be good to have as well as they, with the other changes, allow you to extract the full OwenStateSpace paths segment by segment (i.e. deduce segment start positions, tangents and curvature), which is required for airplane path guidance controllers.

PR here: #1264

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.

Missing Python binding for std::__1::optional<ompl::base::OwenStateSpace::PathType>
2 participants