NumericAL
A header-only C++20 lexer built on REAL
Loading...
Searching...
No Matches
decomposition.hpp
Go to the documentation of this file.
1
22#ifndef NUMERICAL_DECOMPOSITION_HPP
23#define NUMERICAL_DECOMPOSITION_HPP
24
25#include <algorithm>
26#include <array>
27#include <atomic>
28#include <bit>
29#include <cmath>
30#include <complex> // so std::abs resolves for a complex scalar, regardless of include order
31#include <cstddef>
32#include <cstdint>
33#include <cstring>
34#include <functional>
35#include <limits>
36#include <memory>
37#include <numbers>
38#include <optional>
39#include <stdexcept>
40#include <string>
41#include <tuple>
42#include <type_traits>
43#include <utility>
44#include <vector>
45
46#include "matrix.hpp"
47#include "simd.hpp"
48#include "parallel.hpp"
49#include "vector.hpp"
50
51namespace numerical {
52
53 namespace detail {
59 template <typename T>
61 {
62 return pivot;
63 }
64
66 template <typename R>
67 [[nodiscard]] std::complex<R> pivot_divisor(const std::complex<R>& pivot)
68 {
69 return std::complex<R> {1} / pivot;
70 }
71
73 template <typename T>
74 [[nodiscard]] T over_pivot(const T& x,
75 const T& divisor)
76 {
77 return x / divisor;
78 }
79
81 template <typename R>
82 [[nodiscard]] std::complex<R> over_pivot(const std::complex<R>& x,
83 const std::complex<R>& divisor)
84 {
85 return operator_times(x, divisor);
86 }
87
89 template <typename T>
91 const T& a,
92 const T& b)
93 {
94 sum -= a * b;
95 }
96
98 template <typename R>
99 void subtract_term(std::complex<R>& sum,
100 const std::complex<R>& a,
101 const std::complex<R>& b)
102 {
103 sum -= operator_times(a, b);
104 }
105
108 inline constexpr std::size_t lu_block {64};
109
110#ifdef NUMERICAL_X86_COMPILED
115 inline constexpr std::size_t lu_small_order {384};
116#else
120 inline constexpr std::size_t lu_small_order {256};
121#endif
122
124 inline constexpr std::size_t lu_small_block {32};
125
127 [[nodiscard]] constexpr std::size_t lu_block_for(std::size_t n) noexcept
128 {
129 static_assert(std::has_single_bit(lu_small_block) && std::has_single_bit(lu_block), "a panel is a mask away");
130 return n <= lu_small_order ? lu_small_block : lu_block;
131 }
132
136 inline constexpr std::size_t lu_lookahead_columns {1024};
137
141#ifdef NUMERICAL_X86_COMPILED
142 inline constexpr std::size_t lu_update_speedup {gemm_kernel_speedup / 4};
143#else
144 inline constexpr std::size_t lu_update_speedup {gemm_kernel_speedup};
145#endif
146
148 inline constexpr std::size_t narrow_solve {16};
149
150#ifdef NUMERICAL_X86_COMPILED
154 inline constexpr std::size_t slice_columns {48};
155#else
159 inline constexpr std::size_t slice_columns {64};
160#endif
161
162#ifdef NUMERICAL_X86_COMPILED
166 inline constexpr std::size_t slice_solve_order {384};
167#endif
168
169
171 inline constexpr std::size_t solve_leaf {16};
172
174 inline constexpr std::size_t inverse_band {16};
175
178 inline constexpr std::size_t memory_element_weight {4};
179
181 inline constexpr std::size_t svd_band {32};
182
184 inline constexpr std::size_t zeroed_rows {32};
185
186#ifdef NUMERICAL_X86_COMPILED
190 inline constexpr std::size_t panel_copy_rows {8};
191
196 inline constexpr std::size_t panel_tile_rows {256};
197#endif
198
200 inline constexpr std::size_t cholesky_block {64};
201
204 inline constexpr std::size_t cholesky_diagonal_rows {4};
205
208 inline constexpr std::size_t cholesky_together_width {16};
209
213#ifdef NUMERICAL_X86_COMPILED
214 inline constexpr std::size_t cholesky_one_panel {40};
215#else
216 inline constexpr std::size_t cholesky_one_panel {48};
217#endif
218 static_assert(cholesky_one_panel <= cholesky_block, "one panel of cholesky_block columns holds the whole matrix");
219
223 [[nodiscard]] constexpr std::size_t cholesky_block_for(std::size_t n) noexcept
224 {
225 if (n < cholesky_one_panel) {
226 return cholesky_block;
227 }
228 if (n <= 128) {
229 return cholesky_block / 4;
230 }
231 return n <= 256 ? cholesky_block / 2 : cholesky_block;
232 }
233
235 inline constexpr std::size_t cholesky_rows {64};
236
240 inline constexpr std::size_t cholesky_update_columns {64};
241
245 inline constexpr std::size_t cholesky_update_rows {256};
246
247
248 // conj_of / real_part are written as overloads (not `if constexpr`) so each
249 // is a single, fully-covered expression: a complex scalar selects the
250 // std::complex overload, any other scalar the primary template.
251
253 template <typename T>
254 [[nodiscard]] T conj_of(const T& x)
255 {
256 return x;
257 }
258
260 template <typename U>
261 [[nodiscard]] std::complex<U> conj_of(const std::complex<U>& x)
262 {
263 return std::conj(x);
264 }
265
267 template <typename T>
268 using magnitude_t = decltype(std::abs(std::declval<const T&>()));
269
272 template <typename T>
273 [[nodiscard]] T from_double(double x)
274 {
275 return static_cast<T>(static_cast<magnitude_t<T>>(x));
276 }
277
279 template <typename T>
280 [[nodiscard]] double real_part(const T& x)
281 {
282 return static_cast<double>(x);
283 }
284
286 template <typename U>
287 [[nodiscard]] double real_part(const std::complex<U>& x)
288 {
289 return static_cast<double>(x.real());
290 }
291
294 template <typename T>
296 std::conditional_t<std::is_same_v<T, float> || std::is_same_v<T, std::complex<float>>, float, double>;
297
300 template <typename T>
301 inline constexpr double rescue_below {std::is_same_v<value_range_t<T>, float> ? 0x1p-60 : 0x1p-500};
302
307 template <typename T>
308 [[nodiscard]] double rescue_factor(double largest)
309 {
310 constexpr double least {static_cast<double>(std::numeric_limits<value_range_t<T>>::min())};
311 return largest < rescue_below<T> ? std::ldexp(1.0, -std::ilogb(std::max(largest, least))) : 1.0;
312 }
313
319 template <typename T>
320 [[nodiscard]] T phase_of(const T& x)
321 {
322 const T scaled {x * detail::from_double<T>(rescue_factor<T>(static_cast<double>(std::abs(x))))};
323 const double magnitude {static_cast<double>(std::abs(scaled))};
324 return magnitude == 0.0 ? T {1} : scaled / detail::from_double<T>(magnitude);
325 }
326
333 template <typename T>
335 {
336 double largest {0.0};
337 for (const T& element : x) {
338 largest = std::max(largest, static_cast<double>(std::abs(element)));
339 }
340 const double factor {rescue_factor<T>(largest)};
341 for (T& element : x) {
343 }
344 return factor;
345 }
346
349 template <typename T>
350 [[nodiscard]] double norm_at_scale(const T* x,
351 std::size_t count)
352 {
353 double largest {0.0};
354 for (std::size_t i {0}; i < count; ++i) {
355 largest = std::max(largest, static_cast<double>(std::abs(x[i])));
356 }
357 if (largest == 0.0) { // the entries are finite: require_finite runs where every caller starts
358 return largest;
359 }
360 double sum {0.0};
361 for (std::size_t i {0}; i < count; ++i) {
362 const double ratio {static_cast<double>(std::abs(x[i])) / largest};
363 sum += ratio * ratio;
364 }
365 return largest * std::sqrt(sum);
366 }
367
370 inline constexpr double plain_squares_low {0x1p-900};
371
373 inline constexpr double plain_squares_high {0x1p900};
374
377 template <typename T>
379 const T* x,
380 std::size_t count)
381 {
383 return std::sqrt(plain);
384 }
385 return norm_at_scale(x, count);
386 }
387
389 template <typename T>
390 [[nodiscard]] double scaled_norm(const T* x,
391 std::size_t count)
392 {
393 return norm_at_scale(x, count);
394 }
395
397 [[nodiscard]] inline double scaled_norm(const double* x,
398 std::size_t count)
399 {
400 return plain_or_at_scale(dot_product(x, x, count), x, count);
401 }
402
405 [[nodiscard]] inline double scaled_norm(const std::complex<double>* x,
406 std::size_t count)
407 {
408 const auto* const parts {reinterpret_cast<const double*>(x)}; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the layout guarantee above
409 return plain_or_at_scale(dot_product(parts, parts, 2 * count), x, count);
410 }
411
413 template <typename T>
414 [[nodiscard]] double scaled_norm(const buffer<T>& x)
415 {
416 return scaled_norm(x.data(), x.size());
417 }
418
426 [[nodiscard]] inline double rotation_length(double a,
427 double b) noexcept
428 {
429 constexpr double low {0x1p-500};
430 constexpr double high {0x1p500};
431 const double larger {std::max(std::abs(a), std::abs(b))};
432 if (larger > low && larger < high) {
433 return std::sqrt((a * a) + (b * b));
434 }
435 return std::hypot(a, b);
436 }
437 } // namespace detail
438
440 class singular_matrix_error : public std::runtime_error
441 {
442 public:
443
445 explicit singular_matrix_error(const std::string& message)
446 : std::runtime_error(message)
447 {}
448 };
449
452 class non_finite_error : public std::domain_error
453 {
454 public:
455
457 explicit non_finite_error(const std::string& message)
458 : std::domain_error(message)
459 {}
460 };
461
462 namespace detail {
463
465 template <typename T>
466 [[nodiscard]] bool is_finite_entry(const T& x)
467 {
468 return std::isfinite(x);
469 }
470
472 template <typename U>
473 [[nodiscard]] bool is_finite_entry(const std::complex<U>& x)
474 {
475 return std::isfinite(x.real()) && std::isfinite(x.imag());
476 }
477
482 template <typename T>
484 const char* what)
485 {
486 for (std::size_t i {0}; i < a.rows(); ++i) {
487 for (std::size_t j {0}; j < a.cols(); ++j) {
488 if (!is_finite_entry(a(i, j))) {
489 throw non_finite_error(std::string {what} + ": the matrix holds an infinite or NaN entry");
490 }
491 }
492 }
493 }
494
505 [[nodiscard]] inline bool all_finite(const double* x,
506 std::size_t count) noexcept
507 {
508 constexpr std::uint64_t exponent {0x7FF0000000000000ULL};
509 constexpr std::uint64_t carry {std::uint64_t {1} << 52U};
510 std::uint64_t flagged {0};
511 std::size_t i {0};
512#if defined(__GNUC__)
513 using pair = std::uint64_t __attribute__((vector_size(16)));
514 constexpr pair exponents {exponent, exponent};
515 constexpr pair carries {carry, carry};
516 pair first {};
517 pair second {};
518 pair third {};
519 pair fourth {};
520 for (; i + 8 <= count; i += 8) {
521 std::array<pair, 4> bits {};
522 std::memcpy(bits.data(), x + i, sizeof(bits));
523 first |= (bits[0] & exponents) + carries;
524 second |= (bits[1] & exponents) + carries;
525 third |= (bits[2] & exponents) + carries;
526 fourth |= (bits[3] & exponents) + carries;
527 }
528 const pair all {first | second | third | fourth};
529 flagged = all[0] | all[1];
530#endif
531 for (; i < count; ++i) {
532 std::uint64_t bits {};
533 std::memcpy(&bits, x + i, sizeof(bits));
534 flagged |= (bits & exponent) + carry;
535 }
536 return (flagged >> 63U) == 0;
537 }
538
544 template <typename T>
546 {
547 buffer<T> elements(a.rows() * a.cols());
548 parallel_copy(std::to_address(a.begin()), elements.size(), elements.data());
549 return matrix<T> {a.rows(), a.cols(), std::move(elements)};
550 }
551
553 inline constexpr std::size_t finite_chunk {std::size_t {1} << 15U};
554
560 [[nodiscard]] inline bool all_finite_shared(const double* x,
561 std::size_t count)
562 {
563 if (count < 2 * finite_chunk) {
564 return all_finite(x, count);
565 }
566 std::atomic<bool> finite {true};
567 parallel_for((count + finite_chunk - 1) / finite_chunk, memory_element_weight * finite_chunk / 2, [x, count, &finite](std::size_t chunk) {
568 const std::size_t first {chunk * finite_chunk};
569 if (!all_finite(x + first, std::min(finite_chunk, count - first))) {
570 finite.store(false, std::memory_order_relaxed);
571 }
572 });
573 return finite.load(std::memory_order_relaxed);
574 }
575
577 inline void require_finite(const matrix<double>& a,
578 const char* what)
579 {
580 if (!all_finite_shared(std::to_address(a.begin()), a.rows() * a.cols())) {
581 throw non_finite_error(std::string {what} + ": the matrix holds an infinite or NaN entry");
582 }
583 }
584
586 inline void require_finite(const matrix<std::complex<double>>& a,
587 const char* what)
588 {
589 const auto* const parts {reinterpret_cast<const double*>(std::to_address(a.begin()))}; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the layout guarantee above
590 if (!all_finite(parts, 2 * a.rows() * a.cols())) {
591 throw non_finite_error(std::string {what} + ": the matrix holds an infinite or NaN entry");
592 }
593 }
594
597 template <typename T>
599 {
600 for (std::size_t i {0}; i < a.rows(); ++i) {
601 bool finite {std::isfinite(real_part(a(i, i)))};
602 for (std::size_t j {0}; j < i; ++j) {
603 finite = finite && is_finite_entry(a(i, j));
604 }
605 if (!finite) {
606 return false;
607 }
608 }
609 return true;
610 }
611
617 template <typename T>
619 const char* what)
620 {
621 if (!lower_is_finite(a)) {
622 throw non_finite_error(std::string {what} + ": the matrix holds an infinite or NaN entry");
623 }
624 }
625
632 const char* what)
633 {
634 if (!all_finite_shared(std::to_address(a.begin()), a.rows() * a.cols()) && !lower_is_finite(a)) {
635 throw non_finite_error(std::string {what} + ": the matrix holds an infinite or NaN entry");
636 }
637 }
638
640 NUMERICAL_OUT_OF_LINE inline void require_finite_lower(const matrix<std::complex<double>>& a,
641 const char* what)
642 {
643 const auto* const parts {reinterpret_cast<const double*>(std::to_address(a.begin()))}; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the layout guarantee above
644 if (!all_finite(parts, 2 * a.rows() * a.cols()) && !lower_is_finite(a)) {
645 throw non_finite_error(std::string {what} + ": the matrix holds an infinite or NaN entry");
646 }
647 }
648
654 template <typename T>
656 const T* columns,
657 std::size_t height,
658 const T* l,
659 std::size_t terms)
660 {
661 for (std::size_t t {0}; t < terms; ++t) {
662 subtract_product(column, columns + (t * height), conj_of(l[t]), height);
663 }
664 }
665
669 inline void subtract_panel_columns(double* column,
670 const double* columns,
671 std::size_t height,
672 const double* l,
673 std::size_t terms)
674 {
675 subtract_products(column, l, columns, height, terms, height);
676 }
677 } // namespace detail
678
687 template <typename T>
689 {
690 public:
691
697 explicit lu_decomposition(const matrix<T>& a)
698 : lu_decomposition(detail::parallel_copy_of(a))
699 {}
700
707 : n_(a.rows())
708 {
709 if (a.cols() != n_) {
710 throw dimension_error("LU decomposition requires a square matrix");
711 }
712 detail::require_finite(a, "LU decomposition");
713 lu_ = std::move(a).elements();
714 pivots_.resize(n_);
715 for (std::size_t i {0}; i < n_; ++i) {
716 pivots_[i] = i;
717 }
718 factorize();
719 }
720
728 [[nodiscard]] vector<T> solve(const vector<T>& b) const
729 {
730 if (b.size() != n_) {
731 throw dimension_error("solve: right-hand side size differs from the matrix");
732 }
733 if (singular_) {
734 throw singular_matrix_error("solve: matrix is singular");
735 }
736 buffer<T> y(n_, T {});
737 for (std::size_t i {0}; i < n_; ++i) { // forward substitution (unit lower L)
738 T sum {b[pivots_[i]]};
739 for (std::size_t j {0}; j < i; ++j) {
740 detail::subtract_term(sum, lu_[(i * n_) + j], y[j]);
741 }
742 y[i] = sum;
743 }
744 for (std::size_t i {n_}; i-- > 0;) { // back substitution (upper U)
745 T sum {y[i]};
746 for (std::size_t j {i + 1}; j < n_; ++j) {
747 detail::subtract_term(sum, lu_[(i * n_) + j], y[j]);
748 }
750 }
751 return vector<T> {std::move(y)};
752 }
753
756 {
758 for (std::size_t i {0}; i < n_; ++i) {
759 value *= lu_[(i * n_) + i];
760 }
761 return value;
762 }
763
771 [[nodiscard]] std::pair<T, double> log_determinant() const
772 {
773 if (singular_) {
774 return {T {0}, -std::numeric_limits<double>::infinity()};
775 }
776 T sign {static_cast<T>(sign_)};
777 double logarithm {0.0};
778 for (std::size_t i {0}; i < n_; ++i) {
779 const T pivot {lu_[(i * n_) + i]};
781 logarithm += std::log(static_cast<double>(std::abs(pivot)));
782 }
783 return {sign, logarithm};
784 }
785
795 {
796 return inverse(buffer<T> {});
797 }
798
807 {
808 buffer<T> rows {pivoted_inverse(std::move(storage)).elements()};
809 const std::size_t n {n_};
810 T* const at {rows.data()};
811 const std::size_t* const to {pivots_.data()};
812 // Bounds captured by value: by reference, the stores might have changed them, and each would be read again.
814 buffer<T> held(n); // column to[i] of A⁻¹ is column i of U⁻¹ L⁻¹
815 for (std::size_t r {band * detail::inverse_band}; r < std::min(n, (band + 1) * detail::inverse_band); ++r) {
816 std::copy_n(at + (r * n), n, held.data());
817 for (std::size_t i {0}; i < n; ++i) {
818 at[(r * n) + to[i]] = held[i];
819 }
820 }
821 });
822 return matrix<T> {n, n, std::move(rows)};
823 }
824
834 {
835 if (singular_) {
836 throw singular_matrix_error("inverse: matrix is singular");
837 }
838 const std::size_t n {n_};
839 storage.resize(n * n);
840 T* const rows {storage.data()};
842 for (std::size_t r {band * detail::inverse_band}; r < std::min(n, (band + 1) * detail::inverse_band); ++r) {
843 std::fill_n(rows + (r * n), n, T {});
844 rows[(r * n) + r] = T {1};
845 }
846 });
847 solve_rows(storage, n, true);
848 return matrix<T> {n, n, std::move(storage)};
849 }
850
862 [[nodiscard]] matrix<T> solve(const matrix<T>& b) const
863 {
864 if (b.rows() != n_) {
865 throw dimension_error("solve: right-hand side row count differs from the matrix");
866 }
867 if (singular_) {
868 throw singular_matrix_error("solve: matrix is singular");
869 }
870 const std::size_t cols {b.cols()};
871 buffer<T> rows(n_ * cols, T {});
872 for (std::size_t i {0}; i < n_; ++i) {
873 for (std::size_t c {0}; c < cols; ++c) {
874 rows[(i * cols) + c] = b(pivots_[i], c);
875 }
876 }
877 solve_rows(rows, cols);
878 return matrix<T> {n_, cols, std::move(rows)};
879 }
880
890 {
891 if (b.rows() != n_) {
892 throw dimension_error("solve: right-hand side row count differs from the matrix");
893 }
894 if (singular_) {
895 throw singular_matrix_error("solve: matrix is singular");
896 }
897 const std::size_t cols {b.cols()};
898 buffer<T> rows {std::move(b).elements()};
899 // Row i of P B is row pivots_[i] of B: each cycle i → pivots_[i] → … walked once, its first row held aside.
900 std::vector<bool> placed(n_, false);
901 buffer<T> held(cols);
902 for (std::size_t start {0}; start < n_; ++start) {
903 if (placed[start] || pivots_[start] == start) {
904 continue;
905 }
906 std::copy_n(rows.data() + (start * cols), cols, held.data());
907 std::size_t i {start};
908 for (std::size_t from {pivots_[i]}; from != start; i = from, from = pivots_[i]) {
909 std::copy_n(rows.data() + (from * cols), cols, rows.data() + (i * cols));
910 placed[i] = true;
911 }
912 std::copy_n(held.data(), cols, rows.data() + (i * cols));
913 placed[i] = true;
914 }
915 solve_rows(rows, cols);
916 return matrix<T> {n_, cols, std::move(rows)};
917 }
918
922 {
923 return matrix<T> {n_, n_, lu_};
924 }
925
928 {
929 return matrix<T> {n_, n_, std::move(lu_)};
930 }
931
933 [[nodiscard]] const std::vector<std::size_t>& pivots() const noexcept
934 {
935 return pivots_;
936 }
937
940 {
941 return singular_;
942 }
943
944 private:
945
947
957 std::size_t cols,
958 bool lower = false) const
959 {
960 if constexpr (std::is_same_v<T, double>) { // there subtract_products holds its row in registers through the terms
961 // The rows beat the blocked passes on the x86-64 units at any order (the portable path's products there fuse
962 // nothing, the rows would), and at any order elsewhere, by groups (arm64, four columns: order 256 0.65 times the
963 // blocked passes' time under Clang, 0.45 under GCC; order 1024 on every thread 0.91 and 0.62).
964#ifdef NUMERICAL_X86_COMPILED
965 const bool narrow {!lower && !y.empty() && cols <= detail::narrow_solve && detail::gemm_path_current() != gemm_path::portable};
966#else
967 const bool narrow {!lower && !y.empty() && cols <= detail::narrow_solve};
968#endif
969 if (narrow) {
970 solve_narrow(y, cols);
971 return;
972 }
973 }
974 const std::size_t slices {column_slices(cols)};
975 if (slices > 1) { // independent right-hand sides: each thread its own columns, all the blocks, no join per block
976 const std::size_t slice {(((cols + slices - 1) / slices) + 7) / 8 * 8};
977 const auto start {[this, slice, slices, cols, lower](std::size_t s) {
978 return lower ? lower_slice_start(s, slices, cols) : std::min(cols, s * slice);
979 }};
980 detail::parallel_for(lower ? slices : (cols + slice - 1) / slice, n_ * n_ * slice / detail::gemm_kernel_speedup, [&](std::size_t s) {
981 solve_columns(y, cols, start(s), start(s + 1), lower);
982 });
983 return;
984 }
985 solve_columns(y, cols, 0, cols, lower);
986 }
987
994 [[nodiscard]] std::size_t column_slices(std::size_t cols) const
995 {
996#ifdef NUMERICAL_X86_COMPILED
998 return 1;
999 }
1000#endif
1001 if (cols < 2 * detail::slice_columns) {
1002 return 1;
1003 }
1004 return std::min(max_threads(), cols / detail::slice_columns);
1005 }
1006
1013 [[nodiscard]] std::size_t lower_slice_start(std::size_t s,
1014 std::size_t slices,
1015 std::size_t cols) const
1016 {
1017 const auto n {static_cast<double>(n_)};
1018 const auto weight {[n](double c) { return (n * n * c) + (((n * n * n) - ((n - c) * (n - c) * (n - c))) / 3.0); }};
1019 const double target {weight(static_cast<double>(cols)) * static_cast<double>(s) / static_cast<double>(slices)};
1020 std::size_t low {0};
1021 std::size_t high {cols};
1022 while (low < high) { // the fewest columns that weigh the target
1023 const std::size_t middle {low + ((high - low) / 2)};
1024 if (weight(static_cast<double>(middle)) < target) {
1025 low = middle + 1;
1026 }
1027 else {
1028 high = middle;
1029 }
1030 }
1031 return std::min(cols, (low + 7) / 8 * 8);
1032 }
1033
1039 std::size_t cols,
1040 std::size_t c0,
1041 std::size_t c1,
1042 bool lower) const
1043 {
1044 constexpr std::size_t width {64};
1045 const std::size_t chunks {(c1 - c0 + width - 1) / width};
1046 for (std::size_t k0 {0}; k0 < n_; k0 += block_) { // forward: unit-lower L
1047 const std::size_t end {std::min(k0 + block_, n_)};
1048 const std::size_t reach {lower ? std::min(end, c1) : c1}; // the block's rows' columns that can be nonzero
1049 if (reach <= c0) {
1050 continue; // the slice's columns are zero in these rows and every row past them
1051 }
1052 detail::parallel_for((reach - c0 + width - 1) / width, (end - k0) * (end - k0) * width / 2, [&](std::size_t chunk) {
1053 const std::size_t first {c0 + (chunk * width)};
1054 const std::size_t last {std::min(first + width, reach)};
1055 for (std::size_t i {k0 + 1}; i < end; ++i) {
1056 detail::subtract_products(y.data() + (i * cols) + first, lu_.data() + (i * n_) + k0, y.data() + (k0 * cols) + first, cols, i - k0, last - first);
1057 }
1058 });
1059 subtract_block(y, cols, end, n_, k0, end, c0, reach);
1060 }
1061 for (std::size_t k1 {n_}; k1 > 0;) { // backward: upper U, the blocks of the forward pass, last first
1062 const std::size_t k0 {panel_of(k1 - 1)};
1063 detail::parallel_for(chunks, (k1 - k0) * (k1 - k0) * width / 2, [&](std::size_t chunk) {
1064 const std::size_t first {c0 + (chunk * width)};
1065 const std::size_t last {std::min(first + width, c1)};
1066 for (std::size_t i {k1}; i-- > k0;) {
1067 detail::subtract_products(y.data() + (i * cols) + first, lu_.data() + (i * n_) + i + 1, y.data() + ((i + 1) * cols) + first, cols, k1 - (i + 1), last - first);
1068 const T divisor {detail::pivot_divisor(lu_[(i * n_) + i])};
1069 for (std::size_t c {first}; c < last; ++c) {
1070 y[(i * cols) + c] = detail::over_pivot(y[(i * cols) + c], divisor);
1071 }
1072 }
1073 });
1074 subtract_block(y, cols, 0, k0, k0, k1, c0, c1);
1075 k1 = k0;
1076 }
1077 }
1078
1089 std::size_t cols) const
1090 {
1091 // Forward, by groups of rows: their terms before the group are independent of one another, and go together
1092 // (\ref detail::subtract_rows), the group's own triangle after; each element's terms in ascending order still.
1093 constexpr std::size_t group {detail::product_rows};
1094 static_assert(detail::lu_small_block % group == 0 && detail::lu_block % group == 0, "a panel is a whole number of groups");
1095 std::size_t i0 {0};
1096 for (; i0 + group <= n_; i0 += group) {
1097 if (i0 > 0) {
1098 detail::subtract_rows(y.data() + (i0 * cols), cols, lu_.data() + (i0 * n_), n_, y.data(), cols, i0, cols);
1099 }
1100 for (std::size_t i {i0 + 1}; i < i0 + group; ++i) {
1101 detail::subtract_products(y.data() + (i * cols), lu_.data() + (i * n_) + i0, y.data() + (i0 * cols), cols, i - i0, cols);
1102 }
1103 }
1104 for (std::size_t i {std::max<std::size_t>(i0, 1)}; i < n_; ++i) {
1105 detail::subtract_products(y.data() + (i * cols), lu_.data() + (i * n_), y.data(), cols, i, cols);
1106 }
1107 // Backward, each row's terms block by block from the last down to its own, ascending within each: a group's
1108 // terms in the blocks past its own are independent of one another and go together (\ref detail::subtract_rows),
1109 // in that same order; its own block's after, row by row (\p finish). The rows past the last whole group lie in
1110 // the last block, a panel being a whole number of groups: they have their own block's terms alone.
1111 const std::size_t last {panel_of(n_ - 1)};
1112 const auto finish {[this, &y, cols](std::size_t i) {
1113 const std::size_t to {std::min(panel_of(i) + block_, n_)};
1114 if (i + 1 < to) {
1115 detail::subtract_products(y.data() + (i * cols), lu_.data() + (i * n_) + i + 1, y.data() + ((i + 1) * cols), cols, to - (i + 1), cols);
1116 }
1117 const T divisor {detail::pivot_divisor(lu_[(i * n_) + i])};
1118 for (std::size_t c {0}; c < cols; ++c) {
1119 y[(i * cols) + c] = detail::over_pivot(y[(i * cols) + c], divisor);
1120 }
1121 }};
1122 std::size_t i1 {n_};
1123 for (; i1 % group != 0; --i1) {
1124 finish(i1 - 1);
1125 }
1126 for (; i1 > 0; i1 -= group) {
1127 const std::size_t top {i1 - group};
1128 const std::size_t own {panel_of(top)};
1129 for (std::size_t b0 {last}; b0 > own; b0 -= block_) {
1130 detail::subtract_rows(y.data() + (top * cols), cols, lu_.data() + (top * n_) + b0, n_, y.data() + (b0 * cols), cols, std::min(block_, n_ - b0), cols);
1131 }
1132 for (std::size_t i {i1}; i-- > top;) {
1133 finish(i);
1134 }
1135 }
1136 }
1137
1139 [[nodiscard]] std::size_t panel_of(std::size_t i) const noexcept
1140 {
1141 return i & ~(block_ - 1);
1142 }
1143
1147 std::size_t cols,
1148 std::size_t r0,
1149 std::size_t r1,
1150 std::size_t c0,
1151 std::size_t c1,
1152 std::size_t first,
1153 std::size_t last) const
1154 {
1155 if (r0 < r1) {
1156 const buffer<T> minus {negated_block(r0, r1, c0, c1)};
1157 detail::gemm(r1 - r0, last - first, c1 - c0, minus.data(), c1 - c0, y.data() + (c0 * cols) + first, cols,
1158 y.data() + (r0 * cols) + first, cols);
1159 }
1160 }
1161
1164 std::size_t r0,
1165 std::size_t r1,
1166 std::size_t c0,
1167 std::size_t c1) const
1168 {
1169 out.resize((r1 - r0) * (c1 - c0));
1170 for (std::size_t r {r0}; r < r1; ++r) {
1171 for (std::size_t c {c0}; c < c1; ++c) {
1172 out[((r - r0) * (c1 - c0)) + (c - c0)] = -lu_[(r * n_) + c];
1173 }
1174 }
1175 }
1176
1179 std::size_t r1,
1180 std::size_t c0,
1181 std::size_t c1) const
1182 {
1183 buffer<T> out((r1 - r0) * (c1 - c0), T {});
1184 for (std::size_t r {r0}; r < r1; ++r) {
1185 for (std::size_t c {c0}; c < c1; ++c) {
1186 out[((r - r0) * (c1 - c0)) + (c - c0)] = -lu_[(r * n_) + c];
1187 }
1188 }
1189 return out;
1190 }
1191
1192 // Right-looking, by panels of block_ columns, as LAPACK's dgetrf: each panel is factorised
1193 // with partial pivoting (whole rows swapped), its rows of U to the right are solved against the panel's
1194 // unit-lower L, and the trailing block takes the product of the two through detail::gemm, where the
1195 // work of a large factorisation is. Each element still receives its updates in ascending order of the
1196 // pivot, so without multiply-add fusion the factors are the unblocked algorithm's to the bit; an order
1197 // up to one panel is that algorithm.
1198 //
1199 // With a look-ahead of one panel: the trailing update reaches the next panel's columns first, then the
1200 // next panel is factorised on the calling thread while the workers update the columns right of it (the
1201 // panel, a column at a time, would otherwise hold every other thread idle). Its row swaps reach those
1202 // columns once both are done: an update reads its rows of L from a copy and writes row by row, so a swap
1203 // after it is the swap before it, and the factors stay the ones above, to the bit.
1204 //
1205 // A pivot's swap reaches the columns left of its panel only at the end (\ref swap_left_columns): nothing reads
1206 // them before, and on the calling thread each swap held up the panel the other threads wait for.
1208 {
1209 // Every panel in the one column-major storage, of the first (and tallest) one's size: a fresh one each panel was
1210 // mapped anew and faulted in page by page, on the critical path (order 1000 on six AVX2 cores: 24 µs of a panel's
1211 // 470, and its copy in 97 µs instead of about 40).
1212 buffer<T> panel(n_ * std::min(block_, n_));
1213 const std::size_t end {std::min(block_, n_)};
1214 factorize_panel(panel, 0, end);
1215 if (end < n_) {
1217 }
1219 }
1220
1227 std::size_t first_end)
1228 {
1229 // −L₂₁ and its packing for the product's tile, in storage kept from panel to panel: packed once, it serves every
1230 // block of the trailing update, where each block's product packed it again (6.9 % of the time on six AVX2 cores
1231 // at order 1000).
1234 std::size_t end {first_end};
1235 for (std::size_t k0 {0}; end < n_;) {
1236 const std::size_t next {std::min(end + block_, n_)};
1237 solve_panel_rows(k0, end);
1238 negated_block_into(minus_l, end, n_, k0, end);
1239 const detail::packed_left<T> left {detail::pack_left(n_ - end, block_, end - k0, minus_l.data(), end - k0, packing)};
1240 update_columns(left, k0, end, next);
1241 const std::size_t right {n_ - next};
1242 if (right > detail::lu_lookahead_columns) { // one product of the whole trailing block, then the panel
1243 update_columns(left, k0, next, n_);
1244 factorize_panel(panel, end, next);
1245 k0 = end;
1246 end = next;
1247 continue;
1248 }
1249 // About two blocks of columns for each thread the work pays for: a product too small to share ran one block for
1250 // each thread the machine has (order 256 on eight arm64 threads: 329 → 311 µs); two, not one, as on x86-64 a
1251 // block of half the columns stays in its cache, and the balance beat one (six AVX2 cores, order 1000: 12 %).
1252 const std::size_t work {(n_ - end) * right * (end - k0) / detail::lu_update_speedup};
1253 const std::size_t helpers {std::clamp(work / detail::work_per_thread, std::size_t {1}, max_threads())};
1254 const std::size_t width {std::max(block_, (right + (2 * helpers) - 1) / (2 * helpers))};
1255 const std::size_t blocks {(right + width - 1) / width};
1256 swap_limit_ = next;
1257 detail::parallel_for_beside(blocks, (n_ - end) * width * (end - k0) / detail::lu_update_speedup,
1258 [&](std::size_t block) {
1259 const std::size_t first {next + (block * width)};
1260 update_columns(left, k0, first, std::min(first + width, n_));
1261 },
1262 [&] { factorize_panel(panel, end, next); });
1263 swap_limit_ = n_;
1264 for (const auto& [a, b] : deferred_swaps_) {
1265 for (std::size_t c {next}; c < n_; ++c) {
1266 std::swap(lu_[(a * n_) + c], lu_[(b * n_) + c]);
1267 }
1268 }
1269 deferred_swaps_.clear();
1270 k0 = end;
1271 end = next;
1272 }
1273 }
1274
1284 std::size_t k0,
1285 std::size_t end)
1286 {
1287 const std::size_t rows {n_ - k0};
1288 const std::size_t width {end - k0};
1289 transpose_panel(panel, rows, k0, end, true); // column j of the panel at panel[j * rows], every element written
1290 factorize_columns(panel, rows, 0, width, k0, end);
1291 transpose_panel(panel, rows, k0, end, false);
1292 }
1293
1299 std::size_t rows,
1300 std::size_t k0,
1301 std::size_t end,
1302 bool in)
1303 {
1304 const std::size_t width {end - k0};
1305#ifdef NUMERICAL_X86_COMPILED
1306 if (rows >= detail::panel_tile_rows) {
1307 T* const base {lu_.data() + (k0 * n_) + k0};
1308 for (std::size_t r0 {0}; r0 < rows; r0 += detail::panel_copy_rows) {
1309 const std::size_t r1 {std::min(r0 + detail::panel_copy_rows, rows)};
1310 if (in) {
1311 for (std::size_t j {0}; j < width; ++j) {
1312 for (std::size_t r {r0}; r < r1; ++r) {
1313 panel[(j * rows) + r] = base[(r * n_) + j];
1314 }
1315 }
1316 }
1317 else {
1318 for (std::size_t j {0}; j < width; ++j) {
1319 for (std::size_t r {r0}; r < r1; ++r) {
1320 base[(r * n_) + j] = panel[(j * rows) + r];
1321 }
1322 }
1323 }
1324 }
1325 return;
1326 }
1327#endif
1328 for (std::size_t r {0}; r < rows; ++r) {
1329 for (std::size_t j {0}; j < width; ++j) {
1330 if (in) {
1331 panel[(j * rows) + r] = lu_[((k0 + r) * n_) + k0 + j];
1332 }
1333 else {
1334 lu_[((k0 + r) * n_) + k0 + j] = panel[(j * rows) + r];
1335 }
1336 }
1337 }
1338 }
1339
1350 std::size_t rows,
1351 std::size_t first,
1352 std::size_t last,
1353 std::size_t k0,
1354 std::size_t end)
1355 {
1356 // A leaf, or a panel too short for its products to pay for packing (order 64 on arm64: 30 µs unblocked,
1357 // 37 recursive).
1358 if (last - first <= detail::solve_leaf || rows - first < 2 * block_) {
1359 for (std::size_t j {first}; j < last; ++j) {
1360 T * column {panel.data() + (j * rows)};
1361 std::size_t pivot {0}; // from row j
1362 detail::magnitude_t<T> best {std::abs(column[j])};
1363 for (std::size_t r {detail::largest_magnitude_vectors(column + j, rows - j, best, pivot)}; r < rows - j; ++r) {
1364 const detail::magnitude_t<T> mag {std::abs(column[j + r])};
1365 if (mag > best) {
1366 best = mag;
1367 pivot = r;
1368 }
1369 }
1370 pivot += j;
1371 if (best == detail::magnitude_t<T> {}) {
1372 singular_ = true;
1373 continue; // a zero pivot column: leave it, the determinant becomes 0
1374 }
1375 if (pivot != j) {
1376 for (std::size_t c {0}; c < end - k0; ++c) { // the whole panel row
1377 std::swap(panel[(c * rows) + j], panel[(c * rows) + pivot]);
1378 }
1379 swap_rows_outside(k0 + j, k0 + pivot, k0, end);
1380 std::swap(pivots_[k0 + j], pivots_[k0 + pivot]);
1381 sign_ = -sign_;
1382 }
1384 for (std::size_t r {j + 1}; r < rows; ++r) {
1385 column[r] = detail::over_pivot(column[r], divisor); // the multiplier, in L's slot
1386 }
1387 for (std::size_t c {j + 1}; c < last; ++c) {
1388 T* target {panel.data() + (c * rows)};
1389 detail::subtract_product(target + j + 1, column + j + 1, target[j], rows - (j + 1));
1390 }
1391 }
1392 return;
1393 }
1394 const std::size_t mid {first + ((last - first) / 2)};
1395 factorize_columns(panel, rows, first, mid, k0, end);
1396 for (std::size_t c {mid}; c < last; ++c) { // the top of the right half: rows [first, mid)
1397 T* target {panel.data() + (c * rows)};
1398 for (std::size_t j {first}; j < mid; ++j) {
1399 detail::subtract_product(target + j + 1, panel.data() + (j * rows) + j + 1, target[j], mid - (j + 1));
1400 }
1401 }
1402 // The bottom, rows [mid, rows): A₂₂ −= L₂₁ U₁₂, column-major, so as its transpose, row-major:
1403 // A₂₂ᵀ += (−U₁₂ᵀ) L₂₁ᵀ.
1404 const std::size_t depth {mid - first};
1405 buffer<T> minus_u((last - mid) * depth, T {});
1406 for (std::size_t c {mid}; c < last; ++c) {
1407 for (std::size_t j {first}; j < mid; ++j) {
1408 minus_u[((c - mid) * depth) + (j - first)] = -panel[(c * rows) + j];
1409 }
1410 }
1411 // The plain loop, which rounds as the tiles do (the same bits): a product this narrow packed a sliver of the
1412 // panel's whole height for each few columns (six x86-64 AVX2 cores, LU of order 1000: 6.86 → 6.36 ms).
1413 detail::gemm_rows(last - mid, rows - mid, depth, minus_u.data(), depth, panel.data() + (first * rows) + mid, rows,
1414 panel.data() + (mid * rows) + mid, rows);
1415 factorize_columns(panel, rows, mid, last, k0, end);
1416 }
1417
1421 void swap_rows_outside(std::size_t a,
1422 std::size_t b,
1423 std::size_t k0,
1424 std::size_t end)
1425 {
1426 if (k0 > 0) {
1427 left_swaps_.emplace_back(a, b);
1428 }
1429 for (std::size_t c {end}; c < swap_limit_; ++c) {
1430 std::swap(lu_[(a * n_) + c], lu_[(b * n_) + c]);
1431 }
1432 if (swap_limit_ < n_) {
1433 deferred_swaps_.emplace_back(a, b);
1434 }
1435 }
1436
1443 {
1444 if (left_swaps_.empty()) {
1445 return;
1446 }
1447 const std::size_t panels {(n_ - 1) / block_}; // every panel but the last, which no later swap reaches
1448 detail::parallel_for(panels, left_swaps_.size() * block_, [&](std::size_t p) {
1449 const std::size_t c0 {p * block_};
1450 const std::size_t c1 {c0 + block_};
1451 const auto first {std::lower_bound(left_swaps_.begin(), left_swaps_.end(), c1,
1452 [](const auto& swap, std::size_t row) { return swap.first < row; })};
1453 for (auto swap {first}; swap != left_swaps_.end(); ++swap) {
1454 std::swap_ranges(lu_.data() + (swap->first * n_) + c0, lu_.data() + (swap->first * n_) + c1,
1455 lu_.data() + (swap->second * n_) + c0);
1456 }
1457 });
1458 left_swaps_.clear();
1459 }
1460
1464 void solve_panel_rows(std::size_t k0,
1465 std::size_t end)
1466 {
1467 constexpr std::size_t width {64};
1468 const std::size_t blocks {((n_ - end) + width - 1) / width};
1469 detail::parallel_for(blocks, (end - k0) * (end - k0) * width / 2, [&](std::size_t block) {
1470 const std::size_t first {end + (block * width)};
1471 const std::size_t last {std::min(first + width, n_)};
1472 for (std::size_t i {k0 + 1}; i < end; ++i) {
1473 detail::subtract_products(lu_.data() + (i * n_) + first, lu_.data() + (i * n_) + k0, lu_.data() + (k0 * n_) + first, n_, i - k0, last - first);
1474 }
1475 });
1476 }
1477
1481 std::size_t k0,
1482 std::size_t c0,
1483 std::size_t c1)
1484 {
1485 const std::size_t end {k0 + left.k};
1486 detail::gemm_with(left, c1 - c0, lu_.data() + (k0 * n_) + c0, n_, lu_.data() + (end * n_) + c0, n_);
1487 }
1488
1489 std::size_t n_;
1490 std::size_t block_ {detail::lu_block_for(n_)};
1492 std::vector<std::size_t> pivots_;
1493 int sign_ {1};
1494 bool singular_ {false};
1495 std::size_t swap_limit_ {n_};
1496 std::vector<std::pair<std::size_t, std::size_t>> deferred_swaps_;
1497 std::vector<std::pair<std::size_t, std::size_t>> left_swaps_;
1498 };
1499
1508 template <typename T>
1509 [[nodiscard]] vector<T> solve(matrix<T> a,
1510 const vector<T>& b)
1511 {
1512 return lu_decomposition<T> {std::move(a)}.solve(b);
1513 }
1514
1524 template <typename T>
1525 [[nodiscard]] matrix<T> solve(matrix<T> a,
1526 const matrix<T>& b)
1527 {
1528 return lu_decomposition<T> {std::move(a)}.solve(b);
1529 }
1530
1532 template <typename T>
1533 [[nodiscard]] T determinant(matrix<T> a)
1534 {
1535 return lu_decomposition<T> {std::move(a)}.determinant();
1536 }
1537
1545 template <typename T>
1546 [[nodiscard]] matrix<T> inverse(matrix<T> a)
1547 {
1548 return lu_decomposition<T> {std::move(a)}.inverse();
1549 }
1550
1552 class not_positive_definite_error : public std::runtime_error
1553 {
1554 public:
1555
1557 explicit not_positive_definite_error(const std::string& message,
1558 std::size_t order = 0)
1559 : std::runtime_error(message), order_ {order}
1560 {}
1561
1563 [[nodiscard]] std::size_t order() const noexcept
1564 {
1565 return order_;
1566 }
1567
1568 private:
1569
1570 std::size_t order_;
1571 };
1572
1583 template <typename T>
1585 {
1586 public:
1587
1595 : cholesky_decomposition(detail::parallel_copy_of(a))
1596 {}
1597
1606 : n_(a.rows())
1607 {
1608 if (a.cols() != n_) {
1609 throw dimension_error("Cholesky decomposition requires a square matrix");
1610 }
1611 detail::require_finite_lower(a, "Cholesky decomposition");
1612 lower_ = std::move(a).elements(); // the upper triangle is never read, and zeroed once factorised
1613 factorize();
1614 }
1615
1617 [[nodiscard]] matrix<T> lower() const&
1618 {
1619 return matrix<T> {n_, n_, lower_};
1620 }
1621
1623 [[nodiscard]] matrix<T> lower() &&
1624 {
1625 return matrix<T> {n_, n_, std::move(lower_)};
1626 }
1627
1634 [[nodiscard]] vector<T> solve(const vector<T>& b) const
1635 {
1636 if (b.size() != n_) {
1637 throw dimension_error("solve: right-hand side size differs from the matrix");
1638 }
1639 buffer<T> y(n_, T {});
1640 for (std::size_t i {0}; i < n_; ++i) { // forward: L y = b
1641 T sum {b[i]};
1642 for (std::size_t k {0}; k < i; ++k) {
1643 sum -= lower_[(i * n_) + k] * y[k];
1644 }
1645 y[i] = sum / lower_[(i * n_) + i];
1646 }
1647 buffer<T> x(n_, T {});
1648 for (std::size_t i {n_}; i-- > 0;) { // back: Lᴴ x = y, (Lᴴ)[i][k] = conj(L[k][i])
1649 T sum {y[i]};
1650 for (std::size_t k {i + 1}; k < n_; ++k) {
1651 sum -= detail::conj_of(lower_[(k * n_) + i]) * x[k];
1652 }
1653 x[i] = sum / detail::conj_of(lower_[(i * n_) + i]);
1654 }
1655 return vector<T> {std::move(x)};
1656 }
1657
1659 [[nodiscard]] T determinant() const
1660 {
1661 T value {static_cast<T>(1)};
1662 for (std::size_t i {0}; i < n_; ++i) {
1663 value *= lower_[(i * n_) + i] * lower_[(i * n_) + i];
1664 }
1665 return value;
1666 }
1667
1668 private:
1669
1670 // By panels of block_ columns: the panel's diagonal block row by row, the rows below it
1671 // by leaves (each depends only on the diagonal block; factor_rows_below), then the lower part of the trailing block takes
1672 // A22 += (-L21) L21ᴴ from the matrix product kernel. Every element still receives its terms in ascending
1673 // order, so without multiply-add fusion the factor is the unblocked algorithm's to the bit.
1675 {
1676 buffer<T> packing; // L₂₁ packed for the trailing update's tiles, kept from panel to panel
1677 buffer<T> minus_adjoint; // −L₂₁ᴴ, row-major, the same
1678 for (std::size_t k0 {0}; k0 < n_; k0 += block_) {
1679 const std::size_t end {std::min(k0 + block_, n_)};
1680 std::size_t i {k0};
1681 for (; end - k0 >= detail::cholesky_together_width && i + detail::cholesky_diagonal_rows <= end; i += detail::cholesky_diagonal_rows) {
1682 factor_rows_together(i, k0);
1683 for (std::size_t r {0}; r < detail::cholesky_diagonal_rows; ++r) { // their triangle, row after row
1684 factor_row(i + r, k0, i, i + r + 1);
1685 }
1686 }
1687 for (; i < end; ++i) {
1688 factor_row(i, k0, k0, i + 1);
1689 }
1690 if (end < n_) {
1691 minus_adjoint.resize((end - k0) * (n_ - end));
1692 factor_rows_below(k0, end, minus_adjoint);
1693 update_trailing(k0, end, minus_adjoint, packing);
1694 }
1695 }
1696 T* const rows {lower_.data()};
1697 const std::size_t n {n_};
1698 // The upper triangle zeroed by bands of rows on the threads: a pass bound by the memory, half a row an element.
1699 detail::parallel_for((n + detail::zeroed_rows - 1) / detail::zeroed_rows, detail::memory_element_weight * detail::zeroed_rows * n / 2, [rows, n](std::size_t band) {
1700 for (std::size_t i {band * detail::zeroed_rows}; i < std::min(n, (band + 1) * detail::zeroed_rows); ++i) {
1701 std::fill(rows + (i * n) + i + 1, rows + ((i + 1) * n), T {});
1702 }
1703 });
1704 }
1705
1713 void factor_rows_together(std::size_t i,
1714 std::size_t k0)
1715 {
1716 std::array<T, detail::cholesky_diagonal_rows> sums {};
1717 for (std::size_t j {k0}; j < i; ++j) {
1718 const T* const above {lower_.data() + (j * n_)};
1720 for (std::size_t r {0}; r < detail::cholesky_diagonal_rows; ++r) {
1721 sums[r] = lower_[((i + r) * n_) + j];
1722 }
1723 for (std::size_t k {k0}; k < j; ++k) {
1724 const T other {detail::conj_of(above[k])};
1726 for (std::size_t r {0}; r < detail::cholesky_diagonal_rows; ++r) {
1727 sums[r] -= lower_[((i + r) * n_) + k] * other;
1728 }
1729 }
1731 for (std::size_t r {0}; r < detail::cholesky_diagonal_rows; ++r) {
1732 lower_[((i + r) * n_) + j] = sums[r] / above[j];
1733 }
1734 }
1735 }
1736
1742 void factor_row(std::size_t i,
1743 std::size_t k0,
1744 std::size_t first,
1745 std::size_t last)
1746 {
1747 for (std::size_t j {first}; j < last; ++j) {
1748 T sum {lower_[(i * n_) + j]};
1749 for (std::size_t k {k0}; k < j; ++k) {
1750 sum -= lower_[(i * n_) + k] * detail::conj_of(lower_[(j * n_) + k]);
1751 }
1752 if (i == j) {
1753 const double diagonal {detail::real_part(sum)};
1754 if (diagonal <= 0.0) {
1755 throw not_positive_definite_error("Cholesky: matrix is not positive-definite", i + 1);
1756 }
1757 lower_[(i * n_) + i] = detail::from_double<T>(std::sqrt(diagonal));
1758 }
1759 else {
1760 lower_[(i * n_) + j] = sum / lower_[(j * n_) + j];
1761 }
1762 }
1763 }
1764
1777 void factor_rows_below(std::size_t k0,
1778 std::size_t end,
1779 buffer<T>& minus_adjoint)
1780 {
1781 const std::size_t width {end - k0};
1782 const std::size_t chunks {((n_ - end) + detail::cholesky_rows - 1) / detail::cholesky_rows};
1783 detail::parallel_for(chunks, detail::cholesky_rows * width * width / 2, [&](std::size_t chunk) {
1784 const std::size_t first {end + (chunk * detail::cholesky_rows)};
1785 const std::size_t height {std::min(detail::cholesky_rows, n_ - first)};
1786 buffer<T> panel(height * width); // column j at panel[j * height], every element written
1787 for (std::size_t r {0}; r < height; ++r) {
1788 for (std::size_t j {0}; j < width; ++j) {
1789 panel[(j * height) + r] = lower_[((first + r) * n_) + k0 + j];
1790 }
1791 }
1792 constexpr std::size_t sub {16};
1793 buffer<T> minus_conj;
1794 for (std::size_t s0 {0}; s0 < width; s0 += sub) {
1795 const std::size_t s1 {std::min(s0 + sub, width)};
1796 if (s0 > 0) { // the earlier sub-blocks' terms, through the product kernel: Pᵀ[s0, s1) += (−conj L₁₁[s0, s1) × [0, s0)) Pᵀ[0, s0)
1797 minus_conj.resize((s1 - s0) * s0);
1798 for (std::size_t j {s0}; j < s1; ++j) {
1799 for (std::size_t k {0}; k < s0; ++k) {
1800 minus_conj[((j - s0) * s0) + k] = -detail::conj_of(lower_[((k0 + j) * n_) + k0 + k]);
1801 }
1802 }
1803 // The plain loop, which rounds as the tiles do (the same bits), where the packed
1804 // product packed slivers too small to pay for it (six AVX2 cores' one, order 1000:
1805 // this pass 2.82 → 2.23 ms).
1806 detail::gemm_rows(s1 - s0, height, s0, minus_conj.data(), s0, panel.data(), height, panel.data() + (s0 * height), height);
1807 }
1808 for (std::size_t j {s0}; j < s1; ++j) {
1809 T* const column {panel.data() + (j * height)};
1810 detail::subtract_panel_columns(column, panel.data() + (s0 * height), height, lower_.data() + ((k0 + j) * n_) + k0 + s0, j - s0);
1811 const T pivot {lower_[((k0 + j) * n_) + k0 + j]};
1812 for (std::size_t r {0}; r < height; ++r) {
1813 column[r] = column[r] / pivot;
1814 }
1815 }
1816 }
1817 const std::size_t below {n_ - end};
1818#ifdef NUMERICAL_X86_COMPILED
1819 // On the x86-64 units the factor's rows, then −L₂₁ᴴ's columns, each written along: written
1820 // together, one of them went across (one AVX2 core, order 1000: this pass 2.23 → 1.97 ms).
1821 // Together on arm64, where apart was 3 % slower under GCC 14 (orders 48 to 128).
1822 for (std::size_t r {0}; r < height; ++r) {
1823 for (std::size_t j {0}; j < width; ++j) {
1824 lower_[((first + r) * n_) + k0 + j] = panel[(j * height) + r];
1825 }
1826 }
1827 for (std::size_t j {0}; j < width; ++j) {
1828 const T* const from {panel.data() + (j * height)};
1829 T* const to {minus_adjoint.data() + (j * below) + (first - end)};
1830 for (std::size_t r {0}; r < height; ++r) {
1831 to[r] = -detail::conj_of(from[r]);
1832 }
1833 }
1834#else
1835 for (std::size_t r {0}; r < height; ++r) {
1836 for (std::size_t j {0}; j < width; ++j) {
1837 const T value {panel[(j * height) + r]};
1838 lower_[((first + r) * n_) + k0 + j] = value;
1839 minus_adjoint[(j * below) + (first - end + r)] = -detail::conj_of(value);
1840 }
1841 }
1842#endif
1843 });
1844 }
1845
1857 void update_trailing(std::size_t k0,
1858 std::size_t end,
1859 const buffer<T>& minus_adjoint,
1860 buffer<T>& packing)
1861 {
1862 const std::size_t rows {n_ - end};
1863 const std::size_t depth {end - k0};
1864#ifdef NUMERICAL_X86_COMPILED
1865 // On the x86-64 units a real product the kernel packs runs on the triangle's tiles alone (detail::gemm_triangle,
1866 // each element the tiles' sum below), where the blocks below computed the part of each diagonal block past the
1867 // diagonal: six AVX2 cores, order 500 787 → 721 µs. On arm64 the blocks stay, faster there (all cores, order 500:
1868 // 0.85 ms, by the triangle 1.03: the AMX takes −L₂₁ packed once for every block). A smaller or complex product by
1869 // the blocks.
1870 if constexpr (std::is_same_v<T, double>) {
1871 if (depth >= detail::gemm_min_depth && rows * rows >= ((detail::gemm_packed_threshold + depth) - 1) / depth) {
1872 detail::gemm_triangle<detail::a_rows, detail::b_rows>(rows, depth, lower_.data() + (end * n_) + k0, n_, minus_adjoint.data(), rows,
1873 lower_.data() + (end * n_) + end, n_, true);
1874 return;
1875 }
1876 }
1877#endif
1878 const std::size_t step {std::max(block_, detail::cholesky_update_columns)};
1879 // Packed once where a block of rows serves several tiles: past two tiles' rows (one AVX2 core, the shared packing
1880 // cost order 300 5 % and 500 3 %, where a block of columns has one or two tiles), else each tile packs its own.
1881 const bool shared {rows > 2 * detail::cholesky_update_rows};
1882 const T* const l21 {lower_.data() + (end * n_) + k0};
1883 const detail::packed_left<T> left {shared ? detail::pack_left(rows, step, depth, l21, n_, packing)
1884 : detail::read_in_place(rows, depth, l21, n_)};
1885 const std::size_t granule {std::max<std::size_t>(1, left.granule)}; // a row at least
1886 const std::size_t height {std::max(granule, detail::cholesky_update_rows / granule * granule)};
1887 std::vector<std::pair<std::size_t, std::size_t>> tiles; // each one's first column and first row, below the panel
1888 for (std::size_t jb {0}; jb < rows; jb += step) {
1889 for (std::size_t r0 {jb / granule * granule}; r0 < rows; r0 += height) {
1890 tiles.emplace_back(jb, r0);
1891 }
1892 }
1893 detail::parallel_for(tiles.size(), height * step * depth / detail::gemm_kernel_speedup, [&](std::size_t t) {
1894 const auto [jb, r0] {tiles[t]};
1895 detail::gemm_with(detail::left_rows(left, r0, std::min(height, rows - r0)), std::min(step, rows - jb),
1896 minus_adjoint.data() + jb, rows, lower_.data() + ((end + r0) * n_) + end + jb, n_);
1897 });
1898 }
1899
1900 std::size_t n_;
1901 std::size_t block_ {detail::cholesky_block_for(n_)};
1903 };
1904
1906 template <typename T>
1908 {
1909 return cholesky_decomposition<T> {std::move(a)};
1910 }
1911
1912 namespace detail {
1913
1916 template <typename T>
1917 struct thin_qr
1918 {
1921 };
1922
1936 template <typename T>
1938 std::size_t cols,
1939 const buffer<T>& v,
1940 std::size_t first,
1941 std::size_t from)
1942 {
1943 constexpr std::size_t width {64};
1944 const std::size_t span {cols - from};
1945 parallel_for((span + width - 1) / width, 2 * v.size() * width, [&](std::size_t chunk) {
1946 const std::size_t start {from + (chunk * width)};
1947 const std::size_t count {std::min(width, cols - start)};
1948 buffer<T> w(count, T {});
1949 for (std::size_t i {0}; i < v.size(); ++i) {
1950 const T weight {conj_of(v[i])};
1951 const T* row {a.data() + ((first + i) * cols) + start};
1952 add_product(w.data(), weight, row, count);
1953 }
1954 for (std::size_t i {0}; i < v.size(); ++i) {
1955 const T scale {static_cast<T>(2) * v[i]};
1956 T* row {a.data() + ((first + i) * cols) + start};
1957 subtract_product(row, scale, w.data(), count);
1958 }
1959 });
1960 }
1961
1963 template <typename T>
1965 {
1966 std::size_t first;
1967 std::size_t height;
1968 std::size_t width;
1971 };
1972
1978 template <typename T>
1979 [[nodiscard]] reflector_block<T> gather_reflectors(const std::vector<buffer<T>>& reflectors,
1980 std::size_t k0,
1981 std::size_t k1,
1982 std::size_t rows,
1983 const std::vector<T>* taus = nullptr)
1984 {
1985 reflector_block<T> block {.first = k0, .height = rows - k0, .width = k1 - k0, .v = {}, .t = {}};
1986 const std::size_t b {block.width};
1987 block.v.assign(block.height * b, T {});
1988 block.t.assign(b * b, T {});
1989 for (std::size_t j {0}; j < b; ++j) {
1990 const buffer<T>& v {reflectors[k0 + j]};
1991 for (std::size_t r {0}; r < v.size(); ++r) {
1992 block.v[((j + r) * b) + j] = v[r];
1993 }
1994 }
1995 // Vᴴ V once, through the matrix product kernel: its column j above the diagonal is V(:, 0…j-1)ᴴ v_j.
1996 buffer<T> v_adjoint(b * block.height, T {});
1997 for (std::size_t r {0}; r < block.height; ++r) {
1998 for (std::size_t j {0}; j < b; ++j) {
1999 v_adjoint[(j * block.height) + r] = conj_of(block.v[(r * b) + j]);
2000 }
2001 }
2002 buffer<T> gram(b * b, T {});
2003 gemm(b, b, block.height, v_adjoint.data(), block.height, block.v.data(), b, gram.data(), b);
2004 for (std::size_t j {0}; j < b; ++j) {
2005 if (reflectors[k0 + j].empty()) {
2006 continue; // the identity: a zero column of T
2007 }
2008 const T tau {taus == nullptr ? static_cast<T>(2) : (*taus)[k0 + j]};
2009 for (std::size_t i {0}; i < j; ++i) { // T(0…j-1, j) = −τ_j T(0…j-1, 0…j-1) z, z = V(:, 0…j-1)ᴴ v_j
2010 T sum {};
2011 for (std::size_t p {i}; p < j; ++p) {
2012 sum += block.t[(i * b) + p] * gram[(p * b) + j];
2013 }
2014 block.t[(i * b) + j] = -tau * sum;
2015 }
2016 block.t[(j * b) + j] = tau;
2017 }
2018 return block;
2019 }
2020
2026 template <typename T>
2028 bool adjoint,
2029 buffer<T>& a,
2030 std::size_t cols,
2031 std::size_t from,
2032 std::size_t to)
2033 {
2034 const std::size_t b {block.width};
2035 const std::size_t h {block.height};
2036 const std::size_t w {to - from};
2037 buffer<T> v_adjoint(b * h, T {});
2038 buffer<T> minus_v(h * b, T {});
2039 for (std::size_t r {0}; r < h; ++r) {
2040 for (std::size_t j {0}; j < b; ++j) {
2041 v_adjoint[(j * h) + r] = conj_of(block.v[(r * b) + j]);
2042 minus_v[(r * b) + j] = -block.v[(r * b) + j];
2043 }
2044 }
2045 T* top {a.data() + (block.first * cols) + from};
2046 buffer<T> projection(b * w, T {});
2047 gemm(b, w, h, v_adjoint.data(), h, top, cols, projection.data(), w);
2048 buffer<T> scaled(b * w, T {});
2049 for (std::size_t i {0}; i < b; ++i) {
2050 for (std::size_t p {0}; p < b; ++p) {
2051 const T factor {adjoint ? conj_of(block.t[(p * b) + i]) : block.t[(i * b) + p]};
2052 if (factor == T {}) {
2053 continue; // T is triangular: half of these are zero
2054 }
2055 add_product(scaled.data() + (i * w), factor, projection.data() + (p * w), w);
2056 }
2057 }
2058 gemm(h, w, b, minus_v.data(), b, scaled.data(), w, top, cols);
2059 }
2060
2069 template <typename T>
2071 buffer<T>& at,
2072 std::size_t cols,
2073 std::size_t from,
2074 std::size_t to,
2075 bool adjoint = false)
2076 {
2077 const std::size_t b {block.width};
2078 const std::size_t h {block.height};
2079 const std::size_t r {to - from};
2080 const auto v_conjugate {std::make_unique_for_overwrite<T[]>(h * b)}; // conj(V), h × b, written whole
2081 const auto minus_v_t {std::make_unique_for_overwrite<T[]>(b * h)}; // −Vᵀ, b × h
2082 const auto t_transposed {std::make_unique_for_overwrite<T[]>(b * b)}; // Tᵀ, or conj(T)
2083 for (std::size_t row {0}; row < h; ++row) {
2084 for (std::size_t j {0}; j < b; ++j) {
2085 v_conjugate[(row * b) + j] = conj_of(block.v[(row * b) + j]);
2086 minus_v_t[(j * h) + row] = -block.v[(row * b) + j];
2087 }
2088 }
2089 for (std::size_t i {0}; i < b; ++i) {
2090 for (std::size_t p {0}; p < b; ++p) {
2091 t_transposed[(p * b) + i] = adjoint ? conj_of(block.t[(p * b) + i]) : block.t[(i * b) + p];
2092 }
2093 }
2094 T* left {at.data() + (from * cols) + block.first};
2095 buffer<T> projection(r * b, T {});
2096 gemm(r, b, h, left, cols, v_conjugate.get(), b, projection.data(), b);
2097 buffer<T> scaled(r * b, T {});
2098 gemm(r, b, b, projection.data(), b, t_transposed.get(), b, scaled.data(), b);
2099 gemm(r, h, b, scaled.data(), b, minus_v_t.get(), h, left, cols);
2100 }
2101
2103 template <typename T>
2104 requires std::is_floating_point_v<T>
2105 [[nodiscard]] T conjugate_dot(const T* x,
2106 const T* y,
2107 std::size_t count)
2108 {
2109 return dot_product(x, y, count);
2110 }
2111
2113 template <typename T>
2114 [[nodiscard]] T conjugate_dot(const T* x,
2115 const T* y,
2116 std::size_t count)
2117 {
2118 return conjugate_dot_product(x, y, count);
2119 }
2120
2126 template <typename T>
2127 void reflect_columns(T* panel,
2128 std::size_t height,
2129 const buffer<T>& v,
2130 std::size_t first,
2131 std::size_t from,
2132 std::size_t to,
2133 const T& s = T {2})
2134 {
2135 for (std::size_t j {from}; j < to; ++j) {
2136 T* const column {panel + (j * height) + first};
2137 const T scaled {s * conjugate_dot(v.data(), column, v.size())};
2138 subtract_product(column, scaled, v.data(), v.size());
2139 }
2140 }
2141
2143 inline constexpr std::size_t qr_block {32};
2144
2158 template <typename T>
2159 void factor_qr_panel(T* panel,
2160 std::size_t ld,
2161 std::size_t top,
2162 std::vector<buffer<T>>& reflectors,
2163 std::vector<T>& taus,
2164 std::size_t k0,
2165 std::size_t k1)
2166 {
2167 for (std::size_t k {k0}; k < k1; ++k) {
2168 const std::size_t c {k - k0};
2169 T* const x {panel + (c * ld) + (k - top)};
2170 buffer<T> w(x, x + (ld + top - k));
2171 const double factor {rescale_tiny(w)}; // w, τ and the signs the same at any scale; β scaled back
2172 const T alpha {w[0]};
2173 const double below {scaled_norm(w.data() + 1, w.size() - 1)};
2174 if (below == 0.0 && alpha == detail::from_double<T>(detail::real_part(alpha))) {
2175 continue; // the identity: nothing below the diagonal, and the diagonal real
2176 }
2177 const double beta {-std::copysign(scaled_norm(w), detail::real_part(alpha))};
2178 const T tau {(detail::from_double<T>(beta) - alpha) / detail::from_double<T>(beta)};
2179 const T scale {T {1} / (alpha - detail::from_double<T>(beta))}; // |α − β| ≥ ‖x‖: no cancellation
2180 w[0] = T {1};
2181 for (std::size_t r {1}; r < w.size(); ++r) {
2182 w[r] *= scale;
2183 }
2184 reflect_columns(panel, ld, w, k - top, c + 1, k1 - k0, conj_of(tau)); // Hᴴ on the panel's columns past k
2185 x[0] = detail::from_double<T>(beta / factor);
2186 std::copy(w.begin() + 1, w.end(), x + 1);
2187 reflectors[k] = std::move(w);
2188 taus[k] = tau;
2189 }
2190 }
2191
2193 template <typename T>
2195 {
2197 std::vector<reflector_block<T>> blocks;
2198 };
2199
2208 template <typename T>
2209 [[nodiscard]] std::vector<reflector_block<T>> reflect_in_columns(buffer<T>& columns,
2210 std::size_t rows,
2211 std::size_t cols)
2212 {
2213 const std::size_t depth {std::min(rows, cols)};
2214 std::vector<buffer<T>> reflectors(depth); // w_k on rows k…m-1; empty for the identity
2215 std::vector<T> taus(depth, T {});
2216 std::vector<reflector_block<T>> blocks;
2217 for (std::size_t k0 {0}; k0 < depth; k0 += qr_block) {
2218 const std::size_t k1 {std::min(k0 + qr_block, depth)};
2219 factor_qr_panel(columns.data() + (k0 * rows), rows, 0, reflectors, taus, k0, k1);
2220 blocks.push_back(gather_reflectors(reflectors, k0, k1, rows, &taus));
2221 if (k1 < cols) { // right of the panel: H_{k1-1} … H_{k0} A = (I − V T Vᴴ)ᴴ A
2222 apply_transposed_reflector_block(blocks.back(), columns, rows, k1, cols, true);
2223 }
2224 }
2225 return blocks;
2226 }
2227
2230 inline constexpr std::size_t qr_transpose_tile {32};
2231
2234 template <typename T>
2235 void transpose_into(const T* from,
2236 std::size_t rows,
2237 std::size_t cols,
2238 T* to)
2239 {
2240 parallel_for((rows + qr_transpose_tile - 1) / qr_transpose_tile, memory_element_weight * qr_transpose_tile * cols, [from, rows, cols, to](std::size_t band) {
2241 const std::size_t i0 {band * qr_transpose_tile};
2242 for (std::size_t j0 {0}; j0 < cols; j0 += qr_transpose_tile) {
2243 for (std::size_t j {j0}; j < std::min(j0 + qr_transpose_tile, cols); ++j) {
2244 for (std::size_t i {i0}; i < std::min(i0 + qr_transpose_tile, rows); ++i) {
2245 to[(j * rows) + i] = from[(i * cols) + j];
2246 }
2247 }
2248 }
2249 });
2250 }
2251
2257 template <typename T>
2259 std::size_t rows,
2260 std::size_t cols,
2261 std::vector<buffer<T>>& reflectors,
2262 std::vector<T>& taus,
2263 std::size_t k0,
2264 std::size_t k1)
2265 {
2266 const std::size_t height {rows - k0};
2267 const std::size_t width {k1 - k0};
2268 buffer<T> panel(height * width, T {});
2269 for (std::size_t i {0}; i < height; ++i) {
2270 for (std::size_t j {0}; j < width; ++j) {
2271 panel[(j * height) + i] = a[((k0 + i) * cols) + k0 + j];
2272 }
2273 }
2274 factor_qr_panel(panel.data(), height, k0, reflectors, taus, k0, k1);
2275 for (std::size_t i {0}; i < height; ++i) {
2276 for (std::size_t j {0}; j < width; ++j) {
2277 a[((k0 + i) * cols) + k0 + j] = panel[(j * height) + i];
2278 }
2279 }
2280 }
2281
2291 template <typename T>
2292 [[nodiscard]] std::vector<reflector_block<T>> reflect_in_rows(buffer<T>& a,
2293 std::size_t rows,
2294 std::size_t cols,
2295 buffer<T>* r = nullptr)
2296 {
2297 const std::size_t depth {std::min(rows, cols)};
2298 std::vector<buffer<T>> reflectors(depth); // w_k on rows k…m-1; empty for the identity
2299 std::vector<T> taus(depth, T {});
2300 if (r != nullptr) {
2301 *r = buffer<T>(depth * cols, T {}); // constructed, not assigned: the generic fill of assign took 4 % of qr at order 128
2302 }
2303 std::vector<reflector_block<T>> blocks;
2304 for (std::size_t k0 {0}; k0 < depth; k0 += qr_block) {
2305 const std::size_t k1 {std::min(k0 + qr_block, depth)};
2306 factor_row_major_panel(a, rows, cols, reflectors, taus, k0, k1);
2307 blocks.push_back(gather_reflectors(reflectors, k0, k1, rows, &taus));
2308 if (k1 < cols) { // right of the panel: H_{k1-1} … H_{k0} A = (I − V T Vᴴ)ᴴ A
2309 apply_reflector_block(blocks.back(), true, a, cols, k1, cols);
2310 }
2311 }
2312 for (std::size_t i {0}; r != nullptr && i < depth; ++i) {
2313 for (std::size_t j {i}; j < cols; ++j) {
2314 (*r)[(i * cols) + j] = a[(i * cols) + j];
2315 }
2316 }
2317 return blocks;
2318 }
2319
2328 template <typename T>
2330 std::size_t rows,
2331 std::size_t cols)
2332 {
2333#ifdef NUMERICAL_X86_COMPILED
2334 const std::size_t depth {std::min(rows, cols)};
2335 buffer<T> columns(rows * cols);
2336 transpose_into(a.data(), rows, cols, columns.data());
2337 qr_reflections<T> out {.r = buffer<T>(depth * cols), .blocks = reflect_in_columns(columns, rows, cols)};
2338 for (std::size_t j0 {0}; j0 < cols; j0 += qr_transpose_tile) { // R, row-major: written whole, zeros below
2339 for (std::size_t i0 {0}; i0 < depth; i0 += qr_transpose_tile) {
2340 for (std::size_t i {i0}; i < std::min(i0 + qr_transpose_tile, depth); ++i) {
2341 for (std::size_t j {j0}; j < std::min(j0 + qr_transpose_tile, cols); ++j) {
2342 out.r[(i * cols) + j] = i <= j ? columns[(j * rows) + i] : T {};
2343 }
2344 }
2345 }
2346 }
2347#else
2349 out.blocks = reflect_in_rows(a, rows, cols, &out.r);
2350#endif
2351 return out;
2352 }
2353
2362 template <typename T>
2364 const std::vector<reflector_block<T>>& blocks,
2365 std::size_t rows,
2366 std::size_t first,
2367 std::size_t count,
2368 bool transposed)
2369 {
2370#ifdef NUMERICAL_X86_COMPILED
2371 buffer<T> formed;
2372 buffer<T>& qt {transposed ? out : formed};
2373 qt = buffer<T>(count * rows, T {});
2374 for (std::size_t c {0}; c < count; ++c) {
2375 qt[(c * rows) + first + c] = T {1};
2376 }
2377 for (std::size_t b {blocks.size()}; b-- > 0;) {
2378 apply_transposed_reflector_block(blocks[b], qt, rows, std::max(first, blocks[b].first) - first, count);
2379 }
2380 if (!transposed) {
2381 out.resize(rows * count);
2382 transpose_into(qt.data(), count, rows, out.data());
2383 }
2384#else
2385 buffer<T> formed;
2386 buffer<T>& q {transposed ? formed : out};
2387 q = buffer<T>(rows * count, T {});
2388 for (std::size_t c {0}; c < count; ++c) {
2389 q[((first + c) * count) + c] = T {1};
2390 }
2391 for (std::size_t b {blocks.size()}; b-- > 0;) {
2392 apply_reflector_block(blocks[b], false, q, count, std::max(first, blocks[b].first) - first, count);
2393 }
2394 if (transposed) {
2395 out.resize(count * rows);
2396 transpose_into(q.data(), rows, count, out.data());
2397 }
2398#endif
2399 }
2400
2418 template <typename T>
2420 std::size_t rows,
2421 std::size_t cols,
2422 bool complete = false)
2423 {
2424 const std::size_t depth {std::min(rows, cols)};
2425 const std::size_t width {complete ? rows : depth};
2426 qr_reflections<T> factors {householder_reflect(std::move(a), rows, cols)};
2427 // Q = H₀ H₁ … applied to the columns [first, first + count) of I, each column's own product: the first `depth`
2428 // formed alone whether Q is complete or not, so that the complete Q's are the reduced one's to the bit (formed
2429 // together, the products' tiles differ, and a fused multiply-add may round a column otherwise).
2430 const auto formed {[&](std::size_t first, std::size_t count) {
2431 buffer<T> columns;
2432 form_q_into(columns, factors.blocks, rows, first, count, false);
2433 return columns;
2434 }};
2435 thin_qr<T> out {.q = formed(0, depth), .r = std::move(factors.r)};
2436 if (width > depth) {
2437 const buffer<T> rest {formed(depth, width - depth)};
2438 buffer<T> whole(rows * width, T {});
2439 for (std::size_t i {0}; i < rows; ++i) {
2440 std::copy_n(out.q.data() + (i * depth), depth, whole.data() + (i * width));
2441 std::copy_n(rest.data() + (i * (width - depth)), width - depth, whole.data() + (i * width) + depth);
2442 }
2443 out.q = std::move(whole);
2444 }
2445 for (std::size_t k {0}; k < depth; ++k) { // R's diagonal real and non-negative: rotate row k, column k
2446 const T phase {phase_of(out.r[(k * cols) + k])};
2447 for (std::size_t j {k}; j < cols; ++j) {
2448 out.r[(k * cols) + j] *= conj_of(phase);
2449 }
2450 for (std::size_t i {0}; i < rows; ++i) {
2451 out.q[(i * width) + k] *= phase;
2452 }
2453 }
2454 return out;
2455 }
2456
2459 [[nodiscard]] inline double rank_tolerance(double largest,
2460 std::size_t rows,
2461 std::size_t cols)
2462 {
2463 return largest * std::numeric_limits<double>::epsilon() * static_cast<double>(std::max(rows, cols));
2464 }
2465 } // namespace detail
2466
2474 {
2475 public:
2476
2478 constexpr rank_cutoff() noexcept = default;
2479
2484 [[nodiscard]] static rank_cutoff relative(double ratio)
2485 {
2486 return rank_cutoff {checked(ratio), false};
2487 }
2488
2493 [[nodiscard]] static rank_cutoff absolute(double magnitude)
2494 {
2495 return rank_cutoff {checked(magnitude), true};
2496 }
2497
2499 [[nodiscard]] double magnitude(double largest,
2500 std::size_t rows,
2501 std::size_t cols) const noexcept
2502 {
2503 if (absolute_) {
2504 return value_;
2505 }
2506 return value_ < 0.0 ? detail::rank_tolerance(largest, rows, cols) : largest * value_;
2507 }
2508
2510 [[nodiscard]] rank_cutoff scaled(int exponent) const noexcept
2511 {
2512 return absolute_ ? rank_cutoff {std::ldexp(value_, -exponent), true} : *this;
2513 }
2514
2515 private:
2516
2517 constexpr rank_cutoff(double value,
2518 bool absolute) noexcept
2519 : value_(value), absolute_(absolute)
2520 {}
2521
2522 [[nodiscard]] static double checked(double value)
2523 {
2524 if (!(value >= 0.0)) {
2525 throw std::invalid_argument("rank cutoff: a tolerance is non-negative");
2526 }
2527 return value;
2528 }
2529
2530 double value_ {-1.0};
2531 bool absolute_ {false};
2532 };
2533
2535 enum class qr_mode
2536 {
2537 reduced,
2538 complete
2539 };
2540
2553 template <typename T>
2555 {
2556 public:
2557
2563 explicit qr_decomposition(const matrix<T>& a,
2564 qr_mode mode = qr_mode::reduced)
2565 : qr_decomposition(detail::parallel_copy_of(a),
2566 mode)
2567 {}
2568
2576 qr_mode mode = qr_mode::reduced)
2577 : rows_(a.rows()), cols_(a.cols()), depth_(std::min(rows_, cols_)),
2578 width_(mode == qr_mode::complete ? rows_ : depth_)
2579 {
2580 detail::require_finite(a, "QR decomposition");
2581 detail::thin_qr<T> factors {detail::householder_qr(std::move(a).elements(), rows_, cols_, mode == qr_mode::complete)};
2582 q_ = std::move(factors.q);
2583 r_ = std::move(factors.r);
2584 r_.resize(width_ * cols_, T {}); // complete: R's rows past k are zero
2585 }
2586
2588 [[nodiscard]] matrix<T> q() const&
2589 {
2590 return matrix<T> {rows_, width_, q_};
2591 }
2592
2594 [[nodiscard]] matrix<T> q() &&
2595 {
2596 return matrix<T> {rows_, width_, std::move(q_)};
2597 }
2598
2600 [[nodiscard]] matrix<T> r() const&
2601 {
2602 return matrix<T> {width_, cols_, r_};
2603 }
2604
2606 [[nodiscard]] matrix<T> r() &&
2607 {
2608 return matrix<T> {width_, cols_, std::move(r_)};
2609 }
2610
2620 [[nodiscard]] vector<T> solve(const vector<T>& b) const
2621 {
2622 if (b.size() != rows_) {
2623 throw dimension_error("solve: right-hand side size differs from the matrix");
2624 }
2625 if (rows_ < cols_) {
2626 throw dimension_error("QR solve: the system is underdetermined (lstsq gives its minimum-norm solution)");
2627 }
2628 double largest {0.0};
2629 for (std::size_t i {0}; i < cols_; ++i) {
2630 largest = std::max(largest, detail::real_part(r_[(i * cols_) + i]));
2631 }
2632 const double tolerance {detail::rank_tolerance(largest, rows_, cols_)};
2633 buffer<T> y(cols_, T {}); // y = Qᴴ b
2634 for (std::size_t i {0}; i < cols_; ++i) {
2635 if (detail::real_part(r_[(i * cols_) + i]) <= tolerance) {
2636 throw singular_matrix_error("QR solve: the matrix is rank-deficient (lstsq gives its minimum-norm solution)");
2637 }
2638 T sum {};
2639 for (std::size_t k {0}; k < rows_; ++k) {
2640 sum += detail::conj_of(q_[(k * width_) + i]) * b[k];
2641 }
2642 y[i] = sum;
2643 }
2644 buffer<T> x(cols_, T {}); // back substitution R x = y
2645 for (std::size_t i {cols_}; i-- > 0;) {
2646 T sum {y[i]};
2647 for (std::size_t j {i + 1}; j < cols_; ++j) {
2648 sum -= r_[(i * cols_) + j] * x[j];
2649 }
2650 x[i] = sum / r_[(i * cols_) + i];
2651 }
2652 return vector<T> {std::move(x)};
2653 }
2654
2655 private:
2656
2657 std::size_t rows_;
2658 std::size_t cols_;
2659 std::size_t depth_;
2660 std::size_t width_;
2663 };
2664
2666 template <typename T>
2667 [[nodiscard]] qr_decomposition<T> qr(const matrix<T>& a)
2668 {
2669 return qr_decomposition<T> {a};
2670 }
2671
2673 class convergence_error : public std::runtime_error
2674 {
2675 public:
2676
2678 explicit convergence_error(const std::string& message)
2679 : std::runtime_error(message)
2680 {}
2681 };
2682
2683 namespace detail {
2684
2687 inline constexpr std::size_t jacobi_sweeps {60};
2688
2690 template <typename T>
2691 requires std::is_floating_point_v<T>
2693 const T* x,
2694 const T& f,
2695 std::size_t count)
2696 {
2697 add_product(y, f, x, count);
2698 }
2699
2706 template <typename T>
2708 const T* x,
2709 const T& f,
2710 std::size_t count)
2711 {
2712 using R = typename T::value_type;
2713 const R c {f.real()};
2714 const R d {f.imag()};
2715 for (std::size_t i {0}; i < count; ++i) {
2716 const R a {x[i].real()};
2717 const R b {-x[i].imag()};
2718 const R ac {a * c};
2719 const R bd {b * d};
2720 const R ad {a * d};
2721 const R bc {b * c};
2722 const R re {ac - bd};
2723 const R im {ad + bc};
2724 y[i] = T {y[i].real() + re, y[i].imag() + im};
2725 }
2726 }
2727
2729 using symmetric_row_kernel = double (*)(const double*, const double*, double, double*, std::size_t);
2730
2732 using symmetric_rows_kernel = std::array<double, symmetric_row_group> (*)(const double*, std::size_t, const double*,
2733 double*, std::size_t);
2734
2739 template <typename T>
2740 [[nodiscard]] T hermitian_row(symmetric_row_kernel /*kernel*/,
2741 const T* row,
2742 const T* v,
2743 T* out,
2744 std::size_t i)
2745 {
2746 add_conjugate_product(out, row, v[i], i);
2747 return dot_product(row, v, i + 1);
2748 }
2749
2750#ifdef NUMERICAL_NEON_COMPILED
2757 inline std::complex<double> neon_hermitian_row(const double* row,
2758 const double* v,
2759 double vr,
2760 double vi,
2761 double* out,
2762 std::size_t count) noexcept
2763 {
2764 const float64x2_t real {vr, -vr};
2765 const float64x2_t imag {vi, vi};
2766 float64x2_t same[2] {vdupq_n_f64(0.0), vdupq_n_f64(0.0)};
2767 float64x2_t across[2] {vdupq_n_f64(0.0), vdupq_n_f64(0.0)};
2768 std::size_t j {0};
2769 for (; j + 2 <= count; j += 2) {
2770 for (std::size_t l {0}; l < 2; ++l) {
2771 const float64x2_t r {vld1q_f64(row + (2 * (j + l)))};
2772 const float64x2_t x {vld1q_f64(v + (2 * (j + l)))};
2773 const float64x2_t s {vextq_f64(r, r, 1)};
2774 float64x2_t o {vld1q_f64(out + (2 * (j + l)))};
2775 o = vfmaq_f64(o, real, r);
2776 o = vfmaq_f64(o, imag, s);
2777 same[l] = vfmaq_f64(same[l], r, x);
2778 across[l] = vfmaq_f64(across[l], s, x);
2779 vst1q_f64(out + (2 * (j + l)), o);
2780 }
2781 }
2782 const float64x2_t sum_same {vaddq_f64(same[0], same[1])};
2783 const float64x2_t sum_across {vaddq_f64(across[0], across[1])};
2784 double re {vgetq_lane_f64(sum_same, 0) - vgetq_lane_f64(sum_same, 1)};
2785 double im {vgetq_lane_f64(sum_across, 0) + vgetq_lane_f64(sum_across, 1)};
2786 for (; j < count; ++j) {
2787 const double rr {row[2 * j]};
2788 const double ri {row[(2 * j) + 1]};
2789 out[2 * j] = std::fma(vi, ri, std::fma(vr, rr, out[2 * j]));
2790 out[(2 * j) + 1] = std::fma(vi, rr, std::fma(-vr, ri, out[(2 * j) + 1]));
2791 re = std::fma(-ri, v[(2 * j) + 1], std::fma(rr, v[2 * j], re));
2792 im = std::fma(rr, v[(2 * j) + 1], std::fma(ri, v[2 * j], im));
2793 }
2794 return std::complex<double> {re, im};
2795 }
2796
2797#endif
2798
2803 [[nodiscard]] inline std::complex<double> hermitian_row(symmetric_row_kernel kernel,
2804 const std::complex<double>* row,
2805 const std::complex<double>* v,
2806 std::complex<double>* out,
2807 std::size_t i)
2808 {
2809#ifdef NUMERICAL_X86_COMPILED
2810 // A complex number's parts are an array of two (the standard's layout guarantee): addressed as doubles.
2811 const gemm_path path {gemm_path_current()};
2812 if (path == gemm_path::avx512 || path == gemm_path::avx2) {
2813 const auto* const values {reinterpret_cast<const double*>(v)}; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the layout guarantee above
2814 const auto* const parts {reinterpret_cast<const double*>(row)}; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the layout guarantee above
2815 auto* const into {reinterpret_cast<double*>(out)}; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the layout guarantee above
2816 const std::complex<double> lower {path == gemm_path::avx512
2817 ? avx512_hermitian_row(parts, values, v[i].real(), v[i].imag(), into, i)
2818 : avx2_hermitian_row(parts, values, v[i].real(), v[i].imag(), into, i)};
2819 return lower + (row[i] * v[i]);
2820 }
2821#endif
2822#ifdef NUMERICAL_NEON_COMPILED
2823 if (const gemm_path neon_path {gemm_path_current()}; neon_path == gemm_path::neon || neon_path == gemm_path::amx) {
2824 const auto* const values {reinterpret_cast<const double*>(v)}; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the layout guarantee above
2825 const auto* const parts {reinterpret_cast<const double*>(row)}; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the layout guarantee above
2826 auto* const into {reinterpret_cast<double*>(out)}; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the layout guarantee above
2827 const std::complex<double> lower {neon_hermitian_row(parts, values, v[i].real(), v[i].imag(), into, i)};
2828 return lower + (row[i] * v[i]);
2829 }
2830#endif
2831 return hermitian_row<std::complex<double>>(kernel, row, v, out, i);
2832 }
2833
2835 [[nodiscard]] inline double hermitian_row(symmetric_row_kernel kernel,
2836 const double* row,
2837 const double* v,
2838 double* out,
2839 std::size_t i)
2840 {
2841 return kernel(row, v, v[i], out, i) + (row[i] * v[i]);
2842 }
2843
2850 template <typename T>
2851 [[nodiscard]] std::size_t symmetric_row_speedup()
2852 {
2853#ifdef NUMERICAL_X86_COMPILED
2854 if constexpr (std::is_same_v<T, double>) {
2855 if (gemm_path_current() != gemm_path::portable) {
2856 return vector_speedup;
2857 }
2858 }
2859#endif
2860 return 1;
2861 }
2862
2878 template <typename T>
2879 void hermitian_product(const T* block,
2880 std::size_t n,
2881 std::size_t m,
2882 const T* v,
2883 T* w,
2884 buffer<T>& scratch)
2885 {
2886 constexpr std::size_t rows_per_task {32};
2887 const std::size_t tasks {(m + rows_per_task - 1) / rows_per_task};
2888 if (scratch.size() < tasks * m) {
2889 scratch.resize(tasks * m, T {});
2890 }
2891 symmetric_row_kernel kernel {[](const double* row, const double* x, double f, double* out, std::size_t count) {
2892 return symmetric_row(row, x, f, out, count);
2893 }};
2894 symmetric_rows_kernel group {[](const double* rows, std::size_t ld, const double* x, double* out, std::size_t i) {
2895 return symmetric_rows(rows, ld, x, out, i);
2896 }};
2897#ifdef NUMERICAL_X86_COMPILED
2898 if constexpr (std::is_same_v<T, double>) {
2899 const gemm_path path {gemm_path_current()};
2900 if (path == gemm_path::avx512) {
2901 kernel = &avx512_symmetric_row;
2902 group = &avx512_symmetric_rows;
2903 }
2904 else if (path == gemm_path::avx2) {
2905 kernel = &avx2_symmetric_row;
2906 group = &avx2_symmetric_rows;
2907 }
2908 }
2909#endif
2910 parallel_for(tasks, rows_per_task * m * symmetric_row_speedup<T>(), [&](std::size_t task) {
2911 const std::size_t first {task * rows_per_task};
2912 const std::size_t last {std::min(first + rows_per_task, m)};
2913 T* own {scratch.data() + (task * m)};
2914 std::fill(own, own + last, T {}); // a task's rows reach w[0…last) alone
2915 std::size_t i {first};
2916 if constexpr (std::is_same_v<T, double>) { // rows by groups: v and own read once a group
2917 for (; i + symmetric_row_group <= last; i += symmetric_row_group) {
2918 const std::array<double, symmetric_row_group> sums {group(block + (i * n), n, v, own, i)};
2919 for (std::size_t r {0}; r < symmetric_row_group; ++r) {
2920 own[i + r] += sums[r];
2921 }
2922 }
2923 }
2924 for (; i < last; ++i) {
2925 own[i] += hermitian_row(kernel, block + (i * n), v, own, i);
2926 }
2927 });
2928 std::fill(w, w + m, T {});
2929 for (std::size_t task {0}; task < tasks; ++task) { // the partials in order, each a contiguous update
2930 add_product(w, T {1}, scratch.data() + (task * m), std::min((task + 1) * rows_per_task, m));
2931 }
2932 }
2933
2937#ifdef NUMERICAL_X86_COMPILED
2938 inline constexpr std::size_t corrected_product_least {256};
2939#else
2940 inline constexpr std::size_t corrected_product_least {512};
2941#endif
2942
2944 template <typename T>
2945 requires std::is_floating_point_v<T>
2947 const T* f,
2948 const T* x,
2949 std::size_t ldx,
2950 std::size_t terms,
2951 std::size_t count)
2952 {
2953 subtract_products(y, f, x, ldx, terms, count);
2954 }
2955
2963 template <split_complex T>
2965 const T* f,
2966 const T* x,
2967 std::size_t ldx,
2968 std::size_t terms,
2969 std::size_t count)
2970 {
2971#ifdef NUMERICAL_X86_COMPILED
2972 if constexpr (std::is_same_v<T, std::complex<double>>) { // its own fused kernel on the x86-64 units, as fast
2973 if (const gemm_path path {gemm_path_current()}; path == gemm_path::avx2 || path == gemm_path::avx512) {
2974 subtract_products(y, f, x, ldx, terms, count);
2975 return;
2976 }
2977 }
2978#endif
2979 using R = typename T::value_type;
2980 for (std::size_t t {0}; t < terms; ++t) {
2981 const R c {f[t].real()};
2982 const R d {f[t].imag()};
2983 const T* const row {x + (t * ldx)};
2984 for (std::size_t i {0}; i < count; ++i) {
2985 const R a {row[i].real()};
2986 const R b {row[i].imag()};
2987 const R ac {c * a};
2988 const R bd {d * b};
2989 const R ad {c * b};
2990 const R bc {d * a};
2991 const R re {ac - bd};
2992 const R im {ad + bc};
2993 y[i] = T {y[i].real() - re, y[i].imag() - im};
2994 }
2995 }
2996 }
2997
3017 template <typename T>
3018 void corrected_hermitian_product(const T* block,
3019 std::size_t n,
3020 std::size_t m,
3021 const T* v,
3022 T* w,
3023 buffer<T>& scratch,
3024 const T* corrections,
3025 std::size_t ldc,
3026 std::size_t terms,
3027 T* factors,
3028 T* next = nullptr,
3029 const T* next_factors = nullptr)
3030 {
3031 constexpr std::size_t rows_per_task {32};
3032 const std::size_t tasks {(m + rows_per_task - 1) / rows_per_task};
3033 if (scratch.size() < tasks * m) {
3034 scratch.resize(tasks * m, T {});
3035 }
3036 symmetric_row_kernel kernel {[](const double* row, const double* x, double f, double* out, std::size_t count) {
3037 return symmetric_row(row, x, f, out, count);
3038 }};
3039 symmetric_rows_kernel group {[](const double* rows, std::size_t ld, const double* x, double* out, std::size_t i) {
3040 return symmetric_rows(rows, ld, x, out, i);
3041 }};
3042#ifdef NUMERICAL_X86_COMPILED
3043 if constexpr (std::is_same_v<T, double>) {
3044 const gemm_path path {gemm_path_current()};
3045 if (path == gemm_path::avx512) {
3046 kernel = &avx512_symmetric_row;
3047 group = &avx512_symmetric_rows;
3048 }
3049 else if (path == gemm_path::avx2) {
3050 kernel = &avx2_symmetric_row;
3051 group = &avx2_symmetric_rows;
3052 }
3053 }
3054#endif
3055 // Below corrected_product_least rows the coefficients and the bands run on the caller: woken threads cost more
3056 // than they take (an eigh of order 128, six x86-64 cores, +13 %).
3057 const bool share {m >= corrected_product_least};
3058 const std::size_t pairs {terms / 2};
3059 const auto coefficients = [&](std::size_t p) {
3060 factors[2 * p] = conjugate_dot(corrections + (((2 * p) + 1) * ldc), v, m);
3061 factors[(2 * p) + 1] = conjugate_dot(corrections + ((2 * p) * ldc), v, m);
3062 };
3063 parallel_for(tasks + (share ? pairs : 0), rows_per_task * m * symmetric_row_speedup<T>(), [&](std::size_t task) {
3064 if (task >= tasks) { // a pair of coefficients: an earlier step's w and v against v
3065 coefficients(task - tasks);
3066 return;
3067 }
3068 const std::size_t first {task * rows_per_task};
3069 const std::size_t last {std::min(first + rows_per_task, m)};
3070 T* own {scratch.data() + (task * m)};
3071 std::fill(own, own + last, T {});
3072 std::size_t i {first};
3073 if constexpr (std::is_same_v<T, double>) {
3074 for (; i + symmetric_row_group <= last; i += symmetric_row_group) {
3075 const std::array<double, symmetric_row_group> sums {group(block + (i * n), n, v, own, i)};
3076 for (std::size_t r {0}; r < symmetric_row_group; ++r) {
3077 own[i + r] += sums[r];
3078 }
3079 }
3080 }
3081 for (; i < last; ++i) {
3082 own[i] += hermitian_row(kernel, block + (i * n), v, own, i);
3083 }
3084 });
3085 for (std::size_t p {0}; !share && p < pairs; ++p) {
3086 coefficients(p);
3087 }
3088 constexpr std::size_t band {256};
3089 const auto bands = [&](std::size_t b) {
3090 const std::size_t first {b * band};
3091 const std::size_t count {std::min(band, m - first)};
3092 std::fill(w + first, w + first + count, T {});
3093 for (std::size_t task {0}; task < tasks; ++task) { // the partials in order, as hermitian_product
3094 const std::size_t reach {std::min((task + 1) * rows_per_task, m)};
3095 if (reach > first) {
3096 add_product(w + first, T {1}, scratch.data() + (task * m) + first, std::min(count, reach - first));
3097 }
3098 }
3099 subtract_corrections(w + first, factors, corrections + first, ldc, terms, count);
3100 if (next != nullptr) { // the next step's column: these earlier steps' terms (next implies an earlier step)
3101 subtract_corrections(next + first, next_factors, corrections + first, ldc, terms, count);
3102 }
3103 };
3104 if (share) {
3105 parallel_for((m + band - 1) / band, band * (tasks + terms) * 8, bands);
3106 return;
3107 }
3108 for (std::size_t b {0}; b < (m + band - 1) / band; ++b) {
3109 bands(b);
3110 }
3111 }
3112
3114 inline constexpr std::size_t tridiagonal_block {32};
3115
3119 inline constexpr std::size_t tridiagonal_band {512};
3120
3123 inline constexpr std::size_t ql_steps_per_value {30};
3124
3129 inline double real_subdiagonal(double t,
3130 double /*delta*/,
3131 double& /*next*/)
3132 {
3133 return t;
3134 }
3135
3143 inline double real_subdiagonal(std::complex<double> t,
3144 std::complex<double> delta,
3145 std::complex<double>& next)
3146 {
3147 next = delta * std::conj(phase_of(t));
3148 return std::abs(t);
3149 }
3150
3152 template <typename T>
3154 {
3155 std::vector<buffer<T>> reflectors;
3157 };
3158
3173 template <typename T>
3175 std::size_t n,
3176 buffer<double>& d,
3177 buffer<double>& e)
3178 {
3179 std::vector<buffer<T>> reflectors(n); // reflectors[k + 1] zeroes column k, acting on rows k + 1…
3180 buffer<T> symv_scratch; // hermitian_product's partials, kept from step to step
3181 buffer<T> sub(n, T {}); // T's subdiagonal before the phases
3182 // The trailing update's factors, for the first panel's size, the largest: one buffer each, mapped once.
3183 const auto left {std::make_unique_for_overwrite<T[]>(n * 2 * tridiagonal_block)}; // row i: −V's then −W's
3184 const auto right {std::make_unique_for_overwrite<T[]>(2 * tridiagonal_block * n)}; // Wᴴ's rows, then Vᴴ's
3185 // By reflection: row 2p holds step k0 + p's vector v over the rows (zero above), row 2p + 1 its w, so that a
3186 // step's terms of the panel's earlier ones are one update through the rows (subtract_products), in their order.
3187 buffer<T> by_row(2 * tridiagonal_block * n);
3188 buffer<T> columns(tridiagonal_block * n); // the panel's columns of A, gathered row by row
3189 buffer<T> factors(2 * tridiagonal_block); // a step's coefficients of those rows
3190 buffer<T> next_factors(2 * tridiagonal_block); // the next column's, applied ahead
3191 bool column_ahead {false}; // whether the step's column took its earlier terms ahead
3192 for (std::size_t k0 {0}; k0 + 1 < n; k0 += tridiagonal_block) {
3193 // A panel of reflections, as LAPACK's dlatrd: the matrix the panel's later steps see is
3194 // A − V Wᴴ − W Vᴴ, applied to the one column and the one product each step needs; the whole trailing
3195 // block takes it after the panel, through the matrix product kernel.
3196 const std::size_t k1 {std::min(k0 + tridiagonal_block, n - 1)};
3197 const std::size_t width {k1 - k0};
3198 std::fill_n(by_row.data(), 2 * width * n, T {});
3199 for (std::size_t i {k0}; i < n; ++i) { // column k from row k down, each row's part read once
3200 for (std::size_t k {k0}; k < std::min(k1, i + 1); ++k) {
3201 columns[((k - k0) * n) + i] = a[(i * n) + k];
3202 }
3203 }
3204 for (std::size_t k {k0}; k < k1; ++k) {
3205 const std::size_t j {k - k0};
3206 const std::size_t m {n - k - 1};
3207 T* const column {columns.data() + (j * n) + k}; // column k from the diagonal down, the panel's earlier steps applied
3208 // The steps before the last were applied during it (column_ahead); the last's two terms, or all, here.
3209 const std::size_t from {column_ahead ? j - 1 : 0};
3210 for (std::size_t p {from}; p < j; ++p) {
3211 factors[2 * (p - from)] = conj_of(by_row[(((2 * p) + 1) * n) + k]); // v's row by w's element
3212 factors[(2 * (p - from)) + 1] = conj_of(by_row[((2 * p) * n) + k]);
3213 }
3214 subtract_corrections(column, factors.data(), by_row.data() + ((2 * from) * n) + k, n, 2 * (j - from), m + 1);
3215 column_ahead = false;
3216 a[(k * n) + k] = column[0];
3217 buffer<T> v(column + 1, column + m + 1); // column k below the diagonal
3218 const double below {m > 1 ? scaled_norm(v.data() + 1, m - 1) : 0.0};
3219 if (below == 0.0) {
3220 sub[k] = v[0]; // already tridiagonal in this column: nothing to reflect
3221 continue;
3222 }
3223 const double up {rescale_tiny(v)}; // the reflector is the same at any scale; α is scaled back
3224 const T alpha {-phase_of(v[0]) * static_cast<T>(scaled_norm(v))}; // the sign that avoids cancellation
3225 sub[k] = alpha / up;
3226 v[0] -= alpha;
3227 const double length {scaled_norm(v)}; // real: each part divided, where a complex division scaled and divided both
3228 for (T& element : v) {
3229 element /= length;
3230 }
3231 const T* block {a.data() + ((k + 1) * n) + k + 1}; // the trailing block, row stride n
3232 buffer<T> w(m, T {});
3233 // w = (A − V Wᴴ − W Vᴴ) v on the trailing block, (Wᴴ v)ₚ and (Vᴴ v)ₚ beside A v; the next column of the
3234 // panel takes these earlier steps' terms in the same pass (its rows are w's).
3235 T* const next {k + 1 < k1 && j > 0 ? columns.data() + ((j + 1) * n) + k + 1 : nullptr};
3236 for (std::size_t p {0}; next != nullptr && p < j; ++p) {
3237 next_factors[2 * p] = conj_of(by_row[(((2 * p) + 1) * n) + k + 1]);
3238 next_factors[(2 * p) + 1] = conj_of(by_row[((2 * p) * n) + k + 1]);
3239 }
3240 corrected_hermitian_product(block, n, m, v.data(), w.data(), symv_scratch, by_row.data() + k + 1, n, 2 * j, factors.data(),
3241 next, next_factors.data());
3242 column_ahead = next != nullptr;
3243 const T c {conjugate_dot(v.data(), w.data(), m)};
3244 for (std::size_t i {0}; i < m; ++i) { // w = 2 B v − 2 (vᴴ B v) v, so H B H = B − v wᴴ − w vᴴ
3245 w[i] = (static_cast<T>(2) * w[i]) - (static_cast<T>(2) * c * v[i]);
3246 by_row[((2 * j) * n) + k + 1 + i] = v[i];
3247 by_row[(((2 * j) + 1) * n) + k + 1 + i] = w[i];
3248 }
3249 reflectors[k + 1] = std::move(v); // acting from row k + 1, as householder_qr's from row k
3250 }
3251 // The trailing block from row and column k1: A −= V Wᴴ + W Vᴴ, one product of depth 2 · width,
3252 // [−V −W] [Wᴴ; Vᴴ], on the lower triangle alone, as LAPACK's dsyr2k: the reduction reads nothing above the
3253 // diagonal. A real product the kernel packs runs on the triangle's tiles alone (\ref gemm_triangle, each
3254 // element the bands' sum); a smaller or complex one by bands of rows, each to the diagonal, past which a band
3255 // computes what is never read.
3256 const std::size_t rest {n - k1};
3257 const std::size_t depth {2 * width};
3258 for (std::size_t i {0}; i < rest; ++i) { // each row of left written whole, in order
3259 for (std::size_t p {0}; p < width; ++p) {
3260 left[(i * depth) + p] = -by_row[((2 * p) * n) + k1 + i];
3261 left[(i * depth) + width + p] = -by_row[(((2 * p) + 1) * n) + k1 + i];
3262 }
3263 }
3264 for (std::size_t p {0}; p < width; ++p) {
3265 for (std::size_t i {0}; i < rest; ++i) {
3266 right[(p * rest) + i] = conj_of(by_row[(((2 * p) + 1) * n) + k1 + i]);
3267 right[((width + p) * rest) + i] = conj_of(by_row[((2 * p) * n) + k1 + i]);
3268 }
3269 }
3270 T* trailing {a.data() + (k1 * n) + k1};
3271 if constexpr (std::is_same_v<T, double>) {
3272 if (depth >= gemm_min_depth && rest * rest >= ((gemm_packed_threshold + depth) - 1) / depth) {
3273 gemm_triangle<a_rows, b_rows>(rest, depth, left.get(), depth, right.get(), rest, trailing, n, true);
3274 continue;
3275 }
3276 }
3277 for (std::size_t r0 {0}; r0 < rest; r0 += tridiagonal_band) {
3278 const std::size_t rows {std::min(tridiagonal_band, rest - r0)};
3279 gemm(rows, r0 + rows, depth, left.get() + (r0 * depth), depth, right.get(), rest, trailing + (r0 * n), n);
3280 }
3281 }
3282 for (std::size_t i {0}; i < n; ++i) {
3283 d[i] = real_part(a[(i * n) + i]);
3284 }
3285 e[n - 1] = 0.0;
3286 // D T Dᴴ is real with D = diag(δ): δ₀ = 1, δ_{k+1} = δ_k · conj(phase(t_k)); then M = Q Dᴴ.
3287 buffer<T> delta(n, T {1});
3288 for (std::size_t k {0}; k + 1 < n; ++k) {
3289 e[k] = real_subdiagonal(sub[k], delta[k], delta[k + 1]);
3290 }
3291 return {.reflectors = std::move(reflectors), .delta = std::move(delta)};
3292 }
3293
3299 template <typename T>
3301 std::size_t n)
3302 {
3303 buffer<T> mt(n * n, T {});
3304 for (std::size_t i {0}; i < n; ++i) {
3305 mt[(i * n) + i] = T {1};
3306 }
3307 for (std::size_t k1 {n}; k1 > 1;) {
3308 const std::size_t k0 {k1 - std::min(k1 - 1, qr_block)};
3309 apply_transposed_reflector_block(gather_reflectors(reduction.reflectors, k0, k1, n), mt, n, k0, n);
3310 k1 = k0;
3311 }
3312 for (std::size_t j {0}; j < n; ++j) { // row j of Mᵀ = Dᴴ Qᵀ: row j of Qᵀ times conj(δⱼ)
3313 const T factor {conj_of(reduction.delta[j])};
3314 for (std::size_t i {0}; i < n; ++i) {
3315 mt[(j * n) + i] *= factor;
3316 }
3317 }
3318 return mt;
3319 }
3320
3326 template <typename T>
3328 buffer<T>& at,
3329 std::size_t n)
3330 {
3331 if constexpr (!std::is_floating_point_v<T>) { // a real reduction's phases are all 1
3332 parallel_for(n, n, [&](std::size_t i) {
3333 for (std::size_t j {0}; j < n; ++j) {
3334 at[(i * n) + j] *= conj_of(reduction.delta[j]);
3335 }
3336 });
3337 }
3338 for (std::size_t k1 {n}; k1 > 1;) {
3339 const std::size_t k0 {k1 - std::min(k1 - 1, qr_block)};
3340 apply_transposed_reflector_block(gather_reflectors(reduction.reflectors, k0, k1, n), at, n, 0, n);
3341 k1 = k0;
3342 }
3343 }
3344
3348 {
3349 enum class kind : std::uint8_t
3350 {
3351 rotate,
3352 negate,
3353 swap,
3354 };
3355
3357 std::size_t i;
3358 std::size_t j;
3359 double c;
3360 double s;
3361 };
3362
3366 {
3367 std::size_t first;
3368 std::size_t count;
3369 bool down;
3370 };
3371
3373 [[nodiscard]] inline std::vector<rotation_run> rotation_runs(const std::vector<row_operation>& operations)
3374 {
3375 std::vector<rotation_run> runs;
3376 for (std::size_t t {0}; t < operations.size();) {
3377 const row_operation& op {operations[t]};
3378 std::size_t count {1};
3379 bool down {false};
3380 if (op.what == row_operation::kind::rotate && t + 1 < operations.size()
3381 && operations[t + 1].what == row_operation::kind::rotate) {
3382 down = operations[t + 1].j == op.i;
3383 for (; t + count < operations.size(); ++count) {
3384 const row_operation& previous {operations[t + count - 1]};
3385 const row_operation& next {operations[t + count]};
3386 const bool chained {down ? next.j == previous.i : next.i == previous.j};
3387 if (next.what != row_operation::kind::rotate || !chained) {
3388 break;
3389 }
3390 }
3391 }
3392 runs.push_back({.first = t, .count = count, .down = down});
3393 t += count;
3394 }
3395 return runs;
3396 }
3397
3407 template <typename T>
3409 std::size_t n,
3410 const std::vector<row_operation>& operations)
3411 {
3412 constexpr std::size_t width {64};
3413 const std::vector<rotation_run> runs {rotation_runs(operations)};
3414 parallel_for((n + width - 1) / width, operations.size() * width, [&](std::size_t chunk) {
3415 const std::size_t first {chunk * width};
3416 const std::size_t count {std::min(first + width, n) - first};
3417 std::array<T, width> carry {};
3418 for (const rotation_run& run : runs) {
3419 const row_operation& start {operations[run.first]};
3420 if (run.count > 1) {
3421 // Down: rotation u's first row is rotation u+1's second; up: its second is the next's first.
3422 const T* source {m.data() + ((run.down ? start.j : start.i) * n) + first};
3423 std::copy(source, source + count, carry.begin());
3424 for (std::size_t u {run.first}; u < run.first + run.count; ++u) {
3425 const row_operation& op {operations[u]};
3426 const T c {detail::from_double<T>(op.c)};
3427 const T s {detail::from_double<T>(op.s)};
3428 if (run.down) { // the carried row is op.j: op.i is read, op.j written
3429 const T* row {m.data() + (op.i * n) + first};
3430 T* other {m.data() + (op.j * n) + first};
3431 for (std::size_t k {0}; k < count; ++k) {
3432 const T a {row[k]};
3433 const T b {carry[k]};
3434 other[k] = (c * b) - (s * a);
3435 carry[k] = (c * a) + (s * b);
3436 }
3437 }
3438 else { // the carried row is op.i: op.j is read, op.i written
3439 T* row {m.data() + (op.i * n) + first};
3440 const T* other {m.data() + (op.j * n) + first};
3441 for (std::size_t k {0}; k < count; ++k) {
3442 const T a {carry[k]};
3443 const T b {other[k]};
3444 row[k] = (c * a) + (s * b);
3445 carry[k] = (c * b) - (s * a);
3446 }
3447 }
3448 }
3449 const row_operation& end {operations[run.first + run.count - 1]};
3450 T* target {m.data() + ((run.down ? end.i : end.j) * n) + first};
3451 std::copy(carry.begin(), carry.begin() + static_cast<std::ptrdiff_t>(count), target);
3452 continue;
3453 }
3454 T* row {m.data() + (start.i * n) + first};
3455 T* other {m.data() + (start.j * n) + first};
3456 // The kind is tested outside the column loop, which then vectorises.
3457 if (start.what == row_operation::kind::rotate) {
3458 const T c {detail::from_double<T>(start.c)};
3459 const T s {detail::from_double<T>(start.s)};
3460 for (std::size_t k {0}; k < count; ++k) {
3461 const T a {row[k]};
3462 const T b {other[k]};
3463 row[k] = (c * a) + (s * b);
3464 other[k] = (c * b) - (s * a);
3465 }
3466 }
3467 else if (start.what == row_operation::kind::negate) {
3468 for (std::size_t k {0}; k < count; ++k) {
3469 row[k] = -row[k];
3470 }
3471 }
3472 else {
3473 std::swap_ranges(row, row + count, other);
3474 }
3475 }
3476 });
3477 }
3478
3490 template <typename T>
3492 buffer<double>& e,
3493 buffer<T>& zt,
3494 std::size_t n,
3495 std::size_t max_steps)
3496 {
3497 const double epsilon {std::numeric_limits<double>::epsilon()};
3498 double shift {0.0};
3499 double largest {0.0};
3500 std::size_t steps {0};
3501 // The iteration reads only d and e: its rotations of zt's rows are recorded and applied once at the
3502 // end, in parallel by columns.
3503 std::vector<row_operation> on_zt;
3504 const bool record {!zt.empty()}; // the eigenvalues alone record nothing
3505 for (std::size_t l {0}; l < n; ++l) {
3506 largest = std::max(largest, std::abs(d[l]) + std::abs(e[l]));
3507 std::size_t m {l};
3508 while (std::abs(e[m]) > epsilon * largest) { // e[n-1] = 0 stops it
3509 ++m;
3510 }
3511 while (m > l && std::abs(e[l]) > epsilon * largest) {
3512 if (steps == max_steps) {
3513 throw convergence_error("symmetric eigendecomposition: QL did not converge within its steps");
3514 }
3515 ++steps;
3516 const double g0 {d[l]};
3517 double p {(d[l + 1] - g0) / (2.0 * e[l])};
3518 double r {rotation_length(p, 1.0)};
3519 r = p < 0.0 ? -r : r;
3520 d[l] = e[l] / (p + r);
3521 d[l + 1] = e[l] * (p + r);
3522 const double dl1 {d[l + 1]};
3523 const double h0 {g0 - d[l]};
3524 for (std::size_t i {l + 2}; i < n; ++i) {
3525 d[i] -= h0;
3526 }
3527 shift += h0;
3528 p = d[m];
3529 double c {1.0};
3530 double c2 {1.0};
3531 double c3 {1.0};
3532 const double el1 {e[l + 1]};
3533 double s {0.0};
3534 double s2 {0.0};
3535 for (std::size_t i {m}; i-- > l;) {
3536 c3 = c2;
3537 c2 = c;
3538 s2 = s;
3539 const double g {c * e[i]};
3540 const double h {c * p};
3541 r = rotation_length(p, e[i]);
3542 e[i + 1] = s * r;
3543 s = e[i] / r;
3544 c = p / r;
3545 p = (c * d[i]) - (s * g);
3546 d[i + 1] = h + (s * ((c * g) + (s * d[i])));
3547 // rows i and i+1 (columns i and i+1 of Z): upper ← c upper − s lower, lower ← c lower + s upper
3548 if (record) {
3549 on_zt.push_back({row_operation::kind::rotate, i, i + 1, c, -s});
3550 }
3551 }
3552 p = -s * s2 * c3 * el1 * e[l] / dl1;
3553 e[l] = s * p;
3554 d[l] = c * p;
3555 }
3556 d[l] += shift;
3557 e[l] = 0.0;
3558 }
3559 if (record) {
3560 apply_row_operations(zt, n, on_zt);
3561 }
3562 }
3563
3565 inline constexpr std::size_t divide_leaf {32};
3566
3569 inline constexpr std::size_t secular_iterations {100};
3570
3573 {
3574 std::size_t origin;
3575 double offset;
3576 };
3577
3586 [[nodiscard]] inline double quadratic_step(double a,
3587 double b,
3588 double c)
3589 {
3590 const double root {std::sqrt(std::abs((a * a) - (4.0 * b * c)))};
3591 return a <= 0.0 ? (a - root) / (2.0 * c) : (2.0 * b) / (a + root);
3592 }
3593
3612 template <typename Offset>
3614 double rho,
3615 std::size_t i,
3616 std::size_t max_iterations,
3617 double* delta,
3618 const Offset& offset)
3619 {
3620 const std::size_t k {z.size()};
3621 const double rhoinv {1.0 / rho};
3622 const bool last {i + 1 == k};
3623 double weight {0.0};
3624 for (const double element : z) {
3625 weight += element * element;
3626 }
3627 std::size_t origin {i};
3628 double lower {0.0};
3629 double upper {2.0 * rho * weight}; // the last root is at most ρ‖z‖² past its pole: the equation is positive here
3630 if (!last) {
3631 const double gap {offset(i, i + 1)};
3632 double f {rhoinv};
3633 for (std::size_t j {0}; j < k; ++j) {
3634 f += z[j] * z[j] / (offset(i, j) - (gap / 2.0));
3635 }
3636 upper = gap / 2.0;
3637 if (f < 0.0) { // the root is nearer the pole past it
3638 origin = i + 1;
3639 lower = -gap / 2.0;
3640 upper = 0.0;
3641 }
3642 }
3643 double tau {(lower + upper) / 2.0};
3644 for (std::size_t iteration {0}; iteration < max_iterations; ++iteration) {
3645 double psi {0.0}; // the terms of the poles up to i, and their derivative
3646 double dpsi {0.0};
3647 double phi {0.0}; // those of the poles past i
3648 double dphi {0.0};
3649 // The two sides apart, each in its order: a term chose its sum by a branch (an SVD of order 128 on one arm64
3650 // core: 1.87 → 1.68 ms, eigh 1.02 → 0.82).
3651 for (std::size_t j {0}; j <= i; ++j) {
3652 delta[j] = offset(origin, j) - tau;
3653 const double t {z[j] / delta[j]};
3654 psi += z[j] * t;
3655 dpsi += t * t;
3656 }
3657 for (std::size_t j {i + 1}; j < k; ++j) {
3658 delta[j] = offset(origin, j) - tau;
3659 const double t {z[j] / delta[j]};
3660 phi += z[j] * t;
3661 dphi += t * t;
3662 }
3663 const double w {rhoinv + psi + phi};
3664 if (std::abs(w) <= std::numeric_limits<double>::epsilon()
3665 * ((8.0 * (phi - psi + rhoinv)) + (3.0 * std::abs(tau) * (dpsi + dphi)))) {
3666 return {.origin = origin, .offset = tau};
3667 }
3668 (w < 0.0 ? lower : upper) = tau; // the equation increases with x
3669 // The fit c + s₁/(δᵢ − η) + s₂/(δᵢ₊₁ − η) matching the value and the slope of each side: C η² − A η + B = 0
3670 // (the last root has no pole to its right: its δᵢ₊₁ term is absent, A = δᵢ w − δᵢ² ψ' over C = w − δᵢ ψ').
3671 const double a1 {delta[i]};
3672 const double a2 {last ? 0.0 : delta[i + 1]};
3673 const double c {w - (a1 * dpsi) - (a2 * dphi)};
3674 const double a {last ? (a1 * c) + (a1 * a1 * dpsi) : ((a1 + a2) * w) - (a1 * a2 * (dpsi + dphi))};
3675 const double b {last ? 0.0 : a1 * a2 * w};
3676 const double next {tau + (last ? a / c : quadratic_step(a, b, c))};
3677 // A step out of the bracket (or not a number): bisect. The test above stops within a few units in the
3678 // last place of the root, before the bracket runs out of points.
3679 tau = next > lower && next < upper ? next : (lower + upper) / 2.0;
3680 }
3681 throw convergence_error("the secular equation did not converge");
3682 }
3683
3695 [[nodiscard]] inline double secular_root(const buffer<double>& d,
3696 const buffer<double>& z,
3697 double rho,
3698 std::size_t i,
3699 std::size_t max_iterations,
3700 double* delta)
3701 {
3702 const secular_solution root {solve_secular(z, rho, i, max_iterations, delta,
3703 [&d](std::size_t o, std::size_t j) { return d[j] - d[o]; })};
3704 return d[root.origin] + root.offset;
3705 }
3706
3708 inline constexpr std::size_t lowner_block {32};
3709
3712 {
3713 std::unique_ptr<double[]> data;
3714 std::size_t size;
3715 };
3716
3720 {
3722 workspace_buffer updated {};
3723 workspace_buffer columns {};
3725 };
3726
3729 inline constexpr std::size_t first_touch_chunk {std::size_t {1} << 15};
3730
3737 [[nodiscard]] inline double* workspace_of(workspace_buffer& buffer,
3738 std::size_t size)
3739 {
3740 if (buffer.size < size) {
3741 buffer.data = std::make_unique_for_overwrite<double[]>(size);
3742 buffer.size = size;
3743 double* const elements {buffer.data.get()};
3744 parallel_for((size + first_touch_chunk - 1) / first_touch_chunk, first_touch_chunk, [&](std::size_t chunk) {
3745 const std::size_t first {chunk * first_touch_chunk};
3746 std::fill(elements + first, elements + std::min(first + first_touch_chunk, size), 0.0);
3747 });
3748 }
3749 return buffer.data.get();
3750 }
3751
3754 inline constexpr std::size_t kept_workspace {std::size_t {1} << 18};
3755
3761 [[nodiscard]] inline merge_workspace& thread_workspace()
3762 {
3763 thread_local merge_workspace work;
3764 return work;
3765 }
3766
3769 {
3770 for (workspace_buffer* buffer : {&work.gaps, &work.updated, &work.columns, &work.factor}) {
3771 if (buffer->size > kept_workspace) {
3772 buffer->data.reset();
3773 buffer->size = 0;
3774 }
3775 }
3776 }
3777
3792 double rho,
3793 double* q,
3794 std::size_t ldq,
3795 std::size_t n,
3796 std::size_t split,
3797 merge_workspace& work)
3798 {
3799 // Which halves of the rows each column reaches: 1 the upper, 2 the lower.
3800 std::vector<unsigned> reach(n);
3801 for (std::size_t j {0}; j < n; ++j) {
3802 reach[j] = j < split ? 1U : 2U;
3803 }
3804 for (double& element : z) { // ‖z‖ = √2: the rows of two orthogonal matrices
3805 element /= std::numbers::sqrt2;
3806 }
3807 rho *= 2.0;
3808 std::vector<std::size_t> order(n);
3809 for (std::size_t j {0}; j < n; ++j) {
3810 order[j] = j;
3811 }
3812 std::sort(order.begin(), order.end(), [&d](std::size_t x, std::size_t y) { return d[x] < d[y]; });
3813 double largest_d {0.0};
3814 double largest_z {0.0};
3815 for (std::size_t j {0}; j < n; ++j) {
3816 largest_d = std::max(largest_d, std::abs(d[j]));
3817 largest_z = std::max(largest_z, std::abs(z[j]));
3818 }
3819 const double tolerance {8.0 * std::numeric_limits<double>::epsilon() * std::max(largest_d, largest_z)};
3820 std::vector<std::size_t> kept; // the columns the secular equation updates, by ascending d
3821 std::vector<std::size_t> deflated; // the columns that keep their eigenvalue
3822 std::size_t pending {n}; // the last column kept so far, still open to a rotation with the next
3823 for (const std::size_t j : order) {
3824 if (rho * std::abs(z[j]) <= tolerance) {
3825 deflated.push_back(j);
3826 continue;
3827 }
3828 if (pending == n) {
3829 pending = j;
3830 continue;
3831 }
3832 const double length {std::hypot(z[j], z[pending])};
3833 const double c {z[j] / length};
3834 const double s {-z[pending] / length};
3835 if (std::abs((d[j] - d[pending]) * c * s) <= tolerance) {
3836 // Close poles: a rotation of the two columns zeroes the pending one's weight, which then deflates.
3837 z[j] = length;
3838 z[pending] = 0.0;
3839 for (std::size_t r {0}; r < n; ++r) {
3840 const double x {q[(r * ldq) + pending]};
3841 const double y {q[(r * ldq) + j]};
3842 q[(r * ldq) + pending] = (c * x) + (s * y);
3843 q[(r * ldq) + j] = (c * y) - (s * x);
3844 }
3845 reach[j] = reach[pending] = reach[j] | reach[pending];
3846 const double rotated {(d[pending] * c * c) + (d[j] * s * s)};
3847 d[j] = (d[pending] * s * s) + (d[j] * c * c);
3848 d[pending] = rotated;
3849 deflated.push_back(pending);
3850 }
3851 else {
3852 kept.push_back(pending);
3853 }
3854 pending = j;
3855 }
3856 if (pending != n) {
3857 kept.push_back(pending);
3858 }
3859 const std::size_t k {kept.size()};
3860 buffer<double> poles(k, double {});
3861 buffer<double> weights(k, double {});
3862 double norm {0.0};
3863 for (std::size_t i {0}; i < k; ++i) {
3864 poles[i] = d[kept[i]];
3865 weights[i] = z[kept[i]];
3866 norm += weights[i] * weights[i];
3867 }
3868 norm = std::sqrt(norm);
3869 for (double& weight : weights) {
3870 weight /= norm;
3871 }
3872 const double scaled_rho {rho * norm * norm};
3873 buffer<double> roots(k, double {});
3874 // gaps[i * k + j] = poles[j] − roots[i], every element written by its root's solve.
3875 double* const gaps {workspace_of(work.gaps, k * k)};
3876 parallel_for(k, 8 * k, [&](std::size_t i) {
3877 roots[i] = secular_root(poles, weights, scaled_rho, i, secular_iterations, gaps + (i * k));
3878 });
3879 // Löwner: ẑⱼ² = −Πᵢ (dⱼ − λᵢ) / Πᵢ≠ⱼ (dⱼ − dᵢ), each pole paired with its root, by blocks of poles, each pole's
3880 // product in ascending order of the roots (a row of gaps at a time, along the block): the same operations in
3881 // the same order on any number of threads.
3882 buffer<double> signed_weights(k, double {});
3883 parallel_for((k + lowner_block - 1) / lowner_block, lowner_block * k, [&](std::size_t block) {
3884 const std::size_t first {block * lowner_block};
3885 const std::size_t last {std::min(first + lowner_block, k)};
3886 std::array<double, lowner_block> product {};
3887 for (std::size_t j {first}; j < last; ++j) {
3888 product[j - first] = gaps[(j * k) + j];
3889 }
3890 for (std::size_t i {0}; i < k; ++i) {
3891 for (std::size_t j {first}; j < last; ++j) {
3892 if (i != j) {
3893 product[j - first] *= gaps[(i * k) + j] / (poles[j] - poles[i]);
3894 }
3895 }
3896 }
3897 for (std::size_t j {first}; j < last; ++j) {
3898 signed_weights[j] = std::copysign(std::sqrt(-product[j - first]), weights[j]);
3899 }
3900 });
3901 // u[j][i] = ẑⱼ / (dⱼ − λᵢ), column i roots[i]'s eigenvector in the kept basis, in gaps' place: gaps[i][j] and
3902 // gaps[j][i] are read, then each written as the other's quotient, tile by tile across the diagonal.
3903 double* const u {gaps};
3904 const std::size_t tiles {(k + lowner_block - 1) / lowner_block};
3905 parallel_for(tiles * tiles, lowner_block * lowner_block, [&](std::size_t tile) {
3906 const std::size_t ti {tile / tiles};
3907 const std::size_t tj {tile % tiles};
3908 if (ti > tj) {
3909 return; // the pair of tiles is (tj, ti)'s
3910 }
3911 for (std::size_t i {ti * lowner_block}; i < std::min((ti + 1) * lowner_block, k); ++i) {
3912 for (std::size_t j {std::max(tj * lowner_block, ti == tj ? i : 0)}; j < std::min((tj + 1) * lowner_block, k); ++j) {
3913 const double above {gaps[(i * k) + j]};
3914 const double below {gaps[(j * k) + i]};
3915 u[(j * k) + i] = signed_weights[j] / above;
3916 u[(i * k) + j] = signed_weights[i] / below;
3917 }
3918 }
3919 });
3920 buffer<double> length(k, 0.0);
3921 for (std::size_t j {0}; j < k; ++j) { // by rows of u, each a contiguous update of every column's sum
3922 for (std::size_t i {0}; i < k; ++i) {
3923 length[i] += u[(j * k) + i] * u[(j * k) + i];
3924 }
3925 }
3926 for (double& l : length) {
3927 l = std::sqrt(l);
3928 }
3929 parallel_for(k, k, [&](std::size_t j) {
3930 for (std::size_t i {0}; i < k; ++i) {
3931 u[(j * k) + i] /= length[i];
3932 }
3933 });
3934 // updated = q(:, kept) · u, by halves of the rows: a half's rows take the kept columns that reach it, and
3935 // u's rows for those, the others being zero there.
3936 double* const updated {workspace_of(work.updated, n * k)};
3937 for (const auto& [half, first, rows] : {std::tuple {1U, std::size_t {0}, split}, std::tuple {2U, split, n - split}}) {
3938 std::vector<std::size_t> reaching;
3939 for (std::size_t i {0}; i < k; ++i) {
3940 if ((reach[kept[i]] & half) != 0U) {
3941 reaching.push_back(i);
3942 }
3943 }
3944 const std::size_t width {reaching.size()};
3945 double* const columns {workspace_of(work.columns, rows * width)};
3946 double* const factor {workspace_of(work.factor, width * k)};
3947 parallel_for(rows, width + k, [&](std::size_t r) {
3948 for (std::size_t c {0}; c < width; ++c) {
3949 columns[(r * width) + c] = q[((first + r) * ldq) + kept[reaching[c]]];
3950 }
3951 std::fill_n(updated + ((first + r) * k), k, 0.0); // the product adds to it
3952 });
3953 parallel_for(width, k, [&](std::size_t c) { std::copy_n(u + (reaching[c] * k), k, factor + (c * k)); });
3954 gemm(rows, k, width, columns, width, factor, k, updated + (first * k), k);
3955 }
3956 // Every eigenpair, then by ascending eigenvalue: column j of the result is column source[j] of the
3957 // updated ones (below k) or deflated column source[j] − k of q, written row by row.
3958 buffer<double> values(n, double {});
3959 for (std::size_t i {0}; i < k; ++i) {
3960 values[i] = roots[i];
3961 }
3962 for (std::size_t i {0}; i < deflated.size(); ++i) {
3963 values[k + i] = d[deflated[i]];
3964 }
3965 for (std::size_t j {0}; j < n; ++j) {
3966 order[j] = j;
3967 }
3968 std::sort(order.begin(), order.end(), [&values](std::size_t x, std::size_t y) { return values[x] < values[y]; });
3969 for (std::size_t j {0}; j < n; ++j) {
3970 d[j] = values[order[j]];
3971 }
3972 parallel_for(n, n, [&](std::size_t r) {
3973 const buffer<double> before(q + (r * ldq), q + (r * ldq) + n);
3974 for (std::size_t j {0}; j < n; ++j) {
3975 const std::size_t source {order[j]};
3976 q[(r * ldq) + j] = source < k ? updated[(r * k) + source] : before[deflated[source - k]];
3977 }
3978 });
3979 }
3980
3987 {
3988 double* d {};
3989 double* work_d {};
3990 const double* work_e {};
3991 std::size_t n {};
3992 double* q {};
3993 std::size_t ldq {};
3994 int exponent {0};
3995 buffer<double> scaled_d {};
3996 buffer<double> scaled_e {};
3997 std::size_t n1 {0};
3998 double beta {};
3999 std::size_t depth {};
4000 std::size_t first {};
4001 std::size_t second {};
4002 };
4003
4012 NUMERICAL_OUT_OF_LINE inline void plan_divide(std::vector<divide_node>& nodes,
4013 double* d,
4014 const double* e,
4015 std::size_t n,
4016 double* q,
4017 std::size_t ldq,
4018 std::size_t depth)
4019 {
4020 double largest {0.0};
4021 for (std::size_t i {0}; i < n; ++i) {
4022 largest = std::max({largest, std::abs(d[i]), i + 1 < n ? std::abs(e[i]) : 0.0});
4023 }
4024 divide_node node {.d = d, .work_d = d, .work_e = e, .n = n, .q = q, .ldq = ldq, .depth = depth};
4025 (void)std::frexp(largest, &node.exponent);
4026 if (node.exponent != 0) {
4027 node.scaled_d.assign(n, double {});
4028 node.scaled_e.assign(n, 0.0);
4029 for (std::size_t i {0}; i < n; ++i) {
4030 node.scaled_d[i] = std::ldexp(d[i], -node.exponent);
4031 node.scaled_e[i] = i + 1 < n ? std::ldexp(e[i], -node.exponent) : 0.0;
4032 }
4033 node.work_d = node.scaled_d.data(); // the buffers' storage stays put as the node moves into the vector
4034 node.work_e = node.scaled_e.data();
4035 }
4036 if (n <= divide_leaf) {
4037 nodes.push_back(std::move(node));
4038 return;
4039 }
4040 node.n1 = n / 2;
4041 node.beta = node.work_e[node.n1 - 1];
4042 node.work_d[node.n1 - 1] -= std::abs(node.beta);
4043 node.work_d[node.n1] -= std::abs(node.beta);
4044 double* const work_d {node.work_d};
4045 const double* const work_e {node.work_e};
4046 const std::size_t n1 {node.n1};
4047 const std::size_t index {nodes.size()};
4048 nodes.push_back(std::move(node));
4049 nodes[index].first = nodes.size();
4050 plan_divide(nodes, work_d, work_e, n1, q, ldq, depth + 1);
4051 nodes[index].second = nodes.size();
4052 plan_divide(nodes, work_d + n1, work_e + n1, n - n1, q + (n1 * ldq) + n1, ldq, depth + 1);
4053 }
4054
4058 {
4059 const std::size_t n {node.n};
4060 buffer<double> diagonal(node.work_d, node.work_d + n);
4061 buffer<double> sub(n, 0.0);
4062 std::copy(node.work_e, node.work_e + (n - 1), sub.begin());
4063 buffer<double> zt(n * n, 0.0);
4064 for (std::size_t i {0}; i < n; ++i) {
4065 zt[(i * n) + i] = 1.0;
4066 }
4067 tridiagonal_ql(diagonal, sub, zt, n, ql_steps_per_value * n);
4068 std::vector<std::size_t> order(n);
4069 for (std::size_t i {0}; i < n; ++i) {
4070 order[i] = i;
4071 }
4072 std::sort(order.begin(), order.end(), [&diagonal](std::size_t x, std::size_t y) { return diagonal[x] < diagonal[y]; });
4073 for (std::size_t j {0}; j < n; ++j) {
4074 node.work_d[j] = diagonal[order[j]];
4075 for (std::size_t r {0}; r < n; ++r) {
4076 node.q[(r * node.ldq) + j] = zt[(order[j] * n) + r];
4077 }
4078 }
4079 }
4080
4083 merge_workspace& work)
4084 {
4085 const std::size_t n {node.n};
4086 const std::size_t n1 {node.n1};
4087 const std::size_t n2 {n - n1};
4088 double* const q {node.q};
4089 buffer<double> z(n, double {});
4090 for (std::size_t i {0}; i < n1; ++i) {
4091 z[i] = q[((n1 - 1) * node.ldq) + i];
4092 }
4093 for (std::size_t i {0}; i < n2; ++i) {
4094 z[n1 + i] = std::copysign(1.0, node.beta) * q[(n1 * node.ldq) + n1 + i];
4095 }
4096 buffer<double> values(node.work_d, node.work_d + n);
4097 merge_rank_one(values, std::move(z), std::abs(node.beta), q, node.ldq, n, n1, work);
4098 std::copy(values.begin(), values.end(), node.work_d);
4099 }
4100
4103 {
4104 if (node.exponent != 0) {
4105 for (std::size_t i {0}; i < node.n; ++i) {
4106 node.d[i] = std::ldexp(node.work_d[i], node.exponent);
4107 }
4108 }
4109 }
4110
4112 NUMERICAL_OUT_OF_LINE inline void solve_divide_subtree(std::vector<divide_node>& nodes,
4113 std::size_t index,
4114 merge_workspace& work)
4115 {
4116 const divide_node& node {nodes[index]};
4117 if (node.n1 == 0) {
4118 solve_divide_leaf(node);
4119 }
4120 else {
4121 solve_divide_subtree(nodes, node.first, work);
4122 solve_divide_subtree(nodes, node.second, work);
4123 join_divide_node(node, work);
4124 }
4125 unscale_divide_node(node);
4126 }
4127
4144 inline void divide_and_conquer(double* d,
4145 const double* e,
4146 std::size_t n,
4147 buffer<double>& q)
4148 {
4149 q.assign(n * n, 0.0);
4150 std::vector<divide_node> nodes;
4151 plan_divide(nodes, d, e, n, q.data(), n, 0);
4152 const auto cut {static_cast<std::size_t>(std::bit_width(max_threads() - 1))};
4153 std::vector<std::size_t> tasks;
4154 for (std::size_t i {0}; i < nodes.size(); ++i) {
4155 if (nodes[i].depth == cut || (nodes[i].depth < cut && nodes[i].n1 == 0)) {
4156 tasks.push_back(i);
4157 }
4158 }
4159 const std::size_t task_order {nodes[tasks[0]].n};
4160 parallel_for(tasks.size(), tasks.size() > 1 ? task_order * task_order * task_order : 0, [&](std::size_t t) {
4161 solve_divide_subtree(nodes, tasks[t], thread_workspace());
4163 });
4164 for (std::size_t level {cut}; level-- > 0;) {
4165 std::vector<std::size_t> joins;
4166 for (std::size_t i {0}; i < nodes.size(); ++i) {
4167 if (nodes[i].n1 != 0 && nodes[i].depth == level) {
4168 joins.push_back(i);
4169 }
4170 }
4171 if (joins.empty()) { // a tree shallower than the cut
4172 continue;
4173 }
4174 if (joins.size() == 1) {
4175 join_divide_node(nodes[joins[0]], thread_workspace());
4177 unscale_divide_node(nodes[joins[0]]);
4178 continue;
4179 }
4180 const std::size_t order {nodes[joins[0]].n};
4181 parallel_for(joins.size(), order * order * order / 4, [&](std::size_t j) {
4182 join_divide_node(nodes[joins[j]], thread_workspace());
4183 release_large_workspace(thread_workspace());
4184 unscale_divide_node(nodes[joins[j]]);
4185 });
4186 }
4187 }
4188
4190 template <typename T>
4192 buffer<T>& zt,
4193 std::size_t n)
4194 {
4195 buffer<T> converted(n * n, T {});
4196 for (std::size_t i {0}; i < n * n; ++i) {
4197 converted[i] = from_double<T>(left[i]);
4198 }
4199 buffer<T> product(n * n, T {});
4200 gemm(n, n, n, converted.data(), n, zt.data(), n, product.data(), n);
4201 zt = std::move(product);
4202 }
4203
4205 inline void multiply_real_left(const buffer<double>& left,
4206 buffer<double>& zt,
4207 std::size_t n)
4208 {
4209 buffer<double> product(n * n, 0.0);
4210 gemm(n, n, n, left.data(), n, zt.data(), n, product.data(), n);
4211 zt = std::move(product);
4212 }
4213
4215 inline void multiply_real_left(const buffer<double>& left,
4216 buffer<std::complex<double>>& zt,
4217 std::size_t n)
4218 {
4219 buffer<double> re(n * n, double {});
4220 buffer<double> im(n * n, double {});
4221 for (std::size_t i {0}; i < n * n; ++i) {
4222 re[i] = zt[i].real();
4223 im[i] = zt[i].imag();
4224 }
4225 multiply_real_left(left, re, n);
4226 multiply_real_left(left, im, n);
4227 for (std::size_t i {0}; i < n * n; ++i) {
4228 zt[i] = {re[i], im[i]};
4229 }
4230 }
4231
4238 template <typename T>
4240 buffer<double>& e,
4241 const tridiagonal_reduction<T>& reduction,
4242 std::size_t n)
4243 {
4244 if (n <= divide_leaf) {
4245 buffer<T> zt {tridiagonal_basis(reduction, n)};
4246 tridiagonal_ql(d, e, zt, n, ql_steps_per_value * n);
4247 return zt;
4248 }
4250 divide_and_conquer(d.data(), e.data(), n, q);
4251 buffer<T> at(n * n, T {}); // row j: the eigenvector of d[j], in the tridiagonal's basis, then the matrix's
4252 parallel_for(n, n, [&](std::size_t j) {
4253 for (std::size_t r {0}; r < n; ++r) {
4254 at[(j * n) + r] = from_double<T>(q[(r * n) + j]);
4255 }
4256 });
4257 to_matrix_basis(reduction, at, n);
4258 return at;
4259 }
4260
4262 [[nodiscard]] inline double jacobi_off_diagonal(const buffer<double>& a,
4263 std::size_t n)
4264 {
4265 double sum {0.0};
4266 for (std::size_t p {0}; p < n; ++p) {
4267 for (std::size_t q {p + 1}; q < n; ++q) {
4268 sum += a[(p * n) + q] * a[(p * n) + q];
4269 }
4270 }
4271 return std::sqrt(2.0 * sum);
4272 }
4273
4276 buffer<double>& v,
4277 std::size_t n,
4278 std::size_t p,
4279 std::size_t q)
4280 {
4281 const double apq {a[(p * n) + q]};
4282 if (apq == 0.0) {
4283 return; // already zero — nothing to rotate
4284 }
4285 const double app {a[(p * n) + p]};
4286 const double aqq {a[(q * n) + q]};
4287 const double theta {(aqq - app) / (2.0 * apq)};
4288 const double t {(theta >= 0.0 ? 1.0 : -1.0) / (std::abs(theta) + std::sqrt((theta * theta) + 1.0))};
4289 const double c {1.0 / std::sqrt((t * t) + 1.0)};
4290 const double s {t * c};
4291 a[(p * n) + p] = (c * c * app) - (2.0 * s * c * apq) + (s * s * aqq);
4292 a[(q * n) + q] = (s * s * app) + (2.0 * s * c * apq) + (c * c * aqq);
4293 a[(p * n) + q] = 0.0;
4294 a[(q * n) + p] = 0.0;
4295 for (std::size_t i {0}; i < n; ++i) {
4296 if (i != p && i != q) {
4297 const double aip {a[(i * n) + p]};
4298 const double aiq {a[(i * n) + q]};
4299 a[(i * n) + p] = (c * aip) - (s * aiq);
4300 a[(p * n) + i] = a[(i * n) + p];
4301 a[(i * n) + q] = (s * aip) + (c * aiq);
4302 a[(q * n) + i] = a[(i * n) + q];
4303 }
4304 }
4305 for (std::size_t i {0}; i < n; ++i) {
4306 const double vip {v[(i * n) + p]};
4307 const double viq {v[(i * n) + q]};
4308 v[(i * n) + p] = (c * vip) - (s * viq);
4309 v[(i * n) + q] = (s * vip) + (c * viq);
4310 }
4311 }
4312
4323 buffer<double>& v,
4324 std::size_t n,
4325 double frobenius,
4326 std::size_t max_sweeps)
4327 {
4328 // The off-diagonal norm strictly decreases each rotation and converges to 0,
4329 // so this terminates; the threshold is relative to the matrix scale.
4330 const double threshold {1e-12 * frobenius};
4331 for (std::size_t sweeps {0}; jacobi_off_diagonal(a, n) > threshold; ++sweeps) {
4332 if (sweeps == max_sweeps) {
4333 throw convergence_error("symmetric eigendecomposition: Jacobi did not converge within its sweeps");
4334 }
4335 for (std::size_t p {0}; p < n; ++p) {
4336 for (std::size_t q {p + 1}; q < n; ++q) {
4337 jacobi_rotate(a, v, n, p, q);
4338 }
4339 }
4340 }
4341 }
4342
4344 [[nodiscard]] inline std::vector<std::size_t> ascending_order(const buffer<double>& diagonal)
4345 {
4346 std::vector<std::size_t> order(diagonal.size());
4347 for (std::size_t i {0}; i < order.size(); ++i) {
4348 order[i] = i;
4349 }
4350 std::sort(order.begin(), order.end(), [&diagonal](std::size_t x, std::size_t y) { return diagonal[x] < diagonal[y]; });
4351 return order;
4352 }
4353
4359 template <typename T>
4360 [[nodiscard]] buffer<T> columns_by_value(const buffer<T>& zt,
4361 const std::vector<std::size_t>& order,
4362 std::size_t n)
4363 {
4364 buffer<T> out(n * n, T {});
4365 parallel_for((n + transpose_tile - 1) / transpose_tile, transpose_tile * n, [&](std::size_t block) {
4366 const std::size_t r0 {block * transpose_tile};
4367 const std::size_t r1 {std::min(r0 + transpose_tile, n)};
4368 for (std::size_t c0 {0}; c0 < n; c0 += transpose_tile) {
4369 for (std::size_t c {c0}; c < std::min(c0 + transpose_tile, n); ++c) {
4370 const T* const source {zt.data() + (order[c] * n)};
4371 for (std::size_t r {r0}; r < r1; ++r) {
4372 out[(r * n) + c] = source[r];
4373 }
4374 }
4375 }
4376 });
4377 return out;
4378 }
4379 } // namespace detail
4380
4382 enum class eigen_method
4383 {
4384 tridiagonal_ql,
4385 jacobi,
4386 divide_and_conquer
4387 };
4388
4402 {
4403 public:
4404
4412 explicit symmetric_eigen(const matrix<double>& input,
4413 eigen_method method = eigen_method::divide_and_conquer)
4414 : n_(input.rows())
4415 {
4416 if (input.cols() != n_) {
4417 throw dimension_error("symmetric eigendecomposition requires a square matrix");
4418 }
4419 detail::require_finite_lower(input, "symmetric eigendecomposition");
4420 // Work on the matrix divided by its largest entry: a sum of squares of entries near 1e170
4421 // overflows and of entries near 1e-170 underflows to 0, and either stops the sweeps before a
4422 // single rotation. The eigenvalues are scaled back; the eigenvectors do not depend on it.
4423 double scale {0.0};
4424 for (std::size_t i {0}; i < n_; ++i) {
4425 for (std::size_t j {0}; j <= i; ++j) {
4426 scale = std::max(scale, std::abs(input(i, j)));
4427 }
4428 }
4429 if (scale == 0.0) {
4430 scale = 1.0;
4431 }
4432 buffer<double> a(n_ * n_, double {});
4433 buffer<double> diagonal(n_, double {});
4434 if (method == eigen_method::jacobi) {
4435 buffer<double> v(n_ * n_, 0.0);
4436 double frobenius_squared {0.0};
4437 for (std::size_t i {0}; i < n_; ++i) {
4438 v[(i * n_) + i] = 1.0;
4439 for (std::size_t j {0}; j < n_; ++j) {
4440 a[(i * n_) + j] = input(std::max(i, j), std::min(i, j)) / scale; // the lower triangle, mirrored
4441 frobenius_squared += a[(i * n_) + j] * a[(i * n_) + j];
4442 }
4443 }
4444 detail::cyclic_jacobi(a, v, n_, std::sqrt(frobenius_squared), detail::jacobi_sweeps);
4445 for (std::size_t i {0}; i < n_; ++i) {
4446 diagonal[i] = a[(i * n_) + i] * scale;
4447 }
4448 extract_sorted(diagonal, v);
4449 return;
4450 }
4451 for (std::size_t i {0}; i < n_; ++i) { // the lower triangle alone: the reduction reads nothing above it
4452 for (std::size_t j {0}; j <= i; ++j) {
4453 a[(i * n_) + j] = input(i, j) / scale;
4454 }
4455 }
4456 buffer<double> zt;
4457 if (n_ > 0) {
4458 buffer<double> off(n_, double {});
4459 const detail::tridiagonal_reduction<double> reduction {detail::tridiagonalize(a, n_, diagonal, off)};
4460 if (method == eigen_method::tridiagonal_ql) {
4461 zt = detail::tridiagonal_basis(reduction, n_);
4462 detail::tridiagonal_ql(diagonal, off, zt, n_, detail::ql_steps_per_value * n_);
4463 }
4464 else {
4465 zt = detail::tridiagonal_divide_and_conquer(diagonal, off, reduction, n_);
4466 }
4467 }
4468 const std::vector<std::size_t> order {detail::ascending_order(diagonal)};
4469 buffer<double> values(n_, double {});
4470 for (std::size_t c {0}; c < n_; ++c) {
4471 values[c] = diagonal[order[c]] * scale;
4472 }
4473 values_ = vector<double> {std::move(values)};
4474 vectors_ = matrix<double> {n_, n_, detail::columns_by_value(zt, order, n_)};
4475 }
4476
4478 [[nodiscard]] const vector<double>& values() const noexcept
4479 {
4480 return values_;
4481 }
4482
4484 [[nodiscard]] const matrix<double>& vectors() const& noexcept
4485 {
4486 return vectors_;
4487 }
4488
4490 [[nodiscard]] matrix<double> vectors() &&
4491 {
4492 return std::move(vectors_);
4493 }
4494
4495 private:
4496
4498 void extract_sorted(const buffer<double>& diagonal,
4499 const buffer<double>& v)
4500 {
4501 std::vector<std::size_t> order(n_);
4502 for (std::size_t i {0}; i < n_; ++i) {
4503 order[i] = i;
4504 }
4505 std::sort(order.begin(), order.end(),
4506 [&diagonal](std::size_t x, std::size_t y) {
4507 return diagonal[x] < diagonal[y];
4508 });
4509 buffer<double> values(n_, double {});
4510 buffer<double> vectors(n_ * n_, double {});
4511 for (std::size_t col {0}; col < n_; ++col) {
4512 values[col] = diagonal[order[col]];
4513 for (std::size_t row {0}; row < n_; ++row) {
4514 vectors[(row * n_) + col] = v[(row * n_) + order[col]];
4515 }
4516 }
4517 values_ = vector<double> {std::move(values)};
4518 vectors_ = matrix<double> {n_, n_, std::move(vectors)};
4519 }
4520
4521 std::size_t n_;
4524 };
4525
4537 {
4538 public:
4539
4551 explicit hermitian_eigen(const matrix<std::complex<double>>& input,
4552 eigen_method method = eigen_method::divide_and_conquer)
4553 : n_(input.rows())
4554 {
4555 if (method == eigen_method::jacobi) {
4556 throw std::invalid_argument("Hermitian eigendecomposition: the Jacobi method is for real symmetric matrices");
4557 }
4558 if (input.cols() != n_) {
4559 throw dimension_error("Hermitian eigendecomposition requires a square matrix");
4560 }
4561 detail::require_finite_lower(input, "Hermitian eigendecomposition");
4562 // Work on the matrix divided by its largest entry, as symmetric_eigen does; the vectors do not depend
4563 // on it. The entries read: the lower triangle, and the diagonal's real part (its imaginary part is ignored,
4564 // so it scales nothing: an infinite one made every value NaN, a huge one wrong).
4565 double scale {0.0};
4566 for (std::size_t i {0}; i < n_; ++i) {
4567 for (std::size_t j {0}; j < i; ++j) {
4568 scale = std::max(scale, std::abs(input(i, j)));
4569 }
4570 scale = std::max(scale, std::abs(input(i, i).real()));
4571 }
4572 if (scale == 0.0) {
4573 scale = 1.0;
4574 }
4575 buffer<std::complex<double>> a(n_ * n_, std::complex<double> {});
4576 for (std::size_t i {0}; i < n_; ++i) { // the lower triangle alone, a real diagonal: the reduction reads no more
4577 for (std::size_t j {0}; j <= i; ++j) {
4578 a[(i * n_) + j] = (i == j ? std::complex<double> {input(i, j).real(), 0.0} : input(i, j)) / scale;
4579 }
4580 }
4581 buffer<double> diagonal(n_, double {});
4583 if (n_ > 0) {
4584 buffer<double> off(n_, double {});
4585 const detail::tridiagonal_reduction<std::complex<double>> reduction {detail::tridiagonalize(a, n_, diagonal, off)};
4586 if (method == eigen_method::tridiagonal_ql) {
4587 zt = detail::tridiagonal_basis(reduction, n_);
4588 detail::tridiagonal_ql(diagonal, off, zt, n_, detail::ql_steps_per_value * n_);
4589 }
4590 else {
4591 zt = detail::tridiagonal_divide_and_conquer(diagonal, off, reduction, n_);
4592 }
4593 }
4594 const std::vector<std::size_t> order {detail::ascending_order(diagonal)};
4595 buffer<double> values(n_, double {});
4596 for (std::size_t c {0}; c < n_; ++c) {
4597 values[c] = diagonal[order[c]] * scale;
4598 }
4599 values_ = vector<double> {std::move(values)};
4600 vectors_ = matrix<std::complex<double>> {n_, n_, detail::columns_by_value(zt, order, n_)};
4601 }
4602
4604 [[nodiscard]] const vector<double>& values() const noexcept
4605 {
4606 return values_;
4607 }
4608
4610 [[nodiscard]] const matrix<std::complex<double>>& vectors() const& noexcept
4611 {
4612 return vectors_;
4613 }
4614
4617 {
4618 return std::move(vectors_);
4619 }
4620
4621 private:
4622
4623 std::size_t n_;
4626 };
4627
4628 namespace detail {
4629
4632 inline constexpr double jacobi_tiny {std::numeric_limits<double>::min() / std::numeric_limits<double>::epsilon()};
4633
4635 inline constexpr double jacobi_large {1e150};
4636
4647 template <typename T>
4649 buffer<T>& v,
4650 std::size_t n,
4651 std::size_t max_sweeps)
4652 {
4653 // The sweeps rotate columns: work on the transposes, where each column is a contiguous row. A walk
4654 // down the columns of the row-major arrays strides by n on every access and runs several times slower.
4655 const auto transpose_square {[n](buffer<T>& m) {
4656 for (std::size_t i {0}; i < n; ++i) {
4657 for (std::size_t j {i + 1}; j < n; ++j) {
4658 std::swap(m[(i * n) + j], m[(j * n) + i]);
4659 }
4660 }
4661 }};
4662 transpose_square(w);
4663 transpose_square(v);
4664 // Orthogonal to the rounding of the columns' storage: a float column holds no inner product to double's.
4665 const double epsilon {static_cast<double>(std::numeric_limits<value_range_t<T>>::epsilon())};
4666 for (std::size_t sweep {1};; ++sweep) {
4667 bool rotated {false};
4668 for (std::size_t p {0}; p < n; ++p) {
4669 for (std::size_t q {p + 1}; q < n; ++q) {
4670 const T* wp {w.data() + (p * n)};
4671 const T* wq {w.data() + (q * n)};
4672 double alpha {0.0};
4673 double beta {0.0};
4674 T gamma {};
4675 for (std::size_t i {0}; i < n; ++i) {
4676 alpha += static_cast<double>(std::norm(wp[i]));
4677 beta += static_cast<double>(std::norm(wq[i]));
4678 gamma += conj_of(wp[i]) * wq[i];
4679 }
4680 double magnitude {static_cast<double>(std::abs(gamma))};
4681 double zeta {};
4682 if (std::min(alpha, beta) < jacobi_tiny) {
4683 // A column whose squares underflow (rows of a far smaller scale than the largest): its sum of squares
4684 // is 0 or has lost its digits, and the test below never holds. The pair is judged on the columns over
4685 // their norms, which need no squares: the cosine, and ζ from the ratio of the norms.
4686 const double norm_p {scaled_norm(wp, n)};
4687 const double norm_q {scaled_norm(wq, n)};
4688 if (norm_p == 0.0 || norm_q == 0.0) {
4689 continue; // a zero column is orthogonal to any other
4690 }
4691 T cosine {};
4692 for (std::size_t i {0}; i < n; ++i) {
4693 cosine += conj_of(wp[i] / detail::from_double<T>(norm_p)) * (wq[i] / detail::from_double<T>(norm_q));
4694 }
4695 const double size {static_cast<double>(std::abs(cosine))};
4696 zeta = ((norm_q / norm_p) - (norm_p / norm_q)) / (2.0 * size);
4697 if (size <= epsilon) {
4698 continue; // orthogonal to rounding
4699 }
4700 gamma = cosine; // the phase is γ's
4701 magnitude = size;
4702 }
4703 else {
4704 if (magnitude <= epsilon * std::sqrt(alpha) * std::sqrt(beta)) {
4705 continue; // already orthogonal to rounding (or a zero column)
4706 }
4707 zeta = (beta - alpha) / (2.0 * magnitude);
4708 }
4709 // Past jacobi_large, ζ² overflows and t would be 0 (no rotation, ever): there |ζ| + √(1 + ζ²) is 2|ζ| exactly,
4710 // and t is 1 / (2|ζ|). A tangent that still underflows to 0 is a rotation below the smallest double.
4711 const double t {(zeta >= 0.0 ? 1.0 : -1.0) * (std::abs(zeta) > jacobi_large ? 0.5 / std::abs(zeta) : 1.0 / (std::abs(zeta) + std::sqrt(1.0 + (zeta * zeta))))};
4712 if (t == 0.0) {
4713 continue; // a rotation below the smallest double: the pair is as orthogonal as it can be
4714 }
4715 rotated = true;
4716 // Turn column q by the phase of γ so that the pair's inner product is real, then rotate.
4717 const T turn {conj_of(gamma) / detail::from_double<T>(magnitude)};
4718 const double c {1.0 / std::sqrt(1.0 + (t * t))};
4719 const double s {c * t};
4720 for (buffer<T>* side : {&w, &v}) {
4721 T* mp {side->data() + (p * n)};
4722 T* mq {side->data() + (q * n)};
4723 for (std::size_t i {0}; i < n; ++i) {
4724 const T left {mp[i]};
4725 const T right {turn * mq[i]};
4726 mp[i] = (detail::from_double<T>(c) * left) - (detail::from_double<T>(s) * right);
4727 mq[i] = (detail::from_double<T>(s) * left) + (detail::from_double<T>(c) * right);
4728 }
4729 }
4730 }
4731 }
4732 if (!rotated) {
4733 transpose_square(w);
4734 transpose_square(v);
4735 return;
4736 }
4737 if (sweep == max_sweeps) {
4738 throw convergence_error("SVD: one-sided Jacobi did not converge within its sweeps");
4739 }
4740 }
4741 }
4742
4745 inline constexpr std::size_t svd_qr_steps_per_value {30};
4746
4750 inline constexpr std::size_t bidiagonal_crossover {128};
4751
4755 inline constexpr std::size_t bidiagonal_basis_crossover {64};
4756
4769 template <typename T>
4770 void form_bidiagonal_bases(const std::vector<buffer<T>>& left,
4771 std::vector<buffer<T>> right,
4772 const buffer<T>& left_phase,
4773 const buffer<T>& right_phase,
4774 std::size_t n,
4775 buffer<T>& ut,
4776 buffer<T>& vt)
4777 {
4778 // U_b = L₀ᴴ L₁ᴴ … with L_kᴴ = H_k diag(conj phase at k), and V_b = R₀ R₁ … with R_k = H'_k diag(phase
4779 // at k+1). A phase on row k commutes with every later reflector, which acts on rows past it, so
4780 // U_b = (H₀ H₁ …) diag(conj phases) and V_b = (H'₀ H'₁ …) diag(phases): the reflectors are applied to the
4781 // diagonal by blocks, through the matrix product kernel, as QR's Q is formed.
4782 ut.assign(n * n, T {}); // Uᵀ and Vᵀ, formed transposed: diagonal to start, the reflectors applied to them
4783 vt.assign(n * n, T {});
4784 for (std::size_t k {0}; k < n; ++k) {
4785 ut[(k * n) + k] = conj_of(left_phase[k]);
4786 vt[(k * n) + k] = k == 0 ? T {1} : right_phase[k - 1];
4787 }
4788 std::vector<buffer<T>> shifted(n); // right[k] acts from row k+1: as reflector k+1 of a QR
4789 for (std::size_t k {0}; k + 1 < n; ++k) {
4790 shifted[k + 1] = std::move(right[k]);
4791 }
4792 if (n < bidiagonal_basis_crossover) { // a reflector at a time, the last first, as LAPACK's dorg2r
4793 for (std::size_t k {n}; k-- > 0;) {
4794 reflect_columns(ut.data(), n, left[k], k, k, n);
4795 reflect_columns(vt.data(), n, shifted[k], k, k, n);
4796 }
4797 return;
4798 }
4799 for (std::size_t k1 {n}; k1 > 0;) { // blocks of qr_block reflectors, the last first
4800 const std::size_t k0 {(k1 - 1) / qr_block * qr_block};
4801 apply_transposed_reflector_block(gather_reflectors(left, k0, k1, n), ut, n, k0, n);
4802 apply_transposed_reflector_block(gather_reflectors(shifted, k0, k1, n), vt, n, k0, n);
4803 k1 = k0;
4804 }
4805 }
4806
4811#ifdef NUMERICAL_X86_COMPILED
4812 inline constexpr std::size_t bidiagonal_shared_least {256};
4813#endif
4814
4816 inline constexpr std::size_t bidiagonal_block {32};
4817
4821#ifdef NUMERICAL_X86_COMPILED
4822 inline constexpr std::size_t bidiagonal_product_weight {4};
4823#else
4824 inline constexpr std::size_t bidiagonal_product_weight {1};
4825#endif
4826
4827 template <typename T>
4828 void bidiagonalize_unblocked(buffer<T>& b,
4829 std::size_t n,
4830 buffer<double>& d,
4831 buffer<double>& e,
4832 buffer<T>& ut,
4833 buffer<T>& vt,
4834 bool form = true);
4835
4840 template <typename T>
4842 {
4843 const double up {rescale_tiny(x)}; // the reflector is the same at any scale; β is scaled back
4844 const auto norm {static_cast<T>(scaled_norm(x))};
4845 const T beta {-phase_of(x[0]) * norm};
4846 x[0] -= beta;
4847 const auto length {static_cast<T>(scaled_norm(x))};
4848 for (T& element : x) {
4849 element /= length;
4850 }
4851 return beta / static_cast<T>(up);
4852 }
4853
4869 template <typename T>
4870 requires std::is_floating_point_v<T>
4872 std::size_t n,
4873 buffer<double>& d,
4874 buffer<double>& e,
4875 buffer<T>& ut,
4876 buffer<T>& vt,
4877 bool form = true)
4878 {
4879 if (n < bidiagonal_crossover) {
4880 bidiagonalize_unblocked(b, n, d, e, ut, vt, form);
4881 return;
4882 }
4883 std::vector<buffer<T>> left(n); // the left reflector of step k, on rows k…, empty where none was needed
4884 std::vector<buffer<T>> right(n); // the right reflector of step k, on columns k+1…, likewise
4885 const auto nonzero {[](const T& value) { return value != T {}; }};
4886 // By reflector, each row over the whole order: row 2p of vx holds step k0 + p's left reflector v (at its rows),
4887 // row 2p + 1 what its right one moves, x = 2 B u (at the rows); row 2p of yu what v moves, y = 2 Bᵀ v (at the
4888 // columns), row 2p + 1 the right reflector u (at its columns). A step's terms of the panel's earlier ones are then
4889 // one update through their rows (subtract_products, a family's rows 2n apart), in their order.
4890 buffer<T> vx(2 * bidiagonal_block * n);
4891 buffer<T> yu(2 * bidiagonal_block * n);
4892 buffer<T> columns(bidiagonal_block * n); // the panel's columns of B, gathered row by row
4893 buffer<T> factors(2 * bidiagonal_block); // a step's coefficients of those rows
4894#ifdef NUMERICAL_X86_COMPILED
4895 buffer<T> next_factors(2 * bidiagonal_block); // the next column's, applied ahead (a shared step)
4896#endif
4897 for (std::size_t k0 {0}; k0 < n; k0 += bidiagonal_block) {
4898 const std::size_t k1 {std::min(k0 + bidiagonal_block, n)};
4899 const std::size_t width {k1 - k0};
4900 std::fill_n(vx.data(), 2 * width * n, T {});
4901 std::fill_n(yu.data(), 2 * width * n, T {});
4902 for (std::size_t r {k0}; r < n; ++r) { // column k from row k down, each row's part read once
4903 for (std::size_t k {k0}; k < std::min(k1, r + 1); ++k) {
4904 columns[((k - k0) * n) + r] = b[(r * n) + k];
4905 }
4906 }
4907 const auto v_row {[&vx, n](std::size_t p, std::size_t from) { return vx.data() + (2 * p * n) + from; }};
4908 const auto x_row {[&vx, n](std::size_t p, std::size_t from) { return vx.data() + (((2 * p) + 1) * n) + from; }};
4909 const auto y_row {[&yu, n](std::size_t p, std::size_t from) { return yu.data() + (2 * p * n) + from; }};
4910 const auto u_row {[&yu, n](std::size_t p, std::size_t from) { return yu.data() + (((2 * p) + 1) * n) + from; }};
4911#ifdef NUMERICAL_X86_COMPILED
4912 bool column_ahead {false}; // whether the step's column took the steps before the last ahead (a shared step)
4913#endif
4914 for (std::size_t k {k0}; k < k1; ++k) {
4915 const std::size_t i {k - k0};
4916#ifdef NUMERICAL_X86_COMPILED
4917 const std::size_t from {column_ahead ? i - 1 : 0};
4918 column_ahead = false;
4919#else
4920 constexpr std::size_t from {0};
4921#endif
4922 for (std::size_t p {from}; p < i; ++p) { // v's row by y's element, x's by u's
4923 factors[2 * (p - from)] = *y_row(p, k);
4924 factors[(2 * (p - from)) + 1] = *u_row(p, k);
4925 }
4926 T* const column {columns.data() + (i * n) + k}; // column k from the diagonal down, the panel's earlier steps applied
4927 subtract_products(column, factors.data(), v_row(from, k), n, 2 * (i - from), n - k);
4928 buffer<T> x(column, column + (n - k));
4929 if (std::any_of(x.begin() + 1, x.end(), nonzero)) { // something below the diagonal to reflect
4930 d[k] = static_cast<double>(householder_in_place(x));
4931 // y = 2 (B − V Yᵀ − X Uᵀ)ᵀ v on the columns past k: Bᵀ v by contiguous rows, then the panel's terms.
4932 buffer<T> by_vx(2 * i, T {}); // Vᵀ v and Xᵀ v over the earlier steps, interleaved as their rows
4933 for (std::size_t p {0}; p < i; ++p) {
4934 by_vx[2 * p] = dot_product(v_row(p, k), x.data(), n - k);
4935 by_vx[(2 * p) + 1] = dot_product(x_row(p, k), x.data(), n - k);
4936 }
4937 const std::size_t span {n - k - 1};
4938 constexpr std::size_t chunk_width {64};
4939 // Bᵀ v as the terms −(−v)·B, the chunk held through them (negation is exact, so each column takes the same
4940 // additions): row by row, each term stored the chunk back (singular values of order 512 on one x86-64
4941 // core: 46 → 42 ms).
4942 buffer<T> minus(x.size(), T {});
4943 std::transform(x.begin(), x.end(), minus.begin(), std::negate<T> {});
4944 parallel_for((span + chunk_width - 1) / chunk_width, bidiagonal_product_weight * (n - k) * chunk_width, [&](std::size_t chunk) {
4945 const std::size_t first {k + 1 + (chunk * chunk_width)};
4946 const std::size_t count {std::min(chunk_width, n - first)};
4947 buffer<T> sum(count, T {});
4948 subtract_products(sum.data(), minus.data(), b.data() + (k * n) + first, n, n - k, count);
4949 subtract_products(sum.data(), by_vx.data(), y_row(0, first), n, 2 * i, count);
4950 for (std::size_t c {0}; c < count; ++c) {
4951 *y_row(i, first + c) = static_cast<T>(2) * sum[c];
4952 }
4953 });
4954 std::copy(x.begin(), x.end(), v_row(i, k));
4955 left[k] = std::move(x);
4956 }
4957 else {
4958 d[k] = static_cast<double>(x[0]);
4959 }
4960 if (k + 1 == n) {
4961 break;
4962 }
4963 buffer<T> y(b.begin() + static_cast<std::ptrdiff_t>((k * n) + k + 1), // row k past the diagonal, the left
4964 b.begin() + static_cast<std::ptrdiff_t>((k + 1) * n)); // reflection included
4965 for (std::size_t p {0}; p <= i; ++p) {
4966 factors[p] = *v_row(p, k);
4967 }
4968 subtract_products(y.data(), factors.data(), y_row(0, k + 1), 2 * n, i + 1, n - k - 1);
4969 for (std::size_t p {0}; p < i; ++p) {
4970 factors[p] = *x_row(p, k);
4971 }
4972 subtract_products(y.data(), factors.data(), u_row(0, k + 1), 2 * n, i, n - k - 1);
4973 if (std::any_of(y.begin() + 1, y.end(), nonzero)) { // something past the superdiagonal to reflect
4974 e[k] = static_cast<double>(householder_in_place(y));
4975 // x = 2 (B − V Yᵀ − X Uᵀ) u on the rows past k: B u by rows, then the panel's terms.
4976 const std::size_t rows {n - k - 1};
4977 buffer<T> by_y(i + 1, T {}); // Yᵀ u over this and the earlier steps, Uᵀ u over the earlier
4978 buffer<T> by_u(i, T {});
4979 buffer<T> moved(rows, T {});
4980#ifdef NUMERICAL_X86_COMPILED
4981 if (rows >= bidiagonal_shared_least) {
4982 // The coefficients run among B u's tasks, and the corrections by bands on the threads, the next column
4983 // taking the earlier steps' terms in the same pass (its rows are these): each element the operations,
4984 // in the order, of the branch above.
4985 parallel_for(rows + (2 * i) + 1, bidiagonal_product_weight * 2 * (n - k), [&](std::size_t task) {
4986 if (task < rows) {
4987 moved[task] = dot_product(b.data() + ((k + 1 + task) * n) + k + 1, y.data(), y.size());
4988 }
4989 else if (task - rows <= i) {
4990 by_y[task - rows] = dot_product(y_row(task - rows, k + 1), y.data(), y.size());
4991 }
4992 else {
4993 by_u[task - rows - i - 1] = dot_product(u_row(task - rows - i - 1, k + 1), y.data(), y.size());
4994 }
4995 });
4996 T* const next {k + 1 < k1 && i > 0 ? columns.data() + ((i + 1) * n) + k + 1 : nullptr};
4997 for (std::size_t p {0}; next != nullptr && p < i; ++p) { // the next column's terms of the steps before this
4998 next_factors[2 * p] = *y_row(p, k + 1);
4999 next_factors[(2 * p) + 1] = *u_row(p, k + 1);
5000 }
5001 constexpr std::size_t band {256};
5002 parallel_for((rows + band - 1) / band, band * 4 * (i + 1) * 8, [&](std::size_t c) {
5003 const std::size_t first {c * band};
5004 const std::size_t count {std::min(band, rows - first)};
5005 subtract_products(moved.data() + first, by_y.data(), v_row(0, k + 1 + first), 2 * n, i + 1, count);
5006 subtract_products(moved.data() + first, by_u.data(), x_row(0, k + 1 + first), 2 * n, i, count);
5007 for (std::size_t r {first}; r < first + count; ++r) {
5008 *x_row(i, k + 1 + r) = static_cast<T>(2) * moved[r];
5009 }
5010 if (next != nullptr) {
5011 subtract_products(next + first, next_factors.data(), v_row(0, k + 1 + first), n, 2 * i, count);
5012 }
5013 });
5014 column_ahead = next != nullptr;
5015 }
5016 else
5017#endif
5018 {
5019 for (std::size_t p {0}; p <= i; ++p) {
5020 by_y[p] = dot_product(y_row(p, k + 1), y.data(), y.size());
5021 }
5022 for (std::size_t p {0}; p < i; ++p) {
5023 by_u[p] = dot_product(u_row(p, k + 1), y.data(), y.size());
5024 }
5025 parallel_for(rows, bidiagonal_product_weight * 2 * (n - k), [&](std::size_t row) {
5026 moved[row] = dot_product(b.data() + ((k + 1 + row) * n) + k + 1, y.data(), y.size());
5027 });
5028 subtract_products(moved.data(), by_y.data(), v_row(0, k + 1), 2 * n, i + 1, rows);
5029 subtract_products(moved.data(), by_u.data(), x_row(0, k + 1), 2 * n, i, rows);
5030 for (std::size_t r {0}; r < rows; ++r) {
5031 *x_row(i, k + 1 + r) = static_cast<T>(2) * moved[r];
5032 }
5033 }
5034
5035 std::copy(y.begin(), y.end(), u_row(i, k + 1));
5036 right[k] = std::move(y);
5037 }
5038 else {
5039 e[k] = static_cast<double>(y[0]);
5040 }
5041 }
5042 if (k1 < n) { // the trailing block from row and column k1: B −= V Yᵀ + X Uᵀ
5043 const std::size_t rest {n - k1};
5044 buffer<T> minus_v(rest * width, T {});
5045 buffer<T> minus_x(rest * width, T {});
5046 for (std::size_t r {0}; r < rest; ++r) {
5047 for (std::size_t p {0}; p < width; ++p) {
5048 minus_v[(r * width) + p] = -*v_row(p, k1 + r);
5049 minus_x[(r * width) + p] = -*x_row(p, k1 + r);
5050 }
5051 }
5052 T* trailing {b.data() + (k1 * n) + k1};
5053 gemm(rest, rest, width, minus_v.data(), width, y_row(0, k1), 2 * n, trailing, n);
5054 gemm(rest, rest, width, minus_x.data(), width, u_row(0, k1), 2 * n, trailing, n);
5055 }
5056 }
5057 e[n - 1] = 0.0;
5058 if (form) {
5059 form_bidiagonal_bases(left, std::move(right), buffer<T>(n, T {1}), buffer<T>(n, T {1}), n, ut, vt);
5060 }
5061 }
5062
5077 template <typename T>
5079 std::size_t n,
5080 buffer<double>& d,
5081 buffer<double>& e,
5082 buffer<T>& ut,
5083 buffer<T>& vt,
5084 bool form)
5085 {
5086 std::vector<buffer<T>> left(n); // the left reflector of step k, empty where none was needed
5087 std::vector<buffer<T>> right(n); // the right reflector acting on columns k+1…, likewise
5088 buffer<T> left_phase(n, T {1});
5089 buffer<T> right_phase(n, T {1});
5090 const auto reflector {[](buffer<T>& v) { // unit v with (I − 2 v vᴴ) x ∥ e₁
5091 (void)rescale_tiny(v); // the same reflector at any scale
5092 const double norm {scaled_norm(v)};
5093 v[0] += phase_of(v[0]) * detail::from_double<T>(norm);
5094 const auto length {detail::from_double<T>(scaled_norm(v))};
5095 for (T & element : v) {
5096 element /= length;
5097 }
5098 }};
5099 for (std::size_t k {0}; k < n; ++k) {
5100 buffer<T> x(n - k, T {}); // column k from the diagonal down
5101 for (std::size_t i {k}; i < n; ++i) {
5102 x[i - k] = b[(i * n) + k];
5103 }
5104 if (scaled_norm(x.data() + 1, x.size() - 1) > 0.0) {
5105 reflector(x);
5106 reflect_rows(b, n, x, k, k);
5107 left[k] = std::move(x);
5108 }
5109 left_phase[k] = conj_of(phase_of(b[(k * n) + k])); // row k times it puts |b_kk| on the diagonal
5110 for (std::size_t j {k}; j < n; ++j) {
5111 b[(k * n) + j] *= left_phase[k];
5112 }
5113 d[k] = static_cast<double>(std::abs(b[(k * n) + k]));
5114 if (k + 1 == n) {
5115 break;
5116 }
5117 buffer<T> y(n - k - 1, T {}); // row k right of the diagonal, conjugated: B ← B H with H yᴴ ∥ e₁
5118 for (std::size_t j {k + 1}; j < n; ++j) {
5119 y[j - k - 1] = conj_of(b[(k * n) + j]);
5120 }
5121 if (y.size() > 1 && scaled_norm(y.data() + 1, y.size() - 1) > 0.0) {
5122 reflector(y);
5123 parallel_for(n - k, 2 * y.size(), [&](std::size_t r) { // row ← row (I − 2 y yᴴ); rows above k are zero there
5124 T* row {b.data() + ((k + r) * n) + k + 1};
5125 const T scale {static_cast<T>(2) * dot_product(row, y.data(), y.size())};
5126 for (std::size_t j {0}; j < y.size(); ++j) {
5127 row[j] -= scale * conj_of(y[j]);
5128 }
5129 });
5130 right[k] = std::move(y);
5131 }
5132 right_phase[k] = conj_of(phase_of(b[(k * n) + k + 1])); // column k+1 times it: |b_k,k+1| above
5133 for (std::size_t i {k}; i < n; ++i) {
5134 b[(i * n) + k + 1] *= right_phase[k];
5135 }
5136 e[k] = static_cast<double>(std::abs(b[(k * n) + k + 1]));
5137 }
5138 if (n > 0) {
5139 e[n - 1] = 0.0;
5140 }
5141 if (form) {
5142 form_bidiagonal_bases(left, std::move(right), left_phase, right_phase, n, ut, vt);
5143 }
5144 }
5145
5147 template <typename T>
5148 requires(!std::is_floating_point_v<T>)
5150 std::size_t n,
5151 buffer<double>& d,
5152 buffer<double>& e,
5153 buffer<T>& ut,
5154 buffer<T>& vt,
5155 bool form = true)
5156 {
5157 bidiagonalize_unblocked(b, n, d, e, ut, vt, form);
5158 }
5159
5173 template <typename T>
5175 buffer<double>& e,
5176 buffer<T>& ut,
5177 buffer<T>& vt,
5178 std::size_t n,
5179 std::size_t max_steps)
5180 {
5181 // The iteration reads only d and e: what it does to the rows of vt and ut is recorded and applied once
5182 // at the end, in parallel by columns.
5183 std::vector<row_operation> on_vt;
5184 std::vector<row_operation> on_ut;
5185 const bool record {!vt.empty()}; // the singular values alone record nothing
5186 const double epsilon {std::numeric_limits<double>::epsilon()};
5187 const double tiny {std::numeric_limits<double>::min() / epsilon};
5188 constexpr std::size_t none {std::numeric_limits<std::size_t>::max()};
5189 std::size_t p {n};
5190 std::size_t steps {0};
5191 while (p > 0) {
5192 // The largest k < p−1 with a negligible e[k] (none: the loop ran out and wrapped).
5193 std::size_t k {p - 1};
5194 while (k-- > 0) {
5195 if (std::abs(e[k]) <= tiny + (epsilon * (std::abs(d[k]) + std::abs(d[k + 1])))) {
5196 e[k] = 0.0;
5197 break;
5198 }
5199 }
5200 const std::size_t top {k == none ? 0 : k + 1}; // the unreduced block is top…p−1
5201 if (top == p - 1) { // d[p−1] has converged: make it non-negative, sort it into place
5202 if (d[top] < 0.0) {
5203 d[top] = -d[top];
5204 if (record) {
5205 on_vt.push_back({row_operation::kind::negate, top, top, 0.0, 0.0});
5206 }
5207 }
5208 for (std::size_t j {top}; j + 1 < n && d[j] < d[j + 1]; ++j) {
5209 std::swap(d[j], d[j + 1]);
5210 if (record) {
5211 on_vt.push_back({row_operation::kind::swap, j, j + 1, 0.0, 0.0});
5212 on_ut.push_back({row_operation::kind::swap, j, j + 1, 0.0, 0.0});
5213 }
5214 }
5215 --p;
5216 continue;
5217 }
5218 // The last negligible d[zero] in the block (none: there is none).
5219 std::size_t zero {none};
5220 for (std::size_t ks {p}; ks-- > top;) {
5221 const double around {std::abs(e[ks]) + (ks != top ? std::abs(e[ks - 1]) : 0.0)};
5222 if (std::abs(d[ks]) <= tiny + (epsilon * around)) {
5223 d[ks] = 0.0;
5224 zero = ks;
5225 break;
5226 }
5227 }
5228 if (zero == p - 1) { // chase e[p−2] up the block with rotations on the right
5229 double f {e[p - 2]};
5230 e[p - 2] = 0.0;
5231 for (std::size_t j {p - 1}; j-- > top;) {
5232 const double t {rotation_length(d[j], f)};
5233 const double cs {d[j] / t};
5234 const double sn {f / t};
5235 d[j] = t;
5236 if (j != top) {
5237 f = -sn * e[j - 1];
5238 e[j - 1] = cs * e[j - 1];
5239 }
5240 if (record) {
5241 on_vt.push_back({row_operation::kind::rotate, j, p - 1, cs, sn});
5242 }
5243 }
5244 continue;
5245 }
5246 if (zero != none) { // split at d[zero] = 0: chase e[zero] along the row with rotations on the left
5247 double f {e[zero]};
5248 e[zero] = 0.0;
5249 for (std::size_t j {zero + 1}; j < p; ++j) {
5250 const double t {rotation_length(d[j], f)};
5251 const double cs {d[j] / t};
5252 const double sn {f / t};
5253 d[j] = t;
5254 f = -sn * e[j];
5255 e[j] = cs * e[j];
5256 if (record) {
5257 on_ut.push_back({row_operation::kind::rotate, j, zero, cs, sn});
5258 }
5259 }
5260 continue;
5261 }
5262 if (steps == max_steps) {
5263 throw convergence_error("SVD: the bidiagonal QR iteration did not converge within its steps");
5264 }
5265 ++steps;
5266 // One implicit QR step on top…p−1, shifted by the eigenvalue of the trailing 2 × 2 of BᵀB nearer
5267 // its last diagonal entry.
5268 const double scale {std::max({std::abs(d[p - 1]), std::abs(d[p - 2]), std::abs(e[p - 2]), std::abs(d[top]),
5269 std::abs(e[top])})};
5270 const double sp {d[p - 1] / scale};
5271 const double spm1 {d[p - 2] / scale};
5272 const double epm1 {e[p - 2] / scale};
5273 const double sk {d[top] / scale};
5274 const double ek {e[top] / scale};
5275 const double bb {(((spm1 + sp) * (spm1 - sp)) + (epm1 * epm1)) / 2.0};
5276 const double cc {(sp * epm1) * (sp * epm1)};
5277 double shift {0.0};
5278 if (bb != 0.0 || cc != 0.0) {
5279 shift = std::sqrt((bb * bb) + cc);
5280 shift = cc / (bb < 0.0 ? bb - shift : bb + shift);
5281 }
5282 double f {((sk + sp) * (sk - sp)) + shift};
5283 double g {sk * ek};
5284 for (std::size_t j {top}; j + 1 < p; ++j) {
5285 double t {rotation_length(f, g)};
5286 double cs {f / t};
5287 double sn {g / t};
5288 if (j != top) {
5289 e[j - 1] = t;
5290 }
5291 f = (cs * d[j]) + (sn * e[j]);
5292 e[j] = (cs * e[j]) - (sn * d[j]);
5293 g = sn * d[j + 1];
5294 d[j + 1] = cs * d[j + 1];
5295 if (record) {
5296 on_vt.push_back({row_operation::kind::rotate, j, j + 1, cs, sn});
5297 }
5298 t = rotation_length(f, g);
5299 cs = f / t;
5300 sn = g / t;
5301 d[j] = t;
5302 f = (cs * e[j]) + (sn * d[j + 1]);
5303 d[j + 1] = (cs * d[j + 1]) - (sn * e[j]);
5304 g = sn * e[j + 1];
5305 e[j + 1] = cs * e[j + 1];
5306 if (record) {
5307 on_ut.push_back({row_operation::kind::rotate, j, j + 1, cs, sn});
5308 }
5309 }
5310 e[p - 2] = f;
5311 }
5312 if (!vt.empty()) {
5313 apply_row_operations(vt, n, on_vt);
5314 apply_row_operations(ut, n, on_ut);
5315 }
5316 }
5317
5319 template <typename T>
5320 [[nodiscard]] matrix<T> adjoint(const matrix<T>& a)
5321 {
5322 buffer<T> entries(a.rows() * a.cols(), T {});
5323 for (std::size_t i {0}; i < a.rows(); ++i) {
5324 for (std::size_t j {0}; j < a.cols(); ++j) {
5325 entries[(j * a.rows()) + i] = conj_of(a(i, j));
5326 }
5327 }
5328 return matrix<T> {a.cols(), a.rows(), std::move(entries)};
5329 }
5330
5345 [[nodiscard]] inline double singular_secular_root(const buffer<double>& d,
5346 const buffer<double>& z,
5347 std::size_t i,
5348 std::size_t max_iterations,
5349 double* below,
5350 double* above)
5351 {
5352 buffer<double> delta(d.size(), double {});
5353 const secular_solution root {solve_secular(z, 1.0, i, max_iterations, delta.data(), [&d](std::size_t o, std::size_t j) {
5354 return (d[j] - d[o]) * (d[j] + d[o]);
5355 })};
5356 const double pole {d[root.origin]};
5357 const double shift {root.offset / (pole + std::sqrt((pole * pole) + root.offset))}; // σ − d_o from σ² − d_o²
5358 for (std::size_t j {0}; j < d.size(); ++j) {
5359 below[j] = (d[j] - pole) - shift;
5360 above[j] = (d[j] + pole) + shift;
5361 }
5362 return pole + shift;
5363 }
5364
5367 std::size_t cols,
5368 std::size_t a,
5369 std::size_t b,
5370 double c,
5371 double s)
5372 {
5373 for (std::size_t r {0}; r < m.size() / cols; ++r) {
5374 const double x {m[(r * cols) + a]};
5375 const double y {m[(r * cols) + b]};
5376 m[(r * cols) + a] = (c * x) + (s * y);
5377 m[(r * cols) + b] = (c * y) - (s * x);
5378 }
5379 }
5380
5397
5403 {
5404 thread_local arrow_workspace work;
5405 return work;
5406 }
5407
5410 {
5411 for (workspace_buffer* buffer : {&work.below, &work.above, &work.left, &work.right, &work.new_u, &work.new_v,
5412 &work.columns, &work.factor}) {
5413 if (buffer->size > kept_workspace) {
5414 buffer->data.reset();
5415 buffer->size = 0;
5416 }
5417 }
5418 for (buffer<double>* sorted : {&work.sorted_u, &work.sorted_v}) {
5419 if (sorted->capacity() > kept_workspace) {
5420 *sorted = buffer<double> {};
5421 }
5422 }
5423 }
5424
5450 double alpha,
5451 double beta,
5452 buffer<double>& u,
5453 buffer<double>& v,
5454 std::size_t n,
5455 std::size_t m,
5456 std::size_t split,
5457 arrow_workspace& work)
5458 {
5459 // The rows each column reaches: 1 the upper half's, 2 u's row `split` (the arrow's first), 4 the lower half's.
5460 // A rotation of two columns makes each reach the rows of both.
5461 std::vector<unsigned> reach_u(n, 4U);
5462 std::vector<unsigned> reach_v(m, 4U);
5463 reach_u[0] = 2U;
5464 reach_v[0] = m > n ? 5U : 1U; // the upper half's null vector, turned with the lower one's when there is one
5465 for (std::size_t j {1}; j <= split; ++j) {
5466 reach_u[j] = 1U;
5467 reach_v[j] = 1U;
5468 }
5469 const auto turned {[](std::vector<unsigned>& reach, std::size_t a, std::size_t b) {
5470 reach[a] = reach[b] = reach[a] | reach[b];
5471 }};
5472 std::vector<std::size_t> order(n - 1);
5473 for (std::size_t j {1}; j < n; ++j) {
5474 order[j - 1] = j;
5475 }
5476 std::sort(order.begin(), order.end(), [&d](std::size_t x, std::size_t y) { return d[x] < d[y]; });
5477 double largest {std::max(std::abs(alpha), std::abs(beta))};
5478 for (const double value : d) {
5479 largest = std::max(largest, value);
5480 }
5481 if (largest == 0.0) {
5482 return; // a zero arrow: its singular values are zeros, its vectors the bases as they are
5483 }
5484 const double tolerance {8.0 * std::numeric_limits<double>::epsilon() * largest};
5485 if (std::abs(z[0]) <= tolerance) {
5486 z[0] = tolerance; // the zero pole keeps a weight: the arrow moves by no more than the tolerance
5487 }
5488 std::vector<std::size_t> kept {0}; // by ascending d, the zero pole first
5489 std::vector<std::size_t> deflated; // the columns that keep their singular value
5490 std::size_t pending {n}; // the last column kept so far, still open to a rotation with the next
5491 for (const std::size_t j : order) {
5492 if (std::abs(z[j]) <= tolerance) {
5493 deflated.push_back(j);
5494 continue;
5495 }
5496 if (d[j] <= tolerance) {
5497 // Nearly the zero pole: its weight joins the zero pole's, and dⱼ deflates.
5498 const double length {std::hypot(z[0], z[j])};
5499 rotate_columns(v, m, 0, j, z[0] / length, z[j] / length);
5500 turned(reach_v, 0, j);
5501 z[0] = length;
5502 z[j] = 0.0;
5503 deflated.push_back(j);
5504 continue;
5505 }
5506 if (pending != n && d[j] - d[pending] <= tolerance) {
5507 // Close values: one rotation of both rows and columns zeroes the pending one's weight.
5508 const double length {std::hypot(z[j], z[pending])};
5509 const double c {z[j] / length};
5510 const double s {z[pending] / length};
5511 rotate_columns(v, m, j, pending, c, s);
5512 rotate_columns(u, n, j, pending, c, s);
5513 turned(reach_v, j, pending);
5514 turned(reach_u, j, pending);
5515 z[j] = length;
5516 z[pending] = 0.0;
5517 deflated.push_back(pending);
5518 }
5519 else if (pending != n) {
5520 kept.push_back(pending);
5521 }
5522 pending = j;
5523 }
5524 if (pending != n) {
5525 kept.push_back(pending);
5526 }
5527 const std::size_t k {kept.size()};
5528 buffer<double> poles(k, double {});
5529 buffer<double> weights(k, double {});
5530 for (std::size_t i {0}; i < k; ++i) {
5531 poles[i] = d[kept[i]];
5532 weights[i] = z[kept[i]];
5533 }
5534 buffer<double> roots(k, double {});
5535 double* const below {workspace_of(work.below, k * k)}; // below[i * k + j] = poles[j] − roots[i]
5536 double* const above {workspace_of(work.above, k * k)}; // above[i * k + j] = poles[j] + roots[i]
5537 parallel_for(k, 8 * k, [&](std::size_t i) {
5538 roots[i] = singular_secular_root(poles, weights, i, secular_iterations, below + (i * k), above + (i * k));
5539 });
5540 double* const left {workspace_of(work.left, k * k)}; // column i: the left vector of roots[i], in the kept columns' basis
5541 double* const right {workspace_of(work.right, k * k)}; // and the right one
5542 // Löwner: ẑⱼ² = Πᵢ (σᵢ² − dⱼ²) / Πᵢ≠ⱼ (dᵢ² − dⱼ²), each pole paired with its root, by blocks of poles on every
5543 // core, each pole's product in ascending order of the roots (a row of the gaps at a time, along the block).
5544 buffer<double> signed_weights(k, double {});
5545 parallel_for((k + lowner_block - 1) / lowner_block, lowner_block * k, [&](std::size_t block) {
5546 const std::size_t first {block * lowner_block};
5547 const std::size_t last {std::min(first + lowner_block, k)};
5548 std::array<double, lowner_block> product {};
5549 for (std::size_t j {first}; j < last; ++j) {
5550 product[j - first] = -below[(j * k) + j] * above[(j * k) + j];
5551 }
5552 for (std::size_t i {0}; i < k; ++i) {
5553 for (std::size_t j {first}; j < last; ++j) {
5554 if (i != j) {
5555 product[j - first] *= (-below[(i * k) + j] * above[(i * k) + j]) / ((poles[i] - poles[j]) * (poles[i] + poles[j]));
5556 }
5557 }
5558 }
5559 for (std::size_t j {first}; j < last; ++j) {
5560 signed_weights[j] = std::copysign(std::sqrt(product[j - first]), weights[j]);
5561 }
5562 });
5563 // The vectors by rows of poles, each on its own core: right[j][i] = ẑⱼ / (dⱼ² − σᵢ²), read a column of the gaps.
5564 parallel_for(k, 4 * k, [&](std::size_t j) {
5565 for (std::size_t i {0}; i < k; ++i) {
5566 const double ratio {signed_weights[j] / (below[(i * k) + j] * above[(i * k) + j])}; // ẑⱼ / (dⱼ² − σᵢ²)
5567 right[(j * k) + i] = ratio;
5568 left[(j * k) + i] = j == 0 ? -1.0 : poles[j] * ratio;
5569 }
5570 });
5571 for (double* const vectors : {left, right}) {
5572 buffer<double> length(k, 0.0);
5573 for (std::size_t j {0}; j < k; ++j) { // by rows, each a contiguous update of every column's sum
5574 for (std::size_t i {0}; i < k; ++i) {
5575 length[i] += vectors[(j * k) + i] * vectors[(j * k) + i];
5576 }
5577 }
5578 for (double& l : length) {
5579 l = std::sqrt(l);
5580 }
5581 parallel_for(k, k, [&](std::size_t j) {
5582 for (std::size_t i {0}; i < k; ++i) {
5583 vectors[(j * k) + i] /= length[i];
5584 }
5585 });
5586 }
5587 // The kept columns times the arrow's vectors; the deflated columns as they are; then by ascending value.
5588 // By segments of rows, each taking the kept columns that reach it and their rows of the arrow's vectors: the
5589 // others are zero there, and an element's sum without them is the same to the bit.
5590 using segment = std::tuple<std::size_t, std::size_t, unsigned>; // first row, rows, the bit of reach
5591 const auto carried {[&](const buffer<double>& basis, std::size_t rows, const double* small,
5592 const std::vector<unsigned>& reach, const std::vector<segment>& segments, double* product) {
5593 for (const auto& [first, count, bit] : segments) {
5594 std::vector<std::size_t> reaching;
5595 for (std::size_t i {0}; i < k; ++i) {
5596 if ((reach[kept[i]] & bit) != 0U) {
5597 reaching.push_back(i);
5598 }
5599 }
5600 const std::size_t width {reaching.size()};
5601 double* const columns {workspace_of(work.columns, count * width)};
5602 double* const factor {workspace_of(work.factor, width * k)};
5603 parallel_for(count, width + k, [&](std::size_t r) {
5604 for (std::size_t c {0}; c < width; ++c) {
5605 columns[(r * width) + c] = basis[((first + r) * rows) + kept[reaching[c]]];
5606 }
5607 std::fill_n(product + ((first + r) * k), k, 0.0); // the product adds to it
5608 });
5609 for (std::size_t c {0}; c < width; ++c) {
5610 std::copy_n(small + (reaching[c] * k), k, factor + (c * k));
5611 }
5612 gemm(count, k, width, columns, width, factor, k, product + (first * k), k);
5613 }
5614 }};
5615 double* const new_u {workspace_of(work.new_u, n * k)};
5616 double* const new_v {workspace_of(work.new_v, m * k)};
5617 carried(u, n, left, reach_u, {{0, split, 1U}, {split, 1, 2U}, {split + 1, n - split - 1, 4U}}, new_u);
5618 carried(v, m, right, reach_v, {{0, split + 1, 1U}, {split + 1, m - split - 1, 4U}}, new_v);
5619 buffer<double> values(n, double {});
5620 std::vector<std::size_t> from(n); // where each value's vectors are: a kept root (< k) or a deflated column
5621 for (std::size_t i {0}; i < k; ++i) {
5622 values[i] = roots[i];
5623 from[i] = i;
5624 }
5625 for (std::size_t i {0}; i < deflated.size(); ++i) {
5626 values[k + i] = d[deflated[i]];
5627 from[k + i] = k + i;
5628 }
5629 std::vector<std::size_t> rank(n);
5630 for (std::size_t j {0}; j < n; ++j) {
5631 rank[j] = j;
5632 }
5633 std::sort(rank.begin(), rank.end(), [&values](std::size_t x, std::size_t y) { return values[x] < values[y]; });
5634 std::vector<std::size_t> source(n); // by sorted column: a kept root (< k) or a deflated column (k + its place)
5635 for (std::size_t c {0}; c < n; ++c) {
5636 source[c] = from[rank[c]];
5637 d[c] = values[rank[c]];
5638 }
5639 // Row by row on every core, each row written whole (by columns, each write was a row apart).
5640 buffer<double>& sorted_u {work.sorted_u};
5641 buffer<double>& sorted_v {work.sorted_v};
5642 sorted_u.resize(n * n); // written whole below
5643 sorted_v.assign(v.begin(), v.end()); // the null column, if any, stays last
5644 parallel_for(m, n, [&](std::size_t r) { // m ≥ n: the right basis has the rows of both
5645 for (std::size_t c {0}; c < n; ++c) {
5646 if (r < n) {
5647 sorted_u[(r * n) + c] = source[c] < k ? new_u[(r * k) + source[c]] : u[(r * n) + deflated[source[c] - k]];
5648 }
5649 sorted_v[(r * m) + c] = source[c] < k ? new_v[(r * k) + source[c]] : v[(r * m) + deflated[source[c] - k]];
5650 }
5651 });
5652 std::swap(u, sorted_u); // the old bases' storage is kept for the next join
5653 std::swap(v, sorted_v);
5654 }
5655
5657 inline constexpr std::size_t bidiagonal_leaf {32};
5658
5664 {
5665 const double* d {};
5666 const double* e {};
5667 std::size_t n {};
5668 std::size_t extra {};
5669 int exponent {0};
5670 buffer<double> scaled_d {};
5671 buffer<double> scaled_e {};
5672 std::size_t k {0};
5673 std::size_t first {};
5674 std::size_t second {};
5675 std::size_t depth {};
5679 };
5680
5687 NUMERICAL_OUT_OF_LINE inline std::size_t plan_bidiagonal(std::vector<bidiagonal_node>& nodes,
5688 const double* d,
5689 const double* e,
5690 std::size_t n,
5691 std::size_t extra,
5692 std::size_t depth)
5693 {
5694 double largest {0.0};
5695 for (std::size_t i {0}; i < n; ++i) {
5696 largest = std::max({largest, std::abs(d[i]), i + 1 < n + extra ? std::abs(e[i]) : 0.0});
5697 }
5698 bidiagonal_node node {.d = d, .e = e, .n = n, .extra = extra, .depth = depth};
5699 (void)std::frexp(largest, &node.exponent);
5700 if (node.exponent != 0) {
5701 node.scaled_d.assign(n, double {});
5702 node.scaled_e.assign(n, 0.0);
5703 for (std::size_t i {0}; i < n; ++i) {
5704 node.scaled_d[i] = std::ldexp(d[i], -node.exponent);
5705 node.scaled_e[i] = i + 1 < n + extra ? std::ldexp(e[i], -node.exponent) : 0.0;
5706 }
5707 node.d = node.scaled_d.data(); // the buffers' storage stays put as the node moves into the vector
5708 node.e = node.scaled_e.data();
5709 }
5710 const std::size_t index {nodes.size()};
5711 if (n <= bidiagonal_leaf) {
5712 nodes.push_back(std::move(node));
5713 return index;
5714 }
5715 node.k = n / 2;
5716 const double* const work_d {node.d};
5717 const double* const work_e {node.e};
5718 const std::size_t k {node.k};
5719 nodes.push_back(std::move(node));
5720 const std::size_t first {plan_bidiagonal(nodes, work_d, work_e, k, 1, depth + 1)};
5721 const std::size_t second {plan_bidiagonal(nodes, work_d + k + 1, work_e + k + 1, n - k - 1, extra, depth + 1)};
5722 nodes[index].first = first;
5723 nodes[index].second = second;
5724 return index;
5725 }
5726
5729 {
5730 const std::size_t m {node.n + node.extra};
5731 node.sigma.assign(node.n, 0.0);
5732 node.u.assign(node.n * node.n, 0.0);
5733 node.v.assign(m * m, 0.0);
5734 for (std::size_t i {0}; i < m; ++i) {
5735 node.v[(i * m) + i] = 1.0;
5736 }
5737 }
5738
5741 {
5743 const double* const d {node.d};
5744 const double* const e {node.e};
5745 const std::size_t n {node.n};
5746 const std::size_t extra {node.extra};
5747 const std::size_t m {n + extra};
5748 buffer<double>& sigma {node.sigma};
5749 buffer<double>& u {node.u};
5750 buffer<double>& v {node.v};
5751 buffer<double> diagonal(d, d + n);
5752 buffer<double> super(n, 0.0);
5753 std::copy_n(e, n - std::min<std::size_t>(n, 1), super.begin());
5754 // An extra column first: rotations of it against each column, from the last row up, chase its one entry
5755 // out (B G = [B' 0]), so the square B' remains.
5756 double fill {extra == 1 ? e[n - 1] : 0.0};
5757 for (std::size_t j {n}; extra == 1 && j-- > 0;) {
5758 const double length {std::hypot(diagonal[j], fill)};
5759 const double c {length == 0.0 ? 1.0 : diagonal[j] / length};
5760 const double s {length == 0.0 ? 0.0 : fill / length};
5761 diagonal[j] = length;
5762 rotate_columns(v, m, j, n, c, s);
5763 if (j > 0) {
5764 fill = -s * super[j - 1];
5765 super[j - 1] = c * super[j - 1];
5766 }
5767 }
5768 buffer<double> ut(n * n, 0.0);
5769 buffer<double> vt(n * n, 0.0);
5770 for (std::size_t i {0}; i < n; ++i) {
5771 ut[(i * n) + i] = 1.0;
5772 vt[(i * n) + i] = 1.0;
5773 }
5774 bidiagonal_qr(diagonal, super, ut, vt, n, svd_qr_steps_per_value * n); // descending
5775 buffer<double> rotated(v);
5776 for (std::size_t c {0}; c < n; ++c) {
5777 const std::size_t source {n - 1 - c};
5778 sigma[c] = diagonal[source];
5779 for (std::size_t r {0}; r < n; ++r) {
5780 u[(r * n) + c] = ut[(source * n) + r];
5781 }
5782 for (std::size_t r {0}; r < m; ++r) { // G times B''s right vector
5783 double sum {0.0};
5784 for (std::size_t t {0}; t < n; ++t) {
5785 sum += rotated[(r * m) + t] * vt[(source * n) + t];
5786 }
5787 v[(r * m) + c] = sum;
5788 }
5789 }
5790 }
5791
5795 const bidiagonal_node& one,
5796 const bidiagonal_node& two,
5797 arrow_workspace& work)
5798 {
5799 const std::size_t n {node.n};
5800 const std::size_t extra {node.extra};
5801 const std::size_t m {n + extra};
5802 const std::size_t k {node.k};
5803 const std::size_t lower {n - k - 1};
5804 const double alpha {node.d[k]};
5805 const double beta {node.e[k]};
5806 const buffer<double>& sigma1 {one.sigma};
5807 const buffer<double>& u1 {one.u};
5808 const buffer<double>& v1 {one.v};
5809 const buffer<double>& sigma2 {two.sigma};
5810 const buffer<double>& u2 {two.u};
5811 const buffer<double>& v2 {two.v};
5812 buffer<double>& u {node.u};
5813 buffer<double>& v {node.v};
5814 const std::size_t m1 {k + 1};
5815 const std::size_t m2 {lower + extra};
5816 buffer<double> arrow(n, 0.0);
5817 buffer<double> z(n, 0.0);
5818 // The bases: row k is the arrow's first row; the arrow's columns are the null vector, then the halves'.
5819 u[k * n] = 1.0;
5820 for (std::size_t c {0}; c < k; ++c) {
5821 arrow[1 + c] = sigma1[c];
5822 z[1 + c] = alpha * v1[(k * m1) + c];
5823 for (std::size_t r {0}; r < k; ++r) {
5824 u[(r * n) + 1 + c] = u1[(r * k) + c];
5825 }
5826 }
5827 for (std::size_t c {0}; c < lower; ++c) {
5828 arrow[k + 1 + c] = sigma2[c];
5829 z[k + 1 + c] = beta * v2[c];
5830 for (std::size_t r {0}; r < lower; ++r) {
5831 u[((k + 1 + r) * n) + k + 1 + c] = u2[(r * lower) + c];
5832 }
5833 }
5834 std::fill(v.begin(), v.end(), 0.0);
5835 for (std::size_t r {0}; r < m1; ++r) {
5836 v[(r * m) + 0] = v1[(r * m1) + k];
5837 for (std::size_t c {0}; c < k; ++c) {
5838 v[(r * m) + 1 + c] = v1[(r * m1) + c];
5839 }
5840 }
5841 for (std::size_t r {0}; r < m2; ++r) {
5842 for (std::size_t c {0}; c < lower; ++c) {
5843 v[((m1 + r) * m) + k + 1 + c] = v2[(r * m2) + c];
5844 }
5845 }
5846 z[0] = alpha * v1[(k * m1) + k];
5847 if (extra == 1) { // the two null vectors: one joins the arrow, the other is the whole's
5848 for (std::size_t r {0}; r < m2; ++r) {
5849 v[((m1 + r) * m) + n] = v2[(r * m2) + lower];
5850 }
5851 const double other {beta * v2[lower]};
5852 const double length {std::hypot(z[0], other)};
5853 const double c {length == 0.0 ? 1.0 : z[0] / length};
5854 const double s {length == 0.0 ? 0.0 : other / length};
5855 rotate_columns(v, m, 0, n, c, s);
5856 z[0] = length;
5857 }
5858 merge_arrow(arrow, std::move(z), alpha, beta, u, v, n, m, k, work);
5859 node.sigma = std::move(arrow);
5860 }
5861
5864 {
5865 if (node.exponent != 0) {
5866 for (double& value : node.sigma) {
5867 value = std::ldexp(value, node.exponent);
5868 }
5869 }
5870 }
5871
5873 NUMERICAL_OUT_OF_LINE inline void join_bidiagonal_halves(std::vector<bidiagonal_node>& nodes,
5874 std::size_t index,
5875 arrow_workspace& work)
5876 {
5877 bidiagonal_node& node {nodes[index]};
5878 join_bidiagonal_node(node, nodes[node.first], nodes[node.second], work);
5880 nodes[node.first] = bidiagonal_node {}; // read by no other join
5881 nodes[node.second] = bidiagonal_node {};
5882 }
5883
5885 NUMERICAL_OUT_OF_LINE inline void solve_bidiagonal_subtree(std::vector<bidiagonal_node>& nodes,
5886 std::size_t index,
5887 arrow_workspace& work)
5888 {
5889 if (nodes[index].k == 0) {
5890 solve_bidiagonal_leaf(nodes[index]);
5891 unscale_bidiagonal_node(nodes[index]);
5892 return;
5893 }
5894 start_bidiagonal_node(nodes[index]); // before its halves, as the recursion did: their buffers then come back to it
5895 solve_bidiagonal_subtree(nodes, nodes[index].first, work);
5896 solve_bidiagonal_subtree(nodes, nodes[index].second, work);
5897 join_bidiagonal_halves(nodes, index, work);
5898 }
5899
5918 inline void bidiagonal_divide(const double* d,
5919 const double* e,
5920 std::size_t n,
5921 std::size_t extra,
5922 buffer<double>& sigma,
5923 buffer<double>& u,
5924 buffer<double>& v)
5925 {
5926 std::vector<bidiagonal_node> nodes;
5927 (void)plan_bidiagonal(nodes, d, e, n, extra, 0);
5928 const auto cut {static_cast<std::size_t>(std::bit_width(max_threads() - 1))};
5929 std::vector<std::size_t> tasks;
5930 for (std::size_t i {0}; i < nodes.size(); ++i) {
5931 if (nodes[i].depth == cut || (nodes[i].depth < cut && nodes[i].k == 0)) {
5932 tasks.push_back(i);
5933 }
5934 }
5935 const std::size_t task_order {nodes[tasks[0]].n};
5936 parallel_for(tasks.size(), tasks.size() > 1 ? task_order * task_order * task_order : 0, [&](std::size_t t) {
5939 });
5940 for (std::size_t level {cut}; level-- > 0;) {
5941 std::vector<std::size_t> joins;
5942 for (std::size_t i {0}; i < nodes.size(); ++i) {
5943 if (nodes[i].k != 0 && nodes[i].depth == level) {
5944 joins.push_back(i);
5945 }
5946 }
5947 const std::size_t order {joins.empty() ? 0 : nodes[joins[0]].n};
5948 parallel_for(joins.size(), joins.size() > 1 ? order * order * order / 4 : 0, [&](std::size_t j) {
5949 start_bidiagonal_node(nodes[joins[j]]);
5952 });
5953 }
5954 sigma = std::move(nodes[0].sigma);
5955 u = std::move(nodes[0].u);
5956 v = std::move(nodes[0].v);
5957 }
5958
5965 template <typename T>
5967 buffer<double>& e,
5968 buffer<T>& ut,
5969 buffer<T>& vt,
5970 std::size_t n)
5971 {
5972 if (n == 0) {
5973 return;
5974 }
5975 buffer<double> sigma;
5978 bidiagonal_divide(d.data(), e.data(), n, 0, sigma, u, v);
5979 buffer<double> left(n * n, double {}); // row c: the vectors of the c-th largest value
5980 buffer<double> right(n * n, double {});
5981 for (std::size_t c {0}; c < n; ++c) {
5982 d[c] = sigma[n - 1 - c];
5983 for (std::size_t r {0}; r < n; ++r) {
5984 left[(c * n) + r] = u[(r * n) + (n - 1 - c)];
5985 right[(c * n) + r] = v[(r * n) + (n - 1 - c)];
5986 }
5987 }
5988 multiply_real_left(left, ut, n);
5989 multiply_real_left(right, vt, n);
5990 }
5991 } // namespace detail
5992
5994 enum class svd_method
5995 {
5996 bidiagonal_qr,
5997 jacobi,
5998 divide_and_conquer
5999 };
6000
6016 template <typename T>
6018 {
6019 public:
6020
6029 explicit svd_decomposition(const matrix<T>& a,
6030 svd_method method = svd_method::divide_and_conquer,
6031 bool full = false)
6032 {
6033 detail::require_finite(a, "SVD");
6034 if (a.rows() >= a.cols()) {
6035 factor(a, method, full);
6036 }
6037 else { // A = (Aᴴ)ᴴ = (U' Σ V'ᴴ)ᴴ = V' Σ U'ᴴ
6038 factor(detail::adjoint(a), method, full);
6039 std::swap(u_, v_);
6040 }
6041 }
6042
6044 [[nodiscard]] const vector<double>& singular_values() const noexcept
6045 {
6046 return singular_values_;
6047 }
6048
6050 [[nodiscard]] const matrix<T>& u() const& noexcept
6051 {
6052 return u_;
6053 }
6054
6056 [[nodiscard]] matrix<T> u() &&
6057 {
6058 return std::move(u_);
6059 }
6060
6062 [[nodiscard]] const matrix<T>& v() const& noexcept
6063 {
6064 return v_;
6065 }
6066
6069 [[nodiscard]] matrix<T> vh() const
6070 {
6071 return detail::adjoint(v_);
6072 }
6073
6074 private:
6075
6080 [[nodiscard]] static matrix<T> left_vectors(buffer<T> q,
6081 buffer<T> left,
6082 std::size_t rows,
6083 std::size_t cols,
6084 std::size_t width)
6085 {
6086 if (width == cols) {
6087 return matrix<T> {rows, cols, std::move(q)} *matrix<T> {cols, cols, std::move(left)};
6088 }
6089 buffer<T> u(rows * rows, T {});
6090 detail::gemm(rows, cols, cols, q.data(), rows, left.data(), cols, u.data(), rows);
6091 for (std::size_t i {0}; i < rows; ++i) {
6092 for (std::size_t j {cols}; j < rows; ++j) {
6093 u[(i * rows) + j] = q[(i * rows) + j];
6094 }
6095 }
6096 return matrix<T> {rows, rows, std::move(u)};
6097 }
6098
6101 void factor(const matrix<T>& a,
6102 svd_method method,
6103 bool full)
6104 {
6105 const std::size_t rows {a.rows()};
6106 const std::size_t cols {a.cols()};
6107 // Work on A divided by its largest magnitude: the sums of squares in the sweeps overflow near
6108 // 1e155 and underflow near 1e-155. The singular values are scaled back; the vectors do not change.
6109 // Both passes by bands of rows on the threads, bound by the memory: a band's largest magnitude, then the
6110 // largest of those (exact in any order), then the quotients.
6111 const T* const source {std::to_address(a.begin())};
6112 const std::size_t bands {(rows + detail::svd_band - 1) / detail::svd_band};
6113 std::vector<double> largest(bands, 0.0);
6114 detail::parallel_for(bands, detail::memory_element_weight * detail::svd_band * cols, [source, rows, cols, &largest](std::size_t band) {
6115 double most {0.0};
6116 for (std::size_t e {band * detail::svd_band * cols}; e < std::min(rows, (band + 1) * detail::svd_band) * cols; ++e) {
6117 most = std::max(most, static_cast<double>(std::abs(source[e])));
6118 }
6119 largest[band] = most;
6120 });
6121 double scale {*std::max_element(largest.begin(), largest.end())};
6122 if (scale == 0.0) {
6123 scale = 1.0;
6124 }
6125 buffer<T> entries(rows * cols);
6126 T* const into {entries.data()};
6127 const T divisor {detail::from_double<T>(scale)};
6128 detail::parallel_for(bands, detail::memory_element_weight * detail::svd_band * cols, [source, into, rows, cols, divisor](std::size_t band) {
6129 for (std::size_t e {band * detail::svd_band * cols}; e < std::min(rows, (band + 1) * detail::svd_band) * cols; ++e) {
6130 into[e] = source[e] / divisor;
6131 }
6132 });
6133 // A square matrix is reduced to bidiagonal form as it is (as LAPACK's dgesdd does short of a tall one): a QR
6134 // first, its Q formed and multiplied in at the end, took a third of the decomposition's time at order 1024.
6135 const bool direct {rows == cols && method != svd_method::jacobi};
6136 const std::size_t width {full ? rows : cols};
6137 detail::thin_qr<T> factors {direct ? detail::thin_qr<T> {.q = {}, .r = std::move(entries)}
6138 : detail::householder_qr(std::move(entries), rows, cols, full)};
6139 if (method != svd_method::jacobi) {
6140 buffer<double> d(cols, double {});
6141 buffer<double> e(cols, double {});
6142 buffer<T> ut;
6143 buffer<T> vt;
6144 detail::bidiagonalize(factors.r, cols, d, e, ut, vt);
6145 if (method == svd_method::bidiagonal_qr) {
6146 detail::bidiagonal_qr(d, e, ut, vt, cols, detail::svd_qr_steps_per_value * cols);
6147 }
6148 else {
6149 detail::bidiagonal_divide_and_conquer(d, e, ut, vt, cols);
6150 }
6151 buffer<T> left(cols * cols); // rows of ut and vt are the vectors: made columns
6152 buffer<T> right(cols * cols);
6153 detail::transpose_into(ut.data(), cols, cols, left.data());
6154 detail::transpose_into(vt.data(), cols, cols, right.data());
6155 for (double& value : d) {
6156 value *= scale;
6157 }
6158 singular_values_ = vector<double> {std::move(d)};
6159 u_ = direct ? matrix<T> {cols, cols, std::move(left)}
6160 : left_vectors(std::move(factors.q), std::move(left), rows, cols, width);
6161 v_ = matrix<T> {cols, cols, std::move(right)};
6162 return;
6163 }
6164 buffer<T> w {std::move(factors.r)}; // A = Q R, and R V = W with orthogonal columns
6165 buffer<T> right(cols * cols, T {});
6166 for (std::size_t i {0}; i < cols; ++i) {
6167 right[(i * cols) + i] = T {1};
6168 }
6169 detail::one_sided_jacobi(w, right, cols, detail::jacobi_sweeps);
6170
6171 buffer<double> norms(cols, double {});
6172 for (std::size_t j {0}; j < cols; ++j) {
6173 buffer<T> column(cols, T {});
6174 for (std::size_t i {0}; i < cols; ++i) {
6175 column[i] = w[(i * cols) + j];
6176 }
6177 norms[j] = detail::scaled_norm(column);
6178 }
6179 std::vector<std::size_t> order(cols);
6180 for (std::size_t j {0}; j < cols; ++j) {
6181 order[j] = j;
6182 }
6183 std::stable_sort(order.begin(), order.end(), [&norms](std::size_t x, std::size_t y) { return norms[x] > norms[y]; });
6184
6185 // The left vectors of R: each nonzero column of W over its norm; a zero one has none, and takes
6186 // the unit vector that leaves the largest residual against the columns already set, made
6187 // orthogonal to them.
6188 buffer<T> left(cols * cols, T {});
6189 buffer<T> sorted_right(cols * cols, T {});
6190 buffer<double> values(cols, double {});
6191 for (std::size_t d {0}; d < cols; ++d) {
6192 const std::size_t j {order[d]};
6193 values[d] = norms[j] * scale;
6194 for (std::size_t i {0}; i < cols; ++i) {
6195 sorted_right[(i * cols) + d] = right[(i * cols) + j];
6196 }
6197 if (norms[j] > 0.0) {
6198 for (std::size_t i {0}; i < cols; ++i) {
6199 left[(i * cols) + d] = w[(i * cols) + j] / detail::from_double<T>(norms[j]);
6200 }
6201 continue;
6202 }
6203 buffer<T> best;
6204 double best_norm {-1.0};
6205 for (std::size_t k {0}; k < cols; ++k) {
6206 buffer<T> candidate(cols, T {});
6207 candidate[k] = T {1};
6208 orthogonalise(candidate, left, cols, d);
6209 const double norm {detail::scaled_norm(candidate)};
6210 if (norm > best_norm) {
6211 best = std::move(candidate);
6212 best_norm = norm;
6213 }
6214 }
6215 orthogonalise(best, left, cols, d); // a second pass restores orthogonality lost to rounding
6216 const double norm {detail::scaled_norm(best)};
6217 for (std::size_t i {0}; i < cols; ++i) {
6218 left[(i * cols) + d] = best[i] / detail::from_double<T>(norm);
6219 }
6220 }
6221 singular_values_ = vector<double> {std::move(values)};
6222 u_ = left_vectors(std::move(factors.q), std::move(left), rows, cols, width);
6223 v_ = matrix<T> {cols, cols, std::move(sorted_right)};
6224 }
6225
6227 static void orthogonalise(buffer<T>& x,
6228 const buffer<T>& basis,
6229 std::size_t n,
6230 std::size_t count)
6231 {
6232 for (std::size_t c {0}; c < count; ++c) {
6233 T projection {};
6234 for (std::size_t i {0}; i < n; ++i) {
6235 projection += detail::conj_of(basis[(i * n) + c]) * x[i];
6236 }
6237 for (std::size_t i {0}; i < n; ++i) {
6238 x[i] -= projection * basis[(i * n) + c];
6239 }
6240 }
6241 }
6242
6246 };
6247
6248 namespace detail {
6249
6252 [[nodiscard]] inline std::size_t numerical_rank(const vector<double>& sigma,
6253 std::size_t rows,
6254 std::size_t cols,
6255 rank_cutoff cutoff = {})
6256 {
6257 double largest {0.0};
6258 for (std::size_t d {0}; d < sigma.size(); ++d) {
6259 largest = std::max(largest, sigma[d]);
6260 }
6261 const double tolerance {cutoff.magnitude(largest, rows, cols)};
6262 std::size_t rank {0};
6263 for (std::size_t d {0}; d < sigma.size(); ++d) {
6264 rank += static_cast<std::size_t>(sigma[d] > tolerance);
6265 }
6266 return rank;
6267 }
6268
6271 inline constexpr double qr_rank_margin {100.0};
6272
6275 template <typename T>
6276 [[nodiscard]] T scaled_by_two(const T& x,
6277 int exponent)
6278 {
6279 return std::scalbn(x, exponent);
6280 }
6281
6283 template <typename U>
6284 [[nodiscard]] std::complex<U> scaled_by_two(const std::complex<U>& x,
6285 int exponent)
6286 {
6287 return std::complex<U> {std::scalbn(x.real(), exponent), std::scalbn(x.imag(), exponent)};
6288 }
6289
6295 template <typename T>
6296 [[nodiscard]] buffer<T> upper_inverse(const buffer<T>& r,
6297 std::size_t n)
6298 {
6299 constexpr std::size_t width {64};
6300 buffer<T> x(n * n, T {});
6301 parallel_for((n + width - 1) / width, n * n * width / 2, [&](std::size_t chunk) {
6302 const std::size_t first {chunk * width};
6303 const std::size_t last {std::min(first + width, n)};
6304 for (std::size_t i {last}; i-- > 0;) {
6305 T* const row {x.data() + (i * n) + first};
6306 if (i >= first) {
6307 row[i - first] = T {1};
6308 }
6309 subtract_products(row, r.data() + (i * n) + i + 1, x.data() + ((i + 1) * n) + first, n, last - (i + 1), last - first);
6310 for (std::size_t c {0}; c < last - first; ++c) {
6311 row[c] = row[c] / r[(i * n) + i];
6312 }
6313 }
6314 });
6315 return x;
6316 }
6317
6333 template <typename T>
6334 [[nodiscard]] std::optional<matrix<T>> lstsq_by_qr(const matrix<T>& a,
6335 const matrix<T>& b,
6336 rank_cutoff cutoff,
6337 std::pair<buffer<T>, int>* const r = nullptr)
6338 {
6339 const std::size_t m {a.rows()};
6340 const std::size_t n {a.cols()};
6341 const bool wide {m < n};
6342 const std::size_t rows {wide ? n : m}; // of the matrix factorised
6343 const std::size_t cols {wide ? m : n};
6344 double largest {0.0};
6345 for (std::size_t i {0}; i < m; ++i) {
6346 for (std::size_t j {0}; j < n; ++j) {
6347 largest = std::max(largest, static_cast<double>(std::abs(a(i, j))));
6348 }
6349 }
6350 if (largest == 0.0) {
6351 return std::nullopt;
6352 }
6353 const int exponent {std::ilogb(largest)};
6354 buffer<T> entries(rows * cols, T {});
6355 for (std::size_t i {0}; i < m; ++i) {
6356 for (std::size_t j {0}; j < n; ++j) {
6357 if (wide) {
6358 entries[(j * cols) + i] = scaled_by_two(conj_of(a(i, j)), -exponent);
6359 }
6360 else {
6361 entries[(i * cols) + j] = scaled_by_two(a(i, j), -exponent);
6362 }
6363 }
6364 }
6365 qr_reflections<T> factors {householder_reflect(std::move(entries), rows, cols)};
6366 const buffer<T> inverse {upper_inverse(factors.r, cols)};
6367 double r_squares {0.0};
6368 double inverse_squares {0.0};
6369 for (std::size_t e {0}; e < cols * cols; ++e) {
6370 r_squares += static_cast<double>(std::norm(factors.r[e]));
6371 inverse_squares += static_cast<double>(std::norm(inverse[e]));
6372 }
6373 const double smallest_bound {1.0 / std::sqrt(inverse_squares)};
6374 if (!(smallest_bound > qr_rank_margin * cutoff.scaled(exponent).magnitude(std::sqrt(r_squares), m, n))) {
6375 return std::nullopt;
6376 }
6377 const std::size_t k {b.cols()};
6378 buffer<T> x(n * k, T {});
6379 if (wide) { // x = Q [R⁻ᴴ B; 0]: the blocks applied last first
6380 const matrix<T> inverse_adjoint {adjoint(matrix<T> {cols, cols, inverse})};
6381 const buffer<T> right(b.begin(), b.end());
6382 gemm(cols, k, cols, &*inverse_adjoint.begin(), cols, right.data(), k, x.data(), k);
6383 for (std::size_t block {factors.blocks.size()}; block-- > 0;) {
6384 apply_reflector_block(factors.blocks[block], false, x, k, 0, k);
6385 }
6386 }
6387 else { // x = R⁻¹ times the first rows of Qᴴ B: the blocks' adjoints applied first first
6388 buffer<T> y(b.begin(), b.end());
6389 for (const reflector_block<T>& block : factors.blocks) {
6390 apply_reflector_block(block, true, y, k, 0, k);
6391 }
6392 gemm(n, k, cols, inverse.data(), cols, y.data(), k, x.data(), k);
6393 }
6394 for (T& value : x) {
6395 value = scaled_by_two(value, -exponent);
6396 }
6397 if (r != nullptr) {
6398 *r = {std::move(factors.r), exponent};
6399 }
6400 return matrix<T> {n, k, std::move(x)};
6401 }
6402
6408 template <typename T>
6410 std::size_t n)
6411 {
6412 buffer<double> d(n, double {});
6413 buffer<double> e(n, double {});
6414 buffer<T> none;
6415 buffer<T> none_too;
6416 bidiagonalize(b, n, d, e, none, none_too, false);
6417 bidiagonal_qr(d, e, none, none_too, n, svd_qr_steps_per_value * n);
6418 return d;
6419 }
6420
6425 template <typename T>
6427 std::size_t rank,
6428 const matrix<T>& b)
6429 {
6430 const vector<double>& sigma {s.singular_values()};
6431 const std::size_t rows {s.u().rows()};
6432 const std::size_t cols {s.v().rows()};
6433 buffer<T> x(cols * b.cols(), T {});
6434 for (std::size_t c {0}; c < b.cols(); ++c) {
6435 for (std::size_t d {0}; d < rank; ++d) {
6436 T coefficient {}; // (u_dᴴ b_c) / σ_d
6437 for (std::size_t i {0}; i < rows; ++i) {
6438 coefficient += conj_of(s.u()(i, d)) * b(i, c);
6439 }
6440 coefficient /= from_double<T>(sigma[d]);
6441 for (std::size_t j {0}; j < cols; ++j) {
6442 x[(j * b.cols()) + c] += coefficient * s.v()(j, d);
6443 }
6444 }
6445 }
6446 return matrix<T> {cols, b.cols(), std::move(x)};
6447 }
6448 } // namespace detail
6449
6464 template <typename T>
6465 [[nodiscard]] vector<T> lstsq(const matrix<T>& a,
6466 const vector<T>& b,
6467 rank_cutoff cutoff = {})
6468 {
6469 const matrix<T> column {lstsq(a, matrix<T> {b.size(), 1, buffer<T>(b.begin(), b.end())}, cutoff)};
6470 return vector<T> {buffer<T>(column.begin(), column.end())};
6471 }
6472
6486 template <typename T>
6487 [[nodiscard]] matrix<T> lstsq(const matrix<T>& a,
6488 const matrix<T>& b,
6489 rank_cutoff cutoff = {})
6490 {
6491 if (b.rows() != a.rows()) {
6492 throw dimension_error("lstsq: right-hand side size differs from the matrix");
6493 }
6494 detail::require_finite(a, "lstsq");
6495 if (std::optional<matrix<T>> fast {detail::lstsq_by_qr(a, b, cutoff)}) {
6496 return *std::move(fast);
6497 }
6498 const svd_decomposition<T> s {a};
6499 return detail::lstsq_from_svd(s, detail::numerical_rank(s.singular_values(), a.rows(), a.cols(), cutoff), b);
6500 }
6501
6510 template <typename T>
6512 {
6513 detail::require_finite(a, "singular values");
6514 const bool tall {a.rows() >= a.cols()};
6515 const std::size_t rows {tall ? a.rows() : a.cols()};
6516 const std::size_t cols {tall ? a.cols() : a.rows()};
6517 // Divided by its largest magnitude, as the decomposition is: the sums of squares overflow near 1e155.
6518 double scale {0.0};
6519 for (std::size_t i {0}; i < a.rows(); ++i) {
6520 for (std::size_t j {0}; j < a.cols(); ++j) {
6521 scale = std::max(scale, static_cast<double>(std::abs(a(i, j))));
6522 }
6523 }
6524 scale = scale == 0.0 ? 1.0 : scale;
6525 buffer<T> entries(rows * cols, T {});
6526 for (std::size_t i {0}; i < rows; ++i) {
6527 for (std::size_t j {0}; j < cols; ++j) {
6528 entries[(i * cols) + j] = (tall ? a(i, j) : detail::conj_of(a(j, i))) / detail::from_double<T>(scale);
6529 }
6530 }
6531 buffer<T> r {rows == cols ? std::move(entries) : detail::householder_reflect(std::move(entries), rows, cols).r};
6532 buffer<double> d {detail::square_singular_values(std::move(r), cols)};
6533 for (double& value : d) {
6534 value *= scale;
6535 }
6536 return vector<double> {std::move(d)};
6537 }
6538
6548 template <typename T>
6550 {
6551 const std::size_t n {a.rows()};
6552 if (a.cols() != n) {
6553 throw dimension_error("symmetric eigenvalues require a square matrix");
6554 }
6555 detail::require_finite_lower(a, "symmetric eigenvalues");
6556 // Divided by its largest magnitude read, as the decompositions are: a sum of squares overflows near 1e155.
6557 double scale {0.0};
6558 for (std::size_t i {0}; i < n; ++i) {
6559 for (std::size_t j {0}; j < i; ++j) {
6560 scale = std::max(scale, static_cast<double>(std::abs(a(i, j))));
6561 }
6562 scale = std::max(scale, std::abs(detail::real_part(a(i, i))));
6563 }
6564 scale = scale == 0.0 ? 1.0 : scale;
6565 buffer<T> entries(n * n, T {});
6566 for (std::size_t i {0}; i < n; ++i) {
6567 for (std::size_t j {0}; j < i; ++j) {
6568 entries[(i * n) + j] = a(i, j) / detail::from_double<T>(scale);
6569 }
6570 entries[(i * n) + i] = detail::from_double<T>(detail::real_part(a(i, i)) / scale);
6571 }
6572 buffer<double> d(n, double {});
6573 if (n > 0) {
6574 buffer<double> off(n, double {});
6575 buffer<double> none;
6576 static_cast<void>(detail::tridiagonalize(entries, n, d, off));
6577 detail::tridiagonal_ql(d, off, none, n, detail::ql_steps_per_value * n);
6578 }
6579 std::sort(d.begin(), d.end());
6580 for (double& value : d) {
6581 value *= scale;
6582 }
6583 return vector<double> {std::move(d)};
6584 }
6585
6587 template <typename T>
6595
6606 template <typename T>
6608 const matrix<T>& b,
6609 rank_cutoff cutoff = {})
6610 {
6611 if (b.rows() != a.rows()) {
6612 throw dimension_error("lstsq: right-hand side size differs from the matrix");
6613 }
6614 detail::require_finite(a, "lstsq");
6615 // The singular values from what the solution took: the QR's R (A's over a power of two), else the decomposition.
6616 std::pair<buffer<T>, int> r;
6617 std::optional<matrix<T>> fast {detail::lstsq_by_qr(a, b, cutoff, &r)};
6618 std::optional<svd_decomposition<T>> s;
6619 if (!fast) {
6620 s.emplace(a);
6621 }
6622 buffer<double> values {fast ? detail::square_singular_values(std::move(r.first), std::min(a.rows(), a.cols()))
6623 : buffer<double>(s->singular_values().begin(), s->singular_values().end())};
6624 if (fast) {
6625 for (double& value : values) {
6626 value = std::ldexp(value, r.second);
6627 }
6628 }
6629 const vector<double> sigma {std::move(values)};
6630 const std::size_t rank {detail::numerical_rank(sigma, a.rows(), a.cols(), cutoff)};
6631 matrix<T> x {fast ? *std::move(fast) : detail::lstsq_from_svd(*s, rank, b)};
6632 buffer<double> residuals;
6633 if (rank == a.cols() && a.rows() > a.cols()) {
6634 const matrix<T> fitted {a * x};
6635 residuals.assign(b.cols(), 0.0);
6636 for (std::size_t i {0}; i < b.rows(); ++i) {
6637 for (std::size_t c {0}; c < b.cols(); ++c) {
6638 residuals[c] += std::norm(b(i, c) - fitted(i, c)); // |·|², real or complex
6639 }
6640 }
6641 }
6642 return least_squares_solution<T> {.solution = std::move(x),
6643 .residuals = vector<double> {std::move(residuals)},
6644 .rank = rank,
6645 .singular_values = sigma};
6646 }
6647
6648 namespace detail {
6649
6652 template <typename T>
6654 {
6655 const std::size_t n {a.rows()};
6656 buffer<T> entries(n * n, T {});
6657 for (std::size_t i {0}; i < n; ++i) {
6658 for (std::size_t j {0}; j < i; ++j) {
6659 entries[(i * n) + j] = a(i, j);
6660 entries[(j * n) + i] = conj_of(a(i, j));
6661 }
6662 entries[(i * n) + i] = from_double<T>(real_part(a(i, i)));
6663 }
6664 return matrix<T> {n, n, std::move(entries)};
6665 }
6666
6674 template <typename T>
6675 [[nodiscard]] matrix<T> inverse_on_kept(const vector<double>& sigma,
6676 const matrix<T>& right,
6677 const matrix<T>& left,
6678 rank_cutoff cutoff)
6679 {
6680 const std::size_t rows {left.rows()};
6681 const std::size_t cols {right.rows()};
6682 double largest {0.0};
6683 for (std::size_t d {0}; d < sigma.size(); ++d) {
6684 largest = std::max(largest, std::abs(sigma[d]));
6685 }
6686 const double tolerance {cutoff.magnitude(largest, rows, cols)};
6687 std::vector<std::size_t> kept;
6688 for (std::size_t d {0}; d < sigma.size(); ++d) {
6689 if (std::abs(sigma[d]) > tolerance) {
6690 kept.push_back(d);
6691 }
6692 }
6693 const std::size_t rank {kept.size()};
6694 buffer<T> scaled(cols * rank, T {});
6695 buffer<T> left_adjoint(rank * rows, T {});
6696 for (std::size_t e {0}; e < rank; ++e) {
6697 const std::size_t d {kept[e]};
6698 const auto inverse {from_double<T>(1.0 / sigma[d])};
6699 for (std::size_t i {0}; i < cols; ++i) {
6700 scaled[(i * rank) + e] = right(i, d) * inverse;
6701 }
6702 for (std::size_t j {0}; j < rows; ++j) {
6703 left_adjoint[(e * rows) + j] = conj_of(left(j, d));
6704 }
6705 }
6706 buffer<T> out(cols * rows, T {});
6707 gemm(cols, rows, rank, scaled.data(), rank, left_adjoint.data(), rows, out.data(), rows);
6708 return matrix<T> {cols, rows, std::move(out)};
6709 }
6710
6715 template <typename T>
6717 {
6719 }
6720
6723 template <typename T>
6725 {
6726 const vector<double> signed_values {symmetric_eigenvalues(a)};
6727 buffer<double> values(signed_values.begin(), signed_values.end());
6728 for (double& value : values) {
6729 value = std::abs(value);
6730 }
6731 return vector<double> {std::move(values)};
6732 }
6733
6736 {
6737 return eigenvalue_magnitudes(a);
6738 }
6739
6741 [[nodiscard]] inline vector<double> hermitian_singular_values(const matrix<std::complex<double>>& a)
6742 {
6743 return eigenvalue_magnitudes(a);
6744 }
6745
6750 template <typename T>
6752 rank_cutoff cutoff)
6753 {
6755 return inverse_on_kept(s.singular_values(), s.v(), s.u(), cutoff);
6756 }
6757
6760 rank_cutoff cutoff)
6761 {
6762 const symmetric_eigen decomposition {a};
6763 const matrix<double>& vectors {decomposition.vectors()};
6764 return inverse_on_kept(decomposition.values(), vectors, vectors, cutoff);
6765 }
6766
6768 [[nodiscard]] inline matrix<std::complex<double>> hermitian_pseudo_inverse(const matrix<std::complex<double>>& a,
6769 rank_cutoff cutoff)
6770 {
6771 const hermitian_eigen decomposition {a};
6772 const matrix<std::complex<double>>& vectors {decomposition.vectors()};
6773 return inverse_on_kept(decomposition.values(), vectors, vectors, cutoff);
6774 }
6775 } // namespace detail
6776
6788 template <typename T>
6789 [[nodiscard]] std::size_t matrix_rank(const matrix<T>& a,
6790 rank_cutoff cutoff = {},
6791 bool hermitian = false)
6792 {
6793 if (hermitian) {
6794 if (a.cols() != a.rows()) {
6795 throw dimension_error("matrix_rank: a Hermitian matrix is square");
6796 }
6797 detail::require_finite_lower(a, "matrix_rank");
6798 return detail::numerical_rank(detail::hermitian_singular_values(a), a.rows(), a.cols(), cutoff);
6799 }
6800 return detail::numerical_rank(singular_values(a), a.rows(), a.cols(), cutoff);
6801 }
6802
6814 template <typename T>
6815 [[nodiscard]] matrix<T> pinv(const matrix<T>& a,
6816 rank_cutoff cutoff = {},
6817 bool hermitian = false)
6818 {
6819 if (hermitian) {
6820 if (a.cols() != a.rows()) {
6821 throw dimension_error("pinv: a Hermitian matrix is square");
6822 }
6823 detail::require_finite_lower(a, "pinv");
6824 return detail::hermitian_pseudo_inverse(a, cutoff);
6825 }
6826 detail::require_finite(a, "pinv");
6827 const svd_decomposition<T> s {a};
6828 return detail::inverse_on_kept(s.singular_values(), s.v(), s.u(), cutoff);
6829 }
6830} // namespace numerical
6831
6832#endif // NUMERICAL_DECOMPOSITION_HPP
A Cholesky factorisation A = L Lᴴ of a Hermitian positive-definite matrix.
cholesky_decomposition(matrix< T > &&a)
Factorises a in its own storage, an expiring matrix's elements taken over: no copy of them.
vector< T > solve(const vector< T > &b) const
Solves A x = b via L y = b then Lᴴ x = y.
std::size_t n_
The matrix order.
cholesky_decomposition(const matrix< T > &a)
Factorises a.
matrix< T > lower() &&
The lower-triangular factor L, moved out of an expiring decomposition.
void factor_rows_together(std::size_t i, std::size_t k0)
The columns [k0, i) of the detail::cholesky_diagonal_rows rows from i, together: each element depends...
void factor_row(std::size_t i, std::size_t k0, std::size_t first, std::size_t last)
Row i of L in the columns [first, last), the terms of the columns before k0 already subtracted: the u...
T determinant() const
The determinant — the product of the squared diagonal of L (real, positive).
void factor_rows_below(std::size_t k0, std::size_t end, buffer< T > &minus_adjoint)
The rows below the panel [k0, end), L₂₁ = A₂₁ L₁₁⁻ᴴ: chunks of detail::cholesky_rows rows in parallel...
matrix< T > lower() const &
The lower-triangular factor L (with L Lᴴ == A).
void update_trailing(std::size_t k0, std::size_t end, const buffer< T > &minus_adjoint, buffer< T > &packing)
A22 += L21 (−L21ᴴ), by tiles of detail::cholesky_update_columns columns and detail::cholesky_update_r...
buffer< T > lower_
The lower-triangular factor L, row-major (upper triangle zero).
Thrown when an iterative algorithm has not converged within its bound of sweeps.
convergence_error(const std::string &message)
Builds the error.
Thrown when an operation's operands have incompatible shapes.
Definition vector.hpp:35
The eigenvalues and eigenvectors of a complex Hermitian matrix.
matrix< std::complex< double > > vectors_
Eigenvectors as columns.
hermitian_eigen(const matrix< std::complex< double > > &input, eigen_method method=eigen_method::divide_and_conquer)
Diagonalises input.
const matrix< std::complex< double > > & vectors() const &noexcept
The eigenvectors as columns, ordered to match values.
std::size_t n_
The matrix order.
matrix< std::complex< double > > vectors() &&
The eigenvectors, moved out of an expiring decomposition.
vector< double > values_
Eigenvalues, ascending.
const vector< double > & values() const noexcept
The eigenvalues, ascending.
An LU factorisation with partial pivoting of a square matrix.
T determinant() const
The determinant (the pivot sign times the product of U's diagonal; 0 if singular).
void swap_rows_outside(std::size_t a, std::size_t b, std::size_t k0, std::size_t end)
Swaps rows a and b of the factor outside the panel's columns [k0, end): right of the panel up to swap...
std::vector< std::pair< std::size_t, std::size_t > > deferred_swaps_
Swaps left for the columns past it.
matrix< T > factors() const &
L and U in one matrix, as LAPACK's dgetrf leaves them: U on and above the diagonal,...
void update_columns(const detail::packed_left< T > &left, std::size_t k0, std::size_t c0, std::size_t c1)
A₂₂ −= L₂₁ U₁₂ on the trailing columns [c0, c1), as A₂₂ += (−L₂₁) U₁₂ through the matrix product kern...
void solve_rows(buffer< T > &y, std::size_t cols, bool lower=false) const
Performs the pivoted elimination, filling lu_ and pivots_.
std::size_t column_slices(std::size_t cols) const
The column slices solve_rows splits cols right-hand sides into, one a thread, each of detail::slice_c...
const std::vector< std::size_t > & pivots() const noexcept
The row permutation: row i of P A = L U is row pivots()[i] of A.
void factorize_columns(buffer< T > &panel, std::size_t rows, std::size_t first, std::size_t last, std::size_t k0, std::size_t end)
Factorises the columns [first, last) of the panel (column-major, rows rows), every row from first dow...
lu_decomposition(const matrix< T > &a)
Factorises a (in place, into combined L\U with a row permutation).
std::size_t panel_of(std::size_t i) const noexcept
The first row of the panel that holds row i.
vector< T > solve(const vector< T > &b) const
Solves A x = b using the stored factors.
matrix< T > factors() &&
factors, moved out of an expiring decomposition: no copy.
matrix< T > solve(matrix< T > &&b) const
solve on an expiring b, in its own storage: its rows permuted in place (cycle by cycle of the pivots,...
buffer< T > lu_
Combined L (below diag, unit) and U (on/above), row-major.
void negated_block_into(buffer< T > &out, std::size_t r0, std::size_t r1, std::size_t c0, std::size_t c1) const
negated_block into out, resized to it.
std::size_t lower_slice_start(std::size_t s, std::size_t slices, std::size_t cols) const
The first column of slice s of slices (slices itself: cols) where the forward pass is lower: column c...
matrix< T > pivoted_inverse(buffer< T > storage) const
(P A)⁻¹ = U⁻¹ L⁻¹, the inverse of the rows in their pivoted order, in storage (its elements discarded...
void transpose_panel(buffer< T > &panel, std::size_t rows, std::size_t k0, std::size_t end, bool in)
Copies the columns [k0, end) of the factor, every row from k0, into panel (column-major,...
std::vector< std::size_t > pivots_
Row permutation: row now at i came from pivots_[i].
void solve_panel_rows(std::size_t k0, std::size_t end)
U₁₂ = L₁₁⁻¹ A₁₂: the panel's rows right of it, by forward substitution with its unit-lower L....
void swap_left_columns()
Applies left_swaps_ to the columns left of each swap's panel: a panel's columns take,...
void subtract_block(buffer< T > &y, std::size_t cols, std::size_t r0, std::size_t r1, std::size_t c0, std::size_t c1, std::size_t first, std::size_t last) const
Y[r0, r1) −= F[r0, r1) × [c0, c1) · Y[c0, c1), F the factor, through the matrix product kernel (nothi...
std::size_t block_
The panel width.
matrix< T > inverse(buffer< T > storage) const
inverse in storage, its elements discarded: no fresh n × n mapped and faulted in where it holds that ...
int sign_
Permutation sign, for the determinant.
void solve_columns(buffer< T > &y, std::size_t cols, std::size_t c0, std::size_t c1, bool lower) const
solve_rows on the columns [c0, c1) of y (n × cols, row-major): the blocked passes,...
bool singular() const noexcept
Whether a pivot was zero: U is singular, and solve and inverse throw.
void factorize_panel(buffer< T > &panel, std::size_t k0, std::size_t end)
Unblocked LU with partial pivoting of the columns [k0, end), every row below k0.
std::pair< T, double > log_determinant() const
The determinant as its unit-modulus sign and the natural logarithm of its magnitude,...
std::size_t n_
The matrix order.
bool singular_
Set if a zero pivot was met.
std::size_t swap_limit_
The columns a panel's row swaps reach now.
void factorize_trailing(buffer< T > &panel, std::size_t first_end)
The panels after the first (whose columns end at first_end), each with its trailing update....
matrix< T > solve(const matrix< T > &b) const
Solves A X = B for every column of b at once.
std::vector< std::pair< std::size_t, std::size_t > > left_swaps_
Swaps left for the columns before their panel.
matrix< T > inverse() const
The inverse, U⁻¹ L⁻¹ P, by blocks of rows through the matrix product kernel: L⁻¹ from the identity,...
lu_decomposition(matrix< T > &&a)
Factorises a in its own storage, an expiring matrix's elements taken over: no copy of them.
void solve_narrow(buffer< T > &y, std::size_t cols) const
solve_rows for a few right-hand sides: row by row, each one's terms in the order the blocked passes g...
buffer< T > negated_block(std::size_t r0, std::size_t r1, std::size_t c0, std::size_t c1) const
The rows [r0, r1) and columns [c0, c1) of the factor, negated, row-major and contiguous.
A dense, row-major matrix of T with value semantics.
Definition matrix.hpp:35
buffer< T >::const_iterator end() const noexcept
Const iterator past the last element.
Definition matrix.hpp:138
buffer< T >::const_iterator begin() const noexcept
Const iterator to the first element (row-major).
Definition matrix.hpp:132
std::size_t rows() const noexcept
The number of rows.
Definition matrix.hpp:107
std::size_t cols() const noexcept
The number of columns.
Definition matrix.hpp:113
Thrown when a decomposition's input holds an infinite or NaN entry: no factorisation of it means anyt...
non_finite_error(const std::string &message)
Builds the error.
Thrown when a Cholesky factorisation finds the matrix not positive-definite.
std::size_t order() const noexcept
The order of the first leading minor found not positive definite (LAPACK's info), 0 where unknown.
std::size_t order_
The order of the failing leading minor, or 0.
not_positive_definite_error(const std::string &message, std::size_t order=0)
Builds the error.
A QR factorisation A = Q R of an m × n matrix, as NumPy's qr, reduced by default.
vector< T > solve(const vector< T > &b) const
The least-squares solution of A x ≈ b (x = R⁻¹ Qᴴ b) for a full-rank A.
std::size_t depth_
min(m, n).
qr_decomposition(matrix< T > &&a, qr_mode mode=qr_mode::reduced)
Factorises a in its own storage, an expiring matrix's elements taken over: no copy of them.
std::size_t cols_
The number of columns (n).
qr_decomposition(const matrix< T > &a, qr_mode mode=qr_mode::reduced)
Factorises a.
std::size_t width_
Q's columns and R's rows: k, or m complete.
std::size_t rows_
The number of rows (m).
matrix< T > q() &&
Q, moved out of an expiring decomposition (r stays).
matrix< T > q() const &
The factor Q (orthonormal columns): m × k, k = min(m, n), or m × m complete.
buffer< T > q_
The factor Q, row-major, m × width_.
buffer< T > r_
The upper-trapezoidal factor R, row-major, width_ × n.
matrix< T > r() &&
R, moved out of an expiring decomposition (q stays).
matrix< T > r() const &
The upper-trapezoidal factor R: k × n, k = min(m, n), or m × n complete.
The magnitude at or under which a singular value counts as zero, in a rank (matrix_rank),...
constexpr rank_cutoff() noexcept=default
NumPy's default: the largest singular value times the machine epsilon times the larger dimension.
static rank_cutoff absolute(double magnitude)
magnitude itself, whatever the largest singular value.
static double checked(double value)
constexpr rank_cutoff(double value, bool absolute) noexcept
rank_cutoff scaled(int exponent) const noexcept
The same cutoff for the matrix divided by 2^exponent.
double magnitude(double largest, std::size_t rows, std::size_t cols) const noexcept
The cutoff for a rows × cols matrix whose largest singular value is largest.
Thrown when a matrix has no unique solution (it is singular).
singular_matrix_error(const std::string &message)
Builds the error.
The singular value decomposition A = U Σ Vᴴ of an m × n matrix.
matrix< T > u_
m×k left singular vectors.
static void orthogonalise(buffer< T > &x, const buffer< T > &basis, std::size_t n, std::size_t count)
Removes from x its components along the first count columns of the n × n basis.
const matrix< T > & v() const &noexcept
The right singular vectors V (columns): n × k, or n × n full.
matrix< T > v_
n×k right singular vectors.
const matrix< T > & u() const &noexcept
The left singular vectors U (columns): m × k, or m × m full.
svd_decomposition(const matrix< T > &a, svd_method method=svd_method::divide_and_conquer, bool full=false)
Decomposes a.
matrix< T > vh() const
Vᴴ (k × n, or n × n full), so that A = U Σ Vᴴ reads u() * diag(singular_values()) * vh() (full,...
matrix< T > u() &&
U, moved out of an expiring decomposition (v and the singular values stay).
vector< double > singular_values_
Singular values, descending.
static matrix< T > left_vectors(buffer< T > q, buffer< T > left, std::size_t rows, std::size_t cols, std::size_t width)
U from the Householder q (rows × width) and the left vectors left of R (cols × cols): Q times them,...
void factor(const matrix< T > &a, svd_method method, bool full)
Decomposes an m × n a with m ≥ n into u_, singular_values_ and v_, U square when full.
const vector< double > & singular_values() const noexcept
The singular values, descending.
The eigenvalues and eigenvectors of a real symmetric matrix.
matrix< double > vectors_
Eigenvectors as columns.
const matrix< double > & vectors() const &noexcept
The eigenvectors as columns, ordered to match values.
matrix< double > vectors() &&
The eigenvectors, moved out of an expiring decomposition.
void extract_sorted(const buffer< double > &diagonal, const buffer< double > &v)
Sorts the eigenvalues diagonal ascending and reorders v's columns to match.
symmetric_eigen(const matrix< double > &input, eigen_method method=eigen_method::divide_and_conquer)
Diagonalises input.
vector< double > values_
Eigenvalues, ascending.
std::size_t n_
The matrix order.
const vector< double > & values() const noexcept
The eigenvalues, ascending.
A dense vector of T with value semantics.
Definition vector.hpp:86
buffer< T >::const_iterator end() const noexcept
Const iterator past the last element.
Definition vector.hpp:127
std::size_t size() const noexcept
The number of elements.
Definition vector.hpp:109
buffer< T >::const_iterator begin() const noexcept
Const iterator to the first element.
Definition vector.hpp:121
#define NUMERICAL_UNROLL_BLOCK
Definition gemm.hpp:69
#define NUMERICAL_OUT_OF_LINE
Definition gemm.hpp:52
A dense, row-major, value-semantic matrix over a scalar type.
constexpr std::size_t gemm_packed_threshold
Below this many multiply-adds the plain loop (gemm_rows, by blocks of rows held in registers) runs in...
Definition gemm.hpp:513
T symmetric_row(const T *row, const T *v, const T &f, T *out, std::size_t count)
Σ row[i] · v[i] and out[i] += row[i] · f, in order, for a type no native vector holds.
Definition simd.hpp:698
constexpr std::size_t lu_lookahead_columns
The trailing columns at most past which the LU factorises its next panel beside the update of the res...
constexpr std::size_t qr_transpose_tile
The side of the tiles in which the QR transposes (transpose_into): a tile's rows and columns both sta...
double rescale_tiny(buffer< T > &x)
Scales x by rescue_factor of its largest magnitude, exactly, so that a Householder vector built from ...
arrow_workspace & thread_arrow_workspace()
The calling thread's arrow_workspace, kept from one decomposition to the next. A join uses it whole o...
double jacobi_off_diagonal(const buffer< double > &a, std::size_t n)
The Frobenius norm of the strict off-diagonal of the n × n a.
constexpr std::size_t lu_block_for(std::size_t n) noexcept
The panel width of the LU of order n: a power of two, so a row's panel is a mask away,...
void factor_qr_panel(T *panel, std::size_t ld, std::size_t top, std::vector< buffer< T > > &reflectors, std::vector< T > &taus, std::size_t k0, std::size_t k1)
Householder reflections of the columns k0…k1-1 of a column-major matrix, rows from k0,...
constexpr std::size_t cholesky_update_rows
The rows of one tile of a Cholesky update: the tiles of a panel's update, each one product on one thr...
matrix< T > inverse_on_kept(const vector< double > &sigma, const matrix< T > &right, const matrix< T > &left, rank_cutoff cutoff)
Σ_d right_d (1 / σ_d) left_dᴴ over the columns d of right (cols × k) and left (rows × k) whose sigma ...
bool all_finite_shared(const double *x, std::size_t count)
all_finite on the threads by chunks of finite_chunk, past two of them: a pass bound by the memory,...
double rotation_length(double a, double b) noexcept
√(a² + b²) for a plane rotation of the QR and QL iterations, as LAPACK's dlartg takes it: the plain s...
constexpr std::size_t cholesky_block
The columns of one panel of the blocked Cholesky factorisation (as lu_block).
void parallel_for_beside(std::size_t count, std::size_t work_per_index, const Body &body, const Side &side)
Runs side on the caller while the pool's workers run body(i) for every i in [0, count),...
Definition parallel.hpp:618
void bidiagonal_qr(buffer< double > &d, buffer< double > &e, buffer< T > &ut, buffer< T > &vt, std::size_t n, std::size_t max_steps)
The singular values of the real upper bidiagonal (d, e) by implicit-shift QR with deflation (LINPACK'...
vector< double > hermitian_singular_values(const matrix< T > &a)
The singular values of the real symmetric or complex Hermitian matrix whose lower triangle is a's: of...
matrix< T > hermitian_pseudo_inverse(const matrix< T > &a, rank_cutoff cutoff)
The pseudo-inverse of the real symmetric or complex Hermitian matrix whose lower triangle is a's: fro...
double rescue_factor(double largest)
The exact power of two that brings a magnitude below about the square root of the smallest normal num...
constexpr double rescue_below
The magnitude under which rescue_factor scales a T up: about the square root of the smallest normal n...
bool lower_is_finite(const matrix< T > &a)
Whether the lower triangle of the square a, and its diagonal's real part, are finite: entry by entry,...
void form_q_into(buffer< T > &out, const std::vector< reflector_block< T > > &blocks, std::size_t rows, std::size_t first, std::size_t count, bool transposed)
The columns [first, first + count) of Q = H₀ H₁ …, the blocks applied (the last first) to those of th...
constexpr std::size_t lu_update_speedup
The multiply-adds of an LU's trailing update one of the tile's dependent ones stands for,...
void subtract_corrections(T *y, const T *f, const T *x, std::size_t ldx, std::size_t terms, std::size_t count)
subtract_products of a real T, where the reduction's corrections are those of its type.
void add_conjugate_product(T *y, const T *x, const T &f, std::size_t count)
y[i] += conj(x[i]) · f for a real T, where the conjugate is the value: add_product.
void subtract_rows(double *y, std::size_t ldy, const double *f, std::size_t ldf, const double *x, std::size_t ldx, std::size_t terms, std::size_t count)
subtract_rows for double, the units chosen as subtract_products chooses them: each element the value ...
Definition gemm.hpp:217
@ plain
gemm_rows, or a scalar whose products are not shared: A is read in place.
T conjugate_dot_product(const T *x, const T *y, std::size_t count)
Σ conj(x[i]) · y[i], in order, where no native vector holds the parts.
Definition simd.hpp:739
constexpr std::size_t cholesky_together_width
The narrowest Cholesky diagonal block whose rows go together: in a block of 8, the groups' loops cost...
constexpr double plain_squares_low
The smallest plain sum of squares plain_or_at_scale keeps: a term that underflowed is below 2⁻¹⁰²²,...
constexpr std::size_t gemm_kernel_speedup
The multiply-adds the register tile does in the time of one in work_per_thread's dependent chain (abo...
Definition gemm.hpp:497
constexpr std::size_t symmetric_row_group
The rows of a lower triangle symmetric_rows takes at once.
Definition simd.hpp:34
buffer< T > upper_inverse(const buffer< T > &r, std::size_t n)
R⁻¹, upper triangular, of the n × n upper-triangular r (row-major): back substitution against the ide...
qr_reflections< T > householder_reflect(buffer< T > a, std::size_t rows, std::size_t cols)
The reflections of householder_qr, without forming Q: A = Q R with Q the product of the blocks' refle...
constexpr std::size_t divide_leaf
The order up to which tridiagonal_divide_and_conquer diagonalises by implicit QL.
T operator_times(const T &a, const T &b)
a · b, as std::operator* computes it, in line. libc++'s complex product (the four products and their ...
Definition simd.hpp:772
void solve_bidiagonal_leaf(bidiagonal_node &node)
Decomposes the leaf node by implicit-shift QR.
constexpr std::size_t vector_speedup
The multiply-adds a vectorised inner loop does in the time of one in work_per_thread's dependent chai...
Definition parallel.hpp:544
void merge_rank_one(buffer< double > &d, buffer< double > z, double rho, double *q, std::size_t ldq, std::size_t n, std::size_t split, merge_workspace &work)
The eigenvalues of diag(d) + ρ z zᵀ, ascending, into d, and its eigenvectors times q (the row-major n...
void one_sided_jacobi(buffer< T > &w, buffer< T > &v, std::size_t n, std::size_t max_sweeps)
One-sided (Hestenes) Jacobi on the columns of the n × n row-major w: plane rotations,...
vector< double > eigenvalue_magnitudes(const matrix< T > &a)
The magnitudes of the eigenvalues of the real symmetric or complex Hermitian a, its lower triangle re...
constexpr std::size_t slice_columns
The fewest columns an LU solve gives a thread of its own: a narrower slice starves the product kernel...
constexpr std::size_t cholesky_update_columns
The fewest columns of the trailing block one Cholesky update's product takes: each product packs ever...
void add_product(double *y, const double &f, const double *x, std::size_t count)
y[i] += f · x[i] for double, as subtract_product.
Definition gemm.hpp:308
constexpr std::size_t jacobi_sweeps
The sweeps a Jacobi method may take: it converges quadratically, in under 15 on every matrix measured...
std::array< double, symmetric_row_group >(*)(const double *, std::size_t, const double *, double *, std::size_t) symmetric_rows_kernel
A kernel of symmetric_rows' form: symmetric_rows itself, or an x86-64 one.
void subtract_products(double *y, const double *f, const double *x, std::size_t ldx, std::size_t terms, std::size_t count)
y[i] -= Σₜ f[t] · x[(t · ldx) + i] for double, the units chosen as subtract_product chooses them,...
Definition gemm.hpp:194
void require_finite(const matrix< T > &a, const char *what)
The one non-finite policy, checked where every decomposition starts.
T hermitian_row(symmetric_row_kernel, const T *row, const T *v, T *out, std::size_t i)
Row i's terms of a Hermitian product from the lower triangle: out[j] += conj(row[j]) v[i] for j < i,...
std::vector< reflector_block< T > > reflect_in_rows(buffer< T > &a, std::size_t rows, std::size_t cols, buffer< T > *r=nullptr)
The blocks of reflectors of a Householder QR of the row-major a (rows × cols), factorised in place as...
constexpr std::size_t tridiagonal_block
The reflections of one panel of the blocked tridiagonal reduction.
void tridiagonal_ql(buffer< double > &d, buffer< double > &e, buffer< T > &zt, std::size_t n, std::size_t max_steps)
The eigenvalues of the symmetric tridiagonal (d, e) by implicit QL with Wilkinson-type shifts (EISPAC...
constexpr std::size_t work_per_thread
The work one thread must have to pay for starting it: about 60 µs of multiply-adds,...
Definition parallel.hpp:534
double secular_root(const buffer< double > &d, const buffer< double > &z, double rho, std::size_t i, std::size_t max_iterations, double *delta)
Root i of 1/ρ + Σⱼ zⱼ²/(dⱼ − λ) = 0 (d strictly ascending, ‖z‖ = 1), the i-th eigenvalue of diag(d) +...
void parallel_copy(const T *in, std::size_t count, T *out)
Copies the count elements at in to out, split across the threads the work pays for,...
Definition parallel.hpp:703
void join_bidiagonal_node(bidiagonal_node &node, const bidiagonal_node &one, const bidiagonal_node &two, arrow_workspace &work)
Joins the two decomposed halves of node, one and two, by merge_arrow, node started (start_bidiagonal_...
buffer< T > tridiagonal_basis(const tridiagonal_reduction< T > &reduction, std::size_t n)
Mᵀ of a reduction, row-major (its rows are the columns of M): Qᵀ by blocks of reflectors from the las...
void reflect_columns(T *panel, std::size_t height, const buffer< T > &v, std::size_t first, std::size_t from, std::size_t to, const T &s=T {2})
Applies I − s v vᴴ from the left (s 2 for a unit v) to the columns from…to-1 of the column-major pane...
constexpr std::size_t ql_steps_per_value
The implicit QL steps the tridiagonal eigensolver may take per eigenvalue, as LAPACK's dsteqr bounds ...
void gemm_with(const packed_left< T > &left, std::size_t n, const T *b, std::size_t ldb, T *c, std::size_t ldc)
C += A·B for the packed left (pack_left) and the k × n b, as gemm computes it, A read in place here,...
Definition gemm.hpp:3080
T conj_of(const T &x)
The conjugate of a real (or general) scalar — the value itself.
std::size_t plan_bidiagonal(std::vector< bidiagonal_node > &nodes, const double *d, const double *e, std::size_t n, std::size_t extra, std::size_t depth)
Appends the block (d, e, n, extra) and everything below it to nodes, parents before children,...
std::vector< rotation_run > rotation_runs(const std::vector< row_operation > &operations)
The operations split into runs of chained rotations, every other operation a run of one.
constexpr std::size_t kept_workspace
The most elements a buffer of a thread's merge_workspace keeps from one decomposition to the next: 2 ...
constexpr std::size_t cholesky_block_for(std::size_t n) noexcept
The panel width of the Cholesky factorisation of order n: narrower up to order 256,...
void release_large_arrow_workspace(arrow_workspace &work)
Releases the buffers of work longer than kept_workspace.
void factor_row_major_panel(buffer< T > &a, std::size_t rows, std::size_t cols, std::vector< buffer< T > > &reflectors, std::vector< T > &taus, std::size_t k0, std::size_t k1)
factor_qr_panel on the columns k0…k1-1 of the row-major a, rows from k0, in a column-major copy of th...
constexpr std::size_t tridiagonal_band
The rows of each band of the tridiagonal reduction's trailing update: a band reaches its diagonal,...
double norm_at_scale(const T *x, std::size_t count)
The Euclidean norm of the count elements at x, summed at the scale of their largest magnitude: a plai...
constexpr double qr_rank_margin
How far above the rank cutoff a bound on the smallest singular value must stay for lstsq_by_qr to ans...
constexpr std::size_t narrow_solve
The most right-hand sides an LU solve takes row by row rather than by blocks through the product kern...
constexpr std::size_t lu_small_order
The order up to which the LU takes panels of lu_small_block columns: there the panels,...
void transpose_into(const T *from, std::size_t rows, std::size_t cols, T *to)
The row-major rows × cols from into to as its transpose, cols × rows row-major, by tiles,...
tridiagonal_reduction< T > tridiagonalize(buffer< T > &a, std::size_t n, buffer< double > &d, buffer< double > &e)
Reduces the Hermitian (real: symmetric) n × n row-major a, of which the lower triangle is read,...
void subtract_product(double *y, const double &f, const double *x, std::size_t count)
y[i] −= f · x[i] for double on the widest x86-64 units the matrix product's kernel may use (gemm_path...
Definition gemm.hpp:163
bool all_finite(const double *x, std::size_t count) noexcept
Whether the count values at x are all finite: a NaN or an infinity has every exponent bit set,...
void require_finite_lower(const matrix< T > &a, const char *what)
require_finite over what a Hermitian eigensolver reads: the lower triangle, and the diagonal's real p...
constexpr std::size_t bidiagonal_block
The rows from which a step of the blocked bidiagonal reduction shares its coefficients and correction...
void solve_divide_subtree(std::vector< divide_node > &nodes, std::size_t index, merge_workspace &work)
Solves the block index of nodes and everything below it, each block right after its halves.
packed_left< T > pack_left(std::size_t m, std::size_t, std::size_t k, const T *a, std::size_t lda, buffer< T > &)
The m × k a packed once for products with it of about n columns each (gemm_with), in storage (grown a...
Definition gemm.hpp:3026
constexpr std::size_t bidiagonal_crossover
The order below which a real bidiagonal reduction goes a reflection at a time, as LAPACK's dgebrd fal...
void bidiagonal_divide_and_conquer(buffer< double > &d, buffer< double > &e, buffer< T > &ut, buffer< T > &vt, std::size_t n)
The singular values of the upper bidiagonal (d, e) into d, descending, the singular vectors accumulat...
void form_bidiagonal_bases(const std::vector< buffer< T > > &left, std::vector< buffer< T > > right, const buffer< T > &left_phase, const buffer< T > &right_phase, std::size_t n, buffer< T > &ut, buffer< T > &vt)
The bases of a bidiagonal reduction from its reflectors: U_b = (H₀ H₁ …) diag(conj left phases) and V...
constexpr std::size_t finite_chunk
The elements all_finite_shared gives a thread at a time.
std::array< T, symmetric_row_group > symmetric_rows(const T *rows, std::size_t n, const T *v, T *out, std::size_t i)
symmetric_rows one column at a time, for a type no native vector holds.
Definition simd.hpp:715
double rank_tolerance(double largest, std::size_t rows, std::size_t cols)
The magnitude under which a diagonal entry of R or a singular value counts as zero,...
double quadratic_step(double a, double b, double c)
The smaller root η of C η² − A η + B = 0, in the form that does not cancel: a step of solve_secular's...
double * workspace_of(workspace_buffer &buffer, std::size_t size)
buffer at least size elements long, its storage kept for the next join. A larger one is mapped fresh,...
reflector_block< T > gather_reflectors(const std::vector< buffer< T > > &reflectors, std::size_t k0, std::size_t k1, std::size_t rows, const std::vector< T > *taus=nullptr)
Gathers the reflectors reflectors[k0…k1-1] (each I − τ_k v vᴴ: τ_k from taus, else 2 for a unit v; an...
std::optional< matrix< T > > lstsq_by_qr(const matrix< T > &a, const matrix< T > &b, rank_cutoff cutoff, std::pair< buffer< T >, int > *const r=nullptr)
The least-squares solution of A X ≈ B by a QR factorisation, of A, or of Aᴴ for a wide A (the minimum...
void gemm(std::size_t m, std::size_t n, std::size_t k, const T *a, std::size_t lda, const T *b, std::size_t ldb, T *c, std::size_t ldc)
C += A·B, every element of C summed in ascending order of the depth.
Definition gemm.hpp:2740
void subtract_term(T &sum, const T &a, const T &b)
sum −= a · b in one statement, which the compiler may fuse (as it fuses the matrix solve's).
void apply_reflector_block(const reflector_block< T > &block, bool adjoint, buffer< T > &a, std::size_t cols, std::size_t from, std::size_t to)
Applies I − V T Vᴴ (or its adjoint I − V Tᴴ Vᴴ) from the left to the block's rows of a row-major arra...
double plain_or_at_scale(double plain, const T *x, std::size_t count)
√plain, the plain sum of the squares of the count elements at x, where it neither overflowed nor unde...
buffer< T > columns_by_value(const buffer< T > &zt, const std::vector< std::size_t > &order, std::size_t n)
The n × n row-major matrix whose column c is row order[c] of zt (the eigenvectors as rows): by tiles,...
void parallel_for(std::size_t count, std::size_t work_per_index, const Body &body)
Runs body(i) for every i in [0, count).
Definition parallel.hpp:580
void cyclic_jacobi(buffer< double > &a, buffer< double > &v, std::size_t n, double frobenius, std::size_t max_sweeps)
Cyclic Jacobi on the full symmetric a, the rotations accumulated into v.
void merge_arrow(buffer< double > &d, buffer< double > z, double alpha, double beta, buffer< double > &u, buffer< double > &v, std::size_t n, std::size_t m, std::size_t split, arrow_workspace &work)
The singular value decomposition of the arrow M (first row z, diagonal d, d₀ = 0),...
std::conditional_t< std::is_same_v< T, float >||std::is_same_v< T, std::complex< float > >, float, double > value_range_t
The real type whose range a T's values take: float for a single-precision scalar, real or complex; do...
void join_bidiagonal_halves(std::vector< bidiagonal_node > &nodes, std::size_t index, arrow_workspace &work)
Joins the halves of nodes' block index in work, then lets them go.
matrix< T > hermitian_from_lower(const matrix< T > &a)
The real symmetric or complex Hermitian matrix whose lower triangle is the square a's,...
void hermitian_product(const T *block, std::size_t n, std::size_t m, const T *v, T *w, buffer< T > &scratch)
w = B v for the Hermitian m × m block (row stride n) from its lower triangle alone,...
constexpr double jacobi_large
The |ζ| past which one-sided Jacobi takes the rotation's tangent as 1 / (2|ζ|): ζ² stays finite under...
std::size_t numerical_rank(const vector< double > &sigma, std::size_t rows, std::size_t cols, rank_cutoff cutoff={})
How many of the singular values sigma of an rows × cols matrix exceed cutoff: its numerical rank (the...
double real_part(const T &x)
The real part of a real (or general) scalar as a double.
decltype(std::abs(std::declval< const T & >())) magnitude_t
The type std::abs gives for a T: the real type of a complex one, T itself otherwise.
T over_pivot(const T &x, const T &divisor)
x over the pivot whose pivot_divisor is divisor: the same quotient wherever the LU takes it.
gemm_path gemm_path_current() noexcept
The path the next double product takes: the most capable one under the cap that runs here (each path'...
Definition gemm.hpp:145
std::vector< std::size_t > ascending_order(const buffer< double > &diagonal)
The indices of diagonal by ascending value.
void solve_divide_leaf(const divide_node &node)
Solves the leaf node by implicit QL: its eigenvalues ascending into its diagonal, their eigenvectors ...
void release_large_workspace(merge_workspace &work)
Releases the buffers of work longer than kept_workspace.
constexpr std::size_t lu_small_block
The columns of one panel of the LU of an order up to lu_small_order.
void solve_bidiagonal_subtree(std::vector< bidiagonal_node > &nodes, std::size_t index, arrow_workspace &work)
Decomposes the block index of nodes and everything below it, each block right after its halves.
double real_subdiagonal(double t, double, double &)
A real subdiagonal entry of the tridiagonal: kept with its sign (the rotations take any sign).
void subtract_panel_columns(T *column, const T *columns, std::size_t height, const T *l, std::size_t terms)
column −= Σₜ x_t · conj(l[t]) over the terms columns columns (height elements each,...
constexpr std::size_t cholesky_one_panel
The order below which the Cholesky factorisation is one panel: no trailing product,...
bool is_finite_entry(const T &x)
Whether x is finite.
constexpr std::size_t first_touch_chunk
The elements a task of workspace_of touches first: 64 pages of 4 KiB, so a buffer of a few megabytes ...
void apply_row_operations(buffer< T > &m, std::size_t n, const std::vector< row_operation > &operations)
Applies the recorded operations, in order, to the row-major m (n per row). Each column takes them ind...
constexpr std::size_t solve_leaf
The columns of one leaf of the recursive LU panel.
matrix< T > parallel_copy_of(const matrix< T > &a)
A copy of a whose pages the threads fault in (parallel_copy), a decomposition's own storage: the matr...
buffer< double > square_singular_values(buffer< T > b, std::size_t n)
The singular values of the square n × n row-major b, descending, without its vectors: its Golub–Kahan...
constexpr std::size_t qr_block
The reflectors of one panel of the blocked QR.
constexpr std::size_t lu_block
The columns of one panel of the blocked LU: the trailing update, a product of this depth,...
double dot_product(const double *x, const double *y, std::size_t count)
Σ x[i] · y[i] for double, as subtract_product chooses its units (in another order of summation there,...
Definition gemm.hpp:369
void reflect_rows(buffer< T > &a, std::size_t cols, const buffer< T > &v, std::size_t first, std::size_t from)
Applies I − 2 v vᴴ from the left to the rows first…first+|v|-1 of a row-major array,...
void apply_transposed_reflector_block(const reflector_block< T > &block, buffer< T > &at, std::size_t cols, std::size_t from, std::size_t to, bool adjoint=false)
apply_reflector_block to A held transposed: at is Aᵀ (row-major, cols columns), and A ← (I − V T Vᴴ) ...
void rotate_columns(buffer< double > &m, std::size_t cols, std::size_t a, std::size_t b, double c, double s)
Columns a and b of the row-major m (cols per row) rotated: a ← c a + s b, b ← c b − s a.
void bidiagonal_divide(const double *d, const double *e, std::size_t n, std::size_t extra, buffer< double > &sigma, buffer< double > &u, buffer< double > &v)
The singular value decomposition of the upper bidiagonal n × (n + extra) (extra 0 or 1) with diagonal...
T phase_of(const T &x)
The unit-modulus factor of x: x / |x| (its sign, for a real scalar), and 1 at 0. A subnormal x is sca...
void divide_and_conquer(double *d, const double *e, std::size_t n, buffer< double > &q)
The eigenvalues, ascending, of the symmetric tridiagonal with diagonal d[0…n-1] and subdiagonal e[0…n...
constexpr std::size_t lowner_block
The poles a task of merge_rank_one's Löwner products takes, and the side of its tiles of u.
constexpr std::size_t secular_iterations
The iterations one root of the secular equation may take: it converges in a few; the bound turns a no...
T scaled_by_two(const T &x, int exponent)
x times two to the exponent, exactly where the result is a normal number (no power of two is formed,...
constexpr std::size_t corrected_product_least
The rows from which corrected_hermitian_product shares its coefficients and bands with the threads: o...
void join_divide_node(const divide_node &node, merge_workspace &work)
Joins the two solved halves of node by merge_rank_one, in work.
matrix< T > lstsq_from_svd(const svd_decomposition< T > &s, std::size_t rank, const matrix< T > &b)
The minimum-norm least-squares solution of A X ≈ B from the singular value decomposition s of A (m × ...
constexpr std::size_t bidiagonal_leaf
The order up to which bidiagonal_divide decomposes by implicit-shift QR.
constexpr std::size_t product_rows
The rows of y subtract_rows takes at once.
Definition simd.hpp:425
constexpr std::size_t svd_band
The rows of an SVD's input one thread scales and copies at a time.
constexpr std::size_t inverse_band
The rows an LU inverse writes its identity into, and permutes the columns of, on one thread at a time...
void plan_divide(std::vector< divide_node > &nodes, double *d, const double *e, std::size_t n, double *q, std::size_t ldq, std::size_t depth)
Appends the block (d, e, n) and, before it is solved, everything below it to nodes,...
constexpr std::size_t gemm_min_depth
Below this depth the plain loop runs too: each panel reads and writes its tiles of C once,...
Definition gemm.hpp:519
double singular_secular_root(const buffer< double > &d, const buffer< double > &z, std::size_t i, std::size_t max_iterations, double *below, double *above)
Singular value i of the arrow M whose first row is z and whose diagonal is d (d₀ = 0,...
matrix< T > adjoint(const matrix< T > &a)
The conjugate transpose of a.
void to_matrix_basis(const tridiagonal_reduction< T > &reduction, buffer< T > &at, std::size_t n)
The rows of at, vectors in the tridiagonal's basis, taken to the matrix's: at ← at Mᵀ = at Dᴴ Qᵀ,...
void unscale_divide_node(const divide_node &node)
The eigenvalues of a solved node, scaled back, where its parent reads them.
void gemm_rows(std::size_t m, std::size_t n, std::size_t k, const T *a, std::size_t lda, const T *b, std::size_t ldb, T *c, std::size_t ldc)
C += A·B by the plain i-k-j loop (gemm_rows_rounded), each element rounded as the tile of the path in...
Definition gemm.hpp:1445
constexpr double plain_squares_high
The largest plain sum of squares plain_or_at_scale keeps: no term of it overflowed.
constexpr std::size_t bidiagonal_product_weight
The weight of a multiply-add of the bidiagonal reduction's matrix-vector products,...
std::vector< reflector_block< T > > reflect_in_columns(buffer< T > &columns, std::size_t rows, std::size_t cols)
The blocks of reflectors of a Householder QR of the column-major columns (rows per column,...
secular_solution solve_secular(const buffer< double > &z, double rho, std::size_t i, std::size_t max_iterations, double *delta, const Offset &offset)
Root i of 1/ρ + Σⱼ zⱼ²/(Pⱼ − x) = 0, poles P strictly ascending, no zⱼ zero, ρ > 0,...
void rotate(T *x, T *y, std::size_t count, std::size_t stride, double cs, double sn)
(x, y) ← (cs x + sn y, cs y − sn x) over count pairs stride apart: a plane rotation.
Definition eigen.hpp:749
merge_workspace & thread_workspace()
The calling thread's merge_workspace, kept from one decomposition to the next: mapping its pages anew...
void jacobi_rotate(buffer< double > &a, buffer< double > &v, std::size_t n, std::size_t p, std::size_t q)
One Jacobi rotation of the n × n a zeroing a[p][q], accumulated into v.
constexpr std::size_t svd_qr_steps_per_value
The QR steps the bidiagonal singular value iteration may take per value: it converges in two or three...
void unscale_bidiagonal_node(bidiagonal_node &node)
The singular values of a solved node scaled back.
constexpr std::size_t bidiagonal_basis_crossover
The order below which the bases of a bidiagonal reduction are formed a reflector at a time: the block...
void bidiagonalize(buffer< T > &b, std::size_t n, buffer< double > &d, buffer< double > &e, buffer< T > &ut, buffer< T > &vt, bool form=true)
Reduces the real square n × n row-major b to upper-bidiagonal form, B = U_b D V_bᵀ,...
buffer< T > tridiagonal_divide_and_conquer(buffer< double > &d, buffer< double > &e, const tridiagonal_reduction< T > &reduction, std::size_t n)
The eigenvalues of the tridiagonal (d, e) of reduction into d, and the matrix's eigenvectors as the r...
void start_bidiagonal_node(bidiagonal_node &node)
Its singular values and vectors into node's, from scratch: u zero, v the identity.
thin_qr< T > householder_qr(buffer< T > a, std::size_t rows, std::size_t cols, bool complete=false)
Factorises the rows × cols row-major a by Householder reflections, A = Q R with k = min(rows,...
void multiply_real_left(const buffer< double > &left, buffer< T > &zt, std::size_t n)
zt ← left · zt, n × n, row-major, for any other element type: the real factor converted.
T pivot_divisor(const T &pivot)
The divisor over_pivot takes for the pivot pivot. A complex pivot's reciprocal, once,...
constexpr std::size_t zeroed_rows
The rows of a Cholesky factor whose upper part one thread zeroes at a time.
constexpr std::size_t cholesky_rows
The rows of one chunk of a Cholesky panel's rows below it, solved together along each column.
void bidiagonalize_unblocked(buffer< T > &b, std::size_t n, buffer< double > &d, buffer< double > &e, buffer< T > &ut, buffer< T > &vt, bool form=true)
Reduces the square n × n row-major b to real upper-bidiagonal form, B = U_b D V_bᴴ with D real,...
double(*)(const double *, const double *, double, double *, std::size_t) symmetric_row_kernel
A kernel of symmetric_row's form: symmetric_row itself, or an x86-64 one.
constexpr std::size_t cholesky_diagonal_rows
The rows of a Cholesky diagonal block factorised together, one chain of sums each (one x86-64 AVX2 co...
T conjugate_dot(const T *x, const T *y, std::size_t count)
Σ conj(x[i]) · y[i] for a real T, where the conjugate is the value: dot_product.
T from_double(double x)
x, a magnitude carried in double, as the scalar T: converted to T's real type first,...
constexpr std::size_t memory_element_weight
The weight of an element a pass bound by the memory writes or moves, against a multiply-add,...
void corrected_hermitian_product(const T *block, std::size_t n, std::size_t m, const T *v, T *w, buffer< T > &scratch, const T *corrections, std::size_t ldc, std::size_t terms, T *factors, T *next=nullptr, const T *next_factors=nullptr)
hermitian_product corrected by a panel's earlier steps: w = B v − Σₜ fₜ cₜ, the coefficients f[2p] = ...
constexpr double jacobi_tiny
The sum of squares under which one-sided Jacobi judges a pair of columns over their norms: below it a...
std::size_t symmetric_row_speedup()
The weight of a symmetric row's multiply-adds in hermitian_product: on the x86-64 units,...
std::size_t largest_magnitude_vectors(const T *, std::size_t, Magnitude &, std::size_t &)
Only x[0], the one best and at come in with, for a type no native vector holds.
Definition simd.hpp:650
double scaled_norm(const T *x, std::size_t count)
The Euclidean norm of the count elements at x, by norm_at_scale for this type.
T householder_in_place(buffer< T > &x)
The unit Householder vector v with (I − 2 v vᵀ) x = β e₁, in place of x, for a real x.
The NumericAL public API.
Definition amx.hpp:44
std::vector< T, detail::default_init_allocator< T > > buffer
The storage of vector, matrix and tensor, namely a std::vector whose buffer<T>(n) and resize(n) leave...
Definition buffer.hpp:157
std::size_t max_threads()
The threads a kernel may use: the cap if one is set, else the hardware threads (starting none).
Definition parallel.hpp:732
cholesky_decomposition< T > cholesky(matrix< T > a)
Cholesky-factorises a Hermitian positive-definite matrix.
T sum(const tensor< T > &t)
The sum of every element, by pairwise halving (0 for an empty tensor).
vector< T > solve(matrix< T > a, const vector< T > &b)
Solves the linear system A x = b for a square A.
matrix< T > pinv(const matrix< T > &a, rank_cutoff cutoff={}, bool hermitian=false)
The Moore–Penrose pseudo-inverse of the m × n a (n × m), as numpy.linalg.pinv gives it: V Σ⁺ Uᴴ,...
T determinant(matrix< T > a)
The determinant of a square matrix.
least_squares_solution< T > least_squares(const matrix< T > &a, const matrix< T > &b, rank_cutoff cutoff={})
lstsq, with the residuals, the rank and the singular values numpy.linalg.lstsq returns beside the sol...
gemm_path
The kernels a double matrix product can run on (a float one too on arm64), from the most portable....
Definition gemm.hpp:80
@ portable
The compiler's vectors (16 bytes on x86-64 wheels), as the compiler rounds c + a·b (see gemm).
@ amx
The Apple AMX coprocessor, fused multiply-adds; the products it does not suit on the NEON path's tile...
qr_mode
The factors a numerical::qr_decomposition keeps, as numpy.linalg.qr's modes name them.
@ reduced
Q is m × k, R k × n, k = min(m, n): NumPy's default.
@ complete
Q is m × m, its columns past k a basis of the complement of A's column space; R is m × n.
svd_method
How numerical::svd_decomposition computes the decomposition of R, after a Householder QR.
eigen_method
How numerical::symmetric_eigen diagonalises.
@ jacobi
Cyclic Jacobi rotations: several times slower, kept as an independent second method.
std::size_t matrix_rank(const matrix< T > &a, rank_cutoff cutoff={}, bool hermitian=false)
The numerical rank of a, as numpy.linalg.matrix_rank counts it: its singular values above cutoff.
qr_decomposition< T > qr(const matrix< T > &a)
QR-factorises an m × n matrix.
vector< T > lstsq(const matrix< T > &a, const vector< T > &b, rank_cutoff cutoff={})
The minimum-norm least-squares solution of A x ≈ b, as NumPy's lstsq gives it.
T min(const tensor< T > &t)
The smallest element (a NaN, if there is one).
vector< double > singular_values(const matrix< T > &a)
The singular values of a, descending, without its singular vectors, as numpy.linalg....
matrix< T > inverse(matrix< T > a)
The inverse of a square matrix.
vector< double > symmetric_eigenvalues(const matrix< T > &a)
The eigenvalues of the real symmetric or complex Hermitian a, ascending, without its eigenvectors,...
tensor< T > full(std::vector< std::size_t > shape, const T &value)
A tensor of shape with every element value.
A minimal std::thread parallel-for for the hot kernels.
The native vector of float and double the kernels hold their lanes in (GNU vector extensions,...
The buffers merge_arrow works in, kept by each thread from one join to the next (thread_arrow_workspa...
workspace_buffer above
poles[j] + roots[i].
workspace_buffer left
The arrow's left vectors.
workspace_buffer factor
The rows of the arrow's vectors for those columns.
workspace_buffer new_v
The kept columns of v times the right vectors.
workspace_buffer below
poles[j] − roots[i], row by root.
workspace_buffer new_u
The kept columns of u times the left vectors.
workspace_buffer right
Its right vectors.
buffer< double > sorted_u
u's columns by ascending value, swapped with u.
workspace_buffer columns
One segment's rows of the kept columns that reach it.
buffer< double > sorted_v
v's, swapped with v.
One block of bidiagonal_divide's tree: the bidiagonal it decomposes (its own copies scaled by a power...
int exponent
The power of two it is scaled down by.
std::size_t k
The row it splits at; 0 for a leaf.
std::size_t extra
Its columns past its rows, 0 or 1.
buffer< double > v
Its right singular vectors, as columns.
buffer< double > sigma
Its singular values, ascending.
const double * d
The diagonal it is solved on.
const double * e
The superdiagonal it is solved on.
buffer< double > u
Its left singular vectors, as columns.
One block of divide_and_conquer's tree: its rows [0, n) of the diagonal d its parent reads,...
double * d
The diagonal its parent reads, the eigenvalues once solved.
const double * work_e
The subdiagonal it is solved on.
std::size_t ldq
The distance between the rows of q.
double * q
Its diagonal block of the eigenvectors.
double * work_d
The diagonal it is solved on.
std::size_t n1
The order of its first half; 0 for a leaf.
int exponent
The power of two it is scaled down by.
double beta
The coupling of its halves.
The buffers of merge_rank_one, kept from one join to the next: a fresh buffer per join is mapped anew...
workspace_buffer updated
The kept columns of q times u.
workspace_buffer factor
The rows of u for those columns.
workspace_buffer gaps
poles[j] − roots[i], row by root, then the eigenvectors in its place.
workspace_buffer columns
One half's rows of the kept columns that reach it.
A left operand packed once for several products by it (an LU's −L₂₁ against blocks of its U₁₂): the t...
Definition gemm.hpp:2920
std::size_t k
A's columns, the depth.
Definition gemm.hpp:2923
The reflections of a Householder QR, Q left as its blocks of reflectors.
buffer< T > r
R, k × cols upper trapezoidal, its diagonal as it comes.
std::vector< reflector_block< T > > blocks
Q = H₀ H₁ …, block by block of qr_block reflectors.
The reflectors of one panel of the blocked QR, I − V T Vᴴ as LAPACK's dlarft forms it.
std::size_t height
The rows it acts on, first…first+height-1.
buffer< T > v
V, height × width row-major, reflector j from row j (zeros above).
buffer< T > t
T, width × width upper triangular.
std::size_t width
Its reflector count.
std::size_t first
The first row the block acts on.
A run of recorded rotations in which each shares a row with the next, as a sweep of the QL or QR iter...
std::size_t first
Its first operation.
bool down
Whether each next rotation's second row is this one's first.
std::size_t count
Its operations (one for an operation outside any run).
One operation of tridiagonal_ql or bidiagonal_qr on the rows of a vector matrix, recorded to be appli...
std::size_t j
The second row (unused by negate).
std::size_t i
The first row.
double c
The cosine of a rotation.
double s
The sine of a rotation.
A root of a secular equation: the pole it was sought from, and its offset from that pole.
double offset
The root minus the pole.
A thin QR factorisation, row-major: q is rows × cols with orthonormal columns, r is cols × cols upper...
buffer< T > q
The orthonormal factor.
buffer< T > r
The triangular factor.
The unitary M = Q Dᴴ of a reduction to real tridiagonal form, A = M T Mᴴ, kept as its factors.
std::vector< buffer< T > > reflectors
Q = H₁ H₂ …: reflectors[k + 1] zeroes column k, from row k + 1.
buffer< T > delta
D = diag(δ), the unit phases that make T real.
One buffer of merge_workspace: its storage and how many elements it holds.
std::unique_ptr< double[]> data
The elements.
A least-squares solution and what numpy.linalg.lstsq reports beside it (least_squares).
vector< double > singular_values
The singular values of A, descending (singular_values).
matrix< T > solution
The minimum-norm x of A x ≈ B (n × k), lstsq's.
vector< double > residuals
Per column of B, ‖B − A x‖²: for a tall A of full column rank, else empty.
A dense, value-semantic vector over a scalar type.