WPILibC++ 2027.0.0-alpha-5
Loading...
Searching...
No Matches
print_diagnostics.hpp
Go to the documentation of this file.
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
21
22namespace slp {
23
24/// Iteration type.
25enum class IterationType : uint8_t {
26 /// Normal iteration.
28 /// Accepted second-order correction iteration.
30 /// Rejected second-order correction iteration.
32 /// Feasibility restoration iteration.
34};
35
36/// Converts std::chrono::duration to a number of milliseconds rounded to three
37/// decimals.
38template <typename Rep, typename Period = std::ratio<1>>
39constexpr double to_ms(const std::chrono::duration<Rep, Period>& duration) {
40 using std::chrono::duration_cast;
41 using std::chrono::microseconds;
42 return duration_cast<microseconds>(duration).count() / 1e3;
43}
44
45/// Renders value as power of 10.
46///
47/// @tparam Scalar Scalar type.
48/// @param value Value.
49template <typename Scalar>
50std::string power_of_10(Scalar value) {
51 if (value == Scalar(0)) {
52 return " 0";
53 } else {
54 using std::log10;
55 int exponent = static_cast<int>(log10(value));
56
57 if (exponent == 0) {
58 return " 1";
59 } else if (exponent == 1) {
60 return "10";
61 } else {
62 // Gather exponent digits
63 int n = std::abs(exponent);
65 do {
66 digits.emplace_back(n % 10);
67 n /= 10;
68 } while (n > 0);
69
70 std::string output = "10";
71
72 // Append exponent
73 if (exponent < 0) {
74 output += "⁻";
75 }
76 constexpr std::array strs = {"⁰", "¹", "²", "³", "⁴",
77 "⁵", "⁶", "⁷", "⁸", "⁹"};
78 for (const auto& digit : digits | std::views::reverse) {
79 output += strs[digit];
80 }
81
82 return output;
83 }
84 }
85}
86
87#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
88/// Prints error for too few degrees of freedom.
89///
90/// @tparam Scalar Scalar type.
91/// @param c_e The problem's equality constraints cₑ(x) evaluated at the current
92/// iterate.
93template <typename Scalar>
95 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_e) {
96 slp::println("The problem has too few degrees of freedom.");
97 slp::println("Violated constraints (cₑ(x) = 0) in order of declaration:");
98 for (int row = 0; row < c_e.rows(); ++row) {
99 if (c_e[row] < Scalar(0)) {
100 slp::println(" {}/{}: {} = 0", row + 1, c_e.rows(), c_e[row]);
101 }
102 }
103}
104#else
105#define print_too_few_dofs_error(...)
106#endif
107
108#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
109/// Prints equality constraint local infeasibility error.
110///
111/// @tparam Scalar Scalar type.
112/// @param c_e The problem's equality constraints cₑ(x) evaluated at the current
113/// iterate.
114template <typename Scalar>
116 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_e) {
118 "The problem is locally infeasible due to violated equality "
119 "constraints.");
120 slp::println("Violated constraints (cₑ(x) = 0) in order of declaration:");
121 for (int row = 0; row < c_e.rows(); ++row) {
122 if (c_e[row] < Scalar(0)) {
123 slp::println(" {}/{}: {} = 0", row + 1, c_e.rows(), c_e[row]);
124 }
125 }
126}
127#else
128#define print_c_e_local_infeasibility_error(...)
129#endif
130
131#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
132/// Prints inequality constraint local infeasibility error.
133///
134/// @tparam Scalar Scalar type.
135/// @param c_i The problem's inequality constraints cᵢ(x) evaluated at the
136/// current iterate.
137template <typename Scalar>
139 const Eigen::Vector<Scalar, Eigen::Dynamic>& c_i) {
141 "The problem is locally infeasible due to violated inequality "
142 "constraints.");
143 slp::println("Violated constraints (cᵢ(x) ≥ 0) in order of declaration:");
144 for (int row = 0; row < c_i.rows(); ++row) {
145 if (c_i[row] < Scalar(0)) {
146 slp::println(" {}/{}: {} ≥ 0", row + 1, c_i.rows(), c_i[row]);
147 }
148 }
149}
150#else
151#define print_c_i_local_infeasibility_error(...)
152#endif
153
154#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
156 const std::span<const std::pair<Eigen::Index, Eigen::Index>>
157 conflicting_lower_upper_bound_indices) {
159 "The problem is globally infeasible due to conflicting bound "
160 "constraints:");
161 for (const auto& [lower_bound_idx, upper_bound_idx] :
162 conflicting_lower_upper_bound_indices) {
164 " Inequality constraint {} gives a lower bound that is greater than "
165 "the upper bound given by inequality constraint {}",
166 lower_bound_idx, upper_bound_idx);
167 }
168}
169#else
170#define print_bound_constraint_global_infeasibility_error(...)
171#endif
172
173#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
174/// Prints diagnostics for the current iteration.
175///
176/// @tparam Scalar Scalar type.
177/// @param iterations Number of iterations.
178/// @param type The iteration's type.
179/// @param time The iteration duration.
180/// @param error The error.
181/// @param cost The cost.
182/// @param infeasibility The infeasibility.
183/// @param complementarity The complementarity.
184/// @param μ The barrier parameter.
185/// @param δ The Hessian regularization factor.
186/// @param primal_α The primal step size.
187/// @param primal_α_max The max primal step size.
188/// @param α_reduction_factor Factor by which primal_α is reduced during
189/// backtracking.
190/// @param dual_α The dual step size.
191template <typename Scalar, typename Rep, typename Period = std::ratio<1>>
193 const std::chrono::duration<Rep, Period>& time,
194 Scalar error, Scalar cost,
195 Scalar infeasibility, Scalar complementarity,
196 Scalar μ, Scalar δ, Scalar primal_α,
197 Scalar primal_α_max, Scalar α_reduction_factor,
198 Scalar dual_α) {
199 if (iterations % 20 == 0) {
200 if (iterations == 0) {
201 slp::print("┏");
202 } else {
203 slp::print("┢");
204 }
206 "{:━^4}┯{:━^4}┯{:━^9}┯{:━^12}┯{:━^13}┯{:━^12}┯{:━^12}┯{:━^8}┯{:━^5}┯"
207 "{:━^8}┯{:━^8}┯{:━^2}",
208 "", "", "", "", "", "", "", "", "", "", "", "");
209 if (iterations == 0) {
210 slp::println("┓");
211 } else {
212 slp::println("┪");
213 }
215 "┃{:^4}│{:^4}│{:^9}│{:^12}│{:^13}│{:^12}│{:^12}│{:^8}│{:^5}│{:^8}│{:^8}"
216 "│{:^2}┃",
217 "iter", "type", "time (ms)", "error", "cost", "infeas.", "complement.",
218 "μ", "reg", "primal α", "dual α", "↩");
220 "┡{:━^4}┷{:━^4}┷{:━^9}┷{:━^12}┷{:━^13}┷{:━^12}┷{:━^12}┷{:━^8}┷{:━^5}┷"
221 "{:━^8}┷{:━^8}┷{:━^2}┩",
222 "", "", "", "", "", "", "", "", "", "", "", "");
223 }
224
225 // For the number of backtracks, we want x such that:
226 //
227 // αᵐᵃˣrˣ = α
228 //
229 // where r ∈ (0, 1) is the reduction factor.
230 //
231 // rˣ = α/αᵐᵃˣ
232 // ln(rˣ) = ln(α/αᵐᵃˣ)
233 // x ln(r) = ln(α/αᵐᵃˣ)
234 // x = ln(α/αᵐᵃˣ)/ln(r)
235 using std::log;
236 int backtracks =
237 static_cast<int>(log(primal_α / primal_α_max) / log(α_reduction_factor));
238
239 constexpr std::array ITERATION_TYPES = {"norm", "✓SOC", "XSOC", "rest"};
241 "│{:4} {:4} {:9.3f} {:12e} {:13e} {:12e} {:12e} {:.2e} {:<5} {:.2e} "
242 "{:.2e} {:2d}│",
243 iterations, ITERATION_TYPES[slp::to_underlying(type)], to_ms(time), error,
244 cost, infeasibility, complementarity, μ, power_of_10(δ), primal_α, dual_α,
245 backtracks);
246}
247#else
248#define print_iteration_diagnostics(...)
249#endif
250
251#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
252/// Prints bottom of iteration diagnostics table.
254 slp::println("└{:─^108}┘", "");
255}
256#else
257#define print_bottom_iteration_diagnostics(...)
258#endif
259
260/// Renders histogram of the given normalized value.
261///
262/// @tparam Width Width of the histogram in characters.
263/// @param value Normalized value from 0 to 1.
264template <int Width>
265 requires(Width > 0)
266std::string histogram(double value) {
267 value = std::clamp(value, 0.0, 1.0);
268
269 double ipart;
270 int fpart = static_cast<int>(std::modf(value * Width, &ipart) * 8);
271
272 constexpr std::array strs = {" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"};
273 std::string hist;
274
275 int index = 0;
276 while (index < ipart) {
277 hist += strs[8];
278 ++index;
279 }
280 if (fpart > 0) {
281 hist += strs[fpart];
282 ++index;
283 }
284 while (index < Width) {
285 hist += strs[0];
286 ++index;
287 }
288
289 return hist;
290}
291
292#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
293/// Prints solver diagnostics.
294///
295/// @param solve_profilers Solve profilers.
297 const gch::small_vector<SolveProfiler>& solve_profilers) {
298 auto solve_duration = to_ms(solve_profilers[0].total_duration());
299
300 slp::println("┏{:━^21}┯{:━^18}┯{:━^10}┯{:━^9}┯{:━^4}┓", "", "", "", "", "");
301 slp::println("┃{:^21}│{:^18}│{:^10}│{:^9}│{:^4}┃", "solver trace", "percent",
302 "total (ms)", "each (ms)", "runs");
303 slp::println("┡{:━^21}┷{:━^18}┷{:━^10}┷{:━^9}┷{:━^4}┩", "", "", "", "", "");
304
305 for (auto& profiler : solve_profilers) {
306 double norm = solve_duration == 0.0
307 ? (&profiler == &solve_profilers[0] ? 1.0 : 0.0)
308 : to_ms(profiler.total_duration()) / solve_duration;
309 slp::println("│{:<21} {:>6.2f}%▕{}▏ {:>10.3f} {:>9.3f} {:>4}│",
310 profiler.name(), norm * 100.0, histogram<9>(norm),
311 to_ms(profiler.total_duration()),
312 to_ms(profiler.average_duration()), profiler.num_solves());
313 }
314
315 slp::println("└{:─^66}┘", "");
316}
317#else
318#define print_solver_diagnostics(...)
319#endif
320
321#ifndef SLEIPNIR_DISABLE_DIAGNOSTICS
322/// Prints setup diagnostics.
323///
324/// @param setup_profilers Setup profilers.
326 const gch::small_vector<SetupProfiler>& setup_profilers) {
327 auto setup_duration = to_ms(setup_profilers[0].duration());
328
329 // Print heading
330 slp::println("┏{:━^21}┯{:━^18}┯{:━^10}┯{:━^9}┯{:━^4}┓", "", "", "", "", "");
331 slp::println("┃{:^21}│{:^18}│{:^10}│{:^9}│{:^4}┃", "setup trace", "percent",
332 "total (ms)", "each (ms)", "runs");
333 slp::println("┡{:━^21}┷{:━^18}┷{:━^10}┷{:━^9}┷{:━^4}┩", "", "", "", "", "");
334
335 // Print setup profilers
336 for (auto& profiler : setup_profilers) {
337 double norm = setup_duration == 0.0
338 ? (&profiler == &setup_profilers[0] ? 1.0 : 0.0)
339 : to_ms(profiler.duration()) / setup_duration;
340 slp::println("│{:<21} {:>6.2f}%▕{}▏ {:>10.3f} {:>9.3f} {:>4}│",
341 profiler.name(), norm * 100.0, histogram<9>(norm),
342 to_ms(profiler.duration()), to_ms(profiler.duration()), "1");
343 }
344
345 slp::println("└{:─^66}┘", "");
346}
347#else
348#define print_setup_diagnostics(...)
349#endif
350
351} // namespace slp
@ index
Definition base.h:690
wpi::util::SmallVector< T > small_vector
Definition small_vector.hpp:10
Definition to_underlying.hpp:7
void print_iteration_diagnostics(int iterations, IterationType type, const std::chrono::duration< Rep, Period > &time, Scalar error, Scalar cost, Scalar infeasibility, Scalar complementarity, Scalar μ, Scalar δ, Scalar primal_α, Scalar primal_α_max, Scalar α_reduction_factor, Scalar dual_α)
Prints diagnostics for the current iteration.
Definition print_diagnostics.hpp:192
constexpr std::underlying_type_t< Enum > to_underlying(Enum e) noexcept
Definition to_underlying.hpp:10
Variable< Scalar > log10(const Variable< Scalar > &x)
log10() for Variables.
Definition variable.hpp:530
void print_c_i_local_infeasibility_error(const Eigen::Vector< Scalar, Eigen::Dynamic > &c_i)
Prints inequality constraint local infeasibility error.
Definition print_diagnostics.hpp:138
constexpr double to_ms(const std::chrono::duration< Rep, Period > &duration)
Converts std::chrono::duration to a number of milliseconds rounded to three decimals.
Definition print_diagnostics.hpp:39
void print_setup_diagnostics(const gch::small_vector< SetupProfiler > &setup_profilers)
Prints setup diagnostics.
Definition print_diagnostics.hpp:325
std::string histogram(double value)
Renders histogram of the given normalized value.
Definition print_diagnostics.hpp:266
IterationType
Iteration type.
Definition print_diagnostics.hpp:25
@ NORMAL
Normal iteration.
Definition print_diagnostics.hpp:27
@ ACCEPTED_SOC
Accepted second-order correction iteration.
Definition print_diagnostics.hpp:29
@ FEASIBILITY_RESTORATION
Feasibility restoration iteration.
Definition print_diagnostics.hpp:33
@ REJECTED_SOC
Rejected second-order correction iteration.
Definition print_diagnostics.hpp:31
void print_bound_constraint_global_infeasibility_error(const std::span< const std::pair< Eigen::Index, Eigen::Index > > conflicting_lower_upper_bound_indices)
Definition print_diagnostics.hpp:155
Variable< Scalar > log(const Variable< Scalar > &x)
log() for Variables.
Definition variable.hpp:521
std::string power_of_10(Scalar value)
Renders value as power of 10.
Definition print_diagnostics.hpp:50
void print_bottom_iteration_diagnostics()
Prints bottom of iteration diagnostics table.
Definition print_diagnostics.hpp:253
void println(fmt::format_string< T... > fmt, T &&... args)
Wrapper around fmt::println() that squelches write failure exceptions.
Definition print.hpp:37
void print_solver_diagnostics(const gch::small_vector< SolveProfiler > &solve_profilers)
Prints solver diagnostics.
Definition print_diagnostics.hpp:296
void print_too_few_dofs_error(const Eigen::Vector< Scalar, Eigen::Dynamic > &c_e)
Prints error for too few degrees of freedom.
Definition print_diagnostics.hpp:94
void print(fmt::format_string< T... > fmt, T &&... args)
Wrapper around fmt::print() that squelches write failure exceptions.
Definition print.hpp:19
void print_c_e_local_infeasibility_error(const Eigen::Vector< Scalar, Eigen::Dynamic > &c_e)
Prints equality constraint local infeasibility error.
Definition print_diagnostics.hpp:115