Skip to content

Commit 71b9665

Browse files
Add configurable barrier update strategies for interior point methods (#144)
* feat: Add configurable barrier update strategies for interior point methods Implement three barrier parameter update strategies for IPDDP and MSIPDDP solvers: - ADAPTIVE: Current adaptive strategy based on KKT progress (default) - MONOTONIC: Fixed reduction factor for consistent barrier decrease - IPOPT: IPOPT-style update with error threshold criterion This enhancement provides users with flexibility to choose barrier update strategies suited to their specific optimization problems while maintaining backward compatibility with the default adaptive strategy.
1 parent 8972ef8 commit 71b9665

File tree

5 files changed

+367
-102
lines changed

5 files changed

+367
-102
lines changed

examples/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515
# CMakeLists.txt for the CDDP examples
1616

17+
add_executable(test_barrier_strategies test_barrier_strategies.cpp)
18+
target_link_libraries(test_barrier_strategies cddp)
19+
1720
add_executable(cddp_bicycle cddp_bicycle.cpp)
1821
target_link_libraries(cddp_bicycle cddp)
1922

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
Example demonstrating the three barrier update strategies for IPDDP and MSIPDDP
3+
*/
4+
5+
#include <iostream>
6+
#include <memory>
7+
#include <string>
8+
#include <Eigen/Dense>
9+
10+
#include "cddp.hpp"
11+
12+
int main()
13+
{
14+
// Problem setup
15+
const int horizon = 50;
16+
const double timestep = 0.05;
17+
const int state_dim = 3;
18+
const int control_dim = 2;
19+
20+
// Initial and goal states
21+
Eigen::VectorXd X0(state_dim);
22+
X0 << 0.0, 0.0, 0.0; // x, y, theta
23+
24+
Eigen::VectorXd Xg(state_dim);
25+
Xg << 2.0, 2.0, 0.0;
26+
27+
// Test each barrier strategy
28+
std::vector<std::string> solvers = {"IPDDP", "MSIPDDP"};
29+
std::vector<cddp::BarrierStrategy> strategies = {
30+
cddp::BarrierStrategy::ADAPTIVE,
31+
cddp::BarrierStrategy::MONOTONIC,
32+
cddp::BarrierStrategy::IPOPT
33+
};
34+
std::vector<std::string> strategy_names = {"ADAPTIVE", "MONOTONIC", "IPOPT"};
35+
36+
for (const auto& solver_name : solvers)
37+
{
38+
std::cout << "\n========================================\n";
39+
std::cout << "Testing " << solver_name << " Solver\n";
40+
std::cout << "========================================\n";
41+
42+
for (size_t i = 0; i < strategies.size(); ++i)
43+
{
44+
std::cout << "\n--- Barrier Strategy: " << strategy_names[i] << " ---\n";
45+
46+
// Create CDDP solver first
47+
cddp::CDDP cddp_solver(X0, Xg, horizon, timestep);
48+
49+
// Create and set dynamics model
50+
auto dynamics = std::make_unique<cddp::Unicycle>(timestep);
51+
cddp_solver.setDynamicalSystem(std::move(dynamics));
52+
53+
// Create and set objective
54+
Eigen::MatrixXd Q = Eigen::MatrixXd::Identity(state_dim, state_dim);
55+
Q(0, 0) = 10.0; // x
56+
Q(1, 1) = 10.0; // y
57+
Q(2, 2) = 1.0; // theta
58+
59+
Eigen::MatrixXd R = Eigen::MatrixXd::Identity(control_dim, control_dim);
60+
R(0, 0) = 0.1; // linear velocity
61+
R(1, 1) = 0.1; // angular velocity
62+
63+
Eigen::MatrixXd Qf = 100.0 * Q;
64+
65+
std::vector<Eigen::VectorXd> empty_reference;
66+
auto objective = std::make_unique<cddp::QuadraticObjective>(
67+
Q, R, Qf, Xg, empty_reference, timestep);
68+
cddp_solver.setObjective(std::move(objective));
69+
70+
// Add constraints
71+
Eigen::VectorXd u_upper(control_dim);
72+
u_upper << 1.0, 2.0; // max linear vel, max angular vel
73+
74+
cddp_solver.addPathConstraint("ControlConstraint",
75+
std::make_unique<cddp::ControlConstraint>(u_upper));
76+
77+
// State bounds (keep robot in a region)
78+
Eigen::VectorXd x_lower(state_dim), x_upper_state(state_dim);
79+
x_lower << -0.5, -0.5, -M_PI;
80+
x_upper_state << 2.5, 2.5, M_PI;
81+
82+
cddp_solver.addPathConstraint("StateConstraint",
83+
std::make_unique<cddp::StateConstraint>(x_lower, x_upper_state));
84+
85+
// Configure options
86+
cddp::CDDPOptions opts;
87+
opts.max_iterations = 100;
88+
opts.tolerance = 1e-4;
89+
opts.verbose = false;
90+
opts.debug = true; // Enable debug output to see barrier updates
91+
opts.return_iteration_info = true;
92+
93+
// Set barrier strategy
94+
if (solver_name == "IPDDP") {
95+
opts.ipddp.barrier.strategy = strategies[i];
96+
opts.ipddp.barrier.mu_initial = 1.0;
97+
opts.ipddp.barrier.mu_update_factor = 0.2;
98+
opts.ipddp.barrier.mu_update_power = 1.5;
99+
} else {
100+
opts.msipddp.barrier.strategy = strategies[i];
101+
opts.msipddp.barrier.mu_initial = 1.0;
102+
opts.msipddp.barrier.mu_update_factor = 0.2;
103+
opts.msipddp.barrier.mu_update_power = 1.5;
104+
}
105+
106+
cddp_solver.setOptions(opts);
107+
108+
// Initialize with straight line trajectory
109+
std::vector<Eigen::VectorXd> X_init(horizon + 1);
110+
std::vector<Eigen::VectorXd> U_init(horizon);
111+
112+
for (int t = 0; t <= horizon; ++t) {
113+
double alpha = static_cast<double>(t) / horizon;
114+
X_init[t] = (1.0 - alpha) * X0 + alpha * Xg;
115+
}
116+
117+
for (int t = 0; t < horizon; ++t) {
118+
U_init[t] = Eigen::VectorXd::Zero(control_dim);
119+
}
120+
121+
cddp_solver.setInitialTrajectory(X_init, U_init);
122+
123+
// Solve
124+
cddp::CDDPSolution solution = cddp_solver.solve(solver_name);
125+
126+
// Print results
127+
auto status_message = std::any_cast<std::string>(solution.at("status_message"));
128+
auto iterations = std::any_cast<int>(solution.at("iterations_completed"));
129+
auto solve_time = std::any_cast<double>(solution.at("solve_time_ms"));
130+
auto final_cost = std::any_cast<double>(solution.at("final_objective"));
131+
132+
std::cout << "Status: " << status_message << "\n";
133+
std::cout << "Iterations: " << iterations << "\n";
134+
std::cout << "Final cost: " << final_cost << "\n";
135+
std::cout << "Solve time: " << solve_time << " ms\n";
136+
137+
// Extract final barrier parameter
138+
try {
139+
double final_mu = std::any_cast<double>(solution.at("final_barrier_parameter_mu"));
140+
std::cout << "Final barrier μ: " << final_mu << "\n";
141+
} catch (const std::exception& e) {
142+
std::cout << "Final barrier μ: Not available\n";
143+
}
144+
}
145+
}
146+
147+
std::cout << "\n========================================\n";
148+
std::cout << "Test completed successfully!\n";
149+
std::cout << "========================================\n";
150+
151+
return 0;
152+
}

include/cddp-cpp/cddp_core/options.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@
2222

2323
namespace cddp
2424
{
25+
/**
26+
* @brief Barrier parameter update strategy for interior point methods.
27+
*/
28+
enum class BarrierStrategy
29+
{
30+
ADAPTIVE, ///< Adaptive strategy based on KKT progress (default)
31+
MONOTONIC, ///< Monotonic decrease with fixed reduction factor
32+
IPOPT ///< IPOPT-style adaptive barrier update
33+
};
34+
2535
/**
2636
* @brief Options for the line search procedure.
2737
*
@@ -71,6 +81,8 @@ namespace cddp
7181
double min_fraction_to_boundary =
7282
0.99; ///< Minimum fraction to boundary for primal/dual step calculation
7383
///< (tau).
84+
BarrierStrategy strategy =
85+
BarrierStrategy::ADAPTIVE; ///< Barrier parameter update strategy.
7486
};
7587

7688
/**

src/cddp_core/ipddp_solver.cpp

Lines changed: 89 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1838,56 +1838,106 @@ namespace cddp
18381838
return; // No constraints case - no barrier update needed
18391839
}
18401840

1841-
// Compute termination metric from current infeasibility metrics with IPOPT-style scaling
1842-
double scaled_inf_du = computeScaledDualInfeasibility(context);
1843-
double termination_metric = std::max({scaled_inf_du, context.inf_pr_, context.inf_comp_});
1844-
1845-
// More aggressive barrier parameter update strategy
1846-
double barrier_update_threshold = std::max(barrier_opts.mu_update_factor * mu_, mu_ * 2.0);
1847-
1848-
if (termination_metric <= barrier_update_threshold)
1841+
// Select barrier update strategy
1842+
switch (barrier_opts.strategy)
18491843
{
1850-
// Adaptive barrier reduction strategy
1851-
double reduction_factor = barrier_opts.mu_update_factor;
1852-
1853-
if (mu_ > 1e-12)
1844+
case BarrierStrategy::MONOTONIC:
18541845
{
1855-
double kkt_progress_ratio = termination_metric / mu_;
1856-
1857-
// Very aggressive reduction for good KKT satisfaction
1858-
if (kkt_progress_ratio < 0.01)
1859-
{
1860-
reduction_factor = barrier_opts.mu_update_factor * 0.1;
1861-
}
1862-
// Aggressive reduction if we're significantly satisfying KKT conditions
1863-
else if (kkt_progress_ratio < 0.1)
1846+
// Monotonic decrease: always reduce by fixed factor
1847+
mu_ = std::max(barrier_opts.mu_min_value,
1848+
barrier_opts.mu_update_factor * mu_);
1849+
resetFilter(context);
1850+
if (options.debug)
18641851
{
1865-
reduction_factor = barrier_opts.mu_update_factor * 0.3;
1852+
std::cout << "[IPDDP Barrier] Monotonic update: μ = "
1853+
<< std::scientific << std::setprecision(2) << mu_ << std::endl;
18661854
}
1867-
// Moderate reduction if we're moderately satisfying KKT conditions
1868-
else if (kkt_progress_ratio < 0.5)
1855+
break;
1856+
}
1857+
1858+
case BarrierStrategy::IPOPT:
1859+
{
1860+
// IPOPT-style barrier update
1861+
double scaled_inf_du = computeScaledDualInfeasibility(context);
1862+
double error_k = std::max({scaled_inf_du, context.inf_pr_, context.inf_comp_});
1863+
1864+
// IPOPT uses: μ_new = max(ε_tol/10, min(κ_μ * μ, μ^θ_μ))
1865+
// where update happens when error_k ≤ κ_ε * μ
1866+
double kappa_epsilon = 10.0; // IPOPT default
1867+
1868+
if (error_k <= kappa_epsilon * mu_)
18691869
{
1870-
reduction_factor = barrier_opts.mu_update_factor * 0.6;
1870+
double new_mu_linear = barrier_opts.mu_update_factor * mu_;
1871+
double new_mu_superlinear = std::pow(mu_, barrier_opts.mu_update_power);
1872+
mu_ = std::max(options.tolerance / 10.0,
1873+
std::min(new_mu_linear, new_mu_superlinear));
1874+
resetFilter(context);
1875+
if (options.debug)
1876+
{
1877+
std::cout << "[IPDDP Barrier] IPOPT update: error = "
1878+
<< std::scientific << std::setprecision(2) << error_k
1879+
<< "" << kappa_epsilon << " * μ = " << kappa_epsilon * mu_
1880+
<< " → μ = " << mu_ << std::endl;
1881+
}
18711882
}
1872-
// Standard reduction otherwise
1883+
break;
18731884
}
1885+
1886+
case BarrierStrategy::ADAPTIVE:
1887+
default:
1888+
{
1889+
// Current adaptive strategy (default)
1890+
double scaled_inf_du = computeScaledDualInfeasibility(context);
1891+
double termination_metric = std::max({scaled_inf_du, context.inf_pr_, context.inf_comp_});
18741892

1875-
// Update barrier parameter with bounds
1876-
double new_mu_linear = reduction_factor * mu_;
1877-
double new_mu_superlinear = std::pow(mu_, barrier_opts.mu_update_power);
1893+
// More aggressive barrier parameter update strategy
1894+
double barrier_update_threshold = std::max(barrier_opts.mu_update_factor * mu_, mu_ * 2.0);
1895+
1896+
if (termination_metric <= barrier_update_threshold)
1897+
{
1898+
// Adaptive barrier reduction strategy
1899+
double reduction_factor = barrier_opts.mu_update_factor;
18781900

1879-
mu_ = std::max(options.tolerance / 100.0,
1880-
std::min(new_mu_linear, new_mu_superlinear));
1901+
if (mu_ > 1e-12)
1902+
{
1903+
double kkt_progress_ratio = termination_metric / mu_;
18811904

1882-
// Reset filter when barrier parameter changes
1883-
resetFilter(context);
1905+
// Very aggressive reduction for good KKT satisfaction
1906+
if (kkt_progress_ratio < 0.01)
1907+
{
1908+
reduction_factor = barrier_opts.mu_update_factor * 0.1;
1909+
}
1910+
// Aggressive reduction if we're significantly satisfying KKT conditions
1911+
else if (kkt_progress_ratio < 0.1)
1912+
{
1913+
reduction_factor = barrier_opts.mu_update_factor * 0.3;
1914+
}
1915+
// Moderate reduction if we're moderately satisfying KKT conditions
1916+
else if (kkt_progress_ratio < 0.5)
1917+
{
1918+
reduction_factor = barrier_opts.mu_update_factor * 0.6;
1919+
}
1920+
// Standard reduction otherwise
1921+
}
18841922

1885-
if (options.debug)
1886-
{
1887-
std::cout << "[IPDDP Barrier] Termination metric: " << std::scientific << std::setprecision(2)
1888-
<< termination_metric << " (scaled inf_du: " << scaled_inf_du
1889-
<< ", inf_pr: " << context.inf_pr_ << ", inf_comp: " << context.inf_comp_
1890-
<< ") → μ: " << mu_ << std::endl;
1923+
// Update barrier parameter with bounds
1924+
double new_mu_linear = reduction_factor * mu_;
1925+
double new_mu_superlinear = std::pow(mu_, barrier_opts.mu_update_power);
1926+
1927+
mu_ = std::max(options.tolerance / 100.0,
1928+
std::min(new_mu_linear, new_mu_superlinear));
1929+
1930+
// Reset filter when barrier parameter changes
1931+
resetFilter(context);
1932+
1933+
if (options.debug)
1934+
{
1935+
std::cout << "[IPDDP Barrier] Adaptive update: termination metric = "
1936+
<< std::scientific << std::setprecision(2) << termination_metric
1937+
<< " → μ = " << mu_ << std::endl;
1938+
}
1939+
}
1940+
break;
18911941
}
18921942
}
18931943
}

0 commit comments

Comments
 (0)