18#include <gch/small_vector.hpp>
20#include "sleipnir/util/print.hpp"
21#include "sleipnir/util/profiler.hpp"
26enum class IterationType : uint8_t {
30 SECOND_ORDER_CORRECTION,
32 FEASIBILITY_RESTORATION
37template <
typename Rep,
typename Period = std::ratio<1>>
38constexpr double to_ms(
const std::chrono::duration<Rep, Period>& duration) {
39 using std::chrono::duration_cast;
40 using std::chrono::microseconds;
41 return duration_cast<microseconds>(duration).count() / 1e3;
48template <
typename Scalar>
49std::string power_of_10(Scalar value) {
50 if (value == Scalar(0)) {
54 int exponent =
static_cast<int>(log10(value));
58 }
else if (exponent == 1) {
62 int n = std::abs(exponent);
63 gch::small_vector<int> digits;
65 digits.emplace_back(n % 10);
69 std::string output =
"10";
75 constexpr std::array strs{
"⁰",
"¹",
"²",
"³",
"⁴",
76 "⁵",
"⁶",
"⁷",
"⁸",
"⁹"};
77 for (
const auto& digit : digits | std::views::reverse) {
78 output += strs[digit];
86#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
92template <
typename Scalar>
93void print_too_few_dofs_error(
94 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_e) {
95 slp::println(
"The problem has too few degrees of freedom.");
96 slp::println(
"Violated constraints (cₑ(x) = 0) in order of declaration:");
97 for (
int row = 0; row < c_e.rows(); ++row) {
98 if (c_e[row] < Scalar(0)) {
99 slp::println(
" {}/{}: {} = 0", row + 1, c_e.rows(), c_e[row]);
104#define print_too_few_dofs_error(...)
107#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
115template <
typename Scalar>
116void print_c_e_local_infeasibility_error(
117 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_e, Scalar tolerance) {
121 "The problem is locally infeasible due to violated equality "
123 slp::println(
"Violated constraints (cₑ(x) = 0) in order of declaration:");
124 for (
int row = 0; row < c_e.rows(); ++row) {
125 if (abs(c_e[row]) > tolerance) {
126 slp::println(
" {}/{}: {} = 0", row + 1, c_e.rows(), c_e[row]);
131#define print_c_e_local_infeasibility_error(...)
134#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
142template <
typename Scalar>
143void print_c_i_local_infeasibility_error(
144 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_i, Scalar tolerance) {
146 "The problem is locally infeasible due to violated inequality "
148 slp::println(
"Violated constraints (cᵢ(x) ≥ 0) in order of declaration:");
149 for (
int row = 0; row < c_i.rows(); ++row) {
150 if (c_i[row] < -tolerance) {
151 slp::println(
" {}/{}: {} ≥ 0", row + 1, c_i.rows(), c_i[row]);
156#define print_c_i_local_infeasibility_error(...)
159#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
160inline void print_bound_constraint_global_infeasibility_error(
161 const std::span<
const std::pair<Eigen::Index, Eigen::Index>>
162 conflicting_lower_upper_bound_indices) {
164 "The problem is globally infeasible due to conflicting bound "
166 for (
const auto& [lower_bound_idx, upper_bound_idx] :
167 conflicting_lower_upper_bound_indices) {
169 " Inequality constraint {} gives a lower bound that is greater than "
170 "the upper bound given by inequality constraint {}",
171 lower_bound_idx, upper_bound_idx);
175#define print_bound_constraint_global_infeasibility_error(...)
178#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
199template <
typename Scalar,
typename Rep,
typename Period = std::ratio<1>>
200void print_iteration_diagnostics(
int iterations, IterationType type,
201 const std::chrono::duration<Rep, Period>& time,
202 Scalar error, Scalar cost,
203 Scalar infeasibility, Scalar complementarity,
204 Scalar μ, Scalar δ, Scalar γ,
205 Scalar full_primal_step_inf_norm,
206 Scalar full_dual_step_inf_norm,
207 Scalar primal_α, Scalar primal_α_max,
208 Scalar α_reduction_factor, Scalar dual_α) {
209 if (iterations % 20 == 0) {
210 if (iterations == 0) {
211 slp::println(
"┏{:━^119}┓",
"");
213 slp::println(
"┢{:━^119}┪",
"");
216 "┃{:^4} {:^9} {:^10} {:^11} {:^10} {:^8} {:^8} {:^5} {:^5} {:^8} "
217 "{:^8} {:^8} {:^8} {:^2}┃",
218 "iter",
"duration",
"error",
"cost",
"infeas.",
"complem.",
"μ",
"δ",
219 "γ",
"|p_pr|",
"|p_du|",
"α_pr",
"α_du",
"↩");
220 slp::println(
"┡{:━^119}┩",
"");
235 static_cast<int>(log(primal_α / primal_α_max) / log(α_reduction_factor));
237 constexpr std::array ITERATION_TYPES{
" ",
"s",
"r"};
239 "│{:4} {:1} {:9.3f} {:10.4e} {:11.4e} {:10.4e} {:8.2e} {:8.2e} {:<5} "
240 "{:<5} {:8.2e} {:8.2e} {:8.2e} {:8.2e} {:2d}│",
241 iterations, ITERATION_TYPES[std::to_underlying(type)], to_ms(time), error,
242 cost, infeasibility, complementarity, μ, power_of_10(δ), power_of_10(γ),
243 full_primal_step_inf_norm, full_dual_step_inf_norm, primal_α, dual_α,
247#define print_iteration_diagnostics(...)
250#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
252inline void print_bottom_iteration_diagnostics() {
253 slp::println(
"└{:─^119}┘",
"");
256#define print_bottom_iteration_diagnostics(...)
265std::string histogram(
double value) {
266 value = std::clamp(value, 0.0, 1.0);
269 int fpart =
static_cast<int>(std::modf(value * Width, &ipart) * 8);
271 constexpr std::array strs{
" ",
"▏",
"▎",
"▍",
"▌",
"▋",
"▊",
"▉",
"█"};
275 while (index < ipart) {
283 while (index < Width) {
291#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
295inline void print_solver_diagnostics(
296 const gch::small_vector<SolveProfiler>& solve_profilers) {
297 auto solve_duration = to_ms(solve_profilers[0].total_duration());
299 slp::println(
"┏{:━^66}┓",
"");
300 slp::println(
"┃{:^21} {:^18} {:^10} {:^9} {:^4}┃",
"time trace",
"percentage",
301 "total",
"each",
"runs");
302 slp::println(
"┡{:━^66}┩",
"");
304 for (
auto& profiler : solve_profilers) {
305 double norm = solve_duration == 0.0
306 ? (&profiler == &solve_profilers[0] ? 1.0 : 0.0)
307 : to_ms(profiler.total_duration()) / solve_duration;
308 slp::println(
"│{:<21} {:>6.2f}%▕{}▏ {:>10.3f} {:>9.3f} {:>4}│",
309 profiler.name(), norm * 100.0, histogram<9>(norm),
310 to_ms(profiler.total_duration()),
311 to_ms(profiler.average_duration()), profiler.num_solves());
314 slp::println(
"└{:─^66}┘",
"");
317#define print_solver_diagnostics(...)
320#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
324inline void print_setup_diagnostics(
325 const gch::small_vector<SetupProfiler>& setup_profilers) {
326 auto setup_duration = to_ms(setup_profilers[0].duration());
330 "See https://sleipnirgroup.github.io/Sleipnir/md_usage.html#output for "
331 "diagnostic output description.\n");
334 slp::println(
"┏{:━^50}┓",
"");
335 slp::println(
"┃{:^21} {:^18} {:^9}┃",
"time trace",
"percentage",
"duration");
336 slp::println(
"┡{:━^50}┩",
"");
339 for (
auto& profiler : setup_profilers) {
340 double norm = setup_duration == 0.0
341 ? (&profiler == &setup_profilers[0] ? 1.0 : 0.0)
342 : to_ms(profiler.duration()) / setup_duration;
343 slp::println(
"│{:<21} {:>6.2f}%▕{}▏ {:>9.3f}│", profiler.name(),
344 norm * 100.0, histogram<9>(norm), to_ms(profiler.duration()));
347 slp::println(
"└{:─^50}┘",
"");
350#define print_setup_diagnostics(...)