@@ -25,9 +25,22 @@ limitations under the License.
2525
2626namespace cddp {
2727
28+ namespace {
29+
30+ void rollOutNominalTrajectory (CDDP &context) {
31+ const int horizon = context.getHorizon ();
32+
33+ context.X_ [0 ] = context.getInitialState ();
34+ for (int t = 0 ; t < horizon; ++t) {
35+ context.X_ [t + 1 ] = context.getSystem ().getDiscreteDynamics (
36+ context.X_ [t], context.U_ [t], t * context.getTimestep ());
37+ }
38+ }
39+
40+ } // namespace
41+
2842LogDDPSolver::LogDDPSolver ()
29- : mu_(1e-1 ), relaxation_delta_(1e-5 ), constraint_violation_(1e+7 ),
30- ms_segment_length_ (5 ) {}
43+ : mu_(1e-1 ), relaxation_delta_(1e-5 ), constraint_violation_(1e+7 ) {}
3144
3245void LogDDPSolver::initialize (CDDP &context) {
3346 const CDDPOptions &options = context.getOptions ();
@@ -52,14 +65,13 @@ void LogDDPSolver::initialize(CDDP &context) {
5265 bool valid_warm_start =
5366 (k_u_.size () == static_cast <size_t >(horizon) &&
5467 K_u_.size () == static_cast <size_t >(horizon) &&
55- F_.size () == static_cast <size_t >(horizon) &&
5668 context.X_ .size () == static_cast <size_t >(horizon + 1 ) &&
5769 context.U_ .size () == static_cast <size_t >(horizon));
5870
5971 if (valid_warm_start && !k_u_.empty ()) {
6072 for (int t = 0 ; t < horizon; ++t) {
6173 if (k_u_[t].size () != control_dim || K_u_[t].rows () != control_dim ||
62- K_u_[t].cols () != state_dim || F_[t]. size () != state_dim ) {
74+ K_u_[t].cols () != state_dim) {
6375 valid_warm_start = false ;
6476 break ;
6577 }
@@ -99,7 +111,11 @@ void LogDDPSolver::initialize(CDDP &context) {
99111 dV_ = Eigen::Vector2d::Zero ();
100112
101113 constraint_violation_ = std::numeric_limits<double >::infinity ();
102- ms_segment_length_ = options.log_barrier .segment_length ;
114+
115+ // Warm starts may reuse gains with a user-modified state trajectory.
116+ // Re-roll the nominal state sequence so the linearization point stays
117+ // dynamically consistent after defect tracking removal.
118+ rollOutNominalTrajectory (context);
103119
104120 // Evaluate current trajectory and reset filter
105121 evaluateTrajectory (context);
@@ -112,8 +128,9 @@ void LogDDPSolver::initialize(CDDP &context) {
112128 }
113129 }
114130
115- // Cold start: Initialize trajectories with interpolation between initial and
116- // reference states
131+ // Cold start: initialize a nominal trajectory and always roll it out from
132+ // the current control guess so the linearization point is dynamically
133+ // consistent, even when the user provided a same-sized state guess.
117134 if (context.X_ .size () != static_cast <size_t >(horizon + 1 ) ||
118135 context.U_ .size () != static_cast <size_t >(horizon)) {
119136 context.X_ .resize (horizon + 1 );
@@ -133,11 +150,9 @@ void LogDDPSolver::initialize(CDDP &context) {
133150 }
134151 }
135152
136- // Resize dynamics storage
137- F_.resize (horizon);
138- for (int t = 0 ; t < horizon; ++t) {
139- F_[t] = Eigen::VectorXd::Zero (state_dim);
140- }
153+ // Roll out the nominal trajectory for all cold starts, including user-
154+ // supplied trajectories whose state sequence is only an initial guess.
155+ rollOutNominalTrajectory (context);
141156
142157 // Use base class helper for gains
143158 initializeGains (horizon, control_dim, state_dim);
@@ -173,22 +188,6 @@ void LogDDPSolver::initialize(CDDP &context) {
173188
174189 constraint_violation_ = std::numeric_limits<double >::infinity ();
175190
176- ms_segment_length_ = options.log_barrier .segment_length ;
177-
178- // Check if ms_segment_length_ is valid
179- if (ms_segment_length_ < 0 ) {
180- std::cerr << " LogDDP: ms_segment_length_ must be non-negative" << std::endl;
181- throw std::runtime_error (" LogDDP: ms_segment_length_ must be non-negative" );
182- }
183-
184- const std::string &rollout_type = options.log_barrier .rollout_type ;
185- if (rollout_type != " linear" && rollout_type != " nonlinear" &&
186- rollout_type != " hybrid" ) {
187- std::cerr << " LogDDP: Invalid ms_rollout_type: " << rollout_type
188- << std::endl;
189- throw std::runtime_error (" LogDDP: Invalid ms_rollout_type" );
190- }
191-
192191 // Initialize log barrier object
193192 mu_ = options.log_barrier .barrier .mu_initial ;
194193 relaxation_delta_ = options.log_barrier .relaxed_log_barrier_delta ;
@@ -225,9 +224,6 @@ void LogDDPSolver::applyForwardPassResult(CDDP &context,
225224 CDDPSolverBase::applyForwardPassResult (context, result);
226225
227226 // Also update LogDDP-specific state
228- if (result.dynamics_trajectory ) {
229- F_ = *result.dynamics_trajectory ;
230- }
231227 constraint_violation_ = result.constraint_violation ;
232228}
233229
@@ -319,17 +315,9 @@ void LogDDPSolver::evaluateTrajectory(CDDP &context) {
319315 const int horizon = context.getHorizon ();
320316 double cost = 0.0 ;
321317
322- // Rollout dynamics and calculate cost
318+ // Calculate cost over the (already consistent) trajectory
323319 for (int t = 0 ; t < horizon; ++t) {
324- const Eigen::VectorXd &x_t = context.X_ [t];
325- const Eigen::VectorXd &u_t = context.U_ [t];
326-
327- // Compute stage cost using the guessed state/control
328- cost += context.getObjective ().running_cost (x_t , u_t , t);
329-
330- // Compute dynamics
331- F_[t] = context.getSystem ().getDiscreteDynamics (x_t , u_t ,
332- t * context.getTimestep ());
320+ cost += context.getObjective ().running_cost (context.X_ [t], context.U_ [t], t);
333321 }
334322
335323 // Add terminal cost based on the final guessed state
@@ -343,7 +331,6 @@ void LogDDPSolver::resetFilter(CDDP &context) {
343331 // Evaluate log-barrier cost (includes path constraints)
344332 context.merit_function_ = context.cost_ ;
345333 constraint_violation_ = 0.0 ;
346- double defect_violation = 0.0 ;
347334
348335 const auto &constraint_set = context.getConstraintSet ();
349336
@@ -364,12 +351,7 @@ void LogDDPSolver::resetFilter(CDDP &context) {
364351 }
365352 }
366353 }
367-
368- // Add defect violation penalty
369- Eigen::VectorXd d = F_[t] - context.X_ [t + 1 ];
370- defect_violation += d.lpNorm <1 >();
371354 }
372- constraint_violation_ += defect_violation;
373355 context.inf_pr_ = constraint_violation_;
374356}
375357
@@ -502,8 +484,6 @@ bool LogDDPSolver::backwardPass(CDDP &context) {
502484 for (int t = horizon - 1 ; t >= 0 ; --t) {
503485 const Eigen::VectorXd &x = context.X_ [t];
504486 const Eigen::VectorXd &u = context.U_ [t];
505- const Eigen::VectorXd &f = F_[t];
506- const Eigen::VectorXd &d = f - context.X_ [t + 1 ]; // Defect
507487
508488 // Use pre-computed continuous-time dynamics Jacobians
509489 const Eigen::MatrixXd &Fx = F_x_[t];
@@ -517,8 +497,8 @@ bool LogDDPSolver::backwardPass(CDDP &context) {
517497 auto [l_xx, l_uu, l_ux] =
518498 context.getObjective ().getRunningCostHessians (x, u, t);
519499
520- Eigen::VectorXd Q_x = l_x + A.transpose () * ( V_x + V_xx * d) ;
521- Eigen::VectorXd Q_u = l_u + B.transpose () * ( V_x + V_xx * d) ;
500+ Eigen::VectorXd Q_x = l_x + A.transpose () * V_x;
501+ Eigen::VectorXd Q_u = l_u + B.transpose () * V_x;
522502 Eigen::MatrixXd Q_xx = l_xx + A.transpose () * V_xx * A;
523503 Eigen::MatrixXd Q_ux = l_ux + B.transpose () * V_xx * A;
524504 Eigen::MatrixXd Q_uu = l_uu + B.transpose () * V_xx * B;
@@ -631,61 +611,22 @@ ForwardPassResult LogDDPSolver::forwardPass(CDDP &context, double alpha) {
631611 result.control_trajectory = context.U_ ;
632612 result.state_trajectory [0 ] = context.getInitialState ();
633613
634- std::vector<Eigen::VectorXd> F_new = F_;
635-
636614 double cost_new = 0.0 ;
637615 double merit_function_new = 0.0 ;
638616 double rp_err = 0.0 ;
639- double rf_err = 0.0 ;
640617
641- // Rollout loop with multi -shooting logic from original
618+ // Single -shooting rollout
642619 for (int t = 0 ; t < horizon; ++t) {
643620 const Eigen::VectorXd delta_x_t =
644621 result.state_trajectory [t] - context.X_ [t];
645622
646- // Update control
647623 result.control_trajectory [t] =
648624 context.U_ [t] + alpha * k_u_[t] + K_u_[t] * delta_x_t ;
649625
650- // --- Rollout Logic from original ---
651- Eigen::VectorXd dynamics_eval_for_F_new_t;
652-
653- // Determine if the *next* step (t+1) starts a new segment boundary
654- bool is_segment_boundary = (ms_segment_length_ > 0 ) &&
655- ((t + 1 ) % ms_segment_length_ == 0 ) &&
656- (t + 1 < horizon);
657- bool apply_gap_closing_strategy = is_segment_boundary;
658-
659- if (apply_gap_closing_strategy) {
660- if (options.log_barrier .rollout_type == " nonlinear" ) {
661- F_new[t] = context.getSystem ().getDiscreteDynamics (
662- result.state_trajectory [t], result.control_trajectory [t],
663- t * context.getTimestep ());
664- result.state_trajectory [t + 1 ] = context.X_ [t + 1 ] +
665- (F_new[t] - F_[t]) +
666- alpha * (F_[t] - context.X_ [t + 1 ]);
667- } else if (options.log_barrier .rollout_type == " hybrid" ) {
668- F_new[t] = context.getSystem ().getDiscreteDynamics (
669- result.state_trajectory [t], result.control_trajectory [t],
670- t * context.getTimestep ());
671- const auto [Fx, Fu] = context.getSystem ().getJacobians (
672- context.X_ [t], context.U_ [t], t * context.getTimestep ());
673- const Eigen::MatrixXd A =
674- Eigen::MatrixXd::Identity (state_dim, state_dim) +
675- context.getTimestep () * Fx;
676- const Eigen::MatrixXd B = context.getTimestep () * Fu;
677- result.state_trajectory [t + 1 ] =
678- context.X_ [t + 1 ] + (A + B * K_u_[t]) * delta_x_t +
679- alpha * (B * k_u_[t] + F_[t] - context.X_ [t + 1 ]);
680- }
681- } else {
682- result.state_trajectory [t + 1 ] = context.getSystem ().getDiscreteDynamics (
683- result.state_trajectory [t], result.control_trajectory [t],
684- t * context.getTimestep ());
685- F_new[t] = result.state_trajectory [t + 1 ];
686- }
626+ result.state_trajectory [t + 1 ] = context.getSystem ().getDiscreteDynamics (
627+ result.state_trajectory [t], result.control_trajectory [t],
628+ t * context.getTimestep ());
687629
688- // Robustness check
689630 if (!result.state_trajectory [t + 1 ].allFinite () ||
690631 !result.control_trajectory [t].allFinite ()) {
691632 if (options.debug ) {
@@ -698,13 +639,12 @@ ForwardPassResult LogDDPSolver::forwardPass(CDDP &context, double alpha) {
698639 }
699640 }
700641
701- // Cost computation and filter line-search from original
642+ // Cost computation and constraint evaluation
702643 for (int t = 0 ; t < horizon; ++t) {
703644 cost_new += context.getObjective ().running_cost (
704645 result.state_trajectory [t], result.control_trajectory [t], t);
705646
706647 for (const auto &constraint_pair : constraint_set) {
707- const std::string &constraint_name = constraint_pair.first ;
708648 Eigen::VectorXd g_t =
709649 constraint_pair.second ->evaluate (result.state_trajectory [t],
710650 result.control_trajectory [t]) -
@@ -719,18 +659,15 @@ ForwardPassResult LogDDPSolver::forwardPass(CDDP &context, double alpha) {
719659 }
720660 }
721661 }
722-
723- Eigen::VectorXd d = F_new[t] - result.state_trajectory [t + 1 ];
724- rf_err += d.lpNorm <1 >();
725662 }
726663
727664 cost_new +=
728665 context.getObjective ().terminal_cost (result.state_trajectory .back ());
729666 merit_function_new += cost_new;
730667
731- // Filter-based acceptance using original logic with new options structure
668+ // Filter-based acceptance
732669 double constraint_violation_old = constraint_violation_;
733- double constraint_violation_new = rf_err + rp_err;
670+ double constraint_violation_new = rp_err;
734671 double merit_function_old = context.merit_function_ ;
735672 bool filter_acceptance = false ;
736673 double expected_improvement = alpha * dV_ (0 );
@@ -766,7 +703,6 @@ ForwardPassResult LogDDPSolver::forwardPass(CDDP &context, double alpha) {
766703 result.cost = cost_new;
767704 result.merit_function = merit_function_new;
768705 result.constraint_violation = constraint_violation_new;
769- result.dynamics_trajectory = F_new;
770706 }
771707
772708 return result;
0 commit comments