akreon-linalg
Fixed-size linear algebra and numerical methods in C++26.
Loading...
Searching...
No Matches
algebra_util.hpp
Go to the documentation of this file.
1/*
2 * Copyright © 2025-2026 Akreon LLC
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6#pragma once
7
8#include <array>
9#include <cmath>
10#include <limits>
11#include <optional>
12
14#include "vector_concepts.hpp"
15
22#define MAX_STEP_EVAL 256
23
25#if defined(__cpp_lib_constexpr_cmath) && __cpp_lib_constexpr_cmath >= 202306L
26#define LINALG_HAS_CXX26_CONSTEXPR_CMATH 1
27#else
28#define LINALG_HAS_CXX26_CONSTEXPR_CMATH 0
29#endif
30
32#if defined(__cpp_lib_constexpr_complex) && \
33 __cpp_lib_constexpr_complex >= 202306L
34#define LINALG_HAS_CXX26_CONSTEXPR_COMPLEX_MATH 1
35#else
36#define LINALG_HAS_CXX26_CONSTEXPR_COMPLEX_MATH 0
37#endif
38
39namespace linalg {
40
46template <typename T, std::size_t N>
47constexpr auto array_to_tuple(const std::array<T, N>& x) {
48 auto to_tuple = [&]<std::size_t... Is>(const auto& arr,
49 std::index_sequence<Is...>) {
50 return std::make_tuple(arr[Is]...);
51 };
52 return to_tuple(x, std::make_index_sequence<N>{});
53}
54
65template <Field T>
66constexpr bool is_real(const std::complex<T>& z, real_type_t<T> tol = 1e-10) {
67 return z.imag() <= tol;
68}
69
78template <Real T>
79constexpr std::optional<T> constexpr_div(T num, T den) {
80 if (!(den != T(0))) return std::nullopt;
81 return num / den;
82}
83
93template <Real T>
94constexpr T constexpr_div_safe(T num, T den, T fallback = T(0)) {
95 if (const auto r = constexpr_div(num, den); r.has_value()) return *r;
96 return fallback;
97}
98
105template <Real T>
106constexpr T constexpr_pow(T x, std::size_t power) {
107#if LINALG_HAS_CXX26_CONSTEXPR_CMATH
108 return std::pow(x, power);
109#else
110 T res(1);
111 for (std::size_t i = 0; i < power; ++i) res *= x;
112 return res;
113#endif
114}
115
129template <Real T>
130constexpr std::optional<T> constexpr_sqrt(T x, T g = 1e-10, T tol = 1e-10) {
131 if (!(x >= T(0))) return std::nullopt;
132 if (x == T(0)) return T(0);
133
134#if LINALG_HAS_CXX26_CONSTEXPR_CMATH
135 (void)g;
136 (void)tol;
137 return std::sqrt(x);
138#else
139 if (!(g > T(0))) g = T(1);
140
141 auto abs = [](const auto& val) { return val < T(0) ? -val : val; };
142
143 for (std::size_t k = 0; k < MAX_STEP_EVAL; ++k) {
144 if (!(g > T(0))) g = T(1);
145 if (abs(g * g - x) <= tol) return g;
146 g = T(0.5) * (g + constexpr_div_safe(x, g, g));
147 }
148 return g;
149#endif
150}
151
162template <Real T>
163constexpr T constexpr_sqrt_safe(T x, T floor = T(0), T g = 1e-10,
164 T tol = 1e-10) {
165 if (const auto r = constexpr_sqrt(x, g, tol); r.has_value()) return *r;
166
167 const auto floor_safe = (floor >= T(0)) ? floor : T(0);
168 if (const auto rf = constexpr_sqrt(floor_safe, g, tol); rf.has_value())
169 return *rf;
170
171 return T(0);
172}
173
180template <Real T>
181constexpr T constexpr_abs(T x) {
182#if LINALG_HAS_CXX26_CONSTEXPR_CMATH
183 return std::abs(x);
184#else
185 return x < T(0) ? T(-1) * x : x;
186#endif
187}
188
189template <Complex T>
190constexpr auto constexpr_abs(const T& x) {
191#if LINALG_HAS_CXX26_CONSTEXPR_COMPLEX_MATH
192 return std::abs(x);
193#else
194 return constexpr_sqrt_safe(x.real() * x.real() + x.imag() * x.imag());
195#endif
196}
197
211template <Real T>
212constexpr std::optional<T> constexpr_nrt(T x, std::size_t root, T g = 1e-10,
213 T tol = 1e-10) {
214 if (root == 0) return std::nullopt;
215 if (x == T(0)) return T(0);
216
217 T sign = T(1);
218 if (x < T(0)) {
219 if (root % 2 == 0) return std::nullopt;
220 sign = T(-1);
221 x = -x;
222 }
223
224#if LINALG_HAS_CXX26_CONSTEXPR_CMATH
225 (void)g;
226 (void)tol;
227 if (root == 1) return sign * x;
228 if (root == 2) return sign * std::sqrt(x);
229 if (root == 3) return sign * std::cbrt(x);
230 return sign * std::pow(x, T(1) / T(root));
231#else
232 if (!(g > T(0))) g = T(1);
233
234 auto pow_n = [&](T base, std::size_t n) {
235 T pow(1);
236 for (std::size_t i = 0; i < n; ++i) pow *= base;
237 return pow;
238 };
239
240 for (std::size_t k = 0; k < MAX_STEP_EVAL; ++k) {
241 const auto gkm1 = pow_n(g, root - 1);
242 if (!(gkm1 > T(0))) return std::nullopt;
243
244 g = (T(root - 1) * g + constexpr_div_safe(x, gkm1, g)) / T(root);
245 if (constexpr_abs(pow_n(g, root) - x) <= tol) return sign * g;
246 }
247 return sign * g;
248#endif
249}
250
262template <Real T>
263constexpr T constexpr_nrt_safe(T x, std::size_t root, T floor = T(0),
264 T g = 1e-10, T tol = 1e-10) {
265 if (const auto r = constexpr_nrt(x, root, g, tol); r.has_value()) return *r;
266
267 const auto floor_safe = (floor >= T(0)) ? floor : T(0);
268 if (const auto rf = constexpr_nrt(floor_safe, root, g, tol); rf.has_value())
269 return *rf;
270
271 return T(0);
272}
273
283template <Real T>
284constexpr auto constexpr_sin(T x, int terms = 16) {
285#if LINALG_HAS_CXX26_CONSTEXPR_CMATH
286 (void)terms;
287 return std::sin(x);
288#else
289 T sum = x, term = x;
290 for (int k = 1; k < terms; ++k) {
291 term *= (-(x * x)) / (T(2 * k) * T(2 * k + 1));
292 sum += term;
293 }
294 return sum;
295#endif
296}
297
307template <Real T>
308constexpr auto constexpr_cos(T x, int terms = 16) {
309#if LINALG_HAS_CXX26_CONSTEXPR_CMATH
310 (void)terms;
311 return std::cos(x);
312#else
313 T sum = T(1), term = T(1);
314 for (int k = 1; k < terms; ++k) {
315 term *= (-(x * x)) / (T(2 * k - 1) * T(2 * k));
316 sum += term;
317 }
318 return sum;
319#endif
320}
321
332template <Real T>
333constexpr std::optional<T> constexpr_atanh(T x, int terms = 16) {
334 if (constexpr_abs(x) >= T(1)) return std::nullopt;
335#if LINALG_HAS_CXX26_CONSTEXPR_CMATH
336 (void)terms;
337 return std::atanh(x);
338#else
339 const T x2 = x * x;
340 T term(x), sum(x);
341 for (int k = 1; k < terms; ++k) {
342 term *= x2;
343 sum += (term / T(2 * k + 1));
344 }
345 return sum;
346#endif
347}
348
358template <Real T>
359constexpr T constexpr_atanh_safe(T x, T fallback = T(0), int terms = 16) {
360 if (const auto r = constexpr_atanh(x, terms); r.has_value()) return *r;
361 return fallback;
362}
363
371template <Real T>
372constexpr auto constexpr_expi(T theta, int terms = 16) {
373#if LINALG_HAS_CXX26_CONSTEXPR_COMPLEX_MATH
374 (void)terms;
375 return std::exp(std::complex<T>(T(0), theta));
376#else
377 return std::complex<T>(constexpr_cos(theta, terms),
379#endif
380}
381
392template <Real T>
393constexpr auto constexpr_exp(T x, int terms = 16) {
394#if LINALG_HAS_CXX26_CONSTEXPR_CMATH
395 (void)terms;
396 return std::exp(x);
397#else
398 T sum(1), term(1);
399 for (int k = 1; k < terms; ++k) {
400 term *= x / k;
401 sum += term;
402 }
403 return sum;
404#endif
405}
406
414template <Complex T>
415constexpr auto constexpr_exp_lambda(const T& lambda, int terms = 16) {
416#if LINALG_HAS_CXX26_CONSTEXPR_COMPLEX_MATH
417 (void)terms;
418 return std::exp(lambda);
419#else
420 return constexpr_exp(lambda.real()) * constexpr_expi(lambda.imag(), terms);
421#endif
422}
423
451template <Real T>
452constexpr std::optional<T> constexpr_ln(T x_in, int terms = 16) {
453 if (!(x_in > T(0))) return std::nullopt;
454
455#if LINALG_HAS_CXX26_CONSTEXPR_CMATH
456 (void)terms;
457 return std::log(x_in);
458#else
459 T x = x_in;
460 int e = 0;
461
462 for (int i = 0; i < 64 && x > T(2); ++i) {
463 x *= T(0.5);
464 ++e;
465 }
466 for (int i = 0; i < 64 && x < T(0.5); ++i) {
467 x *= T(2);
468 --e;
469 }
470
471 const T y = constexpr_div_safe(x - T(1), x + T(1), T(0));
472 const auto a = constexpr_atanh(y, terms);
473 if (!a) return std::nullopt;
474
475 return T(2) * (*a) + T(e) * ln2_v<T>;
476#endif
477}
478
491template <Real T>
492constexpr T constexpr_ln_safe(T x, T floor = std::numeric_limits<T>::min(),
493 int terms = 16) {
494 const T floor_safe = (floor > T(0)) ? floor : std::numeric_limits<T>::min();
495 const T x_safe = (x > floor_safe) ? x : floor_safe;
496 const auto l = constexpr_ln(x_safe, terms);
497 if (l.has_value()) return *l;
498
499 const auto l_floor = constexpr_ln(floor_safe, terms);
500 return l_floor.has_value() ? *l_floor : T(0);
501}
502
511template <std::size_t Start, std::size_t End, typename Func>
512 requires(End - Start >= 0)
513constexpr void constexpr_for(Func&& f) {
514 if constexpr (Start < End) {
515 f(std::integral_constant<std::size_t, Start>{});
516 constexpr_for<Start + 1, End>(std::forward<Func>(f));
517 }
518}
519
527template <Complex T>
528constexpr bool is_same(const T& a, const T& b, real_type_t<T> tol = 1e-10) {
529 return constexpr_abs(a - b) < tol;
530}
531
538template <VectorContainer Vec>
540constexpr auto argsort_desc(const Vec& x) {
541 static constexpr std::size_t N = vector_dim_v<Vec>;
542
543 std::array<std::size_t, N> p;
544 for (std::size_t i = 0; i < N; ++i) p[i] = i;
545 for (std::size_t i = 0; i + 1 < N; ++i) {
546 std::size_t largest = i;
547 for (std::size_t j = i + 1; j < N; ++j)
548 if (x(p[j]) > x(p[largest])) largest = j;
549 if (largest != i) std::swap(p[i], p[largest]);
550 }
551 return p;
552}
553
554} // namespace linalg
#define MAX_STEP_EVAL
Maximum Newton/refinement iterations used by constexpr utilities.
constexpr std::optional< T > constexpr_ln(T x_in, int terms=16)
natural log
constexpr T constexpr_nrt_safe(T x, std::size_t root, T floor=T(0), T g=1e-10, T tol=1e-10)
Return a scalar nth root with a sanitized fallback radicand.
constexpr auto constexpr_sin(T x, int terms=16)
Approximate sine with a truncated Taylor recurrence.
typename scalar_traits_t< T >::real_type real_type_t
Associated real component type registered for T.
constexpr auto constexpr_expi(T theta, int terms=16)
Approximate exp(i * theta) on the complex unit circle.
constexpr std::optional< T > constexpr_sqrt(T x, T g=1e-10, T tol=1e-10)
Approximate a nonnegative square root with Newton iteration.
constexpr std::optional< T > constexpr_nrt(T x, std::size_t root, T g=1e-10, T tol=1e-10)
Approximate a real nth root with Newton iteration.
constexpr T constexpr_atanh_safe(T x, T fallback=T(0), int terms=16)
constexpr-safe atanh with fallback
constexpr auto constexpr_exp(T x, int terms=16)
Approximate the real exponential with a truncated power series.
constexpr std::optional< T > constexpr_div(T num, T den)
Divide two real scalars with an explicit zero-denominator result.
constexpr void constexpr_for(Func &&f)
constexpr for loop which recursively unrolls a dynamic for loop into compile time recursive loop
constexpr auto array_to_tuple(const std::array< T, N > &x)
Copy a fixed-size array into a tuple of the same length.
constexpr T constexpr_div_safe(T num, T den, T fallback=T(0))
Divide two real scalars, returning a fallback for a zero denominator.
constexpr T constexpr_abs(T x)
Compute real absolute value or complex magnitude.
constexpr T constexpr_ln_safe(T x, T floor=std::numeric_limits< T >::min(), int terms=16)
natural log with domain sanitization and fallback
constexpr auto constexpr_cos(T x, int terms=16)
Approximate cosine with a truncated Taylor recurrence.
constexpr T constexpr_pow(T x, std::size_t power)
Raise a real scalar to a nonnegative integral power.
constexpr T constexpr_sqrt_safe(T x, T floor=T(0), T g=1e-10, T tol=1e-10)
Return a scalar square root with a sanitized fallback radicand.
constexpr bool is_same(const T &a, const T &b, real_type_t< T > tol=1e-10)
Compare complex scalars by magnitude of their difference.
constexpr std::size_t mat_rows_v
Registered compile-time row count after cv/ref normalization.
constexpr bool is_real(const std::complex< T > &z, real_type_t< T > tol=1e-10)
Check whether a complex value's imaginary component is below a bound.
constexpr std::optional< T > constexpr_atanh(T x, int terms=16)
atanh (inverse hyperbolic tangent function)
constexpr auto argsort_desc(const Vec &x)
Return coordinate indices sorted by descending real value.
constexpr auto constexpr_exp_lambda(const T &lambda, int terms=16)
Approximate the exponential of a complex scalar.