Add pycddp Python bindings and CMake install packaging#160
Merged
astomodynamics merged 17 commits intomasterfrom Apr 2, 2026
Merged
Add pycddp Python bindings and CMake install packaging#160astomodynamics merged 17 commits intomasterfrom
astomodynamics merged 17 commits intomasterfrom
Conversation
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.
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.
Pin pybind11 to <3 to avoid incompatibility with older bindings. Install the Python extension and package files into the pycddp target directory directly.
There was a problem hiding this comment.
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
pycddpPython 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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
pycddpPython package exposing the full CDDP solver API (dynamics, objectives, constraints, options, solution)find_package(cddp)config so the C++ library is consumable as an installed packageChanges
Python bindings (
python/)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 typesPyDynamicalSystem,PyConstraintallow subclassing dynamics and constraints from Pythonsolve()and reacquired for Python callbacks; threaded callback exceptions are rethrown on the main threadpython/pycddp/__init__.pyre-exports all public symbols with a clearImportErrormessage;_version.pytracks0.1.0test_pendulum.py,test_options.py,test_constraints.py,test_custom_dynamics.py,test_all_dynamics.py,test_nonlinear_objective.py,test_solver_errors.pypyproject.toml(scikit-build-core backend),.python-versionCMake packaging
cmake/cddpConfig.cmake.in+ install/export rules forcddpTargets, headers, and version fileCDDP_CPP_BUILD_PYTHON,CDDP_CPP_BUILD_EXAMPLESbuild optionsPOSITION_INDEPENDENT_CODEenabled on core library for shared-library consumerstests/package_smoke/) validatesfind_package(cddp)after installRemove Matplot++ dependency
matplotfetch and link from rootCMakeLists.txt#include "matplot/matplot.h"fromcddp.hpptests/test_matplot.cppand stripped plotting-only test code from dynamics testsC++ core
cddp_solver_base.cpp: fixed parallel forward-pass so a successful alpha still wins when another trial throwsTest Plan
pip install -e .[test] && pytest python/tests -q— Python integration testscmake -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 Matplotcmake --build build --target install && ctest --test-dir build -R package_smoke— CMake package smoke testdocker build .