Skip to content

Commit 6fca7e3

Browse files
Implement active-set DDP for unicycle with obstacle avoidance (#83)
* Update Ball constraint definition * active-set DDP state constraint done * Create Unicycle active-set DDP
1 parent 9d77063 commit 6fca7e3

File tree

13 files changed

+599
-210
lines changed

13 files changed

+599
-210
lines changed

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,14 @@ if (CDDP_CPP_TORCH)
191191
set(TORCH_INSTALL_PREFIX ${Torch_DIR}/../../../ CACHE PATH "LibTorch installation directory")
192192
endif()
193193

194+
# OSQP-CPP for ASDDP
195+
FetchContent_Declare(
196+
osqp-cpp
197+
GIT_REPOSITORY https://github.com/astomodynamics/osqp-cpp.git
198+
GIT_TAG master
199+
)
200+
FetchContent_MakeAvailable(osqp-cpp)
201+
FetchContent_GetProperties(osqp-cpp)
194202

195203
# Include directories
196204
include_directories(
@@ -246,6 +254,7 @@ target_link_libraries(${PROJECT_NAME}
246254
Python3::Python
247255
Python3::Module
248256
Python3::NumPy
257+
osqp-cpp
249258
)
250259

251260
if (CDDP_CPP_TORCH)

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ Simple car-like robot with velocity and steering control:
4747

4848
<img src="results/tests/unicycle.gif" width="300" alt="Unicycle Model CDDP">
4949

50+
### Unicycle with Obstacle Avoidance
51+
52+
Simple car-like robot with velocity and steering control:
53+
54+
```bash
55+
./examples/cddp_unicycle_safe // after building
56+
```
57+
58+
<img src="results/tests/unicycle_trajectory_comparison.png" width="300" alt="Unicycle Model CDDP with Obstacle Avoidance">
59+
5060
### Bicycle Model
5161

5262
Bicycle model with velocity and steering control:

examples/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ target_link_libraries(cddp_dubins_car cddp)
2828
add_executable(cddp_unicycle cddp_unicycle.cpp)
2929
target_link_libraries(cddp_unicycle cddp)
3030

31+
add_executable(cddp_unicycle_safe cddp_unicycle_safe.cpp)
32+
target_link_libraries(cddp_unicycle_safe cddp)
33+
3134
add_executable(cddp_manipulator cddp_manipulator.cpp)
3235
target_link_libraries(cddp_manipulator cddp)
3336

examples/cddp_car.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,9 @@ int main() {
183183
options.verbose = true;
184184
options.cost_tolerance = 1e-7;
185185
options.grad_tolerance = 1e-4;
186-
options.regularization_type = "control";
187-
// options.regularization_control = 1.0;
186+
options.regularization_type = "state";
187+
options.regularization_state = 1e-4;
188+
options.regularization_control = 1e-6;
188189
options.debug = false;
189190
options.use_parallel = true;
190191
options.num_threads = 10;
@@ -220,7 +221,7 @@ int main() {
220221
cddp_solver.setInitialTrajectory(X, U);
221222

222223
// Solve the problem
223-
cddp::CDDPSolution solution = cddp_solver.solve();
224+
// cddp::CDDPSolution solution = cddp_solver.solve("CLCDDP");
224225
// ========================================
225226
// CDDP Solution
226227
// ========================================
@@ -229,7 +230,7 @@ int main() {
229230
// Solve Time: 5.507e+05 micro sec
230231
// Final Cost: 1.90517
231232
// ========================================
232-
// cddp::CDDPSolution solution = cddp_solver.solveLogCDDP();
233+
// cddp::CDDPSolution solution = cddp_solver.solve("LogCDDP");
233234
// ========================================
234235
// CDDP Solution
235236
// ========================================
@@ -238,6 +239,7 @@ int main() {
238239
// Solve Time: 5.441e+05 micro sec
239240
// Final Cost: 1.90517
240241
// ========================================
242+
cddp::CDDPSolution solution = cddp_solver.solve("ASCDDP");
241243

242244
// Extract solution trajectories
243245
auto X_sol = solution.state_sequence;
@@ -279,7 +281,7 @@ int main() {
279281
}
280282

281283
// Create the final GIF
282-
animation.createGif("car_parking.gif");
284+
animation.createGif("car_parking_asddp.gif");
283285

284286
return 0;
285287
}

examples/cddp_unicycle_safe.cpp

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
Copyright 2024 Tomo Sasaki
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#include <iostream>
18+
#include <vector>
19+
#include <cmath>
20+
#include <filesystem>
21+
#include <memory>
22+
#include <cstdlib>
23+
24+
#include "cddp.hpp"
25+
26+
namespace plt = matplotlibcpp;
27+
namespace fs = std::filesystem;
28+
29+
int main() {
30+
// Problem parameters
31+
int state_dim = 3;
32+
int control_dim = 2;
33+
int horizon = 100;
34+
double timestep = 0.03;
35+
std::string integration_type = "euler";
36+
37+
// Create a unicycle dynamical system instance
38+
std::unique_ptr<cddp::DynamicalSystem> dyn_system = std::make_unique<cddp::Unicycle>(timestep, integration_type);
39+
40+
// Create objective function
41+
Eigen::MatrixXd Q = Eigen::MatrixXd::Zero(state_dim, state_dim);
42+
Eigen::MatrixXd R = 0.05 * Eigen::MatrixXd::Identity(control_dim, control_dim);
43+
Eigen::MatrixXd Qf = Eigen::MatrixXd::Identity(state_dim, state_dim);
44+
Qf << 100.0, 0.0, 0.0,
45+
0.0, 100.0, 0.0,
46+
0.0, 0.0, 100.0;
47+
Eigen::VectorXd goal_state(state_dim);
48+
goal_state << 2.0, 2.0, M_PI/2.0;
49+
50+
// Create an empty vector for reference states
51+
std::vector<Eigen::VectorXd> empty_reference_states;
52+
auto objective = std::make_unique<cddp::QuadraticObjective>(Q, R, Qf, goal_state, empty_reference_states, timestep);
53+
54+
// Define initial state
55+
Eigen::VectorXd initial_state(state_dim);
56+
initial_state << 0.0, 0.0, M_PI/4.0;
57+
58+
// Set up common CDDP options
59+
cddp::CDDPOptions options;
60+
options.max_iterations = 20;
61+
options.verbose = false;
62+
options.debug = false;
63+
options.cost_tolerance = 1e-5;
64+
options.grad_tolerance = 1e-4;
65+
options.regularization_type = "none";
66+
67+
// Define control box constraint bounds
68+
Eigen::VectorXd control_lower_bound(control_dim);
69+
control_lower_bound << -2.0, -M_PI;
70+
Eigen::VectorXd control_upper_bound(control_dim);
71+
control_upper_bound << 2.0, M_PI;
72+
73+
// --------------------------
74+
// Solve the CDDP problem without the ball constraint
75+
cddp::CDDP solver_baseline(
76+
initial_state,
77+
goal_state,
78+
horizon,
79+
timestep,
80+
std::make_unique<cddp::Unicycle>(timestep, integration_type),
81+
std::make_unique<cddp::QuadraticObjective>(Q, R, Qf, goal_state, empty_reference_states, timestep),
82+
options
83+
);
84+
solver_baseline.setDynamicalSystem(std::make_unique<cddp::Unicycle>(timestep, integration_type));
85+
solver_baseline.setObjective(std::make_unique<cddp::QuadraticObjective>(Q, R, Qf, goal_state, empty_reference_states, timestep));
86+
solver_baseline.addConstraint("ControlBoxConstraint", std::make_unique<cddp::ControlBoxConstraint>(control_lower_bound, control_upper_bound));
87+
88+
// Set an initial trajectory (all states equal to the initial state)
89+
std::vector<Eigen::VectorXd> X_baseline(horizon + 1, Eigen::VectorXd::Zero(state_dim));
90+
std::vector<Eigen::VectorXd> U_baseline(horizon, Eigen::VectorXd::Zero(control_dim));
91+
for (int i = 0; i < horizon + 1; ++i) {
92+
X_baseline[i] = initial_state;
93+
}
94+
solver_baseline.setInitialTrajectory(X_baseline, U_baseline);
95+
96+
cddp::CDDPSolution solution_baseline = solver_baseline.solve("ASCDDP");
97+
auto X_baseline_sol = solution_baseline.state_sequence;
98+
99+
// --------------------------
100+
// 2. Solve with BallConstraint
101+
cddp::CDDP solver_ball(
102+
initial_state,
103+
goal_state,
104+
horizon,
105+
timestep,
106+
std::make_unique<cddp::Unicycle>(timestep, integration_type),
107+
std::make_unique<cddp::QuadraticObjective>(Q, R, Qf, goal_state, empty_reference_states, timestep),
108+
options
109+
);
110+
solver_ball.setDynamicalSystem(std::make_unique<cddp::Unicycle>(timestep, integration_type));
111+
solver_ball.setObjective(std::make_unique<cddp::QuadraticObjective>(Q, R, Qf, goal_state, empty_reference_states, timestep));
112+
solver_ball.addConstraint("ControlBoxConstraint", std::make_unique<cddp::ControlBoxConstraint>(control_lower_bound, control_upper_bound));
113+
double radius = 0.4;
114+
Eigen::Vector2d center(1.0, 1.0);
115+
solver_ball.addConstraint("BallConstraint", std::make_unique<cddp::BallConstraint>(radius, center));
116+
117+
// Set an initial trajectory for the ball-constrained solver
118+
std::vector<Eigen::VectorXd> X_ball(horizon + 1, Eigen::VectorXd::Zero(state_dim));
119+
std::vector<Eigen::VectorXd> U_ball(horizon, Eigen::VectorXd::Zero(control_dim));
120+
for (int i = 0; i < horizon + 1; ++i) {
121+
X_ball[i] = initial_state;
122+
}
123+
solver_ball.setInitialTrajectory(X_ball, U_ball);
124+
125+
cddp::CDDPSolution solution_ball = solver_ball.solve("ASCDDP");
126+
auto X_ball_sol = solution_ball.state_sequence;
127+
128+
// --------------------------
129+
// Plot the trajectories
130+
std::vector<double> x_baseline, y_baseline;
131+
std::vector<double> x_ball, y_ball;
132+
for (const auto &state : X_baseline_sol) {
133+
x_baseline.push_back(state(0));
134+
y_baseline.push_back(state(1));
135+
}
136+
for (const auto &state : X_ball_sol) {
137+
x_ball.push_back(state(0));
138+
y_ball.push_back(state(1));
139+
}
140+
141+
// Plot trajectories on the same figure
142+
plt::figure();
143+
plt::plot(x_baseline, y_baseline, {{"color", "b"}, {"linestyle", "-"}, {"label", "Without Ball Constraint"}});
144+
plt::plot(x_ball, y_ball, {{"color", "r"}, {"linestyle", "-"}, {"label", "With Ball Constraint"}});
145+
146+
147+
148+
// Also plot the ball for reference
149+
std::vector<double> t_ball, x_ball_circle, y_ball_circle;
150+
for (double t = 0.0; t < 2 * M_PI; t += 0.01) {
151+
t_ball.push_back(t);
152+
x_ball_circle.push_back(center(0) + radius * cos(t));
153+
y_ball_circle.push_back(center(1) + radius * sin(t));
154+
}plt::plot(x_ball_circle, y_ball_circle, {{"color", "g"}, {"linestyle", "--"}, {"label", "Ball Constraint"}});
155+
156+
plt::xlabel("x");
157+
plt::ylabel("y");
158+
plt::title("Trajectory Comparison: With vs. Without BallConstraint");
159+
plt::legend();
160+
161+
// Save the comparison plot
162+
std::string plotDirectory = "../results/tests";
163+
if (!fs::exists(plotDirectory)) {
164+
fs::create_directory(plotDirectory);
165+
}
166+
plt::save(plotDirectory + "/trajectory_comparison.png");
167+
std::cout << "Trajectory comparison saved to " << plotDirectory + "/trajectory_comparison.png" << std::endl;
168+
169+
return 0;
170+
}

include/cddp-cpp/cddp_core/cddp_core.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ struct CDDPOptions {
6969

7070
double regularization_state = 1e-6; // Regularization for state
7171
double regularization_state_step = 1.0; // Regularization step for state
72-
double regularization_state_max = 1e4; // Maximum regularization
72+
double regularization_state_max = 1e6; // Maximum regularization
7373
double regularization_state_min = 1e-8; // Minimum regularization
7474
double regularization_state_factor = 1e1; // Factor for state regularization
7575

7676
double regularization_control = 1e-6; // Regularization for control
7777
double regularization_control_step = 1.0; // Regularization step for control
78-
double regularization_control_max = 1e4; // Maximum regularization
78+
double regularization_control_max = 1e5; // Maximum regularization
7979
double regularization_control_min = 1e-8; // Minimum regularization
8080
double regularization_control_factor = 1e1; // Factor for control regularization
8181

include/cddp-cpp/cddp_core/constraint.hpp

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ class BallConstraint : public Constraint {
333333
center_(center),
334334
scale_factor_(scale_factor)
335335
{
336+
dim_ = center.size();
336337
}
337338

338339
int getDualDim() const override {
@@ -343,35 +344,18 @@ class BallConstraint : public Constraint {
343344
Eigen::VectorXd evaluate(const Eigen::VectorXd& state,
344345
const Eigen::VectorXd& control) const override
345346
{
346-
const Eigen::VectorXd& diff = state - center_;
347-
return Eigen::VectorXd::Constant(1, scale_factor_ * diff.squaredNorm());
347+
const Eigen::VectorXd& diff = state.head(dim_) - center_;
348+
return -Eigen::VectorXd::Constant(1, scale_factor_ * diff.squaredNorm());
348349
}
349350

350351
Eigen::VectorXd getLowerBound() const override {
351-
return Eigen::VectorXd::Constant(1, radius_ * radius_);
352+
return -Eigen::VectorXd::Constant(1, std::numeric_limits<double>::infinity());
352353
}
353354

354355
Eigen::VectorXd getUpperBound() const override {
355-
return Eigen::VectorXd::Constant(1, std::numeric_limits<double>::infinity());
356+
return -Eigen::VectorXd::Constant(1, radius_ * radius_) * scale_factor_;
356357
}
357358

358-
359-
Eigen::MatrixXd getStateJacobian(const Eigen::VectorXd& state,
360-
const Eigen::VectorXd& control) const override
361-
{
362-
const Eigen::VectorXd& diff = state - center_;
363-
Eigen::MatrixXd jac(1, state.size());
364-
jac.row(0) = (2.0 * scale_factor_) * diff.transpose();
365-
return jac;
366-
}
367-
368-
Eigen::MatrixXd getControlJacobian(const Eigen::VectorXd& state,
369-
const Eigen::VectorXd& control) const override
370-
{
371-
return Eigen::MatrixXd::Zero(1, control.size());
372-
}
373-
374-
375359
double computeViolation(const Eigen::VectorXd& state,
376360
const Eigen::VectorXd& control) const override
377361
{
@@ -385,12 +369,33 @@ class BallConstraint : public Constraint {
385369
double lb = getLowerBound()(0);
386370
return std::max(0.0, val - lb);
387371
}
372+
373+
Eigen::MatrixXd getStateJacobian(const Eigen::VectorXd& state,
374+
const Eigen::VectorXd& control) const override
375+
{
376+
const Eigen::VectorXd& diff = state.head(dim_) - center_;
377+
Eigen::MatrixXd jac = Eigen::MatrixXd::Zero(1, state.size());
378+
379+
for (int i = 0; i < dim_; ++i) {
380+
jac(0, i) = -2.0 * scale_factor_ * diff(i);
381+
}
382+
383+
return jac;
384+
}
385+
386+
Eigen::MatrixXd getControlJacobian(const Eigen::VectorXd& state,
387+
const Eigen::VectorXd& control) const override
388+
{
389+
return Eigen::MatrixXd::Zero(1, control.size());
390+
}
388391

389392
Eigen::VectorXd getCenter() const { return center_; }
393+
double getRadius() const { return radius_; }
390394

391395
private:
392396
double radius_;
393397
Eigen::VectorXd center_;
398+
int dim_;
394399
double scale_factor_;
395400
};
396401

28 KB
Loading
Loading

0 commit comments

Comments
 (0)