Skip to content

Upgrade Pinocchio Python bindings to nanobind - #2873

Draft
ManifoldFR wants to merge 210 commits into
stack-of-tasks:develfrom
ManifoldFR:pin-nanobind
Draft

Upgrade Pinocchio Python bindings to nanobind#2873
ManifoldFR wants to merge 210 commits into
stack-of-tasks:develfrom
ManifoldFR:pin-nanobind

Conversation

@ManifoldFR

@ManifoldFR ManifoldFR commented Apr 13, 2026

Copy link
Copy Markdown
Member

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
…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
@jorisv jorisv added the pr status wip To not review in weekly meeting label May 11, 2026
@ManifoldFR

Copy link
Copy Markdown
Member Author

Thanks a lot for continuing my work on this @j-matheron ! 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

binding build_all pr status wip To not review in weekly meeting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Annoucement - Switch to nanobind

3 participants