Skip to content

Commit 557c1ca

Browse files
Fix IPDDP warm-start filter regressions
Preserve warm-started path and terminal interior-point state.\nTrack the current path-only filter violation baseline with regressions.
1 parent 82f2de5 commit 557c1ca

3 files changed

Lines changed: 431 additions & 18 deletions

File tree

include/cddp-cpp/cddp_core/ipddp_solver.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
namespace cddp
2727
{
28+
class IPDDPSolverTestAccess;
2829

2930
/**
3031
* @brief Interior Point Differential Dynamic Programming (IPDDP) solver.
@@ -63,6 +64,8 @@ namespace cddp
6364
void printSolutionSummary(const CDDPSolution &solution) const override;
6465

6566
private:
67+
friend class IPDDPSolverTestAccess;
68+
6669
// Constraint derivatives
6770
std::map<std::string, std::vector<Eigen::MatrixXd>> G_x_; ///< State gradients
6871
std::map<std::string, std::vector<Eigen::MatrixXd>> G_u_; ///< Control gradients
@@ -117,6 +120,7 @@ namespace cddp
117120
std::vector<FilterPoint> filter_; ///< Filter for line search
118121
double phi_ = 0.0; ///< Current filter merit value
119122
double theta_ = 0.0; ///< Current filter violation metric
123+
double filter_theta_ = 0.0; ///< Current unfloored filter violation metric
120124

121125
// === Private helper methods ===
122126
void precomputeConstraintGradients(CDDP &context);

src/cddp_core/ipddp_solver.cpp

Lines changed: 217 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,98 @@ namespace cddp
242242
}
243243
}
244244

245+
bool warmstartNeedsReinit(const Eigen::VectorXd &y_current,
246+
const Eigen::VectorXd &s_current,
247+
const Eigen::VectorXd &g_val,
248+
const CDDPOptions &options)
249+
{
250+
if (y_current.size() != g_val.size() || s_current.size() != g_val.size() ||
251+
!y_current.allFinite() || !s_current.allFinite())
252+
{
253+
return true;
254+
}
255+
256+
for (int i = 0; i < g_val.size(); ++i)
257+
{
258+
if (y_current(i) <= EPS_DUAL || s_current(i) <= EPS_SLACK)
259+
{
260+
return true;
261+
}
262+
263+
const double required_slack =
264+
std::max(options.ipddp.slack_var_init_scale,
265+
-g_val(i) + kSlackInteriorOffset);
266+
if (s_current(i) < 0.1 * required_slack)
267+
{
268+
return true;
269+
}
270+
}
271+
272+
return false;
273+
}
274+
275+
void initializeTerminalWarmstartDualSlack(
276+
const CDDP &context,
277+
double mu,
278+
std::map<std::string, Eigen::VectorXd> &G_T,
279+
std::map<std::string, Eigen::VectorXd> &Y_T,
280+
std::map<std::string, Eigen::VectorXd> &S_T,
281+
std::map<std::string, Eigen::VectorXd> &dY_T,
282+
std::map<std::string, Eigen::VectorXd> &dS_T)
283+
{
284+
const CDDPOptions &options = context.getOptions();
285+
const auto layout = getTerminalInequalityLayout(context);
286+
287+
bool has_existing_dual_slack = true;
288+
for (const auto &entry : layout)
289+
{
290+
auto y_it = Y_T.find(entry.name);
291+
auto s_it = S_T.find(entry.name);
292+
if (y_it == Y_T.end() || s_it == S_T.end() ||
293+
y_it->second.size() != entry.dim || s_it->second.size() != entry.dim)
294+
{
295+
has_existing_dual_slack = false;
296+
break;
297+
}
298+
}
299+
300+
for (const auto &entry : layout)
301+
{
302+
const Eigen::VectorXd &g_val = G_T.at(entry.name);
303+
Eigen::VectorXd s_current = Eigen::VectorXd::Zero(entry.dim);
304+
Eigen::VectorXd y_current = Eigen::VectorXd::Zero(entry.dim);
305+
bool need_reinit = !has_existing_dual_slack;
306+
307+
if (!need_reinit)
308+
{
309+
s_current = S_T.at(entry.name);
310+
y_current = Y_T.at(entry.name);
311+
need_reinit =
312+
warmstartNeedsReinit(y_current, s_current, g_val, options);
313+
}
314+
315+
if (need_reinit)
316+
{
317+
s_current = g_val.unaryExpr([&](double g) {
318+
return std::max(options.ipddp.slack_var_init_scale,
319+
-g + kSlackInteriorOffset);
320+
});
321+
for (int i = 0; i < entry.dim; ++i)
322+
{
323+
y_current(i) =
324+
(mu * options.ipddp.dual_var_init_scale) /
325+
std::max(s_current(i), EPS_SLACK);
326+
}
327+
}
328+
329+
repairWarmstartInterior(s_current, y_current, options);
330+
S_T[entry.name] = s_current;
331+
Y_T[entry.name] = y_current;
332+
dS_T[entry.name] = Eigen::VectorXd::Zero(entry.dim);
333+
dY_T[entry.name] = Eigen::VectorXd::Zero(entry.dim);
334+
}
335+
}
336+
245337
void rolloutLinearPolicy(const std::vector<Eigen::MatrixXd> &A,
246338
const std::vector<Eigen::MatrixXd> &B,
247339
const std::vector<Eigen::VectorXd> &d,
@@ -587,9 +679,19 @@ namespace cddp
587679
Lambda_T_eq_ = Eigen::VectorXd::Zero(getTerminalEqualityDim(context));
588680
dLambda_T_eq_ = Eigen::VectorXd::Zero(getTerminalEqualityDim(context));
589681

682+
const auto warmstart_dual = Y_;
683+
const auto warmstart_slack = S_;
684+
const auto warmstart_terminal_dual = Y_T_;
685+
const auto warmstart_terminal_slack = S_T_;
590686
initializeConstraintStorage(context);
687+
Y_ = warmstart_dual;
688+
S_ = warmstart_slack;
689+
Y_T_ = warmstart_terminal_dual;
690+
S_T_ = warmstart_terminal_slack;
591691
evaluateTrajectoryWarmStart(context);
592692
initializeDualSlackVariablesWarmStart(context);
693+
initializeTerminalWarmstartDualSlack(context, mu_, G_T_, Y_T_, S_T_,
694+
dY_T_, dS_T_);
593695
G_ = G_raw_;
594696
resetFilter(context);
595697
return;
@@ -614,7 +716,15 @@ namespace cddp
614716
Lambda_T_eq_ = Eigen::VectorXd::Zero(getTerminalEqualityDim(context));
615717
dLambda_T_eq_ = Eigen::VectorXd::Zero(getTerminalEqualityDim(context));
616718

719+
const auto warmstart_dual = Y_;
720+
const auto warmstart_slack = S_;
721+
const auto warmstart_terminal_dual = Y_T_;
722+
const auto warmstart_terminal_slack = S_T_;
617723
initializeConstraintStorage(context);
724+
Y_ = warmstart_dual;
725+
S_ = warmstart_slack;
726+
Y_T_ = warmstart_terminal_dual;
727+
S_T_ = warmstart_terminal_slack;
618728

619729
// Re-rollout trajectory with provided controls
620730
if (static_cast<int>(context.U_.size()) != horizon)
@@ -660,6 +770,8 @@ namespace cddp
660770
context.alpha_pr_ = 1.0;
661771
context.alpha_du_ = 1.0;
662772
initializeDualSlackVariablesWarmStart(context);
773+
initializeTerminalWarmstartDualSlack(context, mu_, G_T_, Y_T_, S_T_,
774+
dY_T_, dS_T_);
663775
G_ = G_raw_;
664776
resetFilter(context);
665777
context.inf_du_ = 0.0;
@@ -1473,13 +1585,15 @@ namespace cddp
14731585
double expected_improvement = alpha_pr * dV_(0);
14741586
double constraint_violation_old =
14751587
filter_.empty() ? 0.0 : filter_.back().constraint_violation;
1588+
const double high_violation_reference =
1589+
filter_.empty() ? filter_theta_ : constraint_violation_old;
14761590
double merit_function_old = context.merit_function_;
14771591

14781592
if (theta_new > options.filter.max_violation_threshold)
14791593
{
14801594
if (theta_new <
14811595
(1 - options.filter.violation_acceptance_threshold) *
1482-
constraint_violation_old)
1596+
high_violation_reference)
14831597
{
14841598
filter_acceptance = true;
14851599
}
@@ -1619,6 +1733,7 @@ namespace cddp
16191733
context.inf_pr_ = result.inf_pr;
16201734
context.inf_comp_ = result.inf_comp;
16211735
phi_ = result.merit_function;
1736+
filter_theta_ = result.theta;
16221737
theta_ = result.theta;
16231738

16241739
// Update barrier parameter before convergence check (matching old solve loop timing)
@@ -2019,7 +2134,85 @@ namespace cddp
20192134

20202135
void IPDDPSolver::initializeDualSlackVariablesWarmStart(CDDP &context)
20212136
{
2022-
initializeDualSlackVariables(context);
2137+
const CDDPOptions &options = context.getOptions();
2138+
const int horizon = context.getHorizon();
2139+
const auto &constraint_set = context.getConstraintSet();
2140+
2141+
bool has_existing_dual_slack = true;
2142+
for (const auto &constraint_pair : constraint_set)
2143+
{
2144+
const std::string &constraint_name = constraint_pair.first;
2145+
auto y_it = Y_.find(constraint_name);
2146+
auto s_it = S_.find(constraint_name);
2147+
if (y_it == Y_.end() || s_it == S_.end() ||
2148+
y_it->second.size() != static_cast<size_t>(horizon) ||
2149+
s_it->second.size() != static_cast<size_t>(horizon))
2150+
{
2151+
has_existing_dual_slack = false;
2152+
break;
2153+
}
2154+
}
2155+
2156+
for (const auto &constraint_pair : constraint_set)
2157+
{
2158+
const std::string &constraint_name = constraint_pair.first;
2159+
const int dual_dim = constraint_pair.second->getDualDim();
2160+
2161+
if (!has_existing_dual_slack)
2162+
{
2163+
Y_[constraint_name].resize(horizon);
2164+
S_[constraint_name].resize(horizon);
2165+
}
2166+
2167+
dY_[constraint_name].resize(horizon);
2168+
dS_[constraint_name].resize(horizon);
2169+
k_y_[constraint_name].resize(horizon);
2170+
K_y_[constraint_name].resize(horizon);
2171+
k_s_[constraint_name].resize(horizon);
2172+
K_s_[constraint_name].resize(horizon);
2173+
2174+
for (int t = 0; t < horizon; ++t)
2175+
{
2176+
const Eigen::VectorXd &g_val = G_raw_[constraint_name][t];
2177+
Eigen::VectorXd s_current = Eigen::VectorXd::Zero(dual_dim);
2178+
Eigen::VectorXd y_current = Eigen::VectorXd::Zero(dual_dim);
2179+
bool need_reinit = !has_existing_dual_slack;
2180+
2181+
if (!need_reinit)
2182+
{
2183+
s_current = S_[constraint_name][t];
2184+
y_current = Y_[constraint_name][t];
2185+
need_reinit =
2186+
warmstartNeedsReinit(y_current, s_current, g_val, options);
2187+
}
2188+
2189+
if (need_reinit)
2190+
{
2191+
s_current = g_val.unaryExpr([&](double g) {
2192+
return std::max(options.ipddp.slack_var_init_scale,
2193+
-g + kSlackInteriorOffset);
2194+
});
2195+
for (int i = 0; i < dual_dim; ++i)
2196+
{
2197+
y_current(i) =
2198+
(mu_ * options.ipddp.dual_var_init_scale) /
2199+
std::max(s_current(i), EPS_SLACK);
2200+
}
2201+
}
2202+
2203+
repairWarmstartInterior(s_current, y_current, options);
2204+
Y_[constraint_name][t] = y_current;
2205+
S_[constraint_name][t] = s_current;
2206+
dY_[constraint_name][t] = Eigen::VectorXd::Zero(dual_dim);
2207+
dS_[constraint_name][t] = Eigen::VectorXd::Zero(dual_dim);
2208+
k_y_[constraint_name][t] = Eigen::VectorXd::Zero(dual_dim);
2209+
K_y_[constraint_name][t] =
2210+
Eigen::MatrixXd::Zero(dual_dim, context.getStateDim());
2211+
k_s_[constraint_name][t] = Eigen::VectorXd::Zero(dual_dim);
2212+
K_s_[constraint_name][t] =
2213+
Eigen::MatrixXd::Zero(dual_dim, context.getStateDim());
2214+
}
2215+
}
20232216
}
20242217

20252218
void IPDDPSolver::initializeDualSlackVariables(CDDP &context)
@@ -2098,16 +2291,18 @@ namespace cddp
20982291
context.inf_pr_ = inf_pr;
20992292
context.inf_comp_ = inf_comp;
21002293
phi_ = context.merit_function_;
2101-
theta_ =
2294+
filter_theta_ =
21022295
std::max(computeTheta(context.getOptions(), G_, S_,
21032296
has_terminal_ineq ? &G_T_ : nullptr,
21042297
has_terminal_ineq ? &S_T_ : nullptr,
21052298
has_terminal_eq ? &h_T : nullptr),
2106-
std::max(context.getOptions().ipddp.theta_0_floor, 1e-8));
2299+
1e-8);
2300+
theta_ = std::max(filter_theta_,
2301+
std::max(context.getOptions().ipddp.theta_0_floor, 1e-8));
21072302
filter_.clear();
21082303
if (has_terminal_ineq || has_terminal_eq)
21092304
{
2110-
acceptFilterEntry(phi_, theta_);
2305+
acceptFilterEntry(phi_, filter_theta_);
21112306
}
21122307
}
21132308

@@ -2208,31 +2403,37 @@ namespace cddp
22082403
}
22092404
}
22102405

2406+
const bool has_terminal_ineq = !getTerminalInequalityLayout(context).empty();
2407+
const bool has_terminal_eq = getTerminalEqualityDim(context) > 0;
2408+
Eigen::VectorXd h_T =
2409+
has_terminal_eq
2410+
? evaluateTerminalEqualityResidual(context, context.X_.back())
2411+
: Eigen::VectorXd::Zero(0);
2412+
const double filter_theta =
2413+
std::max(computeTheta(options, G_, S_,
2414+
has_terminal_ineq ? &G_T_ : nullptr,
2415+
has_terminal_ineq ? &S_T_ : nullptr,
2416+
has_terminal_eq ? &h_T : nullptr),
2417+
1e-8);
2418+
22112419
const bool reset_filter = (mu_ < mu_old) && (mu_ > 0.0);
22122420
if (reset_filter)
22132421
{
22142422
filter_.clear();
22152423
if (getTerminalEqualityDim(context) > 0 ||
22162424
!getTerminalInequalityLayout(context).empty())
22172425
{
2218-
acceptFilterEntry(phi_, theta_);
2426+
acceptFilterEntry(phi_, filter_theta);
22192427
}
22202428
}
22212429
else
22222430
{
2223-
acceptFilterEntry(phi_, theta_);
2431+
acceptFilterEntry(phi_, filter_theta);
22242432
if (static_cast<int>(filter_.size()) > options.ipddp.max_filter_size)
22252433
{
22262434
detail::pruneFilterToBestPoints(filter_);
22272435
}
22282436
}
2229-
2230-
const bool has_terminal_ineq = !getTerminalInequalityLayout(context).empty();
2231-
const bool has_terminal_eq = getTerminalEqualityDim(context) > 0;
2232-
Eigen::VectorXd h_T =
2233-
has_terminal_eq
2234-
? evaluateTerminalEqualityResidual(context, context.X_.back())
2235-
: Eigen::VectorXd::Zero(0);
22362437
const auto [inf_pr, inf_comp] = computePrimalAndComplementarity(
22372438
context, G_, S_, Y_, mu_, has_terminal_ineq ? &G_T_ : nullptr,
22382439
has_terminal_ineq ? &S_T_ : nullptr, has_terminal_ineq ? &Y_T_ : nullptr,
@@ -2244,10 +2445,8 @@ namespace cddp
22442445
has_terminal_eq ? &Lambda_T_eq_ : nullptr,
22452446
has_terminal_eq ? &h_T : nullptr);
22462447
phi_ = context.merit_function_;
2247-
theta_ = std::max(computeTheta(options, G_, S_,
2248-
has_terminal_ineq ? &G_T_ : nullptr,
2249-
has_terminal_ineq ? &S_T_ : nullptr,
2250-
has_terminal_eq ? &h_T : nullptr),
2448+
filter_theta_ = filter_theta;
2449+
theta_ = std::max(filter_theta,
22512450
std::max(options.ipddp.theta_0_floor, 1e-8));
22522451
}
22532452

0 commit comments

Comments
 (0)