Sleipnir C++ API
Loading...
Searching...
No Matches
print_diagnostics.hpp
1// Copyright (c) Sleipnir contributors
2
3#pragma once
4
5#include <stdint.h>
6
7#include <algorithm>
8#include <array>
9#include <chrono>
10#include <cmath>
11#include <ranges>
12#include <string>
13#include <utility>
14
15#include <Eigen/Core>
16#include <gch/small_vector.hpp>
17
18#include "sleipnir/util/print.hpp"
19#include "sleipnir/util/setup_profiler.hpp"
20#include "sleipnir/util/solve_profiler.hpp"
21
22namespace slp {
23
27enum class IterationType : uint8_t {
29 NORMAL,
31 ACCEPTED_SOC,
33 REJECTED_SOC
34};
35
40template <typename Rep, typename Period = std::ratio<1>>
41constexpr double to_ms(const std::chrono::duration<Rep, Period>& duration) {
42 using std::chrono::duration_cast;
43 using std::chrono::microseconds;
44 return duration_cast<microseconds>(duration).count() / 1e3;
45}
46
53template <typename Scalar>
54std::string power_of_10(Scalar value) {
55 if (value == Scalar(0)) {
56 return " 0";
57 } else {
58 using std::log10;
59 int exponent = static_cast<int>(log10(value));
60
61 if (exponent == 0) {
62 return " 1";
63 } else if (exponent == 1) {
64 return "10";
65 } else {
66 // Gather exponent digits
67 int n = std::abs(exponent);
68 gch::small_vector<int> digits;
69 do {
70 digits.emplace_back(n % 10);
71 n /= 10;
72 } while (n > 0);
73
74 std::string output = "10";
75
76 // Append exponent
77 if (exponent < 0) {
78 output += "⁻";
79 }
80 constexpr std::array strs = {"⁰", "¹", "²", "³", "⁴",
81 "⁵", "⁶", "⁷", "⁸", "⁹"};
82 for (const auto& digit : digits | std::views::reverse) {
83 output += strs[digit];
84 }
85
86 return output;
87 }
88 }
89}
90
91#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
99template <typename Scalar>
100void print_too_few_dofs_error(
101 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_e) {
102 slp::println("The problem has too few degrees of freedom.");
103 slp::println("Violated constraints (cₑ(x) = 0) in order of declaration:");
104 for (int row = 0; row < c_e.rows(); ++row) {
105 if (c_e[row] < Scalar(0)) {
106 slp::println(" {}/{}: {} = 0", row + 1, c_e.rows(), c_e[row]);
107 }
108 }
109}
110#else
111#define print_too_few_dofs_error(...)
112#endif
113
114#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
122template <typename Scalar>
123void print_c_e_local_infeasibility_error(
124 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_e) {
125 slp::println(
126 "The problem is locally infeasible due to violated equality "
127 "constraints.");
128 slp::println("Violated constraints (cₑ(x) = 0) in order of declaration:");
129 for (int row = 0; row < c_e.rows(); ++row) {
130 if (c_e[row] < Scalar(0)) {
131 slp::println(" {}/{}: {} = 0", row + 1, c_e.rows(), c_e[row]);
132 }
133 }
134}
135#else
136#define print_c_e_local_infeasibility_error(...)
137#endif
138
139#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
147template <typename Scalar>
148void print_c_i_local_infeasibility_error(
149 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_i) {
150 slp::println(
151 "The problem is locally infeasible due to violated inequality "
152 "constraints.");
153 slp::println("Violated constraints (cᵢ(x) ≥ 0) in order of declaration:");
154 for (int row = 0; row < c_i.rows(); ++row) {
155 if (c_i[row] < Scalar(0)) {
156 slp::println(" {}/{}: {} ≥ 0", row + 1, c_i.rows(), c_i[row]);
157 }
158 }
159}
160#else
161#define print_c_i_local_infeasibility_error(...)
162#endif
163
164#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
165inline void print_bound_constraint_global_infeasibility_error(
166 const std::span<const std::pair<Eigen::Index, Eigen::Index>>
167 conflicting_lower_upper_bound_indices) {
168 slp::println(
169 "The problem is globally infeasible due to conflicting bound "
170 "constraints:");
171 for (const auto& [lower_bound_idx, upper_bound_idx] :
172 conflicting_lower_upper_bound_indices) {
173 slp::println(
174 " Inequality constraint {} gives a lower bound that is greater than "
175 "the upper bound given by inequality constraint {}",
176 lower_bound_idx, upper_bound_idx);
177 }
178}
179#else
180#define print_bound_constraint_global_infeasibility_error(...)
181#endif
182
183#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
203template <typename Scalar, typename Rep, typename Period = std::ratio<1>>
204void print_iteration_diagnostics(int iterations, IterationType type,
205 const std::chrono::duration<Rep, Period>& time,
206 Scalar error, Scalar cost,
207 Scalar infeasibility, Scalar complementarity,
208 Scalar μ, Scalar δ, Scalar primal_α,
209 Scalar primal_α_max, Scalar α_reduction_factor,
210 Scalar dual_α) {
211 if (iterations % 20 == 0) {
212 if (iterations == 0) {
213 slp::print("┏");
214 } else {
215 slp::print("┢");
216 }
217 slp::print(
218 "{:━^4}┯{:━^4}┯{:━^9}┯{:━^12}┯{:━^13}┯{:━^12}┯{:━^12}┯{:━^8}┯{:━^5}┯"
219 "{:━^8}┯{:━^8}┯{:━^2}",
220 "", "", "", "", "", "", "", "", "", "", "", "");
221 if (iterations == 0) {
222 slp::println("┓");
223 } else {
224 slp::println("┪");
225 }
226 slp::println(
227 "┃{:^4}│{:^4}│{:^9}│{:^12}│{:^13}│{:^12}│{:^12}│{:^8}│{:^5}│{:^8}│{:^8}"
228 "│{:^2}┃",
229 "iter", "type", "time (ms)", "error", "cost", "infeas.", "complement.",
230 "μ", "reg", "primal α", "dual α", "↩");
231 slp::println(
232 "┡{:━^4}┷{:━^4}┷{:━^9}┷{:━^12}┷{:━^13}┷{:━^12}┷{:━^12}┷{:━^8}┷{:━^5}┷"
233 "{:━^8}┷{:━^8}┷{:━^2}┩",
234 "", "", "", "", "", "", "", "", "", "", "", "");
235 }
236
237 // For the number of backtracks, we want x such that:
238 //
239 // αᵐᵃˣrˣ = α
240 //
241 // where r ∈ (0, 1) is the reduction factor.
242 //
243 // rˣ = α/αᵐᵃˣ
244 // ln(rˣ) = ln(α/αᵐᵃˣ)
245 // x ln(r) = ln(α/αᵐᵃˣ)
246 // x = ln(α/αᵐᵃˣ)/ln(r)
247 using std::log;
248 int backtracks =
249 static_cast<int>(log(primal_α / primal_α_max) / log(α_reduction_factor));
250
251 constexpr std::array ITERATION_TYPES = {"norm", "✓SOC", "XSOC"};
252 slp::println(
253 "│{:4} {:4} {:9.3f} {:12e} {:13e} {:12e} {:12e} {:.2e} {:<5} {:.2e} "
254 "{:.2e} {:2d}│",
255 iterations, ITERATION_TYPES[std::to_underlying(type)], to_ms(time), error,
256 cost, infeasibility, complementarity, μ, power_of_10(δ), primal_α, dual_α,
257 backtracks);
258}
259#else
260#define print_iteration_diagnostics(...)
261#endif
262
263#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
267inline void print_bottom_iteration_diagnostics() {
268 slp::println("└{:─^108}┘", "");
269}
270#else
271#define print_bottom_iteration_diagnostics(...)
272#endif
273
280template <int Width>
281 requires(Width > 0)
282std::string histogram(double value) {
283 value = std::clamp(value, 0.0, 1.0);
284
285 double ipart;
286 int fpart = static_cast<int>(std::modf(value * Width, &ipart) * 8);
287
288 constexpr std::array strs = {" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"};
289 std::string hist;
290
291 int index = 0;
292 while (index < ipart) {
293 hist += strs[8];
294 ++index;
295 }
296 if (fpart > 0) {
297 hist += strs[fpart];
298 ++index;
299 }
300 while (index < Width) {
301 hist += strs[0];
302 ++index;
303 }
304
305 return hist;
306}
307
308#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
314inline void print_solver_diagnostics(
315 const gch::small_vector<SolveProfiler>& solve_profilers) {
316 auto solve_duration = to_ms(solve_profilers[0].total_duration());
317
318 slp::println("┏{:━^23}┯{:━^18}┯{:━^10}┯{:━^9}┯{:━^4}┓", "", "", "", "", "");
319 slp::println("┃{:^23}│{:^18}│{:^10}│{:^9}│{:^4}┃", "solver trace", "percent",
320 "total (ms)", "each (ms)", "runs");
321 slp::println("┡{:━^23}┷{:━^18}┷{:━^10}┷{:━^9}┷{:━^4}┩", "", "", "", "", "");
322
323 for (auto& profiler : solve_profilers) {
324 double norm = solve_duration == 0.0
325 ? (&profiler == &solve_profilers[0] ? 1.0 : 0.0)
326 : to_ms(profiler.total_duration()) / solve_duration;
327 slp::println("│{:<23} {:>6.2f}%▕{}▏ {:>10.3f} {:>9.3f} {:>4}│",
328 profiler.name(), norm * 100.0, histogram<9>(norm),
329 to_ms(profiler.total_duration()),
330 to_ms(profiler.average_duration()), profiler.num_solves());
331 }
332
333 slp::println("└{:─^68}┘", "");
334}
335#else
336#define print_solver_diagnostics(...)
337#endif
338
339#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
345inline void print_autodiff_diagnostics(
346 const gch::small_vector<SetupProfiler>& setup_profilers) {
347 auto setup_duration = to_ms(setup_profilers[0].duration());
348
349 // Print heading
350 slp::println("┏{:━^23}┯{:━^18}┯{:━^10}┯{:━^9}┯{:━^4}┓", "", "", "", "", "");
351 slp::println("┃{:^23}│{:^18}│{:^10}│{:^9}│{:^4}┃", "autodiff trace",
352 "percent", "total (ms)", "each (ms)", "runs");
353 slp::println("┡{:━^23}┷{:━^18}┷{:━^10}┷{:━^9}┷{:━^4}┩", "", "", "", "", "");
354
355 // Print setup profilers
356 for (auto& profiler : setup_profilers) {
357 double norm = setup_duration == 0.0
358 ? (&profiler == &setup_profilers[0] ? 1.0 : 0.0)
359 : to_ms(profiler.duration()) / setup_duration;
360 slp::println("│{:<23} {:>6.2f}%▕{}▏ {:>10.3f} {:>9.3f} {:>4}│",
361 profiler.name(), norm * 100.0, histogram<9>(norm),
362 to_ms(profiler.duration()), to_ms(profiler.duration()), "1");
363 }
364
365 slp::println("└{:─^68}┘", "");
366}
367#else
368#define print_autodiff_diagnostics(...)
369#endif
370
371} // namespace slp