Skip to content

Upgrade Pinocchio Python bindings to nanobind#2873

Draft
ManifoldFR wants to merge 204 commits intostack-of-tasks:develfrom
ManifoldFR:pin-nanobind
Draft

Upgrade Pinocchio Python bindings to nanobind#2873
ManifoldFR wants to merge 204 commits intostack-of-tasks:develfrom
ManifoldFR:pin-nanobind

Conversation

@ManifoldFR
Copy link
Copy Markdown
Member

@ManifoldFR ManifoldFR commented Apr 13, 2026

Description

This pull request adds a new set of Python bindings using the modern nanobind Python bindings framework.

The new set of bindings is a full replacement of the Boost.Python bindings, which will be deprecated in the next major version of Pinocchio.

This PR is the sequel to coal-library/coal#659 on the Coal library, and actually depends on the Coal nanobind bindings as well as nanoeigenpy.

Checklist

  • I have run pre-commit run --all-files or pixi run lint
  • I have performed a self-review of my own code
  • I have commented my code where necessary
  • I have made corresponding changes to the doxygen documentation
  • I have added tests that prove my fix or feature works
  • I have updated the CHANGELOG or added the "no changelog" label if it's a CI-related issue
  • I have updated the README credits section

Task checklist

  • Math

    • RPY (with submodule)
    • Tridiagonal matrix
    • Linalg (eigenvalue/eigenvector/inverse)
    • Lanczos decomposition
    • Gram-Schmidt
  • Spatial

    • Inertia
      • Inertia
      • LogCholeskyParams
      • PseudoInertia
    • SE3
    • Motion
    • Symmetric
    • explog
  • Multibody

    • Model
    • Data
    • GeometryModel
    • GeometryData
    • Lie groups
    • Joints
      • Generic JointModel/JointData
      • Variant + base visitor
      • Derived classes (with specific add-ons)
      • Joint Lie groups
    • Pool (class ModelPool...)
  • Algorithm

    • ABA
    • ABA derivatives
    • CAT (computeAllTerms())
    • Cholesky
    • COM
    • Centroidal
    • Centroidal derivatives
    • CRBA
    • LCABA (added after Lcaba python bindings Simple-Robotics/pinocchio#32)
    • Constrained dynamics
      • Constraint Cholesky
      • Delassus operators
    • Constrained dynamics derivatives
    • Constraint solvers
      • ADMM
      • PGS
    • Contact dynamics
    • Contact inverse dynamics
    • Contact Jacobian
    • Geometry (updateGeometryPlacements()...)
    • Impulse dynamics
    • Impulse dynamics derivatives
    • Jacobian
    • Delassus
    • Kinematic regressor
    • Kinematics
    • Kinematics derivatives
    • Regressor
    • Model (appendModel(), buildReducedModel(), findCommonAncestor(), transformJointIntoMimic())
    • Energy
    • Frames (updateFramePlacement, framesForwardKinematics...)
    • Frames derivatives
    • RNEA
    • RNEA derivatives
    • Parallel algorithms:
      • Parallel ABA
      • Parallel RNEA
      • bind OpenMPException exception OPTIONAL
      • Util (omp_get_max_threads())
  • Collision

    • Broadphase
    • Collision (expose-collision.cpp in old bindings - class ComputeCollision, functions computeCollision[s]()...)
    • "Coal
      • Conversion from coal.Transform3s to pin.SE3 (REVERSE NOT POSSIBLE)
      • Serialization
    • Pool
      • GeometryModel/GeometryData pool
      • Broadphase manager pool
  • Parsers

    • URDF
      • Model
      • Geometry
    • SDF
    • SRDF
    • MJCF
    • Graph
  • Constraints

    • Generic
    • Variant + base visitor
    • Inheritance visitors (subclasses of BinaryKinematicsConstraint{Model,Data}Base, PointConstraint{Model,Data}Base)
    • Derived classes
    • Cones and sets (Coulomb, Dual Coulomb, Box, zero, full space, non-negative orthant)
  • Utilities

    • Type caster for boost::variant (done in pinocchio/bindings/python-nb/utils/boost-variant.hpp)
    • Convert classes (mostly pinocchio/spatial) to numpy arrays using __array__() method, allows
      f = pin.Force()
      f_arr = np.array(f, copy=...)
  • Custom call policy to disallow the use of JointMimic. nanobind has call policies.

  • Serialization (pretty much everything missing)

  • Add Pixi feature and environment

  • Stubs (working)

Known limitations

  • This PR only adds nanobind bindings for the double scalar type (perhaps float).
  • There is no equivalent to the boost::python::convert::implicit struct, which allows to define custom implicit converters between types. There are two avenues for creating implicit converters in nanobind:
    1. {constructor + declare nb::implicitly_convertible/use nb::init_implicit},
    2. creating a type caster.
      This allows defining the coal.Transform3s -> pinocchio::SE3 conversion by registering an extra ctor, but the reverse is not possible.

Other modified

  • pinocchio/visualize/meshcat_visualize.py - remove import of DeprecatedWarning, replaced by Python built-in DeprecationWarning

Other removed

  • pinocchio/deprecation.py - only used in meshcat_visualizer.py through the DeprecatedWarning
  • completely unused header pinocchio/bindings/python/multibody/joint/joint.hpp

Upstream changes

Notes

  • In some of the new Pinocchio 4 stuff we use std::optional whereas we use boost::optional elsewhere (for the bindings mostly):
    • some of the (member) function/ctor overloads in the Pinocchio C++ source currently exist because we couldn't bind boost::optional:
    • we can now in Boost.Python/eigenpy since a while (I added it)
    • we should modernize the source code to use std::optional, and use it everywhere instead of boost::optional
    • nanobind
  • Similarly, we should start using std::string_view where appropriate
  • The __repr__ method nanobind implements when using nb::bind_vector to bind STL vectors uses the elements' __repr__, so a vector of pinocchio.Model has a VERY long representation.
    • We should distinguish __repr__ from __str__ on some classes.
  • I tried to bind the OpenMPException exception in nanobind, inheriting from the mainline Python Exception class using:
    nb::type<OpenMPException>(m, "OpenMPException", PyExc_Exception);
    but the exception class doesn't inherit from one of the STL exception base class and doesn't implement the member function ::what()
  • Conversion to NumPy arrays using __array__ was factored into a new visitor (ToNumpyArrayVisitor). Properly supports copies and kwargs now.
  • Nanobind is stricter than eigenpy was regarding conversion of nominally 2D (but actually 1D) (n,1) NumPy arrays to Eigen types: it's not allowed, which means code as follows:
    model = ...
    qmax = np.ones((model.nq, 1))
    q0 = pin.randomConfiguration(model, -qmax, qmax)  # TypeError in NumPy → Eigen caster

Resolves #2616

bindings : add __bindings_framework__ variable to check bindings framework
unittest/python/rpy : test for TypeError, for nanobind.

unittest/python/rpy : import the correct AngleAxis

examples/rpy : fix yet another permissive numpy shape error
…ay module attributes

- just define them in the __init__ :)
…ef) for output args

- permissive for both C and F order numpy.ndarray
…st() here

- doesn't exist for nanobind bindings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Annoucement - Switch to nanobind

1 participant