Sleipnir C++ API
Loading...
Searching...
No Matches
problem.hpp
1// Copyright (c) Sleipnir contributors
2
3#pragma once
4
5#include <algorithm>
6#include <array>
7#include <cmath>
8#include <concepts>
9#include <cstddef>
10#include <functional>
11#include <iterator>
12#include <memory>
13#include <optional>
14#include <ranges>
15#include <utility>
16
17#include <Eigen/Core>
18#include <Eigen/SparseCore>
19#include <gch/small_vector.hpp>
20
21#include "sleipnir/autodiff/expression_type.hpp"
22#include "sleipnir/autodiff/gradient.hpp"
23#include "sleipnir/autodiff/hessian.hpp"
24#include "sleipnir/autodiff/jacobian.hpp"
25#include "sleipnir/autodiff/variable.hpp"
26#include "sleipnir/autodiff/variable_matrix.hpp"
27#include "sleipnir/optimization/solver/exit_status.hpp"
28#include "sleipnir/optimization/solver/ipm.hpp"
29#include "sleipnir/optimization/solver/ipm_matrix_callbacks.hpp"
30#include "sleipnir/optimization/solver/iteration_info.hpp"
31#include "sleipnir/optimization/solver/newton.hpp"
32#include "sleipnir/optimization/solver/newton_matrix_callbacks.hpp"
33#include "sleipnir/optimization/solver/options.hpp"
34#include "sleipnir/optimization/solver/sqp.hpp"
35#include "sleipnir/optimization/solver/sqp_matrix_callbacks.hpp"
36#include "sleipnir/optimization/solver/util/bounds.hpp"
37#include "sleipnir/optimization/solver/util/problem_scaling.hpp"
38#include "sleipnir/util/empty.hpp"
39#include "sleipnir/util/print.hpp"
40#include "sleipnir/util/print_diagnostics.hpp"
41#include "sleipnir/util/profiler.hpp"
42#include "sleipnir/util/spy.hpp"
43#include "sleipnir/util/symbol_exports.hpp"
44
45namespace slp {
46
70template <typename Scalar>
71class Problem {
72 public:
75
81 [[nodiscard]]
83 m_decision_variables.emplace_back();
84 return m_decision_variables.back();
85 }
86
94 [[nodiscard]]
95 VariableMatrix<Scalar> decision_variable(int rows, int cols = 1) {
96 m_decision_variables.reserve(m_decision_variables.size() + rows * cols);
97
98 VariableMatrix<Scalar> vars{detail::empty, rows, cols};
99
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();
104 }
105 }
106
107 return vars;
108 }
109
121 [[nodiscard]]
123 // We only need to store the lower triangle of an n x n symmetric matrix;
124 // the other elements are duplicates. The lower triangle has (n² + n)/2
125 // elements.
126 //
127 // n
128 // Σ k = (n² + n)/2
129 // k=1
130 m_decision_variables.reserve(m_decision_variables.size() +
131 (rows * rows + rows) / 2);
132
133 VariableMatrix<Scalar> vars{detail::empty, rows, rows};
134
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();
140 }
141 }
142
143 return vars;
144 }
145
155 void minimize(const Variable<Scalar>& cost) { m_f = cost; }
156
166 void minimize(Variable<Scalar>&& cost) { m_f = std::move(cost); }
167
178 // Maximizing a cost function is the same as minimizing its negative
179 m_f = -objective;
180 }
181
192 // Maximizing a cost function is the same as minimizing its negative
193 m_f = -std::move(objective);
194 }
195
201 m_equality_constraints.reserve(m_equality_constraints.size() +
202 constraint.constraints.size());
203 std::ranges::copy(constraint.constraints,
204 std::back_inserter(m_equality_constraints));
205 }
206
212 m_equality_constraints.reserve(m_equality_constraints.size() +
213 constraint.constraints.size());
214 std::ranges::copy(constraint.constraints,
215 std::back_inserter(m_equality_constraints));
216 }
217
223 m_inequality_constraints.reserve(m_inequality_constraints.size() +
224 constraint.constraints.size());
225 std::ranges::copy(constraint.constraints,
226 std::back_inserter(m_inequality_constraints));
227 }
228
234 m_inequality_constraints.reserve(m_inequality_constraints.size() +
235 constraint.constraints.size());
236 std::ranges::copy(constraint.constraints,
237 std::back_inserter(m_inequality_constraints));
238 }
239
243 ExpressionType cost_function_type() const {
244 if (m_f) {
245 return m_f.value().type();
246 } else {
247 return ExpressionType::NONE;
248 }
249 }
250
254 ExpressionType equality_constraint_type() const {
255 if (!m_equality_constraints.empty()) {
256 return std::ranges::max(m_equality_constraints, {},
258 .type();
259 } else {
260 return ExpressionType::NONE;
261 }
262 }
263
267 ExpressionType inequality_constraint_type() const {
268 if (!m_inequality_constraints.empty()) {
269 return std::ranges::max(m_inequality_constraints, {},
271 .type();
272 } else {
273 return ExpressionType::NONE;
274 }
275 }
276
285 ExitStatus solve(const Options& options = Options{}, bool spy = false) {
286 if (options.diagnostics) {
287 print_exit_conditions(options);
288 print_problem_analysis();
289 }
290
291 // If the problem is empty or constant, there's nothing to do
292 if (cost_function_type() <= ExpressionType::CONSTANT &&
293 equality_constraint_type() <= ExpressionType::CONSTANT &&
294 inequality_constraint_type() <= ExpressionType::CONSTANT) {
295 if (options.diagnostics) {
296 slp::println("\nInvoking no-op solver\n");
297 }
298 return ExitStatus::SUCCESS;
299 }
300
301 // Create the initial value column vector
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();
305 }
306
307 // Solve the optimization problem
308 ExitStatus status;
309 if (m_equality_constraints.empty() && m_inequality_constraints.empty()) {
310 if (options.diagnostics) {
311 slp::println("\nInvoking Newton solver\n");
312 }
313
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");
318 }
319
320 status = solve_sqp(options, spy, x);
321 } else {
322 if (options.diagnostics) {
323 slp::println("\nInvoking IPM solver\n");
324 }
325
326 status = solve_ipm(options, spy, x);
327 }
328
329 if (options.diagnostics) {
330 slp::println("\nExit: {}", status);
331 }
332
333 // Assign the solution to the original Variable instances
334 VariableMatrix<Scalar>{m_decision_variables}.set_value(x);
335
336 return status;
337 }
338
344 template <typename F>
345 requires requires(F callback, const IterationInfo<Scalar>& info) {
346 { callback(info) } -> std::same_as<void>;
347 }
349 m_iteration_callbacks.emplace_back(
350 [=, callback =
351 std::forward<F>(callback)](const IterationInfo<Scalar>& info) {
352 callback(info);
353 return false;
354 });
355 }
356
363 template <typename F>
364 requires requires(F callback, const IterationInfo<Scalar>& info) {
365 { callback(info) } -> std::same_as<bool>;
366 }
368 m_iteration_callbacks.emplace_back(std::forward<F>(callback));
369 }
370
372 void clear_callbacks() { m_iteration_callbacks.clear(); }
373
382 template <typename F>
383 requires requires(F callback, const IterationInfo<Scalar>& info) {
384 { callback(info) } -> std::same_as<bool>;
385 }
387 m_persistent_iteration_callbacks.emplace_back(std::forward<F>(callback));
388 }
389
390 private:
391 using DenseVector = Eigen::Vector<Scalar, Eigen::Dynamic>;
392
393 // The list of decision variables, which are the root of the problem's
394 // expression tree
395 gch::small_vector<Variable<Scalar>> m_decision_variables;
396
397 // The cost function: f(x)
398 std::optional<Variable<Scalar>> m_f;
399
400 // The list of equality constraints: cₑ(x) = 0
401 gch::small_vector<Variable<Scalar>> m_equality_constraints;
402
403 // The list of inequality constraints: cᵢ(x) ≥ 0
404 gch::small_vector<Variable<Scalar>> m_inequality_constraints;
405
406 // The iteration callbacks
407 gch::small_vector<std::function<bool(const IterationInfo<Scalar>& info)>>
408 m_iteration_callbacks;
409 gch::small_vector<std::function<bool(const IterationInfo<Scalar>& info)>>
410 m_persistent_iteration_callbacks;
411
412 ExitStatus solve_newton(const Options& options, [[maybe_unused]] bool spy,
413 DenseVector& x) {
414 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
415 using SparseVector = Eigen::SparseVector<Scalar>;
416
417 VariableMatrix<Scalar> x_ad{m_decision_variables};
418
419 // Set up cost function
420 Variable f = m_f.value_or(Scalar(0));
421
422 int num_decision_variables = m_decision_variables.size();
423
424 gch::small_vector<std::function<bool(const IterationInfo<Scalar>& info)>>
425 iteration_callbacks;
426 for (const auto& callback : m_iteration_callbacks) {
427 iteration_callbacks.emplace_back(callback);
428 }
429 for (const auto& callback : m_persistent_iteration_callbacks) {
430 iteration_callbacks.emplace_back(callback);
431 }
432
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");
437
438 ad_setup_profilers[0].start();
439
440 // Set up gradient autodiff
441 ad_setup_profilers[1].start();
442 Gradient g{f, x_ad};
443 ad_setup_profilers[1].stop();
444
445 // Set up Lagrangian Hessian autodiff
446 ad_setup_profilers[2].start();
447 Hessian<Scalar, Eigen::Lower> H{f, x_ad};
448 ad_setup_profilers[2].stop();
449
450 ad_setup_profilers[0].stop();
451
452 if (options.diagnostics) {
453 print_setup_diagnostics(ad_setup_profilers);
454 }
455
456#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
457 // Sparsity pattern files written when spy flag is set
458 std::unique_ptr<Spy<Scalar>> H_spy;
459
460 if (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 {
466 H_spy->add(info.H);
467 return false;
468 });
469 }
470#endif
471
472 // Automatically scale the cost. The problem scaling procedure is
473 // described in more detail in docs/algorithms.md#problem-scaling.
474 x_ad.set_value(x);
475 const ProblemScaling<Scalar> scaling{g.value()};
476
477 NewtonMatrixCallbacks<Scalar> matrix_callbacks{
478 num_decision_variables,
479 [&](const DenseVector& x) -> Scalar {
480 x_ad.set_value(x);
481 return scaling.f * f.value();
482 },
483 [&](const DenseVector& x) -> SparseVector {
484 x_ad.set_value(x);
485 return scaling.f * g.value();
486 },
487 [&](const DenseVector& x) -> SparseMatrix {
488 x_ad.set_value(x);
489 return scaling.f * H.value();
490 },
491 scaling};
492
493 // Invoke Newton solver
494 return newton<Scalar>(matrix_callbacks, iteration_callbacks, options, x);
495 }
496
497 ExitStatus solve_sqp(const Options& options, [[maybe_unused]] bool spy,
498 DenseVector& x) {
499 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
500 using SparseVector = Eigen::SparseVector<Scalar>;
501
502 VariableMatrix<Scalar> x_ad{m_decision_variables};
503
504 // Set up cost function
505 Variable f = m_f.value_or(Scalar(0));
506
507 int num_decision_variables = m_decision_variables.size();
508 int num_equality_constraints = m_equality_constraints.size();
509
510 gch::small_vector<std::function<bool(const IterationInfo<Scalar>& info)>>
511 iteration_callbacks;
512 for (const auto& callback : m_iteration_callbacks) {
513 iteration_callbacks.emplace_back(callback);
514 }
515 for (const auto& callback : m_persistent_iteration_callbacks) {
516 iteration_callbacks.emplace_back(callback);
517 }
518
519 VariableMatrix<Scalar> c_e_ad{m_equality_constraints};
520 VariableMatrix<Scalar> y_ad(num_equality_constraints);
521
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");
529
530 ad_setup_profilers[0].start();
531
532 // Set up gradient autodiff
533 ad_setup_profilers[1].start();
534 Gradient g{f, x_ad};
535 ad_setup_profilers[1].stop();
536
537 ad_setup_profilers[2].start();
538
539 // Set up cost part of Lagrangian Hessian autodiff
540 ad_setup_profilers[3].start();
541 Hessian<Scalar, Eigen::Lower> H_f{f, x_ad};
542 ad_setup_profilers[3].stop();
543
544 // Set up constraint part of Lagrangian Hessian autodiff
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();
548
549 ad_setup_profilers[2].stop();
550
551 // Set up equality constraint Jacobian autodiff
552 ad_setup_profilers[5].start();
553 Jacobian A_e{c_e_ad, x_ad};
554 ad_setup_profilers[5].stop();
555
556 ad_setup_profilers[0].stop();
557
558 if (options.diagnostics) {
559 print_setup_diagnostics(ad_setup_profilers);
560 }
561
562#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
563 // Sparsity pattern files written when spy flag is set
564 std::unique_ptr<Spy<Scalar>> H_spy;
565 std::unique_ptr<Spy<Scalar>> A_e_spy;
566
567 if (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 {
577 H_spy->add(info.H);
578 A_e_spy->add(info.A_e);
579 return false;
580 });
581 }
582#endif
583
584 // Automatically scale the cost and constraints. The problem scaling
585 // procedure is described in more detail in
586 // docs/algorithms.md#problem-scaling.
587 x_ad.set_value(x);
588 const ProblemScaling<Scalar> scaling{g.value(), A_e.value()};
589
590 SQPMatrixCallbacks<Scalar> matrix_callbacks{
591 num_decision_variables,
592 num_equality_constraints,
593 [&](const DenseVector& x) -> Scalar {
594 x_ad.set_value(x);
595 return scaling.f * f.value();
596 },
597 [&](const DenseVector& x) -> SparseVector {
598 x_ad.set_value(x);
599 return scaling.f * g.value();
600 },
601 [&](const DenseVector& x, const DenseVector& y) -> SparseMatrix {
602 x_ad.set_value(x);
603 y_ad.set_value(scaling.c_e.cwiseProduct(y));
604 return scaling.f * H_f.value() + H_c.value();
605 },
606 [&](const DenseVector& x, const DenseVector& y) -> SparseMatrix {
607 x_ad.set_value(x);
608 y_ad.set_value(scaling.c_e.cwiseProduct(y));
609 return H_c.value();
610 },
611 [&](const DenseVector& x) -> DenseVector {
612 x_ad.set_value(x);
613 return scaling.c_e.cwiseProduct(c_e_ad.value());
614 },
615 [&](const DenseVector& x) -> SparseMatrix {
616 x_ad.set_value(x);
617 return scaling.c_e.asDiagonal() * A_e.value();
618 },
619 scaling};
620
621 // Invoke SQP solver
622 return sqp<Scalar>(matrix_callbacks, iteration_callbacks, options, x);
623 }
624
625 ExitStatus solve_ipm(const Options& options, [[maybe_unused]] bool spy,
626 DenseVector& x) {
627 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
628 using SparseVector = Eigen::SparseVector<Scalar>;
629
630 VariableMatrix<Scalar> x_ad{m_decision_variables};
631
632 // Set up cost function
633 Variable f = m_f.value_or(Scalar(0));
634
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();
638
639 gch::small_vector<std::function<bool(const IterationInfo<Scalar>& info)>>
640 iteration_callbacks;
641 for (const auto& callback : m_iteration_callbacks) {
642 iteration_callbacks.emplace_back(callback);
643 }
644 for (const auto& callback : m_persistent_iteration_callbacks) {
645 iteration_callbacks.emplace_back(callback);
646 }
647
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);
652
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");
661
662 ad_setup_profilers[0].start();
663
664 // Set up gradient autodiff
665 ad_setup_profilers[1].start();
666 Gradient g{f, x_ad};
667 ad_setup_profilers[1].stop();
668
669 ad_setup_profilers[2].start();
670
671 // Set up cost part of Lagrangian Hessian autodiff
672 ad_setup_profilers[3].start();
673 Hessian<Scalar, Eigen::Lower> H_f{f, x_ad};
674 ad_setup_profilers[3].stop();
675
676 // Set up constraint part of Lagrangian Hessian autodiff
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,
679 x_ad};
680 ad_setup_profilers[4].stop();
681
682 ad_setup_profilers[2].stop();
683
684 // Set up equality constraint Jacobian autodiff
685 ad_setup_profilers[5].start();
686 Jacobian A_e{c_e_ad, x_ad};
687 ad_setup_profilers[5].stop();
688
689 // Set up inequality constraint Jacobian autodiff
690 ad_setup_profilers[6].start();
691 Jacobian A_i{c_i_ad, x_ad};
692 ad_setup_profilers[6].stop();
693
694 ad_setup_profilers[0].stop();
695
696 if (options.diagnostics) {
697 print_setup_diagnostics(ad_setup_profilers);
698 }
699
700#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
701 // Sparsity pattern files written when spy flag is set
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;
705
706 if (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 {
720 H_spy->add(info.H);
721 A_e_spy->add(info.A_e);
722 A_i_spy->add(info.A_i);
723 return false;
724 });
725 }
726#endif
727
728 const auto [bound_constraint_mask, bounds, conflicting_bound_indices] =
729 get_bounds<Scalar>(m_decision_variables, m_inequality_constraints,
730 A_i.value());
731 if (!conflicting_bound_indices.empty()) {
732 if (options.diagnostics) {
733 print_bound_constraint_global_infeasibility_error(
734 conflicting_bound_indices);
735 }
736 return ExitStatus::GLOBALLY_INFEASIBLE;
737 }
738
739#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
740 project_onto_bounds(x, bounds);
741#endif
742
743 // Automatically scale the cost and constraints. The problem scaling
744 // procedure is described in more detail in
745 // docs/algorithms.md#problem-scaling.
746 x_ad.set_value(x);
747 const ProblemScaling<Scalar> scaling{g.value(), A_e.value(), A_i.value()};
748
749 IPMMatrixCallbacks<Scalar> matrix_callbacks{
750 num_decision_variables,
751 num_equality_constraints,
752 num_inequality_constraints,
753 [&](const DenseVector& x) -> Scalar {
754 x_ad.set_value(x);
755 return scaling.f * f.value();
756 },
757 [&](const DenseVector& x) -> SparseVector {
758 x_ad.set_value(x);
759 return scaling.f * g.value();
760 },
761 [&](const DenseVector& x, const DenseVector& y,
762 const DenseVector& z) -> SparseMatrix {
763 x_ad.set_value(x);
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();
767 },
768 [&](const DenseVector& x, const DenseVector& y,
769 const DenseVector& z) -> SparseMatrix {
770 x_ad.set_value(x);
771 y_ad.set_value(scaling.c_e.cwiseProduct(y));
772 z_ad.set_value(scaling.c_i.cwiseProduct(z));
773 return H_c.value();
774 },
775 [&](const DenseVector& x) -> DenseVector {
776 x_ad.set_value(x);
777 return scaling.c_e.cwiseProduct(c_e_ad.value());
778 },
779 [&](const DenseVector& x) -> SparseMatrix {
780 x_ad.set_value(x);
781 return scaling.c_e.asDiagonal() * A_e.value();
782 },
783 [&](const DenseVector& x) -> DenseVector {
784 x_ad.set_value(x);
785 return scaling.c_i.cwiseProduct(c_i_ad.value());
786 },
787 [&](const DenseVector& x) -> SparseMatrix {
788 x_ad.set_value(x);
789 return scaling.c_i.asDiagonal() * A_i.value();
790 },
791 scaling};
792
793 // Invoke interior-point method solver
794 return ipm<Scalar>(matrix_callbacks, iteration_callbacks, options,
795#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
796 bound_constraint_mask,
797#endif
798 x);
799 }
800
801 void print_exit_conditions(const Options& options) {
802 // Print possible exit conditions
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");
808 }
809 if (std::isfinite(options.max_iterations)) {
810 slp::println(" ↳ executed {} iterations", options.max_iterations);
811 }
812 if (std::isfinite(options.timeout.count())) {
813 slp::println(" ↳ {} elapsed", options.timeout);
814 }
815 }
816
817 void print_problem_analysis() {
818 constexpr std::array types{"no", "constant", "linear", "quadratic",
819 "nonlinear"};
820
821 // Print problem structure
822 slp::println("\nProblem structure:");
823 slp::println(" ↳ {} cost function",
824 types[std::to_underlying(cost_function_type())]);
825 slp::println(" ↳ {} equality constraints",
826 types[std::to_underlying(equality_constraint_type())]);
827 slp::println(" ↳ {} inequality constraints",
828 types[std::to_underlying(inequality_constraint_type())]);
829
830 if (m_decision_variables.size() == 1) {
831 slp::print("\n1 decision variable\n");
832 } else {
833 slp::print("\n{} decision variables\n", m_decision_variables.size());
834 }
835
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())];
841 }
842 for (const auto& [count, name] :
843 std::views::zip(counts, std::array{"empty", "constant", "linear",
844 "quadratic", "nonlinear"})) {
845 if (count > 0) {
846 slp::println(" ↳ {} {}", count, name);
847 }
848 }
849 };
850
851 // Print constraint types
852 if (m_equality_constraints.size() == 1) {
853 slp::println("1 equality constraint");
854 } else {
855 slp::println("{} equality constraints", m_equality_constraints.size());
856 }
857 print_constraint_types(m_equality_constraints);
858 if (m_inequality_constraints.size() == 1) {
859 slp::println("1 inequality constraint");
860 } else {
861 slp::println("{} inequality constraints",
862 m_inequality_constraints.size());
863 }
864 print_constraint_types(m_inequality_constraints);
865 }
866};
867
868extern template class EXPORT_TEMPLATE_DECLARE(SLEIPNIR_DLLEXPORT)
869Problem<double>;
870
871} // namespace slp
Definition intrusive_shared_ptr.hpp:27
Definition problem.hpp:71
VariableMatrix< Scalar > symmetric_decision_variable(int rows)
Definition problem.hpp:122
void subject_to(InequalityConstraints< Scalar > &&constraint)
Definition problem.hpp:233
void add_callback(F &&callback)
Definition problem.hpp:367
void subject_to(EqualityConstraints< Scalar > &&constraint)
Definition problem.hpp:211
ExpressionType equality_constraint_type() const
Definition problem.hpp:254
void maximize(Variable< Scalar > &&objective)
Definition problem.hpp:191
void add_persistent_callback(F &&callback)
Definition problem.hpp:386
void subject_to(const InequalityConstraints< Scalar > &constraint)
Definition problem.hpp:222
ExpressionType inequality_constraint_type() const
Definition problem.hpp:267
void minimize(const Variable< Scalar > &cost)
Definition problem.hpp:155
Problem() noexcept=default
Constructs the optimization problem.
VariableMatrix< Scalar > decision_variable(int rows, int cols=1)
Definition problem.hpp:95
void minimize(Variable< Scalar > &&cost)
Definition problem.hpp:166
void clear_callbacks()
Clears the registered callbacks.
Definition problem.hpp:372
void subject_to(const EqualityConstraints< Scalar > &constraint)
Definition problem.hpp:200
void maximize(const Variable< Scalar > &objective)
Definition problem.hpp:177
void add_callback(F &&callback)
Definition problem.hpp:348
ExpressionType cost_function_type() const
Definition problem.hpp:243
ExitStatus solve(const Options &options=Options{}, bool spy=false)
Definition problem.hpp:285
Variable< Scalar > decision_variable()
Definition problem.hpp:82
Definition variable.hpp:55
Solver options.
Definition options.hpp:13
bool diagnostics
Definition options.hpp:37