Eigen-Contrib  5.0.1
 
Loading...
Searching...
No Matches
StructuredMatrixUtils.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// This Source Code Form is subject to the terms of the Mozilla
5// Public License v. 2.0. If a copy of the MPL was not distributed
6// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
7// SPDX-FileCopyrightText: The Eigen Authors
8// SPDX-License-Identifier: MPL-2.0
9
10// References:
11// [1] N. J. Higham, "Accuracy and Stability of Numerical Algorithms", 2nd ed.,
12// SIAM, 2002, chapter 27. Avoiding spurious overflow by rescaling with
13// powers of two, the technique behind structured_exponent_bound() and the
14// balanced determinant accumulations.
15// [2] P. H. Sterbenz, "Floating-Point Computation", Prentice-Hall, 1974.
16// Scaling by a power of two is exact, so the balanced accumulations
17// introduce no roundoff of their own.
18
19#ifndef EIGEN_STRUCTURED_MATRIX_UTILS_H
20#define EIGEN_STRUCTURED_MATRIX_UTILS_H
21
22// IWYU pragma: private
23#include "./InternalHeaderCheck.h"
24
25namespace Eigen {
26
27namespace internal {
28
29// A distinct shape routes assignment through evalTo/addTo/subTo and lets one
30// product specialization cover every product tag without colliding with DenseShape.
31struct StructuredShape {};
32
33// Below this dimension the FFT setup costs more than a plain O(n^2) evaluation,
34// so the structured operators fall back to a direct segment-based product.
35constexpr Index structured_direct_threshold() { return 32; }
36
37// Below this dimension even the segment-based direct product loses to a plain
38// scalar loop: the per-segment setup dominates when the average segment holds
39// fewer than a couple of packets (measured crossover on AVX2 hardware).
40constexpr Index structured_scalar_threshold() { return 16; }
41
42// Numerical scale exponents are independent of Eigen's configurable dimension
43// index. A 32-bit Index can overflow while accumulating O(n^2) factor
44// exponents for dimensions that are otherwise practical.
45using structured_exponent_type = numext::int64_t;
46
51template <typename Scalar>
52typename NumTraits<Scalar>::Real structured_component_magnitude_impl(const Scalar& z, std::true_type) {
53 using RealScalar = typename NumTraits<Scalar>::Real;
54 using Binary = binary_floating_point_traits<RealScalar>;
55 return numext::bit_cast<RealScalar>(
56 larger_magnitude_bits<RealScalar>(Binary::magnitude(numext::real(z)), Binary::magnitude(numext::imag(z))));
57}
58template <typename Scalar>
59typename NumTraits<Scalar>::Real structured_component_magnitude_impl(const Scalar& z, std::false_type) {
60 return numext::maxi(numext::abs(numext::real(z)), numext::abs(numext::imag(z)));
61}
62template <typename Scalar>
63typename NumTraits<Scalar>::Real structured_component_magnitude(const Scalar& z) {
64 using RealScalar = typename NumTraits<Scalar>::Real;
65 return structured_component_magnitude_impl(z,
66 bool_constant < complex_array_access<Scalar>::value &&
67 use_subnormal_preserving_scaling<RealScalar, RealScalar>::value > ());
68}
69
84template <typename Scalar, bool IsComplex = NumTraits<Scalar>::IsComplex>
85struct structured_balance_impl {
86 using RealScalar = typename NumTraits<Scalar>::Real;
87 template <typename Exponent>
88 static Scalar run(const Scalar& z, Exponent& exponent) {
89 const RealScalar mag = structured_component_magnitude(z);
90 if (numext::is_exactly_zero_no_flush(mag) || !(numext::isfinite)(mag)) return z;
91 const int e = frexp_exponent_preserving_subnormals(mag);
92 exponent += e;
93 return apply_exponent(z, -e);
94 }
95 static Scalar apply_exponent(const Scalar& z, int e) {
96 return Scalar(ldexp_preserving_subnormals(numext::real(z), e), ldexp_preserving_subnormals(numext::imag(z), e));
97 }
98};
99
100template <typename Scalar>
101struct structured_balance_impl<Scalar, false> {
102 template <typename Exponent>
103 static Scalar run(const Scalar& x, Exponent& exponent) {
104 if (numext::is_exactly_zero_no_flush(x) || !(numext::isfinite)(x)) return x;
105 const int e = frexp_exponent_preserving_subnormals(x);
106 exponent += e;
107 return apply_exponent(x, -e);
108 }
109 static Scalar apply_exponent(const Scalar& x, int e) { return ldexp_preserving_subnormals(x, e); }
110};
111
112template <typename Scalar, typename Exponent>
113Scalar structured_balance(const Scalar& z, Exponent& exponent) {
114 return structured_balance_impl<Scalar>::run(z, exponent);
115}
116
121template <typename Scalar, typename Exponent>
122Scalar structured_ldexp_clamped(const Scalar& z, Exponent exponent) {
123 constexpr Exponent kMaxExponent = Exponent(1) << 24;
124 const int e = static_cast<int>(numext::mini(numext::maxi(exponent, -kMaxExponent), kMaxExponent));
125 return structured_balance_impl<Scalar>::apply_exponent(z, e);
126}
127
134template <typename RealVectorType>
135std::vector<Index> structured_svd_permutation(const RealVectorType& mods) {
136 using RealScalar = typename RealVectorType::Scalar;
137 std::vector<Index> perm;
138 perm.reserve(static_cast<std::size_t>(mods.size()));
139 for (Index k = 0; k < mods.size(); ++k) perm.push_back(k);
140 std::stable_sort(perm.begin(), perm.end(), [&mods](Index a, Index b) {
141 const RealScalar ka = mods[a], kb = mods[b];
142 // isgreater is quiet for NaNs; the second clause implements NaN-last ordering.
143 return std::isgreater(ka, kb) || (!(numext::isnan)(ka) && (numext::isnan)(kb));
144 });
145 return perm;
146}
147
162#ifndef EIGEN_AVOID_THREAD_LOCAL
163template <typename RealScalar>
164FFT<RealScalar>& structured_fft_engine() {
165 static thread_local FFT<RealScalar> fft;
166 return fft;
167}
168#else
169template <typename RealScalar>
170FFT<RealScalar> structured_fft_engine() {
171 return FFT<RealScalar>();
172}
173#endif
174
183inline Index fft_next_good_size(Index n) {
184 if (n < 1) return 1;
185 for (Index m = n;; ++m) {
186 Index r = m;
187 while (r % 2 == 0) r /= 2;
188 while (r % 3 == 0) r /= 3;
189 while (r % 5 == 0) r /= 5;
190 if (r == 1) return m;
191 }
192}
193
199template <typename Scalar, bool IsComplex = NumTraits<Scalar>::IsComplex>
200struct structured_scalar_part_impl {
201 template <typename Xpr>
202 static const Xpr& run(const Xpr& xpr) {
203 return xpr;
204 }
205 static const Scalar& run_scalar(const Scalar& x) { return x; }
206};
207
208template <typename Scalar>
209struct structured_scalar_part_impl<Scalar, false> {
210 template <typename Xpr>
211 static typename Xpr::RealReturnType run(const Xpr& xpr) {
212 return xpr.real();
213 }
214 static Scalar run_scalar(const std::complex<Scalar>& x) { return numext::real(x); }
215};
216
234template <typename Xpr>
235bool structured_exponent_bound_finite(const Xpr& x, int& e) {
236 using ScalarTraits = NumTraits<typename Xpr::Scalar>;
237 using RealScalar = typename ScalarTraits::Real;
238 // maxCoeff() asserts on an empty input, and a degenerate operand -- a rank-0
239 // factor, a solve with no right-hand sides -- reaches here legitimately.
240 // An empty operand bounds nothing, so its exponent bound is 0.
241 e = 0;
242 if (x.size() == 0) return true;
243 RealScalar m;
244 if (ScalarTraits::IsComplex)
245 // realView() reduces over both components in one pass, vectorized for
246 // direct-access storage; the strided real()/imag() views never vectorize.
247 m = x.realView().cwiseAbs().maxCoeff();
248 else
249 m = x.cwiseAbs().maxCoeff();
250 // A SIMD unit that flushes subnormal inputs (ARMv7 NEON, Arm FZ, DAZ) reads
251 // an all-subnormal operand as zero; the rescan recovers the largest component
252 // from its representation.
253 m = safe_scaling<RealScalar>::recover_flushed_max_coeff(x, m);
254 e = 0;
255 if (!(numext::isfinite)(m)) return false;
256 if (!numext::is_exactly_zero_no_flush(m)) {
257 e = frexp_exponent_preserving_subnormals(m);
258 if (ScalarTraits::IsComplex) ++e;
259 }
260 return true;
261}
262
265template <typename Xpr>
266int structured_exponent_bound(const Xpr& x) {
267 int e;
268 structured_exponent_bound_finite(x, e);
269 return e;
270}
271
272// The packet form of M *= 2^e. ldexp saturates entrywise for every e without
273// forming a possibly unrepresentable 2^e; for std::complex storage, realView()
274// exposes both components to the real ldexp packets.
275template <typename Xpr, std::enable_if_t<!NumTraits<typename Xpr::Scalar>::IsComplex, bool> = true>
276void structured_ldexp_entries_packet(Xpr& M, int e) {
277 M.array() = M.array().ldexp(e);
278}
279
280template <typename Xpr, std::enable_if_t<complex_array_access<typename Xpr::Scalar>::value, bool> = true>
281void structured_ldexp_entries_packet(Xpr& M, int e) {
282 M.realView().array() = M.realView().array().ldexp(e);
283}
284
285template <typename Xpr, std::enable_if_t<NumTraits<typename Xpr::Scalar>::IsComplex &&
286 !complex_array_access<typename Xpr::Scalar>::value,
287 bool> = true>
288void structured_ldexp_entries_packet(Xpr& M, int e) {
289 using Scalar = typename Xpr::Scalar;
290 M = M.unaryExpr([e](const Scalar& z) { return structured_ldexp_clamped(z, Index(e)); });
291}
292
293template <typename Xpr>
294void structured_ldexp_entries_impl(Xpr& M, int e, int, std::false_type) {
295 structured_ldexp_entries_packet(M, e);
296}
297
298template <typename Xpr>
299void structured_ldexp_entries_impl(Xpr& M, int e, int bound, std::true_type) {
300 using Scalar = typename Xpr::Scalar;
301 using RealScalar = typename NumTraits<Scalar>::Real;
302 // The largest component is at least 2^(componentBound - 1): a complex bound
303 // carries one extra bit for the modulus. The input holds no significant
304 // subnormal when componentBound - 1 >= recovery, and neither does the result
305 // when componentBound - 1 + e >= recovery.
306 const int componentBound = bound - (NumTraits<Scalar>::IsComplex ? 1 : 0);
307 const int recovery = safe_scaling<RealScalar>::subnormal_recovery_exponent();
308 if (componentBound - 1 >= recovery && componentBound - 1 + e >= recovery)
309 structured_ldexp_entries_packet(M, e);
310 else
311 M = M.unaryExpr(scale_by_exponent_op<RealScalar>(e));
312}
313
324template <typename Xpr>
325void structured_ldexp_entries(Xpr& M, int e, int bound) {
326 if (e == 0) return;
327 using Scalar = typename Xpr::Scalar;
328 using RealScalar = typename NumTraits<Scalar>::Real;
329 structured_ldexp_entries_impl(M, e, bound,
330 bool_constant<use_subnormal_preserving_scaling<RealScalar, Scalar>::value>());
331}
332
336template <typename Xpr>
337void structured_ldexp_entries(Xpr& M, int e) {
338 if (e == 0) return;
339 structured_ldexp_entries(M, e, structured_exponent_bound(M));
340}
341
342template <typename Xpr>
343void structured_ldexp_entries_exact_impl(Xpr& M, int e, std::false_type) {
344 structured_ldexp_entries_packet(M, e);
345}
346template <typename Xpr>
347void structured_ldexp_entries_exact_impl(Xpr& M, int e, std::true_type) {
348 using RealScalar = typename NumTraits<typename Xpr::Scalar>::Real;
349 M = M.unaryExpr(scale_by_exponent_op<RealScalar>(e));
350}
351
357template <typename Xpr>
358void structured_ldexp_entries_exact(Xpr& M, int e) {
359 if (e == 0) return;
360 using Scalar = typename Xpr::Scalar;
361 using RealScalar = typename NumTraits<Scalar>::Real;
362 structured_ldexp_entries_exact_impl(M, e,
363 bool_constant<use_subnormal_preserving_scaling<RealScalar, Scalar>::value>());
364}
365
371template <typename ComplexVectorType>
372ComplexVectorType structured_reverse_symbol(const ComplexVectorType& symbol) {
373 const Index p = symbol.size();
374 ComplexVectorType reversed(p);
375 if (p > 0) {
376 reversed[0] = symbol[0];
377 reversed.tail(p - 1) = symbol.tail(p - 1).reverse();
378 }
379 return reversed;
380}
381
392template <typename Scalar, typename Dest, typename Rhs>
393void structured_fft_apply(Dest& dst, const Matrix<std::complex<typename NumTraits<Scalar>::Real>, Dynamic, 1>& symbol,
394 Index outSize, const Rhs& rhs, const Scalar& alpha) {
395 using RealScalar = typename NumTraits<Scalar>::Real;
396 using Complex = std::complex<RealScalar>;
397 using ComplexVector = Matrix<Complex, Dynamic, 1>;
398
399 const Index p = symbol.size();
400 eigen_assert(rhs.rows() <= p && outSize <= p);
401 if (p == 1) {
402 // The length-one DFT is the identity and is unsupported by kissfft.
403 dst.row(0) += alpha * structured_scalar_part_impl<Scalar>::run(Complex(symbol.coeff(0)) *
404 rhs.row(0).template cast<Complex>());
405 return;
406 }
407
408 auto&& fft = structured_fft_engine<RealScalar>();
409 ComplexVector xt = ComplexVector::Zero(p);
410 ComplexVector xf(p), yt(p);
411 for (Index k = 0; k < rhs.cols(); ++k) {
412 xt.head(rhs.rows()) = rhs.col(k).template cast<Complex>();
413 fft.fwd(xf, xt, p);
414 xf.array() *= symbol.array();
415 fft.inv(yt, xf, p);
416 dst.col(k) += alpha * structured_scalar_part_impl<Scalar>::run(yt.head(outSize));
417 }
418}
419
426template <typename SymbolType, typename ModsType, typename RealScalar>
427SymbolType structured_pinv_symbol(const SymbolType& symbol, const ModsType& mods, const RealScalar& tol) {
428 using Complex = typename SymbolType::Scalar;
429 const auto w = (mods.array() < tol).select(RealScalar(0), mods.array().inverse()).template cast<Complex>().eval();
430 return (symbol.array().conjugate() * w * w).matrix();
431}
432
438template <typename SymbolType, typename ModsType>
439typename ModsType::Scalar structured_rank_threshold(const SymbolType& symbol, const ModsType& mods) {
440 using RealScalar = typename ModsType::Scalar;
441 const RealScalar factor = RealScalar(mods.size()) * NumTraits<RealScalar>::epsilon();
442 RealScalar tol = factor * mods.maxCoeff();
443 if (!(numext::isfinite)(tol)) tol = (RealScalar(2) * factor) * (symbol * RealScalar(0.5)).cwiseAbs().maxCoeff();
444 return numext::maxi(tol, (std::numeric_limits<RealScalar>::min)());
445}
446
458template <typename Op, typename Rhs>
459struct structured_product_impl : generic_product_impl_base<Op, Rhs, structured_product_impl<Op, Rhs>> {
460 using Scalar = typename Product<Op, Rhs>::Scalar;
461
462 template <typename Dest>
463 static void evalTo(Dest& dst, const Op& lhs, const Rhs& rhs) {
464 dst.setZero();
465 scaleAndAddTo(dst, lhs, rhs, Scalar(1));
466 }
467
468 template <typename Dest>
469 static void scaleAndAddTo(Dest& dst, const Op& lhs, const Rhs& rhs, const Scalar& alpha) {
470 using RhsNested = typename nested_eval<Rhs, Op::RowsAtCompileTime>::type;
471 RhsNested actualRhs(rhs);
472 lhs.addProduct(dst, actualRhs, alpha);
473 }
474};
475
476} // namespace internal
477
478} // namespace Eigen
479
480#endif // EIGEN_STRUCTURED_MATRIX_UTILS_H
Namespace containing all symbols from the Eigen library.