tolerance.hh
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2// Pass/fail bounds: c(kind) * f(n) * eps, c calibrated (docs/design/verification.md#verification-calibration).
3#pragma once
4
6#include <batchlas/error.hh>
7
8#include <cctype>
9#include <cmath>
10#include <limits>
11#include <cstdio>
12#include <cstdlib>
13#include <mutex>
14#include <stdexcept>
15#include <string>
16
17namespace batchlas::verify {
18
19/// Unit roundoff of T's real type: 2^-24 for float, 2^-53 for double.
20template <class T> constexpr double eps() { return std::numeric_limits<float_t<T>>::epsilon() * 0.5; }
21
22/// max(a, b), NaN if either is NaN (std::max drops a NaN second argument, so a poisoned value would pass).
23inline double nanmax(double a, double b) {
24 if (std::isnan(a) || std::isnan(b)) return std::numeric_limits<double>::quiet_NaN();
25 return a > b ? a : b;
26}
27
28/// orthogonality: Q from Householder reflectors or a direct orthogonalization (geqrf, orgqr, form_q, ortho);
29/// orthogonality_rotations: eigen- and singular vectors from an iterative solver (Jacobi, QR iteration,
30/// divide and conquer, inverse iteration). n per kind, as each was calibrated: orthogonality, the rows
31/// of Q (Householder error grows with the reflector length); orthogonality_rotations, the number of
32/// vectors checked (columns of Q). blas: n is the inner dimension k
33/// (the tuner's componentwise trsm check: k = the order); trsm_residual is normwise and judged under solve.
35
36namespace detail {
37
38inline double coefficient(Check kind) {
39 switch (kind) {
40 case Check::factorization: return 16.0;
41 case Check::solve: return 16.0;
42 case Check::blas: return 8.0;
43 case Check::orthogonality: return 32.0;
44 case Check::orthogonality_rotations: return 256.0;
45 case Check::eigen_residual: return 32.0;
46 case Check::values: return 32.0;
47 }
48 return 16.0;
49}
50
51inline const char* kind_name(Check kind) {
52 switch (kind) {
53 case Check::factorization: return "factorization";
54 case Check::solve: return "solve";
55 case Check::blas: return "blas";
56 case Check::orthogonality: return "orthogonality";
57 case Check::orthogonality_rotations: return "orthogonality_rotations";
58 case Check::eigen_residual: return "eigen_residual";
59 case Check::values: return "values";
60 }
61 return "unknown";
62}
63
64template <class T> constexpr const char* dtype_name() {
65 if constexpr (is_std_complex_v<T>) return std::is_same_v<float_t<T>, float> ? "cfloat" : "cdouble";
66 else return std::is_same_v<T, float> ? "float" : "double";
67}
68
69} // namespace detail
70
71/// A per-site factor on a kind's bound, in either direction. @p reason is mandatory: every factor
72/// is listed by the calibration for a decision (@ref design_verification).
73struct Slack {
74 double factor;
75 const char* reason;
76};
77
78namespace detail {
79
80inline void check_slack(const Slack& s) {
81 if (!(s.factor > 0) || !std::isfinite(s.factor)) throw batchlas::invalid_argument("batchlas::verify::Slack: factor must be finite and > 0");
82 if (!s.reason || !*s.reason) throw batchlas::invalid_argument("batchlas::verify::Slack: a reason is mandatory");
83}
84
85// "kind dtype n value bound factor reason"; bound is the kind's, never the slacked one, and value is raw.
86// BATCHLAS_VERIFY_RECORD is a raw getenv on every check, deliberately not the settings snapshot: this
87// header is outside the library's Settings snapshot (uninstalled batchlas::verify) and tests setenv it mid-process. The handle stays open while the path is
88// unchanged; a new path reopens, unset closes, and a path fopen refuses is retried (nothing recorded).
89inline void record(const char* kind, const char* dtype, int n, double value, double b, double factor, const char* reason) {
90 static std::mutex mu;
91 static std::string open_path;
92 static std::FILE* f = nullptr;
93 std::lock_guard<std::mutex> lock(mu);
94 const char* path = std::getenv("BATCHLAS_VERIFY_RECORD");
95 if (!path || !*path) {
96 if (f) std::fclose(f);
97 f = nullptr;
98 open_path.clear();
99 return;
100 }
101 if (!f || open_path != path) {
102 if (f) std::fclose(f);
103 f = std::fopen(path, "a");
104 open_path = path;
105 }
106 if (!f) return;
107 std::string r(reason);
108 for (char& c : r)
109 if (std::isspace(static_cast<unsigned char>(c))) c = '_';
110 std::fprintf(f, "%s %s %d %.17g %.17g %.17g %s\n", kind, dtype, n, value, b, factor, r.c_str());
111 std::fflush(f); // a test that crashes keeps every line before it
112}
113
114} // namespace detail
115
116/// c(kind) * f(n) * eps<T>(): f(n) = n (below 1 counts as 1), except blas, f(k) = k + 2 (k below 0 counts as 0).
117template <class T> double bound(Check kind, int n) {
118 const double f = kind == Check::blas ? double(n < 0 ? 0 : n) + 2.0 : double(n < 1 ? 1 : n);
119 return detail::coefficient(kind) * f * eps<T>();
120}
121
122/// slack.factor * bound<T>(kind, n). @throws batchlas::invalid_argument for an empty reason or a factor <= 0.
123template <class T> double bound(Check kind, int n, Slack slack) {
124 detail::check_slack(slack);
125 return slack.factor * bound<T>(kind, n);
126}
127
128/// pass without recording: for predicates evaluated on results expected to be wrong.
129template <class T> bool within(Check kind, int n, double value) { return !std::isnan(value) && value <= bound<T>(kind, n); }
130
131template <class T> bool within(Check kind, int n, double value, Slack slack) {
132 return !std::isnan(value) && value <= bound<T>(kind, n, slack);
133}
134
135/// True when @p value is not NaN and within bound<T>(kind, n). With `BATCHLAS_VERIFY_RECORD=<path>`
136/// set, appends "kind dtype n value bound 1 -" to that file (calibration input).
137template <class T> bool pass(Check kind, int n, double value) {
138 detail::record(detail::kind_name(kind), detail::dtype_name<T>(), n, value, bound<T>(kind, n), 1.0, "-");
139 return within<T>(kind, n, value);
140}
141
142/// pass against the slacked bound; records the raw value, the kind's bound, the factor and the reason.
143template <class T> bool pass(Check kind, int n, double value, Slack slack) {
144 detail::check_slack(slack);
145 detail::record(detail::kind_name(kind), detail::dtype_name<T>(), n, value, bound<T>(kind, n), slack.factor, slack.reason);
146 return within<T>(kind, n, value, slack);
147}
148
149/// Bound of pivot_ratio: 1 + 64 eps<T>() (32 machine epsilons), the slack of a cabs1 comparison in T.
150template <class T> double pivot_ratio_bound() { return 1.0 + 64.0 * eps<T>(); }
151
152} // namespace batchlas::verify
The call violates the API contract.
Definition error.hh:77
Enumerations, scalar traits and scalar concepts shared by every public header.
The BatchLAS exception hierarchy.
constexpr bool is_std_complex_v
Value of is_std_complex<T>.
Definition enums.hh:53
typename base_type< T >::type float_t
Shorthand for base_type<T>::type: the precision of T (tolerances, norms, eigenvalues).
Definition enums.hh:41
const char * kind_name(Check kind)
Definition tolerance.hh:51
void record(const char *kind, const char *dtype, int n, double value, double b, double factor, const char *reason)
Definition tolerance.hh:89
void check_slack(const Slack &s)
Definition tolerance.hh:80
double coefficient(Check kind)
Definition tolerance.hh:38
constexpr const char * dtype_name()
Definition tolerance.hh:64
bool within(Check kind, int n, double value)
pass without recording: for predicates evaluated on results expected to be wrong.
Definition tolerance.hh:129
constexpr double eps()
Unit roundoff of T's real type: 2^-24 for float, 2^-53 for double.
Definition tolerance.hh:20
Check
orthogonality: Q from Householder reflectors or a direct orthogonalization (geqrf,...
Definition tolerance.hh:34
double nanmax(double a, double b)
max(a, b), NaN if either is NaN (std::max drops a NaN second argument, so a poisoned value would pass...
Definition tolerance.hh:23
double pivot_ratio_bound()
Bound of pivot_ratio: 1 + 64 eps<T>() (32 machine epsilons), the slack of a cabs1 comparison in T.
Definition tolerance.hh:150
double bound(Check kind, int n)
c(kind) * f(n) * eps<T>(): f(n) = n (below 1 counts as 1), except blas, f(k) = k + 2 (k below 0 count...
Definition tolerance.hh:117
bool pass(Check kind, int n, double value)
True when value is not NaN and within bound<T>(kind, n).
Definition tolerance.hh:137
A per-site factor on a kind's bound, in either direction.
Definition tolerance.hh:73