83 m_decision_variables.emplace_back();
84 return m_decision_variables.back();
96 m_decision_variables.reserve(m_decision_variables.size() + rows * cols);
100 for (
int row = 0; row < rows; ++row) {
101 for (
int col = 0; col < cols; ++col) {
102 m_decision_variables.emplace_back();
103 vars[row, col] = m_decision_variables.back();
130 m_decision_variables.reserve(m_decision_variables.size() +
131 (rows * rows + rows) / 2);
135 for (
int row = 0; row < rows; ++row) {
136 for (
int col = 0; col <= row; ++col) {
137 m_decision_variables.emplace_back();
138 vars[row, col] = m_decision_variables.back();
139 vars[col, row] = m_decision_variables.back();
201 m_equality_constraints.reserve(m_equality_constraints.size() +
204 std::back_inserter(m_equality_constraints));
212 m_equality_constraints.reserve(m_equality_constraints.size() +
215 std::back_inserter(m_equality_constraints));
223 m_inequality_constraints.reserve(m_inequality_constraints.size() +
226 std::back_inserter(m_inequality_constraints));
234 m_inequality_constraints.reserve(m_inequality_constraints.size() +
237 std::back_inserter(m_inequality_constraints));
245 return m_f.value().type();
247 return ExpressionType::NONE;
255 if (!m_equality_constraints.empty()) {
256 return std::ranges::max(m_equality_constraints, {},
260 return ExpressionType::NONE;
268 if (!m_inequality_constraints.empty()) {
269 return std::ranges::max(m_inequality_constraints, {},
273 return ExpressionType::NONE;
287 print_exit_conditions(
options);
288 print_problem_analysis();
295 if (options.diagnostics) {
296 slp::println(
"\nInvoking no-op solver\n");
298 return ExitStatus::SUCCESS;
302 DenseVector x{m_decision_variables.size()};
303 for (
size_t i = 0; i < m_decision_variables.size(); ++i) {
304 x[i] = m_decision_variables[i].value();
309 if (m_equality_constraints.empty() && m_inequality_constraints.empty()) {
310 if (options.diagnostics) {
311 slp::println(
"\nInvoking Newton solver\n");
314 status = solve_newton(options, spy, x);
315 }
else if (m_inequality_constraints.empty()) {
316 if (options.diagnostics) {
317 slp::println(
"\nInvoking SQP solver\n");
320 status = solve_sqp(options, spy, x);
322 if (options.diagnostics) {
323 slp::println(
"\nInvoking IPM solver\n");
326 status = solve_ipm(options, spy, x);
329 if (options.diagnostics) {
330 slp::println(
"\nExit: {}", status);
334 VariableMatrix<Scalar>{m_decision_variables}.set_value(x);
344 template <
typename F>
345 requires requires(F callback,
const IterationInfo<Scalar>& info) {
346 { callback(info) } -> std::same_as<void>;
349 m_iteration_callbacks.emplace_back(
363 template <
typename F>
365 {
callback(info) } -> std::same_as<bool>;
368 m_iteration_callbacks.emplace_back(std::forward<F>(
callback));
382 template <
typename F>
384 {
callback(info) } -> std::same_as<bool>;
387 m_persistent_iteration_callbacks.emplace_back(std::forward<F>(
callback));
391 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
395 gch::small_vector<Variable<Scalar>> m_decision_variables;
398 std::optional<Variable<Scalar>> m_f;
401 gch::small_vector<Variable<Scalar>> m_equality_constraints;
404 gch::small_vector<Variable<Scalar>> m_inequality_constraints;
408 m_iteration_callbacks;
410 m_persistent_iteration_callbacks;
414 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
415 using SparseVector = Eigen::SparseVector<Scalar>;
420 Variable f = m_f.value_or(Scalar(0));
422 int num_decision_variables = m_decision_variables.size();
424 gch::small_vector<std::function<bool(
const IterationInfo<Scalar>& info)>>
426 for (
const auto& callback : m_iteration_callbacks) {
427 iteration_callbacks.emplace_back(callback);
429 for (
const auto& callback : m_persistent_iteration_callbacks) {
430 iteration_callbacks.emplace_back(callback);
433 gch::small_vector<SetupProfiler> ad_setup_profilers;
434 ad_setup_profilers.emplace_back(
"setup");
435 ad_setup_profilers.emplace_back(
"↳ ∇f(x)");
436 ad_setup_profilers.emplace_back(
"↳ ∇²ₓₓL");
438 ad_setup_profilers[0].start();
441 ad_setup_profilers[1].start();
443 ad_setup_profilers[1].stop();
446 ad_setup_profilers[2].start();
447 Hessian<Scalar, Eigen::Lower> H{f, x_ad};
448 ad_setup_profilers[2].stop();
450 ad_setup_profilers[0].stop();
453 print_setup_diagnostics(ad_setup_profilers);
456#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
458 std::unique_ptr<Spy<Scalar>> H_spy;
461 H_spy = std::make_unique<Spy<Scalar>>(
462 "H.spy",
"Hessian",
"Decision variables",
"Decision variables",
463 num_decision_variables, num_decision_variables);
464 iteration_callbacks.push_back(
465 [&](
const IterationInfo<Scalar>& info) ->
bool {
475 const ProblemScaling<Scalar> scaling{g.value()};
477 NewtonMatrixCallbacks<Scalar> matrix_callbacks{
478 num_decision_variables,
479 [&](
const DenseVector& x) -> Scalar {
481 return scaling.f * f.value();
483 [&](
const DenseVector& x) -> SparseVector {
485 return scaling.f * g.value();
487 [&](
const DenseVector& x) -> SparseMatrix {
489 return scaling.f * H.value();
494 return newton<Scalar>(matrix_callbacks, iteration_callbacks, options, x);
497 ExitStatus solve_sqp(
const Options& options, [[maybe_unused]]
bool spy,
499 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
500 using SparseVector = Eigen::SparseVector<Scalar>;
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 gch::small_vector<SetupProfiler> ad_setup_profilers;
523 ad_setup_profilers.emplace_back(
"setup");
524 ad_setup_profilers.emplace_back(
"↳ ∇f(x)");
525 ad_setup_profilers.emplace_back(
"↳ ∇²ₓₓL");
526 ad_setup_profilers.emplace_back(
" ↳ ∇²ₓₓL_f");
527 ad_setup_profilers.emplace_back(
" ↳ ∇²ₓₓL_c");
528 ad_setup_profilers.emplace_back(
"↳ ∂cₑ/∂x");
530 ad_setup_profilers[0].start();
533 ad_setup_profilers[1].start();
535 ad_setup_profilers[1].stop();
537 ad_setup_profilers[2].start();
540 ad_setup_profilers[3].start();
541 Hessian<Scalar, Eigen::Lower> H_f{f, x_ad};
542 ad_setup_profilers[3].stop();
545 ad_setup_profilers[4].start();
546 Hessian<Scalar, Eigen::Lower> H_c{-y_ad.T() * c_e_ad, x_ad};
547 ad_setup_profilers[4].stop();
549 ad_setup_profilers[2].stop();
552 ad_setup_profilers[5].start();
553 Jacobian A_e{c_e_ad, x_ad};
554 ad_setup_profilers[5].stop();
556 ad_setup_profilers[0].stop();
558 if (options.diagnostics) {
559 print_setup_diagnostics(ad_setup_profilers);
562#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
564 std::unique_ptr<Spy<Scalar>> H_spy;
565 std::unique_ptr<Spy<Scalar>> A_e_spy;
568 H_spy = std::make_unique<Spy<Scalar>>(
569 "H.spy",
"Hessian",
"Decision variables",
"Decision variables",
570 num_decision_variables, num_decision_variables);
571 A_e_spy = std::make_unique<Spy<Scalar>>(
572 "A_e.spy",
"Equality constraint Jacobian",
"Constraints",
573 "Decision variables", num_equality_constraints,
574 num_decision_variables);
575 iteration_callbacks.push_back(
576 [&](
const IterationInfo<Scalar>& info) ->
bool {
578 A_e_spy->add(info.A_e);
588 const ProblemScaling<Scalar> scaling{g.value(), A_e.value()};
590 SQPMatrixCallbacks<Scalar> matrix_callbacks{
591 num_decision_variables,
592 num_equality_constraints,
593 [&](
const DenseVector& x) -> Scalar {
595 return scaling.f * f.value();
597 [&](
const DenseVector& x) -> SparseVector {
599 return scaling.f * g.value();
601 [&](
const DenseVector& x,
const DenseVector& y) -> SparseMatrix {
603 y_ad.set_value(scaling.c_e.cwiseProduct(y));
604 return scaling.f * H_f.value() + H_c.value();
606 [&](
const DenseVector& x,
const DenseVector& y) -> SparseMatrix {
608 y_ad.set_value(scaling.c_e.cwiseProduct(y));
611 [&](
const DenseVector& x) -> DenseVector {
613 return scaling.c_e.cwiseProduct(c_e_ad.value());
615 [&](
const DenseVector& x) -> SparseMatrix {
617 return scaling.c_e.asDiagonal() * A_e.value();
622 return sqp<Scalar>(matrix_callbacks, iteration_callbacks, options, x);
625 ExitStatus solve_ipm(
const Options& options, [[maybe_unused]]
bool spy,
627 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
628 using SparseVector = Eigen::SparseVector<Scalar>;
630 VariableMatrix<Scalar> x_ad{m_decision_variables};
633 Variable f = m_f.value_or(Scalar(0));
635 int num_decision_variables = m_decision_variables.size();
636 int num_equality_constraints = m_equality_constraints.size();
637 int num_inequality_constraints = m_inequality_constraints.size();
639 gch::small_vector<std::function<bool(
const IterationInfo<Scalar>& info)>>
641 for (
const auto& callback : m_iteration_callbacks) {
642 iteration_callbacks.emplace_back(callback);
644 for (
const auto& callback : m_persistent_iteration_callbacks) {
645 iteration_callbacks.emplace_back(callback);
648 VariableMatrix<Scalar> c_e_ad{m_equality_constraints};
649 VariableMatrix<Scalar> c_i_ad{m_inequality_constraints};
650 VariableMatrix<Scalar> y_ad(num_equality_constraints);
651 VariableMatrix<Scalar> z_ad(num_inequality_constraints);
653 gch::small_vector<SetupProfiler> ad_setup_profilers;
654 ad_setup_profilers.emplace_back(
"setup");
655 ad_setup_profilers.emplace_back(
"↳ ∇f(x)");
656 ad_setup_profilers.emplace_back(
"↳ ∇²ₓₓL");
657 ad_setup_profilers.emplace_back(
" ↳ ∇²ₓₓL_f");
658 ad_setup_profilers.emplace_back(
" ↳ ∇²ₓₓL_c");
659 ad_setup_profilers.emplace_back(
"↳ ∂cₑ/∂x");
660 ad_setup_profilers.emplace_back(
"↳ ∂cᵢ/∂x");
662 ad_setup_profilers[0].start();
665 ad_setup_profilers[1].start();
667 ad_setup_profilers[1].stop();
669 ad_setup_profilers[2].start();
672 ad_setup_profilers[3].start();
673 Hessian<Scalar, Eigen::Lower> H_f{f, x_ad};
674 ad_setup_profilers[3].stop();
677 ad_setup_profilers[4].start();
678 Hessian<Scalar, Eigen::Lower> H_c{-y_ad.T() * c_e_ad - z_ad.T() * c_i_ad,
680 ad_setup_profilers[4].stop();
682 ad_setup_profilers[2].stop();
685 ad_setup_profilers[5].start();
686 Jacobian A_e{c_e_ad, x_ad};
687 ad_setup_profilers[5].stop();
690 ad_setup_profilers[6].start();
691 Jacobian A_i{c_i_ad, x_ad};
692 ad_setup_profilers[6].stop();
694 ad_setup_profilers[0].stop();
696 if (options.diagnostics) {
697 print_setup_diagnostics(ad_setup_profilers);
700#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
702 std::unique_ptr<Spy<Scalar>> H_spy;
703 std::unique_ptr<Spy<Scalar>> A_e_spy;
704 std::unique_ptr<Spy<Scalar>> A_i_spy;
707 H_spy = std::make_unique<Spy<Scalar>>(
708 "H.spy",
"Hessian",
"Decision variables",
"Decision variables",
709 num_decision_variables, num_decision_variables);
710 A_e_spy = std::make_unique<Spy<Scalar>>(
711 "A_e.spy",
"Equality constraint Jacobian",
"Constraints",
712 "Decision variables", num_equality_constraints,
713 num_decision_variables);
714 A_i_spy = std::make_unique<Spy<Scalar>>(
715 "A_i.spy",
"Inequality constraint Jacobian",
"Constraints",
716 "Decision variables", num_inequality_constraints,
717 num_decision_variables);
718 iteration_callbacks.push_back(
719 [&](
const IterationInfo<Scalar>& info) ->
bool {
721 A_e_spy->add(info.A_e);
722 A_i_spy->add(info.A_i);
728 const auto [bound_constraint_mask, bounds, conflicting_bound_indices] =
729 get_bounds<Scalar>(m_decision_variables, m_inequality_constraints,
731 if (!conflicting_bound_indices.empty()) {
732 if (options.diagnostics) {
733 print_bound_constraint_global_infeasibility_error(
734 conflicting_bound_indices);
736 return ExitStatus::GLOBALLY_INFEASIBLE;
739#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
740 project_onto_bounds(x, bounds);
747 const ProblemScaling<Scalar> scaling{g.value(), A_e.value(), A_i.value()};
749 IPMMatrixCallbacks<Scalar> matrix_callbacks{
750 num_decision_variables,
751 num_equality_constraints,
752 num_inequality_constraints,
753 [&](
const DenseVector& x) -> Scalar {
755 return scaling.f * f.value();
757 [&](
const DenseVector& x) -> SparseVector {
759 return scaling.f * g.value();
761 [&](
const DenseVector& x,
const DenseVector& y,
762 const DenseVector& z) -> SparseMatrix {
764 y_ad.set_value(scaling.c_e.cwiseProduct(y));
765 z_ad.set_value(scaling.c_i.cwiseProduct(z));
766 return scaling.f * H_f.value() + H_c.value();
768 [&](
const DenseVector& x,
const DenseVector& y,
769 const DenseVector& z) -> SparseMatrix {
771 y_ad.set_value(scaling.c_e.cwiseProduct(y));
772 z_ad.set_value(scaling.c_i.cwiseProduct(z));
775 [&](
const DenseVector& x) -> DenseVector {
777 return scaling.c_e.cwiseProduct(c_e_ad.value());
779 [&](
const DenseVector& x) -> SparseMatrix {
781 return scaling.c_e.asDiagonal() * A_e.value();
783 [&](
const DenseVector& x) -> DenseVector {
785 return scaling.c_i.cwiseProduct(c_i_ad.value());
787 [&](
const DenseVector& x) -> SparseMatrix {
789 return scaling.c_i.asDiagonal() * A_i.value();
794 return ipm<Scalar>(matrix_callbacks, iteration_callbacks, options,
795#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
796 bound_constraint_mask,
801 void print_exit_conditions(
const Options& options) {
803 slp::println(
"User-configured exit conditions:");
804 slp::println(
" ↳ error below {}", options.tolerance);
805 if (!m_iteration_callbacks.empty() ||
806 !m_persistent_iteration_callbacks.empty()) {
807 slp::println(
" ↳ iteration callback requested stop");
809 if (std::isfinite(options.max_iterations)) {
810 slp::println(
" ↳ executed {} iterations", options.max_iterations);
812 if (std::isfinite(options.timeout.count())) {
813 slp::println(
" ↳ {} elapsed", options.timeout);
817 void print_problem_analysis() {
818 constexpr std::array types{
"no",
"constant",
"linear",
"quadratic",
822 slp::println(
"\nProblem structure:");
823 slp::println(
" ↳ {} cost function",
825 slp::println(
" ↳ {} equality constraints",
827 slp::println(
" ↳ {} inequality constraints",
830 if (m_decision_variables.size() == 1) {
831 slp::print(
"\n1 decision variable\n");
833 slp::print(
"\n{} decision variables\n", m_decision_variables.size());
836 auto print_constraint_types =
837 [](
const gch::small_vector<Variable<Scalar>>& constraints) {
838 std::array<size_t, 5> counts{};
839 for (
const auto& constraint : constraints) {
840 ++counts[std::to_underlying(constraint.type())];
842 for (
const auto& [count, name] :
843 std::views::zip(counts, std::array{
"empty",
"constant",
"linear",
844 "quadratic",
"nonlinear"})) {
846 slp::println(
" ↳ {} {}", count, name);
852 if (m_equality_constraints.size() == 1) {
853 slp::println(
"1 equality constraint");
855 slp::println(
"{} equality constraints", m_equality_constraints.size());
857 print_constraint_types(m_equality_constraints);
858 if (m_inequality_constraints.size() == 1) {
859 slp::println(
"1 inequality constraint");
861 slp::println(
"{} inequality constraints",
862 m_inequality_constraints.size());
864 print_constraint_types(m_inequality_constraints);