diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 00000000..f68b3b9d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,55 @@
+name: Bug report
+description: Report a reproducible defect in the solver, bindings, build, or docs
+title: "[bug] "
+labels:
+ - bug
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to report a problem.
+
+ Please include a minimal reproduction and the exact commands you ran.
+ - type: textarea
+ id: summary
+ attributes:
+ label: Summary
+ description: What happened, and what did you expect to happen instead?
+ placeholder: A concise description of the bug and expected behavior.
+ validations:
+ required: true
+ - type: textarea
+ id: reproduction
+ attributes:
+ label: Reproduction
+ description: Provide the smallest reproduction you can, including commands and inputs.
+ placeholder: |
+ 1. Configure with ...
+ 2. Run ...
+ 3. Observe ...
+ validations:
+ required: true
+ - type: textarea
+ id: logs
+ attributes:
+ label: Logs and output
+ description: Paste the relevant error message, stack trace, or failing test output.
+ render: text
+ - type: textarea
+ id: environment
+ attributes:
+ label: Environment
+ description: Share the platform and toolchain details relevant to the bug.
+ placeholder: |
+ OS:
+ Compiler:
+ CMake:
+ Python:
+ Build flags:
+ validations:
+ required: true
+ - type: textarea
+ id: context
+ attributes:
+ label: Additional context
+ description: Add anything else that might help debug the issue.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000..e504119a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: true
+contact_links:
+ - name: Security policy
+ url: https://github.com/astomodynamics/cddp-cpp/blob/master/SECURITY.md
+ about: Read the security reporting guidance before disclosing a vulnerability.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 00000000..b59af5ac
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,36 @@
+name: Feature request
+description: Propose an improvement to the solver, examples, bindings, or docs
+title: "[feature] "
+labels:
+ - enhancement
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Please describe the problem first, then the change you want.
+ - type: textarea
+ id: problem
+ attributes:
+ label: Problem or use case
+ description: What limitation, missing capability, or workflow pain does this address?
+ placeholder: I am trying to ...
+ validations:
+ required: true
+ - type: textarea
+ id: proposal
+ attributes:
+ label: Proposed change
+ description: Describe the API, behavior, or workflow you would like to see.
+ placeholder: Add support for ...
+ validations:
+ required: true
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: Alternatives considered
+ description: What workarounds or alternative designs have you considered?
+ - type: textarea
+ id: context
+ attributes:
+ label: Additional context
+ description: Benchmarks, references, examples, or prior art that help clarify the request.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..fbfb40b7
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,16 @@
+## Summary
+- Describe the change and why it is needed.
+
+## Changes
+- List the concrete code, API, testing, or documentation updates.
+
+## Validation
+- [ ] `...`
+
+## Checklist
+- [ ] I ran the relevant tests or explained why they were not run.
+- [ ] I updated documentation when behavior or public interfaces changed.
+- [ ] I kept the change focused and avoided unrelated cleanup.
+
+## Notes
+- Add reviewer context only when it is needed.
diff --git a/.github/workflows/PR_TEMPLATE/pull_request_template.md b/.github/workflows/PR_TEMPLATE/pull_request_template.md
deleted file mode 100644
index 9ea67c13..00000000
--- a/.github/workflows/PR_TEMPLATE/pull_request_template.md
+++ /dev/null
@@ -1,14 +0,0 @@
-## Summary
-
--
-
-## Changes
-
--
-
-## Test Plan
-
-- [ ]
-
-## Notes
-
diff --git a/.gitignore b/.gitignore
index a5716e3c..243ac7e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,7 @@ build/
# Output
plots/
-results/frames/
+results/
# Models
models/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3356a3c5..943047c1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -157,7 +157,6 @@ set(dynamics_model_srcs
src/dynamics_model/spacecraft_nonlinear.cpp
src/dynamics_model/dreyfus_rocket.cpp
src/dynamics_model/spacecraft_landing2d.cpp
- src/dynamics_model/spacecraft_roe.cpp
src/dynamics_model/lti_system.cpp
src/dynamics_model/spacecraft_twobody.cpp
src/dynamics_model/usv_3dof.cpp
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..a86031d3
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,27 @@
+# Code of Conduct
+
+## Our standard
+
+This project expects respectful, technical, and constructive collaboration.
+
+Examples of expected behavior:
+
+- focusing feedback on the code, design, and evidence
+- being clear, patient, and specific in reviews and discussions
+- assuming good intent while still challenging weak technical arguments
+- helping other contributors understand project constraints and expectations
+
+Examples of unacceptable behavior:
+
+- personal attacks, harassment, or discriminatory language
+- hostile or deliberately dismissive review behavior
+- repeated bad-faith argumentation or thread derailment
+- publishing private or sensitive information without permission
+
+## Enforcement
+
+Project maintainers may remove, edit, or reject comments, issues, pull requests, code, and other contributions that violate this code of conduct.
+
+## Reporting
+
+If you experience or witness unacceptable behavior, contact the maintainers through the repository owners' preferred contact path. For security-sensitive matters, use the process described in [SECURITY.md](SECURITY.md).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..8099370e
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,59 @@
+# Contributing
+
+Thanks for contributing to `cddp-cpp`.
+
+## Scope
+
+This project contains:
+
+- the core C++ trajectory optimization library
+- a curated set of C++ reference examples
+- optional Python bindings and portfolio demos
+
+Keep changes focused. Avoid mixing bug fixes, refactors, formatting churn, and documentation cleanup in one pull request unless they are tightly coupled.
+
+## Development setup
+
+### C++ build
+
+```bash
+cmake -S . -B build
+cmake --build build -j4
+ctest --test-dir build --output-on-failure
+```
+
+### C++ examples
+
+```bash
+cmake -S . -B build -DCDDP_CPP_BUILD_EXAMPLES=ON
+cmake --build build --target cddp_pendulum cddp_cartpole cddp_unicycle cddp_quadrotor_point cddp_manipulator -j4
+```
+
+### Python bindings
+
+This repository uses `uv` for Python environment management.
+
+```bash
+uv venv .venv --python 3.12
+uv sync
+source .venv/bin/activate
+pytest -q python/tests
+```
+
+## Pull requests
+
+Before opening a pull request:
+
+1. Make the smallest reasonable change for the problem you are solving.
+2. Add or update tests when behavior changes.
+3. Run the relevant build and test commands for the area you touched.
+4. Update documentation when user-facing behavior, examples, or public APIs change.
+5. Write a clear commit message and PR description.
+
+PRs that include a minimal reproduction, exact validation commands, and a concise explanation of the design tradeoffs are much easier to review.
+
+## Issues
+
+- Use bug reports for reproducible defects.
+- Use feature requests for API additions, solver improvements, or workflow changes.
+- For sensitive security issues, follow [SECURITY.md](SECURITY.md) instead of opening a detailed public issue.
diff --git a/README.md b/README.md
index 825d4f64..5cb6d3b9 100644
--- a/README.md
+++ b/README.md
@@ -29,15 +29,57 @@ $$
$$
## Examples
-The default C++ build currently includes a barrier-strategy comparison example:
+The maintained example surface is now split:
+
+* a small C++ reference set in `examples/`, built when `CDDP_CPP_BUILD_EXAMPLES=ON`
+* the Python portfolio for plotting, animation, and notebook workflows
+
+The kept C++ examples are:
+
+* `cddp_pendulum.cpp`
+* `cddp_cartpole.cpp`
+* `cddp_unicycle.cpp`
+* `cddp_quadrotor_point.cpp`
+* `cddp_manipulator.cpp`
+
+The wider historical C++ example inventory has been removed to keep the example
+surface focused. The kept C++ examples are intentionally minimal and do not depend on
+visualization libraries.
+
+### Python Portfolio
+The Python bindings now ship with a small animation-focused portfolio built on
+top of the same solver models used by the C++ examples:
```bash
-./examples/test_barrier_strategies
+source .venv/bin/activate
+python examples/python_portfolio.py --demo all
```
-Several visualization-focused C++ examples remain in the repository, but they
-are not part of the default build. Use the Python bindings for plotting and
-notebook workflows.
+This generates GIFs under `docs/assets/python_portfolio/` for:
+
+* pendulum swing-up
+* cart-pole swing-up
+* unicycle obstacle avoidance
+* full-lap MPCC racing line tracking
+
+See [docs/python_portfolio.md](docs/python_portfolio.md) for the gallery and
+regeneration command.
+
+Pendulum swing-up:
+
+
+Cart-pole swing-up:
+
+
+Unicycle obstacle avoidance:
+
+
+MPCC racing line tracking:
+
+
+The MPCC portfolio example is a lightweight kinematic contouring-control demo.
+Its vendored track data in `examples/data/` is derived from the
+[`alexliniger/MPCC`](https://github.com/alexliniger/MPCC) project.
## Installation
### Dependencies
@@ -69,8 +111,7 @@ If you want to use this library for ROS2 MPC node, please refer [CDDP MPC Packag
## References
* Y. Tassa, N. Mansard and E. Todorov, "Control-limited differential dynamic programming," 2014 IEEE International Conference on Robotics and Automation (ICRA), 2014, pp. 1168-1175, doi: <10.1109/ICRA.2014.6907001>.
* Pavlov, A., Shames, I., and Manzie, C., “Interior Point Differential Dynamic Programming,” IEEE Transactions on Control Systems Technology, Vol. 29, No. 6, 2021, pp. 2720–2727.
-* Yuval Tassa's iLQG/DDP trajectory optimization:
-* Andrei Pavlov's GitHub repository:
+* Liniger, A., Domahidi, A., and Morari, M., “Optimization-based autonomous racing of 1:43 scale RC cars,” Optimal Control Applications and Methods, 2015. doi: <10.1002/oca.2123>.
## Third Party Libraries
@@ -93,15 +134,13 @@ This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENS
## Collaboration
Contributions are welcome.
-If you'd like to contribute:
-1. Fork the repository.
-2. Create a branch for your change, for example `feature/your-feature-name` or `fix/your-bug-fix`.
-3. Make the change and add or update tests as needed.
-4. Commit with a descriptive message.
-5. Push the branch.
-6. Open a pull request against `master`.
+Start with:
+
+- [CONTRIBUTING.md](CONTRIBUTING.md) for setup, validation, and PR expectations
+- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for community standards
+- [SECURITY.md](SECURITY.md) for vulnerability reporting guidance
-Use GitHub issues for bug reports, questions, or proposed changes.
+Use GitHub issues for bug reports and feature requests, and open pull requests against `master`.
## TODO
* improve python binding ergonomics
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 00000000..c47348ba
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,26 @@
+# Security Policy
+
+## Supported versions
+
+Security fixes are best-effort and are typically made against the current `master` branch.
+
+## Reporting a vulnerability
+
+Please do not publish vulnerability details in a public issue.
+
+Preferred process:
+
+1. Use GitHub's private vulnerability reporting flow for this repository if it is enabled.
+2. If private reporting is not available, open a minimal public issue requesting a secure reporting path, without including exploit details, secrets, or full reproduction steps.
+
+When reporting an issue, include:
+
+- affected version or commit
+- impact and attack surface
+- reproduction steps
+- proof-of-concept details only through a private channel
+- any suggested mitigation or patch direction
+
+## Disclosure
+
+Please allow maintainers reasonable time to investigate and prepare a fix before public disclosure.
diff --git a/docs/assets/python_portfolio/cartpole_swing_up.gif b/docs/assets/python_portfolio/cartpole_swing_up.gif
new file mode 100644
index 00000000..e79ccac8
Binary files /dev/null and b/docs/assets/python_portfolio/cartpole_swing_up.gif differ
diff --git a/docs/assets/python_portfolio/mpcc_racing_line.gif b/docs/assets/python_portfolio/mpcc_racing_line.gif
new file mode 100644
index 00000000..cf443cf5
Binary files /dev/null and b/docs/assets/python_portfolio/mpcc_racing_line.gif differ
diff --git a/docs/assets/python_portfolio/pendulum_swing_up.gif b/docs/assets/python_portfolio/pendulum_swing_up.gif
new file mode 100644
index 00000000..5dc49e2e
Binary files /dev/null and b/docs/assets/python_portfolio/pendulum_swing_up.gif differ
diff --git a/docs/assets/python_portfolio/unicycle_obstacle_avoidance.gif b/docs/assets/python_portfolio/unicycle_obstacle_avoidance.gif
new file mode 100644
index 00000000..053c5483
Binary files /dev/null and b/docs/assets/python_portfolio/unicycle_obstacle_avoidance.gif differ
diff --git a/docs/python_portfolio.md b/docs/python_portfolio.md
new file mode 100644
index 00000000..63e687bb
--- /dev/null
+++ b/docs/python_portfolio.md
@@ -0,0 +1,48 @@
+# Python Portfolio
+
+This gallery is the Python-facing showcase for the `pycddp` bindings. Each
+demo is solved directly from Python, then rendered as an animation with
+Matplotlib.
+
+## Regenerate the gallery
+
+```bash
+source .venv/bin/activate
+python examples/python_portfolio.py --demo all --output-dir docs/assets/python_portfolio
+```
+
+## Demos
+
+### Pendulum Swing-Up
+
+Torque-limited `CLDDP` solve for a damped pendulum.
+
+
+
+### Cart-Pole Swing-Up
+
+Bounded-force `CLDDP` solve that swings the pole upright and settles near the
+origin.
+
+
+
+### Unicycle Obstacle Avoidance
+
+`IPDDP` solve with a circular obstacle constraint, showing how the Python
+bindings can be used for constrained trajectory visualization without building
+the legacy C++ plotting stack.
+
+
+
+### MPCC Racing Line
+
+Full-lap receding-horizon contouring control on a compact racing circuit,
+solved from Python with a custom nonlinear objective and a lightweight bicycle
+model.
+
+This is a compact kinematic MPCC-style showcase rather than a full reproduction
+of the autonomous-racing controller in Liniger et al. The bundled track data is
+derived from the [`alexliniger/MPCC`](https://github.com/alexliniger/MPCC)
+reference implementation.
+
+
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index f34c5263..6e4a576d 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -12,7 +12,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Build only the maintained example used in the default C++ configuration.
+# Curated native reference examples that are built with
+# `CDDP_CPP_BUILD_EXAMPLES=ON`. These are compile-checked examples of the
+# public C++ API, while the Python portfolio remains the visualization path.
-add_executable(test_barrier_strategies test_barrier_strategies.cpp)
-target_link_libraries(test_barrier_strategies cddp)
+function(add_cddp_reference_example target_name source_name)
+ add_executable(${target_name} ${source_name})
+ target_link_libraries(${target_name} PRIVATE cddp)
+endfunction()
+
+add_cddp_reference_example(cddp_pendulum cddp_pendulum.cpp)
+add_cddp_reference_example(cddp_cartpole cddp_cartpole.cpp)
+add_cddp_reference_example(cddp_unicycle cddp_unicycle.cpp)
+add_cddp_reference_example(cddp_quadrotor_point cddp_quadrotor_point.cpp)
+add_cddp_reference_example(cddp_manipulator cddp_manipulator.cpp)
diff --git a/examples/cddp_acrobot.cpp b/examples/cddp_acrobot.cpp
deleted file mode 100644
index b1f00a75..00000000
--- a/examples/cddp_acrobot.cpp
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- Copyright 2025 Tomo Sasaki
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include "cddp.hpp"
-#include "cddp_example_utils.hpp"
-#include "dynamics_model/acrobot.hpp"
-#include "matplot/matplot.h"
-
-using namespace matplot;
-namespace fs = std::filesystem;
-
-int main() {
- int state_dim = 4;
- int control_dim = 1;
- int horizon = 100;
- double timestep = 0.05;
- double Tf = timestep * horizon; // Total time
-
- double l1 = 1.0;
- double l2 = 1.0;
- double m1 = 1.0;
- double m2 = 1.0;
- double J1 = 1.0;
- double J2 = 1.0;
- std::string integration_type = "rk4";
-
- std::unique_ptr system = std::make_unique(
- timestep, l1, l2, m1, m2, J1, J2, integration_type);
-
- // Cost matrices
- Eigen::MatrixXd Q = 10.0 * Eigen::MatrixXd::Identity(state_dim, state_dim);
- Eigen::MatrixXd R = 1e-2 * Eigen::MatrixXd::Identity(control_dim, control_dim);
- Eigen::MatrixXd Qf = 100.0 * Q;
-
- // Goal state
- Eigen::VectorXd goal_state(state_dim);
- goal_state << M_PI/2.0, 0.0, 0.0, 0.0;
-
- std::vector empty_reference_states;
-
- // Initial state
- Eigen::VectorXd initial_state(state_dim);
- initial_state << -M_PI/2.0, 0.0, 0.0, 0.0;
-
- // Create objective
- auto objective = std::make_unique(
- Q, R, Qf, goal_state, empty_reference_states, timestep);
-
- // Control constraints
- Eigen::VectorXd control_lower_bound(control_dim);
- control_lower_bound << -15.0;
- Eigen::VectorXd control_upper_bound(control_dim);
- control_upper_bound << 15.0;
-
- // Solver options
- cddp::CDDPOptions options;
- options.max_iterations = 200;
- options.tolerance = 1e-3;
- options.regularization.initial_value = 1e-4;
- options.use_ilqr = true;
- options.enable_parallel = true;
- options.num_threads = 10;
- options.debug = false;
- options.ipddp.barrier.mu_initial = 1e-1;
-
- // Create CDDP solver
- cddp::CDDP cddp_solver(initial_state, goal_state, horizon, timestep,
- std::move(system), std::move(objective), options);
-
- // Add control constraint
- cddp_solver.addPathConstraint("ControlConstraint",
- std::make_unique(-control_upper_bound, control_upper_bound));
-
- // Initial trajectory
- std::vector X_init(horizon + 1, Eigen::VectorXd::Zero(state_dim));
- std::vector U_init(horizon, Eigen::VectorXd::Zero(control_dim));
-
- // Rollout initial trajectory
- X_init[0] = initial_state;
- auto acrobot = std::make_unique(timestep, l1, l2, m1, m2, J1, J2, integration_type);
- for (int t = 0; t < horizon; ++t) {
- X_init[t + 1] = acrobot->getDiscreteDynamics(X_init[t], U_init[t], t * timestep);
- }
-
- cddp_solver.setInitialTrajectory(X_init, U_init);
-
- // Solve
- cddp::CDDPSolution solution = cddp_solver.solve(cddp::SolverType::IPDDP);
- const auto& X_sol = solution.state_trajectory;
- const auto& U_sol = solution.control_trajectory;
- const auto& t_sol = solution.time_points;
-
- // Create plot directory
- const std::string plotDirectory = "../results/acrobot";
- cddp::example::ensurePlotDir(plotDirectory);
-
- // Extract solution data for animation
- const auto& time_arr = t_sol;
- auto theta1_arr = cddp::example::extractComponent(X_sol, 0);
- auto theta2_arr = cddp::example::extractComponent(X_sol, 1);
-
- // --- Animation ---
- auto fig = figure();
- auto ax = fig->current_axes();
- fig->size(800, 800);
-
- // Generate animation frames
- for (size_t i = 0; i < X_sol.size(); i += 5) {
- cla(ax);
- hold(ax, true);
-
- // Current state
- double theta1 = theta1_arr[i];
- double theta2 = theta2_arr[i];
-
- // Link 1 endpoint
- double x1 = l1 * std::sin(theta1);
- double y1 = -l1 * std::cos(theta1);
-
- // Link 2 endpoint (relative to link 1)
- double x2 = x1 + l2 * std::sin(theta1 + theta2);
- double y2 = y1 - l2 * std::cos(theta1 + theta2);
-
- // Plot link 1
- std::vector link1_x = {0.0, x1};
- std::vector link1_y = {0.0, y1};
- auto link1 = plot(link1_x, link1_y);
- link1->line_style("b-");
- link1->line_width(5);
-
- // Plot link 2
- std::vector link2_x = {x1, x2};
- std::vector link2_y = {y1, y2};
- auto link2 = plot(link2_x, link2_y);
- link2->line_style("r-");
- link2->line_width(5);
-
- // Plot joints as circles
- std::vector joint0_x = {0.0};
- std::vector joint0_y = {0.0};
- auto j0 = scatter(joint0_x, joint0_y);
- j0->marker_size(10);
- j0->marker_color("black");
- j0->marker_style("o");
-
- std::vector joint1_x = {x1};
- std::vector joint1_y = {y1};
- auto j1 = scatter(joint1_x, joint1_y);
- j1->marker_size(8);
- j1->marker_color("gray");
- j1->marker_style("o");
-
- std::vector joint2_x = {x2};
- std::vector joint2_y = {y2};
- auto j2 = scatter(joint2_x, joint2_y);
- j2->marker_size(6);
- j2->marker_color("red");
- j2->marker_style("o");
-
- // Set axis properties
- xlim({-2.5, 2.5});
- ylim({-2.5, 2.5});
- xlabel("x [m]");
- ylabel("y [m]");
- title("Acrobot Animation - Time: " + std::to_string(time_arr[i]) + " s");
- grid(true);
-
- // Save frame
- std::string filename = plotDirectory + "/frame_" + std::to_string(i/5) + ".png";
- fig->save(filename);
- }
-
- // Combine all saved frames into a GIF using ImageMagick's convert tool
- std::string command = "convert -delay 30 " + plotDirectory + "/frame_*.png " + plotDirectory + "/acrobot.gif";
- std::system(command.c_str());
-
- // Clean up frame files
- std::string cleanup_command = "rm " + plotDirectory + "/frame_*.png";
- std::system(cleanup_command.c_str());
-
- std::cout << "Animation saved as " << plotDirectory << "/acrobot.gif" << std::endl;
-
- // Print final state
- std::cout << "\nFinal state:" << std::endl;
- std::cout << "θ₁ = " << X_sol.back()(0) << " rad" << std::endl;
- std::cout << "θ₂ = " << X_sol.back()(1) << " rad" << std::endl;
- std::cout << "θ̇₁ = " << X_sol.back()(2) << " rad/s" << std::endl;
- std::cout << "θ̇₂ = " << X_sol.back()(3) << " rad/s" << std::endl;
-
- return 0;
-}
\ No newline at end of file
diff --git a/examples/cddp_bicycle.cpp b/examples/cddp_bicycle.cpp
deleted file mode 100644
index 5d2c6a59..00000000
--- a/examples/cddp_bicycle.cpp
+++ /dev/null
@@ -1,232 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include
-#include "cddp.hpp"
-#include "cddp_example_utils.hpp"
-#include "matplot/matplot.h"
-
-namespace fs = std::filesystem;
-using namespace matplot;
-
-int main()
-{
- // Problem parameters
- int state_dim = 4; // [x, y, theta, v]
- int control_dim = 2; // [acceleration, steering_angle]
- int horizon = 100;
- double timestep = 0.05;
- std::string integration_type = "euler";
-
- // Create a bicycle instance
- double wheelbase = 1.5; // wheelbase length in meters
- std::unique_ptr system = std::make_unique(timestep, wheelbase, integration_type);
-
- // Create objective function
- Eigen::MatrixXd Q = Eigen::MatrixXd::Zero(state_dim, state_dim);
- Eigen::MatrixXd R = Eigen::MatrixXd::Identity(control_dim, control_dim);
- Eigen::MatrixXd Qf = Eigen::MatrixXd::Identity(state_dim, state_dim);
- Qf << 50.0, 0.0, 0.0, 0.0,
- 0.0, 50.0, 0.0, 0.0,
- 0.0, 0.0, 10.0, 0.0,
- 0.0, 0.0, 0.0, 10.0;
-
- Eigen::VectorXd goal_state(state_dim);
- goal_state << 5.0, 5.0, M_PI / 2.0, 0.0;
-
- std::vector empty_reference_states;
- auto objective = std::make_unique(Q, R, Qf, goal_state, empty_reference_states, timestep);
-
- // Initial state
- Eigen::VectorXd initial_state(state_dim);
- initial_state << 0.0, 0.0, M_PI / 4.0, 0.0;
-
- // Set solver options
- cddp::CDDPOptions options;
- options.max_iterations = 20;
-
- // Create CDDP solver with new API
- cddp::CDDP cddp_solver(initial_state, goal_state, horizon, timestep,
- std::move(system), std::move(objective), options);
-
- // Define and add control constraints
- Eigen::VectorXd control_lower_bound(control_dim);
- control_lower_bound << -10.0, -M_PI / 5;
- Eigen::VectorXd control_upper_bound(control_dim);
- control_upper_bound << 10.0, M_PI / 5;
- cddp_solver.addPathConstraint("ControlConstraint",
- std::make_unique(control_lower_bound, control_upper_bound));
- auto constraint = cddp_solver.getConstraint("ControlConstraint");
- std::vector X(horizon + 1, Eigen::VectorXd::Zero(state_dim));
- std::vector U(horizon, Eigen::VectorXd::Zero(control_dim));
- cddp_solver.setInitialTrajectory(X, U);
-
- // Solve the problem
- cddp::CDDPSolution solution = cddp_solver.solve(cddp::SolverType::CLDDP);
-
- // Extract solution
- const auto& X_sol = solution.state_trajectory;
- const auto& U_sol = solution.control_trajectory;
- const auto& t_sol = solution.time_points;
-
- // Create directory for saving plots
- const std::string plotDirectory = "../results/tests";
- cddp::example::ensurePlotDir(plotDirectory);
-
- // Create a directory for frame images.
- (void) std::system("mkdir -p frames");
-
- // Extract trajectory data
- auto x_arr = cddp::example::extractComponent(X_sol, 0);
- auto y_arr = cddp::example::extractComponent(X_sol, 1);
- auto theta_arr = cddp::example::extractComponent(X_sol, 2);
- auto v_arr = cddp::example::extractComponent(X_sol, 3);
-
- // Extract control inputs
- auto acc_arr = cddp::example::extractComponent(U_sol, 0);
- auto steering_arr = cddp::example::extractComponent(U_sol, 1);
-
- // -----------------------------
- // Plot states and controls
- // -----------------------------
- auto f1 = figure();
- f1->size(1200, 800);
-
- // First subplot: Position Trajectory
- auto ax1 = subplot(2, 2, 0);
- auto plot_handle = plot(ax1, x_arr, y_arr, "-b");
- plot_handle->line_width(3);
- title(ax1, "Position Trajectory");
- xlabel(ax1, "x [m]");
- ylabel(ax1, "y [m]");
-
- // Second subplot: Heading Angle vs Time
- auto ax2 = subplot(2, 2, 1);
- auto heading_plot_handle = plot(ax2, t_sol, theta_arr);
- heading_plot_handle->line_width(3);
- title(ax2, "Heading Angle");
- xlabel(ax2, "Time [s]");
- ylabel(ax2, "theta [rad]");
-
- // Third subplot: Velocity vs Time
- auto ax3 = subplot(2, 2, 2);
- auto velocity_plot_handle = plot(ax3, t_sol, v_arr);
- velocity_plot_handle->line_width(3);
- title(ax3, "Velocity");
- xlabel(ax3, "Time [s]");
- ylabel(ax3, "v [m/s]");
-
- // Fourth subplot: Control Inputs
- auto ax4 = subplot(2, 2, 3);
- auto p1 = plot(ax4, acc_arr, "--b");
- p1->line_width(3);
- p1->display_name("Acceleration");
-
- hold(ax4, true);
- auto p2 = plot(ax4, steering_arr, "--r");
- p2->line_width(3);
- p2->display_name("Steering");
-
- title(ax4, "Control Inputs");
- xlabel(ax4, "Step");
- ylabel(ax4, "Control");
- matplot::legend(ax4);
-
- f1->draw();
- f1->save(plotDirectory + "/bicycle_cddp_results.png");
-
- // -----------------------------
- // Animation: Bicycle Trajectory
- // -----------------------------
- auto f2 = figure();
- f2->size(800, 600);
- auto ax_anim = f2->current_axes();
- if (!ax_anim)
- {
- ax_anim = axes();
- }
-
- double car_length = 0.35;
- double car_width = 0.15;
-
- for (size_t i = 0; i < X_sol.size(); ++i)
- {
- if (i % 10 == 0)
- {
- ax_anim->clear();
- hold(ax_anim, true);
-
- double x = x_arr[i];
- double y = y_arr[i];
- double theta = theta_arr[i];
-
- // Compute bicycle rectangle corners
- std::vector car_x(5), car_y(5);
- car_x[0] = x + car_length / 2 * cos(theta) - car_width / 2 * sin(theta);
- car_y[0] = y + car_length / 2 * sin(theta) + car_width / 2 * cos(theta);
- car_x[1] = x + car_length / 2 * cos(theta) + car_width / 2 * sin(theta);
- car_y[1] = y + car_length / 2 * sin(theta) - car_width / 2 * cos(theta);
- car_x[2] = x - car_length / 2 * cos(theta) + car_width / 2 * sin(theta);
- car_y[2] = y - car_length / 2 * sin(theta) - car_width / 2 * cos(theta);
- car_x[3] = x - car_length / 2 * cos(theta) - car_width / 2 * sin(theta);
- car_y[3] = y - car_length / 2 * sin(theta) + car_width / 2 * cos(theta);
- car_x[4] = car_x[0];
- car_y[4] = car_y[0];
-
- auto car_line = plot(ax_anim, car_x, car_y);
- car_line->color("black");
- car_line->line_style("solid");
- car_line->line_width(2);
- car_line->display_name("Car");
-
- // Plot the front wheel (steerable)
- double wheel_length = car_width * 0.8;
- double steering_angle = U_sol[std::min(i, U_sol.size() - 1)](1);
- std::vector front_wheel_x = {
- x + car_length / 2 * cos(theta),
- x + car_length / 2 * cos(theta) + wheel_length * cos(theta + steering_angle)};
- std::vector front_wheel_y = {
- y + car_length / 2 * sin(theta),
- y + car_length / 2 * sin(theta) + wheel_length * sin(theta + steering_angle)};
- auto front_wheel_line = plot(ax_anim, front_wheel_x, front_wheel_y);
- front_wheel_line->color("red");
- front_wheel_line->line_style("solid");
- front_wheel_line->line_width(2);
- front_wheel_line->display_name("");
-
- // Plot trajectory up to current frame
- std::vector traj_x(x_arr.begin(), x_arr.begin() + i + 1);
- std::vector traj_y(y_arr.begin(), y_arr.begin() + i + 1);
- auto traj_line = plot(ax_anim, traj_x, traj_y);
- traj_line->color("blue");
- traj_line->line_style("solid");
- traj_line->line_width(1.5);
- traj_line->display_name("Trajectory");
-
- title(ax_anim, "Bicycle Trajectory");
- xlabel(ax_anim, "x [m]");
- ylabel(ax_anim, "y [m]");
- xlim(ax_anim, {-1, 6});
- ylim(ax_anim, {-1, 6});
- // legend(ax_anim);
-
- std::string filename = plotDirectory + "/bicycle_frame_" + std::to_string(i) + ".png";
- f2->draw();
- f2->save(filename);
- std::this_thread::sleep_for(std::chrono::milliseconds(80));
- }
- }
-
- // -----------------------------
- // Generate GIF from frames using ImageMagick
- // -----------------------------
- std::string gif_command = "convert -delay 30 " + plotDirectory + "/bicycle_frame_*.png " + plotDirectory + "/bicycle.gif";
- std::system(gif_command.c_str());
-
- std::string cleanup_command = "rm " + plotDirectory + "/bicycle_frame_*.png";
- std::system(cleanup_command.c_str());
-
- return 0;
-}
diff --git a/examples/cddp_car.cpp b/examples/cddp_car.cpp
deleted file mode 100644
index 05b7151a..00000000
--- a/examples/cddp_car.cpp
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- Copyright 2024 Tomo Sasaki
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-#include
-#include
-#include
-#include
-#include
-#include "cddp.hpp"
-#include "cddp_example_utils.hpp"
-#include "matplot/matplot.h"
-
-using namespace matplot;
-namespace fs = std::filesystem;
-
-namespace cddp
-{
-
- class CarParkingObjective : public NonlinearObjective
- {
- public:
- CarParkingObjective(const Eigen::VectorXd &goal_state, double timestep)
- : NonlinearObjective(timestep), reference_state_(goal_state)
- {
- // Control cost coefficients: cu = 1e-2*[1 .01]
- cu_ = Eigen::Vector2d(1e-2, 1e-4);
-
- // Final cost coefficients: cf = [.1 .1 1 .3]
- cf_ = Eigen::Vector4d(0.1, 0.1, 1.0, 0.3);
-
- // Smoothness scales for final cost: pf = [.01 .01 .01 1]
- pf_ = Eigen::Vector4d(0.01, 0.01, 0.01, 1.0);
-
- // Running cost coefficients: cx = 1e-3*[1 1]
- cx_ = Eigen::Vector2d(1e-3, 1e-3);
-
- // Smoothness scales for running cost: px = [.1 .1]
- px_ = Eigen::Vector2d(0.1, 0.1);
- }
-
- double running_cost(const Eigen::VectorXd &state,
- const Eigen::VectorXd &control,
- int index) const override
- {
- // Control cost: lu = cu*u.^2
- double lu = cu_.dot(control.array().square().matrix());
-
- // Running cost on distance from origin: lx = cx*sabs(x(1:2,:),px)
- Eigen::VectorXd xy_state = state.head(2);
- double lx = cx_.dot(sabs(xy_state, px_));
-
- return lu + lx;
- }
-
- double terminal_cost(const Eigen::VectorXd &final_state) const override
- {
- // Final state cost: llf = cf*sabs(x(:,final),pf);
- return cf_.dot(sabs(final_state, pf_)) + running_cost(final_state, Eigen::VectorXd::Zero(2), 0);
- }
-
- private:
- // Helper function for smooth absolute value (pseudo-Huber)
- Eigen::VectorXd sabs(const Eigen::VectorXd &x, const Eigen::VectorXd &p) const
- {
- return ((x.array().square() / p.array().square() + 1.0).sqrt() * p.array() - p.array()).matrix();
- }
-
- Eigen::VectorXd reference_state_;
- Eigen::Vector2d cu_; // Control cost coefficients
- Eigen::Vector4d cf_; // Final cost coefficients
- Eigen::Vector4d pf_; // Smoothness scales for final cost
- Eigen::Vector2d cx_; // Running cost coefficients
- Eigen::Vector2d px_; // Smoothness scales for running cost
- };
-} // namespace cddp
-
-void plotCarBox(const Eigen::VectorXd &state, const Eigen::VectorXd &control,
- double length, double width, const std::string &color,
- axes_handle ax)
-{
- double x = state(0);
- double y = state(1);
- double theta = state(2);
- double steering = control(1);
-
- // Compute the car's four corners (and close the polygon)
- std::vector car_x(5), car_y(5);
-
- // Front right
- car_x[0] = x + length / 2 * cos(theta) - width / 2 * sin(theta);
- car_y[0] = y + length / 2 * sin(theta) + width / 2 * cos(theta);
-
- // Front left
- car_x[1] = x + length / 2 * cos(theta) + width / 2 * sin(theta);
- car_y[1] = y + length / 2 * sin(theta) - width / 2 * cos(theta);
-
- // Rear left
- car_x[2] = x - length / 2 * cos(theta) + width / 2 * sin(theta);
- car_y[2] = y - length / 2 * sin(theta) - width / 2 * cos(theta);
-
- // Rear right
- car_x[3] = x - length / 2 * cos(theta) - width / 2 * sin(theta);
- car_y[3] = y - length / 2 * sin(theta) + width / 2 * cos(theta);
-
- // Close polygon
- car_x[4] = car_x[0];
- car_y[4] = car_y[0];
-
- // Plot car body as a polygon line.
- plot(ax, car_x, car_y, color + "-");
-
- // Plot base point (center of rear axle) as a red circle.
- plot(ax, std::vector{x}, std::vector{y}, "ro");
-
- // Compute steering direction
- double front_x = x + length / 2 * cos(theta);
- double front_y = y + length / 2 * sin(theta);
- double steering_length = width / 2;
- double steering_angle = theta + steering;
- double steering_end_x = front_x + steering_length * cos(steering_angle);
- double steering_end_y = front_y + steering_length * sin(steering_angle);
-
- std::vector steer_x = {front_x, steering_end_x};
- std::vector steer_y = {front_y, steering_end_y};
- plot(ax, steer_x, steer_y, "g-");
-}
-
-int main()
-{
- // Problem parameters
- int state_dim = 4; // [x y theta v]
- int control_dim = 2; // [wheel_angle acceleration]
- int horizon = 500;
- double timestep = 0.03;
- std::string integration_type = "euler";
-
- // Random number generator setup
- std::random_device rd;
- std::mt19937 gen(rd());
- std::normal_distribution d(0.0, 0.1);
-
- // Create car instance
- double wheelbase = 2.0;
-
- // Initial and goal states
- Eigen::VectorXd initial_state(state_dim);
- initial_state << 1.0, 1.0, 1.5 * M_PI, 0.0;
-
- Eigen::VectorXd goal_state(state_dim);
- goal_state << 0.0, 0.0, 0.0, 0.0; // NOT USED IN THIS EXAMPLE
-
- // Control constraints
- Eigen::VectorXd control_lower_bound(control_dim);
- control_lower_bound << -0.5, -2.0; // [steering_angle, acceleration]
- Eigen::VectorXd control_upper_bound(control_dim);
- control_upper_bound << 0.5, 2.0;
-
- // Solver options
- cddp::CDDPOptions options;
- options.max_iterations = 500;
- options.verbose = true;
- options.tolerance = 1e-7;
- options.acceptable_tolerance = 1e-4;
- options.regularization.initial_value = 1e-7;
- options.debug = false;
- options.enable_parallel = true;
- options.num_threads = 10;
-
- // Create CDDP solver with new API
- cddp::CDDP cddp_solver(
- initial_state,
- goal_state,
- horizon,
- timestep,
- std::make_unique(timestep, wheelbase, integration_type),
- std::make_unique(goal_state, timestep),
- options
- );
-
- cddp_solver.addPathConstraint("ControlConstraint",
- std::make_unique(
- control_lower_bound, control_upper_bound));
-
- // Initialize with random controls
- std::vector X(horizon + 1, Eigen::VectorXd::Zero(state_dim));
- std::vector U(horizon, Eigen::VectorXd::Zero(control_dim));
-
- // Random initialization
- for (auto &u : U)
- {
- // u(0) = d(gen); // Random steering
- // u(1) = d(gen); // Random acceleration
- u(0) = 0.01;
- u(1) = 0.01;
- }
-
- X[0] = initial_state;
-
- double J = 0.0;
-
- // Simulate initial trajectory
- for (size_t t = 0; t < horizon; t++)
- {
- J += cddp_solver.getObjective().running_cost(X[t], U[t], t);
- X[t + 1] = cddp_solver.getSystem().getDiscreteDynamics(X[t], U[t], t * timestep);
- }
- J += cddp_solver.getObjective().terminal_cost(X.back());
- std::cout << "Initial cost: " << J << std::endl;
- std::cout << "Initial state: " << X[0].transpose() << std::endl;
- std::cout << "Final state: " << X.back().transpose() << std::endl;
-
- cddp_solver.setInitialTrajectory(X, U);
-
- // Solve the problem
- cddp::CDDPSolution solution = cddp_solver.solve(cddp::SolverType::CLDDP);
- // ========================================
- // CDDP Solution
- // ========================================
- // Converged: Yes
- // Iterations: 157
- // Solve Time: 5.507e+05 micro sec
- // Final Cost: 1.90517
- // ========================================
- // cddp::CDDPSolution solution = cddp_solver.solve(cddp::SolverType::LogDDP);
- // ========================================
- // CDDP Solution
- // ========================================
- // Converged: Yes
- // Iterations: 153
- // Solve Time: 5.441e+05 micro sec
- // Final Cost: 1.90517
- // ========================================
- // ========================================
- // CDDP Solution
- // ========================================
- // Converged: No
- // Iterations: 53
- // Solve Time: 1.1538e+06 micro sec
- // Final Cost: 1.910013e+00
- // ========================================
-
- // Extract solution trajectories
- const auto& X_sol = solution.state_trajectory;
- const auto& U_sol = solution.control_trajectory;
- const auto& t_sol = solution.time_points;
-
- // Prepare trajectory data
- auto x_hist = cddp::example::extractComponent(X_sol, 0);
- auto y_hist = cddp::example::extractComponent(X_sol, 1);
-
- // Car dimensions.
- double car_length = 2.1;
- double car_width = 0.9;
-
- // Create a figure and get current axes.
- auto fig = figure(true);
- auto ax = fig->current_axes();
-
- Eigen::VectorXd empty_control = Eigen::VectorXd::Zero(2);
-
- // Create directory for saving plots
- const std::string plotDirectory = "../results/tests";
- cddp::example::ensurePlotDir(plotDirectory);
-
- // Create a directory for frame images.
- (void) std::system("mkdir -p frames");
-
- // Animation loop: update plot for each time step and save frame.
- for (size_t i = 0; i < X_sol.size(); ++i)
- {
- // Skip every 10th frame for smoother animation.
- if (i % 10 == 0)
- {
- // Clear previous content.
- cla(ax);
- hold(ax, true);
-
- // Plot the full trajectory.
- plot(ax, x_hist, y_hist, "b-");
-
- // Plot goal configuration.
- plotCarBox(goal_state, empty_control, car_length, car_width, "r", ax);
-
- // Plot current car state.
- if (i < U_sol.size())
- plotCarBox(X_sol[i], U_sol[i], car_length, car_width, "k", ax);
- else
- plotCarBox(X_sol[i], empty_control, car_length, car_width, "k", ax);
-
- // Set grid and axis limits.
- grid(ax, true);
- xlim(ax, {-4, 4});
- ylim(ax, {-4, 4});
-
- // Update drawing.
- fig->draw();
-
- // Save the frame to a PNG file.
- std::string frame_filename = plotDirectory + "/frame_" + std::to_string(i) + ".png";
- fig->save(frame_filename);
- std::this_thread::sleep_for(std::chrono::milliseconds(80));
- }
- }
-
- // Combine all saved frames into a GIF using ImageMagick's convert tool.
- std::string command = "convert -delay 15 " + plotDirectory + "/frame_*.png " + plotDirectory + "/car_parking.gif";
- std::system(command.c_str());
-
- std::string cleanup_command = "rm " + plotDirectory + "/frame_*.png";
- std::system(cleanup_command.c_str());
-
- std::cout << "Animation saved as car_parking.gif" << std::endl;
-
- return 0;
-}
-
-// convert -delay 3 ../results/frames/frame_*.png ../results/animations/car_parking.gif
diff --git a/examples/cddp_car_ipddp.cpp b/examples/cddp_car_ipddp.cpp
deleted file mode 100644
index 044cbef5..00000000
--- a/examples/cddp_car_ipddp.cpp
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- Copyright 2024 Tomo Sasaki
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-#include
-#include
-#include
-#include
-#include