Skip to content

Add pycddp Python bindings and CMake install packaging#160

Merged
astomodynamics merged 17 commits intomasterfrom
feat/python-binding
Apr 2, 2026
Merged

Add pycddp Python bindings and CMake install packaging#160
astomodynamics merged 17 commits intomasterfrom
feat/python-binding

Conversation

@astomodynamics
Copy link
Copy Markdown
Owner

@astomodynamics astomodynamics commented Mar 29, 2026

Summary

  • Add a pybind11-based pycddp Python package exposing the full CDDP solver API (dynamics, objectives, constraints, options, solution)
  • Add CMake install/export rules and a find_package(cddp) config so the C++ library is consumable as an installed package
  • Remove Matplot++ dependency from the default build and clean up dead plotting code

Changes

Python bindings (python/)

  • Binding modules (python/src/): bind_solver.cpp, bind_dynamics.cpp, bind_constraints.cpp, bind_objective.cpp, bind_options.cpp, main.cpp — expose all solver variants, 22 dynamics models, quadratic/nonlinear objectives, and 8 constraint types
  • Trampoline classes: PyDynamicalSystem, PyConstraint allow subclassing dynamics and constraints from Python
  • Ownership & GIL: Python-backed solver adapters replace raw-pointer handoff; GIL is released around solve() and reacquired for Python callbacks; threaded callback exceptions are rethrown on the main thread
  • Validation: Python-facing errors for invalid solver names, empty initial trajectories, and autodiff import failures
  • Package layout: python/pycddp/__init__.py re-exports all public symbols with a clear ImportError message; _version.py tracks 0.1.0
  • Python tests: test_pendulum.py, test_options.py, test_constraints.py, test_custom_dynamics.py, test_all_dynamics.py, test_nonlinear_objective.py, test_solver_errors.py
  • Packaging: pyproject.toml (scikit-build-core backend), .python-version

CMake packaging

  • cmake/cddpConfig.cmake.in + install/export rules for cddpTargets, headers, and version file
  • CDDP_CPP_BUILD_PYTHON, CDDP_CPP_BUILD_EXAMPLES build options
  • POSITION_INDEPENDENT_CODE enabled on core library for shared-library consumers
  • Package smoke test (tests/package_smoke/) validates find_package(cddp) after install

Remove Matplot++ dependency

  • Removed matplot fetch and link from root CMakeLists.txt
  • Removed #include "matplot/matplot.h" from cddp.hpp
  • Deleted tests/test_matplot.cpp and stripped plotting-only test code from dynamics tests
  • Legacy visualization examples removed from default build (kept as reference)
  • Docker: removed gnuplot/libjpeg/libpng deps

C++ core

  • cddp_solver_base.cpp: fixed parallel forward-pass so a successful alpha still wins when another trial throws
  • README updated to reflect new build options and Python workflow

Test Plan

  • pip install -e .[test] && pytest python/tests -q — Python integration tests
  • cmake -S . -B build -DCDDP_CPP_BUILD_TESTS=ON -DCDDP_CPP_BUILD_EXAMPLES=OFF && cmake --build build -j && ctest --test-dir build --output-on-failure — C++ tests without Matplot
  • cmake --build build --target install && ctest --test-dir build -R package_smoke — CMake package smoke test
  • Docker build: docker build .

This PR adds a pybind11-based pycddp package with bindings for solver options, dynamics, objectives, constraints, and solver execution, along with Python integration tests and packaging metadata.

It also makes Matplot-dependent includes and test links optional, adds CMake package installation metadata, and prepares the core library for shared-library consumers such as the Python module.
This keeps matplot and CDDP_HAS_MATPLOT available to in-tree builds while removing them from the installed cddp target interface, which avoids the cddpTargets export error.

It also cleans the Docker build step to use explicit CMake and CTest commands and removes the unused Python executable flag.
This updates the installed CMake and Python package layouts so downstream consumers can find cddp headers and import pycddp after install.

It also replaces the unsafe raw-pointer Python ownership handoff with Python-backed solver adapters, acquires the GIL for Python callbacks, and removes the legacy Matplot-driven build surface from the default configuration.
This raises Python-facing errors for invalid solver names and initial trajectories while preserving the existing C++ API behavior.

It also releases the GIL around solve(), rethrows threaded callback failures, and adds regression tests for nonlinear objectives and solution fields.
This restores parallel forward-pass behavior so a successful alpha still wins when another trial throws.

It also adds a Python Constraint trampoline plus regression coverage so custom path constraints can be defined and dispatched through the solver.
Improve Python import/autodiff error reporting and clarify solver ownership docs.
Handle parallel derivative precompute exceptions after draining worker futures,
and delete unreachable Matplot test code instead of preserving dead guards.
@astomodynamics astomodynamics changed the title Add Python bindings and make Matplot integration optional Add pycddp Python bindings and CMake install packaging Mar 30, 2026
Force INSTALL_GTEST off before fetching googletest so enabling CDDP_CPP_BUILD_TESTS does not install the bundled dependency alongside cddp.
Release Python-owned wrapper references under the GIL so bound solver components are cleaned up safely. Also log partial forward-pass thread failures in verbose mode to make solver issues easier to diagnose.
Update the regression assertion to match the current actionable\nbinding message raised when set_initial_trajectory is called\nbefore a dynamical system is configured.
@astomodynamics astomodynamics self-assigned this Mar 30, 2026
@astomodynamics astomodynamics added documentation Improvements or additions to documentation enhancement New feature or request cleanup labels Mar 30, 2026
Pin pybind11 to <3 to avoid incompatibility with older bindings.

Install the Python extension and package files into the pycddp target directory directly.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds first-class Python bindings (pycddp) for the CDDP solver via pybind11, introduces CMake install/export packaging so find_package(cddp) works for installed consumers, and removes the Matplot++ dependency/plotting-only code from the default build.

Changes:

  • Added a pycddp Python package (bindings + trampolines + tests) exposing solver, dynamics, objectives, constraints, and options.
  • Added CMake install/export rules and a package smoke test that builds a consumer project with find_package(cddp).
  • Removed Matplot++ from the default build/test surface and cleaned up plotting-only test code; improved parallel exception handling in the solver base.

Reviewed changes

Copilot reviewed 44 out of 45 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/test_matplot.cpp Removed Matplot++-only test source.
tests/package_smoke/main.cpp Minimal consumer source to validate installed headers build.
tests/package_smoke/CMakeLists.txt Consumer CMake project using find_package(cddp) and cddp::cddp.
tests/package_smoke_test.cmake Scripted install + configure + build smoke test.
tests/dynamics_model/test_spacecraft_roe.cpp Removed Matplot++ include and plotting block.
tests/dynamics_model/test_spacecraft_nonlinear.cpp Removed plotting scaffolding; added finite-state assertions.
tests/dynamics_model/test_spacecraft_linear.cpp Removed Matplot++ include and plotting/helper code.
tests/dynamics_model/test_spacecraft_linear_fuel.cpp Removed Matplot++ namespace usage and plotting/helper code.
tests/dynamics_model/test_mrp_attitude.cpp Removed Matplot++ include/usings; stripped legend calls; formatting.
tests/dynamics_model/test_attitude_dynamics.cpp Removed Matplot++ include and large plotting section; replaced with assertions.
tests/CMakeLists.txt Dropped Matplot link; added PackageInstallSmoke CTest entry.
tests/cddp_core/test_msipddp_solver.cpp Removed Matplot++ include and trailing whitespace.
tests/cddp_core/test_msipddp_core.cpp Removed Matplot++ include and namespace usage.
tests/cddp_core/test_logddp_solver.cpp Removed Matplot++ include and trailing whitespace.
tests/cddp_core/test_ipddp_solver.cpp Removed Matplot++ include and trailing whitespace.
tests/cddp_core/test_constraint.cpp Removed Matplot++ include and visualization-only test cases.
tests/cddp_core/test_clddp_solver.cpp Removed Matplot++ include and trailing whitespace.
tests/cddp_core/test_cddp_core.cpp Added regression tests for parallel forward-pass and derivative-precompute exception behavior.
src/cddp_core/cddp_solver_base.cpp Fixed parallel forward-pass selection when some trials throw; improved parallel derivative precompute exception propagation/logging.
README.md Updated build/examples narrative to reflect Python-first plotting workflow and removed plotting deps.
python/tests/test_solver_errors.py Added Python-facing validation/ImportError regression tests.
python/tests/test_pendulum.py Added end-to-end pendulum solve tests for multiple solvers.
python/tests/test_options.py Added tests for options structs/enums exposure.
python/tests/test_nonlinear_objective.py Added tests for Python-defined nonlinear objective dispatch.
python/tests/test_custom_dynamics.py Added tests for Python-defined dynamics, parallel callback exception surfacing, and autodiff error path.
python/tests/test_constraints.py Added tests for built-in constraints and Python-defined constraint subclass.
python/tests/test_all_dynamics.py Added smoke tests constructing all bound dynamics models.
python/src/main.cpp Defined _pycddp_core pybind11 module entry point.
python/src/bind_solver.cpp Bound solver/solution types; added Python-backed wrappers for lifetime/GIL behavior; validation helpers.
python/src/bind_options.cpp Bound options structs and enums.
python/src/bind_objective.cpp Bound Objective/QuadraticObjective/NonlinearObjective and trampoline.
python/src/bind_dynamics.cpp Bound DynamicalSystem base + many concrete dynamics models; Python trampoline with autodiff error.
python/src/bind_constraints.cpp Bound Constraint base + concrete constraints; Python trampoline.
python/pycddp/_version.py Added package version.
python/pycddp/init.py Public re-exports + actionable ImportError messaging.
python/CMakeLists.txt Added pybind11 module build and install rules.
pyproject.toml Added scikit-build-core Python packaging configuration.
include/cddp-cpp/cddp.hpp Removed Matplot++ include from public umbrella header.
include/cddp-cpp/cddp_core/constraint.hpp Added virtual destructor to Constraint.
examples/CMakeLists.txt Removed visualization-heavy example targets from default build.
Dockerfile Removed plotting deps; updated build/test steps to new CMake flow/options.
CMakeLists.txt Added install/export packaging, build options for examples/python, PIC for core lib, removed Matplot++ FetchContent.
cmake/cddpConfig.cmake.in Added installed package config template with dependencies and targets include.
.python-version Added Python version pin file.
.gitignore Ignored common Python/uv artifacts and native extension outputs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread CMakeLists.txt
Comment thread python/CMakeLists.txt
Comment thread python/src/bind_solver.cpp Outdated
Comment thread python/tests/test_options.py
Comment thread python/src/bind_solver.cpp
Reject abstract base instances before the solver owns them and keep native callbacks on the no-GIL fast path. Add regression coverage for solver aliases, parallel native callbacks, and the isolated import error path.
Export both installed include roots so downstream consumers can use the public cddp-cpp header layout while existing internal quoted includes keep working. Strengthen the package smoke test to compile against installed cddp-cpp headers directly.
Tighten repository-facing prose and remove generic template language.\nKeep the changes limited to documentation and comments without affecting build behavior.
Add a pull request workflow that reviews Python dependency changes.

Enforce a conservative policy for uv lockfile sources and require explicit allowlisting for new top-level dependencies.
Treat uv.lock as optional when the repository does not track it.\nStill fail if the base ref had a lockfile and the current change removes it.
@astomodynamics astomodynamics merged commit a0a0f5c into master Apr 2, 2026
4 checks passed
@astomodynamics astomodynamics deleted the feat/python-binding branch April 2, 2026 20:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cleanup documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants