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/optimization/solver/util/time_trace.hpp"
39#include "sleipnir/util/empty.hpp"
40#include "sleipnir/util/print.hpp"
41#include "sleipnir/util/print_diagnostics.hpp"
42#include "sleipnir/util/spy.hpp"
43#include "sleipnir/util/symbol_exports.hpp"
44#include "sleipnir/util/time_tracer.hpp"
45
46namespace slp {
47
71template <typename Scalar>
72class Problem {
73 public:
75 Problem() noexcept = default;
76
82 [[nodiscard]]
84 m_decision_variables.emplace_back();
85 return m_decision_variables.back();
86 }
87
95 [[nodiscard]]
96 VariableMatrix<Scalar> decision_variable(int rows, int cols = 1) {
97 m_decision_variables.reserve(m_decision_variables.size() + rows * cols);
98
99 VariableMatrix<Scalar> vars{detail::empty, rows, cols};
100
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();
105 }
106 }
107
108 return vars;
109 }
110
122 [[nodiscard]]
124 // We only need to store the lower triangle of an n x n symmetric matrix;
125 // the other elements are duplicates. The lower triangle has (n² + n)/2
126 // elements.
127 //
128 // n
129 // Σ k = (n² + n)/2
130 // k=1
131 m_decision_variables.reserve(m_decision_variables.size() +
132 (rows * rows + rows) / 2);
133
134 VariableMatrix<Scalar> vars{detail::empty, rows, rows};
135
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();
141 }
142 }
143
144 return vars;
145 }
146
156 void minimize(const Variable<Scalar>& cost) { m_f = cost; }
157
167 void minimize(Variable<Scalar>&& cost) { m_f = std::move(cost); }
168
178 void maximize(const Variable<Scalar>& objective) {
179 // Maximizing a cost function is the same as minimizing its negative
180 m_f = -objective;
181 }
182
192 void maximize(Variable<Scalar>&& objective) {
193 // Maximizing a cost function is the same as minimizing its negative
194 m_f = -std::move(objective);
195 }
196
201 void subject_to(const EqualityConstraints<Scalar>& constraint) {
202 m_equality_constraints.reserve(m_equality_constraints.size() +
203 constraint.constraints.size());
204 std::ranges::copy(constraint.constraints,
205 std::back_inserter(m_equality_constraints));
206 }
207
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));
217 }
218
224 m_inequality_constraints.reserve(m_inequality_constraints.size() +
225 constraint.constraints.size());
226 std::ranges::copy(constraint.constraints,
227 std::back_inserter(m_inequality_constraints));
228 }
229
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));
239 }
240
244 ExpressionType cost_function_type() const {
245 if (m_f) {
246 return m_f.value().type();
247 } else {
248 return ExpressionType::NONE;
249 }
250 }
251
255 ExpressionType equality_constraint_type() const {
256 if (!m_equality_constraints.empty()) {
257 return std::ranges::max(m_equality_constraints, {},
259 .type();
260 } else {
261 return ExpressionType::NONE;
262 }
263 }
264
268 ExpressionType inequality_constraint_type() const {
269 if (!m_inequality_constraints.empty()) {
270 return std::ranges::max(m_inequality_constraints, {},
272 .type();
273 } else {
274 return ExpressionType::NONE;
275 }
276 }
277
286 ExitStatus solve(const Options<Scalar>& options = Options<Scalar>{},
287 bool spy = false) {
288 if (options.diagnostics) {
289 print_exit_conditions(options);
290 print_problem_analysis();
291 }
292
293 // If the problem is empty or constant, there's nothing to do
294 if (cost_function_type() <= ExpressionType::CONSTANT &&
295 equality_constraint_type() <= ExpressionType::CONSTANT &&
296 inequality_constraint_type() <= ExpressionType::CONSTANT) {
297 if (options.diagnostics) {
298 slp::println("\nInvoking no-op solver\n");
299 }
300 return ExitStatus::SUCCESS;
301 }
302
303 // Create the initial value column vector
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();
307 }
308
309 // Solve the optimization problem
310 ExitStatus status;
311 if (m_equality_constraints.empty() && m_inequality_constraints.empty()) {
312 if (options.diagnostics) {
313 slp::println("\nInvoking Newton solver\n");
314 }
315
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");
320 }
321
322 status = solve_sqp(options, spy, x);
323 } else {
324 if (options.diagnostics) {
325 slp::println("\nInvoking IPM solver\n");
326 }
327
328 status = solve_ipm(options, spy, x);
329 }
330
331 if (options.diagnostics) {
332 slp::println("\nExit: {}", status);
333 }
334
335 // Assign the solution to the original Variable instances
336 VariableMatrix<Scalar>{m_decision_variables}.set_value(x);
337
338 return status;
339 }
340
346 template <typename F>
347 requires requires(F callback, const IterationInfo<Scalar>& info) {
348 { callback(info) } -> std::same_as<void>;
349 }
350 void add_callback(F&& callback) {
351 m_iteration_callbacks.emplace_back(
352 [=, callback =
353 std::forward<F>(callback)](const IterationInfo<Scalar>& info) {
354 callback(info);
355 return false;
356 });
357 }
358
365 template <typename F>
366 requires requires(F callback, const IterationInfo<Scalar>& info) {
367 { callback(info) } -> std::same_as<bool>;
368 }
369 void add_callback(F&& callback) {
370 m_iteration_callbacks.emplace_back(std::forward<F>(callback));
371 }
372
374 void clear_callbacks() { m_iteration_callbacks.clear(); }
375
384 template <typename F>
385 requires requires(F callback, const IterationInfo<Scalar>& info) {
386 { callback(info) } -> std::same_as<bool>;
387 }
388 void add_persistent_callback(F&& callback) {
389 m_persistent_iteration_callbacks.emplace_back(std::forward<F>(callback));
390 }
391
392 private:
393 using DenseVector = Eigen::VectorX<Scalar>;
394
395 // The list of decision variables, which are the root of the problem's
396 // expression tree
397 gch::small_vector<Variable<Scalar>> m_decision_variables;
398
399 // The cost function: f(x)
400 std::optional<Variable<Scalar>> m_f;
401
402 // The list of equality constraints: cₑ(x) = 0
403 gch::small_vector<Variable<Scalar>> m_equality_constraints;
404
405 // The list of inequality constraints: cᵢ(x) ≥ 0
406 gch::small_vector<Variable<Scalar>> m_inequality_constraints;
407
408 // The iteration callbacks
409 gch::small_vector<std::function<bool(const IterationInfo<Scalar>& info)>>
410 m_iteration_callbacks;
411 gch::small_vector<std::function<bool(const IterationInfo<Scalar>& info)>>
412 m_persistent_iteration_callbacks;
413
414 ExitStatus solve_newton(const Options<Scalar>& options,
415 [[maybe_unused]] bool spy, DenseVector& x) {
416 using SparseMatrix = Eigen::SparseMatrix<Scalar>;
417 using SparseVector = Eigen::SparseVector<Scalar>;
418 using enum SetupTimeTracerID;
419
420 VariableMatrix<Scalar> x_ad{m_decision_variables};
421
422 // Set up cost function
423 Variable f = m_f.value_or(Scalar(0));
424
425 int num_decision_variables = m_decision_variables.size();
426
427 gch::small_vector<std::function<bool(const IterationInfo<Scalar>& info)>>
428 iteration_callbacks;
429 for (const auto& callback : m_iteration_callbacks) {
430 iteration_callbacks.emplace_back(callback);
431 }
432 for (const auto& callback : m_persistent_iteration_callbacks) {
433 iteration_callbacks.emplace_back(callback);
434 }
435
436 TimeTracerTree<SetupTimeTracer, newton_setup_time_trace> ad_setup_tracers{
437 options.diagnostics};
438 ScopedTimeTracer setup_tracer{ad_setup_tracers[SETUP]};
439
440 // Set up gradient autodiff
441 PhaseTimeTracer phase_tracer{ad_setup_tracers[GRADIENT]};
442 Gradient g{f, x_ad};
443
444 // Set up Lagrangian Hessian autodiff
445 phase_tracer.next(ad_setup_tracers[HESSIAN]);
446 Hessian<Scalar, Eigen::Lower> H{f, x_ad};
447
448 phase_tracer.stop();
449 setup_tracer.stop();
450
451 if (options.diagnostics) {
452 print_setup_diagnostics(ad_setup_tracers);
453 }
454
455#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
456 // Sparsity pattern files written when spy flag is set
457 std::unique_ptr<Spy<Scalar>> H_spy;
458
459 if (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 {
465 H_spy->add(info.H);
466 return false;
467 });
468 }
469#endif
470
471 // Automatically scale the cost. The problem scaling procedure is
472 // described in more detail in docs/algorithms.md#problem-scaling.
473 x_ad.set_value(x);
474 const ProblemScaling<Scalar> scaling{g.value()};
475
476 NewtonMatrixCallbacks<Scalar> matrix_callbacks{
477 num_decision_variables,
478 [&](const DenseVector& x) -> Scalar {
479 x_ad.set_value(x);
480 return scaling.f * f.value();
481 },
482 [&](const DenseVector& x) -> SparseVector {
483 x_ad.set_value(x);
484 return scaling.f * g.value();
485 },
486 [&](const DenseVector& x) -> SparseMatrix {
487 x_ad.set_value(x);
488 return scaling.f * H.value();
489 },
490 scaling};
491
492 // Invoke Newton solver
493 return newton<Scalar>(matrix_callbacks, iteration_callbacks, options, x);
494 }
495
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;
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 TimeTracerTree<SetupTimeTracer, sqp_setup_time_trace> ad_setup_tracers{
523 options.diagnostics};
524 ScopedTimeTracer setup_tracer{ad_setup_tracers[SETUP]};
525
526 // Set up gradient autodiff
527 PhaseTimeTracer phase_tracer{ad_setup_tracers[GRADIENT]};
528 Gradient g{f, x_ad};
529
530 phase_tracer.next(ad_setup_tracers[HESSIAN]);
531
532 // Set up cost part of Lagrangian Hessian autodiff
533 PhaseTimeTracer hessian_phase_tracer{ad_setup_tracers[HESSIAN_COST]};
534 Hessian<Scalar, Eigen::Lower> H_f{f, x_ad};
535
536 // Set up constraint part of Lagrangian Hessian autodiff
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};
539
540 hessian_phase_tracer.stop();
541
542 // Set up equality constraint Jacobian autodiff
543 phase_tracer.next(ad_setup_tracers[EQUALITY_JACOBIAN]);
544 Jacobian A_e{c_e_ad, x_ad};
545
546 phase_tracer.stop();
547 setup_tracer.stop();
548
549 if (options.diagnostics) {
550 print_setup_diagnostics(ad_setup_tracers);
551 }
552
553#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
554 // Sparsity pattern files written when spy flag is set
555 std::unique_ptr<Spy<Scalar>> H_spy;
556 std::unique_ptr<Spy<Scalar>> A_e_spy;
557
558 if (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 {
568 H_spy->add(info.H);
569 A_e_spy->add(info.A_e);
570 return false;
571 });
572 }
573#endif
574
575 // Automatically scale the cost and constraints. The problem scaling
576 // procedure is described in more detail in
577 // docs/algorithms.md#problem-scaling.
578 x_ad.set_value(x);
579 const ProblemScaling<Scalar> scaling{g.value(), A_e.value()};
580
581 SQPMatrixCallbacks<Scalar> matrix_callbacks{
582 num_decision_variables,
583 num_equality_constraints,
584 [&](const DenseVector& x) -> Scalar {
585 x_ad.set_value(x);
586 return scaling.f * f.value();
587 },
588 [&](const DenseVector& x) -> SparseVector {
589 x_ad.set_value(x);
590 return scaling.f * g.value();
591 },
592 [&](const DenseVector& x, const DenseVector& y) -> SparseMatrix {
593 x_ad.set_value(x);
594 y_ad.set_value(scaling.c_e.cwiseProduct(y));
595 return scaling.f * H_f.value() + H_c.value();
596 },
597 [&](const DenseVector& x, const DenseVector& y) -> SparseMatrix {
598 x_ad.set_value(x);
599 y_ad.set_value(scaling.c_e.cwiseProduct(y));
600 return H_c.value();
601 },
602 [&](const DenseVector& x) -> DenseVector {
603 x_ad.set_value(x);
604 return scaling.c_e.cwiseProduct(c_e_ad.value());
605 },
606 [&](const DenseVector& x) -> SparseMatrix {
607 x_ad.set_value(x);
608 return scaling.c_e.asDiagonal() * A_e.value();
609 },
610 scaling};
611
612 // Invoke SQP solver
613 return sqp<Scalar>(matrix_callbacks, iteration_callbacks, options, x);
614 }
615
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;
621
622 VariableMatrix<Scalar> x_ad{m_decision_variables};
623
624 // Set up cost function
625 Variable f = m_f.value_or(Scalar(0));
626
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();
630
631 gch::small_vector<std::function<bool(const IterationInfo<Scalar>& info)>>
632 iteration_callbacks;
633 for (const auto& callback : m_iteration_callbacks) {
634 iteration_callbacks.emplace_back(callback);
635 }
636 for (const auto& callback : m_persistent_iteration_callbacks) {
637 iteration_callbacks.emplace_back(callback);
638 }
639
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);
644
645 TimeTracerTree<SetupTimeTracer, ipm_setup_time_trace> ad_setup_tracers{
646 options.diagnostics};
647 ScopedTimeTracer setup_tracer{ad_setup_tracers[SETUP]};
648
649 // Set up gradient autodiff
650 PhaseTimeTracer phase_tracer{ad_setup_tracers[GRADIENT]};
651 Gradient g{f, x_ad};
652
653 phase_tracer.next(ad_setup_tracers[HESSIAN]);
654
655 // Set up cost part of Lagrangian Hessian autodiff
656 PhaseTimeTracer hessian_phase_tracer{ad_setup_tracers[HESSIAN_COST]};
657 Hessian<Scalar, Eigen::Lower> H_f{f, x_ad};
658
659 // Set up constraint part of Lagrangian Hessian autodiff
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,
662 x_ad};
663
664 hessian_phase_tracer.stop();
665
666 // Set up equality constraint Jacobian autodiff
667 phase_tracer.next(ad_setup_tracers[EQUALITY_JACOBIAN]);
668 Jacobian A_e{c_e_ad, x_ad};
669
670 // Set up inequality constraint Jacobian autodiff
671 phase_tracer.next(ad_setup_tracers[INEQUALITY_JACOBIAN]);
672 Jacobian A_i{c_i_ad, x_ad};
673
674 phase_tracer.stop();
675 setup_tracer.stop();
676
677 if (options.diagnostics) {
678 print_setup_diagnostics(ad_setup_tracers);
679 }
680
681#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
682 // Sparsity pattern files written when spy flag is set
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;
686
687 if (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 {
701 H_spy->add(info.H);
702 A_e_spy->add(info.A_e);
703 A_i_spy->add(info.A_i);
704 return false;
705 });
706 }
707#endif
708
709 const auto [bound_constraint_mask, bounds, conflicting_bound_indices] =
710 get_bounds<Scalar>(m_decision_variables, m_inequality_constraints,
711 A_i.value());
712 if (!conflicting_bound_indices.empty()) {
713 if (options.diagnostics) {
714 print_bound_constraint_global_infeasibility_error(
715 conflicting_bound_indices);
716 }
717 return ExitStatus::GLOBALLY_INFEASIBLE;
718 }
719
720#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
721 project_onto_bounds(x, bounds);
722#endif
723
724 // Automatically scale the cost and constraints. The problem scaling
725 // procedure is described in more detail in
726 // docs/algorithms.md#problem-scaling.
727 x_ad.set_value(x);
728 const ProblemScaling<Scalar> scaling{g.value(), A_e.value(), A_i.value()};
729
730 IPMMatrixCallbacks<Scalar> matrix_callbacks{
731 num_decision_variables,
732 num_equality_constraints,
733 num_inequality_constraints,
734 [&](const DenseVector& x) -> Scalar {
735 x_ad.set_value(x);
736 return scaling.f * f.value();
737 },
738 [&](const DenseVector& x) -> SparseVector {
739 x_ad.set_value(x);
740 return scaling.f * g.value();
741 },
742 [&](const DenseVector& x, const DenseVector& y,
743 const DenseVector& z) -> SparseMatrix {
744 x_ad.set_value(x);
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();
748 },
749 [&](const DenseVector& x, const DenseVector& y,
750 const DenseVector& z) -> SparseMatrix {
751 x_ad.set_value(x);
752 y_ad.set_value(scaling.c_e.cwiseProduct(y));
753 z_ad.set_value(scaling.c_i.cwiseProduct(z));
754 return H_c.value();
755 },
756 [&](const DenseVector& x) -> DenseVector {
757 x_ad.set_value(x);
758 return scaling.c_e.cwiseProduct(c_e_ad.value());
759 },
760 [&](const DenseVector& x) -> SparseMatrix {
761 x_ad.set_value(x);
762 return scaling.c_e.asDiagonal() * A_e.value();
763 },
764 [&](const DenseVector& x) -> DenseVector {
765 x_ad.set_value(x);
766 return scaling.c_i.cwiseProduct(c_i_ad.value());
767 },
768 [&](const DenseVector& x) -> SparseMatrix {
769 x_ad.set_value(x);
770 return scaling.c_i.asDiagonal() * A_i.value();
771 },
772 scaling};
773
774 // Invoke interior-point method solver
775 return ipm<Scalar>(matrix_callbacks, iteration_callbacks, options,
776#ifdef SLEIPNIR_ENABLE_BOUND_PROJECTION
777 bound_constraint_mask,
778#endif
779 x);
780 }
781
782 void print_exit_conditions(const Options<Scalar>& options) {
783 // Print possible exit conditions
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");
789 }
790 if (std::isfinite(options.max_iterations)) {
791 slp::println(" ↳ executed {} iterations", options.max_iterations);
792 }
793 if (std::isfinite(options.timeout.count())) {
794 slp::println(" ↳ {} elapsed", options.timeout);
795 }
796 }
797
798 void print_problem_analysis() {
799 constexpr std::array types{"no", "constant", "linear", "quadratic",
800 "nonlinear"};
801
802 // Print problem structure
803 slp::println("\nProblem structure:");
804 slp::println(" ↳ {} cost function",
805 types[std::to_underlying(cost_function_type())]);
806 slp::println(" ↳ {} equality constraints",
807 types[std::to_underlying(equality_constraint_type())]);
808 slp::println(" ↳ {} inequality constraints",
809 types[std::to_underlying(inequality_constraint_type())]);
810
811 if (m_decision_variables.size() == 1) {
812 slp::print("\n1 decision variable\n");
813 } else {
814 slp::print("\n{} decision variables\n", m_decision_variables.size());
815 }
816
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())];
822 }
823 for (const auto& [count, name] :
824 std::views::zip(counts, std::array{"empty", "constant", "linear",
825 "quadratic", "nonlinear"})) {
826 if (count > 0) {
827 slp::println(" ↳ {} {}", count, name);
828 }
829 }
830 };
831
832 // Print constraint types
833 if (m_equality_constraints.size() == 1) {
834 slp::println("1 equality constraint");
835 } else {
836 slp::println("{} equality constraints", m_equality_constraints.size());
837 }
838 print_constraint_types(m_equality_constraints);
839 if (m_inequality_constraints.size() == 1) {
840 slp::println("1 inequality constraint");
841 } else {
842 slp::println("{} inequality constraints",
843 m_inequality_constraints.size());
844 }
845 print_constraint_types(m_inequality_constraints);
846 }
847};
848
849extern template class EXPORT_TEMPLATE_DECLARE(SLEIPNIR_DLLEXPORT)
850Problem<double>;
851
852} // namespace slp
Definition problem.hpp:72
VariableMatrix< Scalar > symmetric_decision_variable(int rows)
Definition problem.hpp:123
void subject_to(InequalityConstraints< Scalar > &&constraint)
Definition problem.hpp:234
void add_callback(F &&callback)
Definition problem.hpp:369
ExitStatus solve(const Options< Scalar > &options=Options< Scalar >{}, bool spy=false)
Definition problem.hpp:286
void subject_to(EqualityConstraints< Scalar > &&constraint)
Definition problem.hpp:212
ExpressionType equality_constraint_type() const
Definition problem.hpp:255
void maximize(Variable< Scalar > &&objective)
Definition problem.hpp:192
void add_persistent_callback(F &&callback)
Definition problem.hpp:388
void subject_to(const InequalityConstraints< Scalar > &constraint)
Definition problem.hpp:223
ExpressionType inequality_constraint_type() const
Definition problem.hpp:268
void minimize(const Variable< Scalar > &cost)
Definition problem.hpp:156
Problem() noexcept=default
Constructs the optimization problem.
VariableMatrix< Scalar > decision_variable(int rows, int cols=1)
Definition problem.hpp:96
void minimize(Variable< Scalar > &&cost)
Definition problem.hpp:167
void clear_callbacks()
Clears the registered callbacks.
Definition problem.hpp:374
void subject_to(const EqualityConstraints< Scalar > &constraint)
Definition problem.hpp:201
void maximize(const Variable< Scalar > &objective)
Definition problem.hpp:178
void add_callback(F &&callback)
Definition problem.hpp:350
ExpressionType cost_function_type() const
Definition problem.hpp:244
Variable< Scalar > decision_variable()
Definition problem.hpp:83
Definition variable_matrix.hpp:37
Definition variable.hpp:56
Definition variable.hpp:808
gch::small_vector< Variable< Scalar > > constraints
A vector of scalar equality constraints.
Definition variable.hpp:810
Definition variable.hpp:865
gch::small_vector< Variable< Scalar > > constraints
A vector of scalar inequality constraints.
Definition variable.hpp:867
Definition iteration_info.hpp:14
Definition options.hpp:14
bool diagnostics
Definition options.hpp:38