84 m_decision_variables.emplace_back();
85 return m_decision_variables.back();
97 m_decision_variables.reserve(m_decision_variables.size() + rows * cols);
101 for (
int row = 0; row < rows; ++row) {
102 for (
int col = 0; col < cols; ++col) {
103 m_decision_variables.emplace_back();
104 vars[row, col] = m_decision_variables.back();
131 m_decision_variables.reserve(m_decision_variables.size() +
132 (rows * rows + rows) / 2);
136 for (
int row = 0; row < rows; ++row) {
137 for (
int col = 0; col <= row; ++col) {
138 m_decision_variables.emplace_back();
139 vars[row, col] = m_decision_variables.back();
140 vars[col, row] = m_decision_variables.back();
194 m_f = -std::move(objective);
202 m_equality_constraints.reserve(m_equality_constraints.size() +
205 std::back_inserter(m_equality_constraints));
213 m_equality_constraints.reserve(m_equality_constraints.size() +
214 constraint.constraints.size());
215 std::ranges::copy(constraint.constraints,
216 std::back_inserter(m_equality_constraints));
224 m_inequality_constraints.reserve(m_inequality_constraints.size() +
227 std::back_inserter(m_inequality_constraints));
235 m_inequality_constraints.reserve(m_inequality_constraints.size() +
236 constraint.constraints.size());
237 std::ranges::copy(constraint.constraints,
238 std::back_inserter(m_inequality_constraints));
246 return m_f.value().type();
248 return ExpressionType::NONE;
256 if (!m_equality_constraints.empty()) {
257 return std::ranges::max(m_equality_constraints, {},
261 return ExpressionType::NONE;
269 if (!m_inequality_constraints.empty()) {
270 return std::ranges::max(m_inequality_constraints, {},
274 return ExpressionType::NONE;
288 if (options.diagnostics) {
289 print_exit_conditions(options);
290 print_problem_analysis();
297 if (options.diagnostics) {
298 slp::println(
"\nInvoking no-op solver\n");
300 return ExitStatus::SUCCESS;
304 DenseVector x{m_decision_variables.size()};
305 for (
size_t i = 0; i < m_decision_variables.size(); ++i) {
306 x[i] = m_decision_variables[i].value();
311 if (m_equality_constraints.empty() && m_inequality_constraints.empty()) {
312 if (options.diagnostics) {
313 slp::println(
"\nInvoking Newton solver\n");
316 status = solve_newton(options, spy, x);
317 }
else if (m_inequality_constraints.empty()) {
318 if (options.diagnostics) {
319 slp::println(
"\nInvoking SQP solver\n");
322 status = solve_sqp(options, spy, x);
324 if (options.diagnostics) {
325 slp::println(
"\nInvoking IPM solver\n");
328 status = solve_ipm(options, spy, x);
331 if (options.diagnostics) {
332 slp::println(
"\nExit: {}", status);
336 VariableMatrix<Scalar>{m_decision_variables}.set_value(x);
346 template <
typename F>
347 requires requires(F callback,
const IterationInfo<Scalar>& info) {
348 { callback(info) } -> std::same_as<void>;
351 m_iteration_callbacks.emplace_back(
365 template <
typename F>
367 { callback(info) } -> std::same_as<bool>;
370 m_iteration_callbacks.emplace_back(std::forward<F>(callback));
384 template <
typename F>
386 { callback(info) } -> std::same_as<bool>;
389 m_persistent_iteration_callbacks.emplace_back(std::forward<F>(callback));
393 using DenseVector = Eigen::VectorX<Scalar>;
397 gch::small_vector<Variable<Scalar>> m_decision_variables;
400 std::optional<Variable<Scalar>> m_f;
403 gch::small_vector<Variable<Scalar>> m_equality_constraints;
406 gch::small_vector<Variable<Scalar>> m_inequality_constraints;
410 m_iteration_callbacks;
412 m_persistent_iteration_callbacks;
415 [[maybe_unused]]
bool spy, DenseVector& x) {
416 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
417 using SparseVector = Eigen::SparseVector<Scalar>;
418 using enum SetupTimeTracerID;
423 Variable f = m_f.value_or(Scalar(0));
425 int num_decision_variables = m_decision_variables.size();
427 gch::small_vector<std::function<bool(
const IterationInfo<Scalar>& info)>>
429 for (
const auto& callback : m_iteration_callbacks) {
430 iteration_callbacks.emplace_back(callback);
432 for (
const auto& callback : m_persistent_iteration_callbacks) {
433 iteration_callbacks.emplace_back(callback);
436 TimeTracerTree<SetupTimeTracer, newton_setup_time_trace> ad_setup_tracers{
438 ScopedTimeTracer setup_tracer{ad_setup_tracers[SETUP]};
441 PhaseTimeTracer phase_tracer{ad_setup_tracers[GRADIENT]};
445 phase_tracer.next(ad_setup_tracers[HESSIAN]);
446 Hessian<Scalar, Eigen::Lower> H{f, x_ad};
452 print_setup_diagnostics(ad_setup_tracers);
455#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
457 std::unique_ptr<Spy<Scalar>> H_spy;
460 H_spy = std::make_unique<Spy<Scalar>>(
461 "H.spy",
"Hessian",
"Decision variables",
"Decision variables",
462 num_decision_variables, num_decision_variables);
463 iteration_callbacks.push_back(
464 [&](
const IterationInfo<Scalar>& info) ->
bool {
474 const ProblemScaling<Scalar> scaling{g.value()};
476 NewtonMatrixCallbacks<Scalar> matrix_callbacks{
477 num_decision_variables,
478 [&](
const DenseVector& x) -> Scalar {
480 return scaling.f * f.value();
482 [&](
const DenseVector& x) -> SparseVector {
484 return scaling.f * g.value();
486 [&](
const DenseVector& x) -> SparseMatrix {
488 return scaling.f * H.value();
493 return newton<Scalar>(matrix_callbacks, iteration_callbacks, options, x);
496 ExitStatus solve_sqp(
const Options<Scalar>& options,
497 [[maybe_unused]]
bool spy, DenseVector& x) {
498 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
499 using SparseVector = Eigen::SparseVector<Scalar>;
500 using enum SetupTimeTracerID;
502 VariableMatrix<Scalar> x_ad{m_decision_variables};
505 Variable f = m_f.value_or(Scalar(0));
507 int num_decision_variables = m_decision_variables.size();
508 int num_equality_constraints = m_equality_constraints.size();
510 gch::small_vector<std::function<bool(
const IterationInfo<Scalar>& info)>>
512 for (
const auto& callback : m_iteration_callbacks) {
513 iteration_callbacks.emplace_back(callback);
515 for (
const auto& callback : m_persistent_iteration_callbacks) {
516 iteration_callbacks.emplace_back(callback);
519 VariableMatrix<Scalar> c_e_ad{m_equality_constraints};
520 VariableMatrix<Scalar> y_ad(num_equality_constraints);
522 TimeTracerTree<SetupTimeTracer, sqp_setup_time_trace> ad_setup_tracers{
523 options.diagnostics};
524 ScopedTimeTracer setup_tracer{ad_setup_tracers[SETUP]};
527 PhaseTimeTracer phase_tracer{ad_setup_tracers[GRADIENT]};
530 phase_tracer.next(ad_setup_tracers[HESSIAN]);
533 PhaseTimeTracer hessian_phase_tracer{ad_setup_tracers[HESSIAN_COST]};
534 Hessian<Scalar, Eigen::Lower> H_f{f, x_ad};
537 hessian_phase_tracer.next(ad_setup_tracers[HESSIAN_CONSTRAINTS]);
538 Hessian<Scalar, Eigen::Lower> H_c{-y_ad.T() * c_e_ad, x_ad};
540 hessian_phase_tracer.stop();
543 phase_tracer.next(ad_setup_tracers[EQUALITY_JACOBIAN]);
544 Jacobian A_e{c_e_ad, x_ad};
549 if (options.diagnostics) {
550 print_setup_diagnostics(ad_setup_tracers);
553#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
555 std::unique_ptr<Spy<Scalar>> H_spy;
556 std::unique_ptr<Spy<Scalar>> A_e_spy;
559 H_spy = std::make_unique<Spy<Scalar>>(
560 "H.spy",
"Hessian",
"Decision variables",
"Decision variables",
561 num_decision_variables, num_decision_variables);
562 A_e_spy = std::make_unique<Spy<Scalar>>(
563 "A_e.spy",
"Equality constraint Jacobian",
"Constraints",
564 "Decision variables", num_equality_constraints,
565 num_decision_variables);
566 iteration_callbacks.push_back(
567 [&](
const IterationInfo<Scalar>& info) ->
bool {
569 A_e_spy->add(info.A_e);
579 const ProblemScaling<Scalar> scaling{g.value(), A_e.value()};
581 SQPMatrixCallbacks<Scalar> matrix_callbacks{
582 num_decision_variables,
583 num_equality_constraints,
584 [&](
const DenseVector& x) -> Scalar {
586 return scaling.f * f.value();
588 [&](
const DenseVector& x) -> SparseVector {
590 return scaling.f * g.value();
592 [&](
const DenseVector& x,
const DenseVector& y) -> SparseMatrix {
594 y_ad.set_value(scaling.c_e.cwiseProduct(y));
595 return scaling.f * H_f.value() + H_c.value();
597 [&](
const DenseVector& x,
const DenseVector& y) -> SparseMatrix {
599 y_ad.set_value(scaling.c_e.cwiseProduct(y));
602 [&](
const DenseVector& x) -> DenseVector {
604 return scaling.c_e.cwiseProduct(c_e_ad.value());
606 [&](
const DenseVector& x) -> SparseMatrix {
608 return scaling.c_e.asDiagonal() * A_e.value();
613 return sqp<Scalar>(matrix_callbacks, iteration_callbacks, options, x);
616 ExitStatus solve_ipm(
const Options<Scalar>& options,
617 [[maybe_unused]]
bool spy, DenseVector& x) {
618 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
619 using SparseVector = Eigen::SparseVector<Scalar>;
620 using enum SetupTimeTracerID;
622 VariableMatrix<Scalar> x_ad{m_decision_variables};
625 Variable f = m_f.value_or(Scalar(0));
627 int num_decision_variables = m_decision_variables.size();
628 int num_equality_constraints = m_equality_constraints.size();
629 int num_inequality_constraints = m_inequality_constraints.size();
631 gch::small_vector<std::function<bool(
const IterationInfo<Scalar>& info)>>
633 for (
const auto& callback : m_iteration_callbacks) {
634 iteration_callbacks.emplace_back(callback);
636 for (
const auto& callback : m_persistent_iteration_callbacks) {
637 iteration_callbacks.emplace_back(callback);
640 VariableMatrix<Scalar> c_e_ad{m_equality_constraints};
641 VariableMatrix<Scalar> c_i_ad{m_inequality_constraints};
642 VariableMatrix<Scalar> y_ad(num_equality_constraints);
643 VariableMatrix<Scalar> z_ad(num_inequality_constraints);
645 TimeTracerTree<SetupTimeTracer, ipm_setup_time_trace> ad_setup_tracers{
646 options.diagnostics};
647 ScopedTimeTracer setup_tracer{ad_setup_tracers[SETUP]};
650 PhaseTimeTracer phase_tracer{ad_setup_tracers[GRADIENT]};
653 phase_tracer.next(ad_setup_tracers[HESSIAN]);
656 PhaseTimeTracer hessian_phase_tracer{ad_setup_tracers[HESSIAN_COST]};
657 Hessian<Scalar, Eigen::Lower> H_f{f, x_ad};
660 hessian_phase_tracer.next(ad_setup_tracers[HESSIAN_CONSTRAINTS]);
661 Hessian<Scalar, Eigen::Lower> H_c{-y_ad.T() * c_e_ad - z_ad.T() * c_i_ad,
664 hessian_phase_tracer.stop();
667 phase_tracer.next(ad_setup_tracers[EQUALITY_JACOBIAN]);
668 Jacobian A_e{c_e_ad, x_ad};
671 phase_tracer.next(ad_setup_tracers[INEQUALITY_JACOBIAN]);
672 Jacobian A_i{c_i_ad, x_ad};
677 if (options.diagnostics) {
678 print_setup_diagnostics(ad_setup_tracers);
681#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
683 std::unique_ptr<Spy<Scalar>> H_spy;
684 std::unique_ptr<Spy<Scalar>> A_e_spy;
685 std::unique_ptr<Spy<Scalar>> A_i_spy;
688 H_spy = std::make_unique<Spy<Scalar>>(
689 "H.spy",
"Hessian",
"Decision variables",
"Decision variables",
690 num_decision_variables, num_decision_variables);
691 A_e_spy = std::make_unique<Spy<Scalar>>(
692 "A_e.spy",
"Equality constraint Jacobian",
"Constraints",
693 "Decision variables", num_equality_constraints,
694 num_decision_variables);
695 A_i_spy = std::make_unique<Spy<Scalar>>(
696 "A_i.spy",
"Inequality constraint Jacobian",
"Constraints",
697 "Decision variables", num_inequality_constraints,
698 num_decision_variables);
699 iteration_callbacks.push_back(
700 [&](
const IterationInfo<Scalar>& info) ->
bool {
702 A_e_spy->add(info.A_e);
703 A_i_spy->add(info.A_i);
709 const auto [bound_constraint_mask, bounds, conflicting_bound_indices] =
710 get_bounds<Scalar>(m_decision_variables, m_inequality_constraints,
712 if (!conflicting_bound_indices.empty()) {
713 if (options.diagnostics) {
714 print_bound_constraint_global_infeasibility_error(
715 conflicting_bound_indices);
717 return ExitStatus::GLOBALLY_INFEASIBLE;
720#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
721 project_onto_bounds(x, bounds);
728 const ProblemScaling<Scalar> scaling{g.value(), A_e.value(), A_i.value()};
730 IPMMatrixCallbacks<Scalar> matrix_callbacks{
731 num_decision_variables,
732 num_equality_constraints,
733 num_inequality_constraints,
734 [&](
const DenseVector& x) -> Scalar {
736 return scaling.f * f.value();
738 [&](
const DenseVector& x) -> SparseVector {
740 return scaling.f * g.value();
742 [&](
const DenseVector& x,
const DenseVector& y,
743 const DenseVector& z) -> SparseMatrix {
745 y_ad.set_value(scaling.c_e.cwiseProduct(y));
746 z_ad.set_value(scaling.c_i.cwiseProduct(z));
747 return scaling.f * H_f.value() + H_c.value();
749 [&](
const DenseVector& x,
const DenseVector& y,
750 const DenseVector& z) -> SparseMatrix {
752 y_ad.set_value(scaling.c_e.cwiseProduct(y));
753 z_ad.set_value(scaling.c_i.cwiseProduct(z));
756 [&](
const DenseVector& x) -> DenseVector {
758 return scaling.c_e.cwiseProduct(c_e_ad.value());
760 [&](
const DenseVector& x) -> SparseMatrix {
762 return scaling.c_e.asDiagonal() * A_e.value();
764 [&](
const DenseVector& x) -> DenseVector {
766 return scaling.c_i.cwiseProduct(c_i_ad.value());
768 [&](
const DenseVector& x) -> SparseMatrix {
770 return scaling.c_i.asDiagonal() * A_i.value();
775 return ipm<Scalar>(matrix_callbacks, iteration_callbacks, options,
776#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
777 bound_constraint_mask,
782 void print_exit_conditions(
const Options<Scalar>& options) {
784 slp::println(
"User-configured exit conditions:");
785 slp::println(
" ↳ error below {}", options.tolerance);
786 if (!m_iteration_callbacks.empty() ||
787 !m_persistent_iteration_callbacks.empty()) {
788 slp::println(
" ↳ iteration callback requested stop");
790 if (std::isfinite(options.max_iterations)) {
791 slp::println(
" ↳ executed {} iterations", options.max_iterations);
793 if (std::isfinite(options.timeout.count())) {
794 slp::println(
" ↳ {} elapsed", options.timeout);
798 void print_problem_analysis() {
799 constexpr std::array types{
"no",
"constant",
"linear",
"quadratic",
803 slp::println(
"\nProblem structure:");
804 slp::println(
" ↳ {} cost function",
806 slp::println(
" ↳ {} equality constraints",
808 slp::println(
" ↳ {} inequality constraints",
811 if (m_decision_variables.size() == 1) {
812 slp::print(
"\n1 decision variable\n");
814 slp::print(
"\n{} decision variables\n", m_decision_variables.size());
817 auto print_constraint_types =
818 [](
const gch::small_vector<Variable<Scalar>>& constraints) {
819 std::array<size_t, 5> counts{};
820 for (
const auto& constraint : constraints) {
821 ++counts[std::to_underlying(constraint.type())];
823 for (
const auto& [count, name] :
824 std::views::zip(counts, std::array{
"empty",
"constant",
"linear",
825 "quadratic",
"nonlinear"})) {
827 slp::println(
" ↳ {} {}", count, name);
833 if (m_equality_constraints.size() == 1) {
834 slp::println(
"1 equality constraint");
836 slp::println(
"{} equality constraints", m_equality_constraints.size());
838 print_constraint_types(m_equality_constraints);
839 if (m_inequality_constraints.size() == 1) {
840 slp::println(
"1 inequality constraint");
842 slp::println(
"{} inequality constraints",
843 m_inequality_constraints.size());
845 print_constraint_types(m_inequality_constraints);