Eigen-Contrib  5.0.1
 
Loading...
Searching...
No Matches
Circulant.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] P. J. Davis, "Circulant Matrices", Wiley, 1979. Diagonalization of a
12// circulant matrix by the DFT and the closed-form eigenstructure used by
13// eigenvalues()/eigenvectors(); the SVD and pseudo-inverse below follow
14// from it by taking moduli/phases of the eigenvalues.
15// [2] R. M. Gray, "Toeplitz and Circulant Matrices: A Review", Foundations and
16// Trends in Communications and Information Theory, 2(3), 2006.
17// [3] G. H. Golub and C. F. Van Loan, "Matrix Computations", 4th ed., Johns
18// Hopkins University Press, 2013, chapter 4.8 (circulant systems and
19// FFT-based products) and chapter 5.4 (numerical rank conventions).
20// [4] J. J. Dongarra, J. R. Bunch, C. B. Moler and G. W. Stewart, "LINPACK
21// Users' Guide", SIAM, 1979. determinant()'s balanced accumulation follows
22// the convention of its xGEDI routines, which return determinants as a
23// (fraction, exponent) pair to avoid spurious overflow/underflow.
24// [5] P. H. Sterbenz, "Floating-Point Computation", Prentice-Hall, 1974.
25// Scaling by a power of two is exact, the property the balanced
26// accumulation relies on.
27
28#ifndef EIGEN_STRUCTURED_CIRCULANT_H
29#define EIGEN_STRUCTURED_CIRCULANT_H
30
31// IWYU pragma: private
32#include "./InternalHeaderCheck.h"
33
34namespace Eigen {
35
36template <typename Scalar_, int Size_ = Dynamic>
37class Circulant;
38
39namespace internal {
40
41template <typename Scalar_, int Size_>
42struct traits<Circulant<Scalar_, Size_>> {
43 using Scalar = Scalar_;
44 using StorageKind = Dense;
45 using XprKind = MatrixXpr;
46 using StorageIndex = int;
47 static constexpr int RowsAtCompileTime = Size_;
48 static constexpr int ColsAtCompileTime = Size_;
49 static constexpr int MaxRowsAtCompileTime = Size_;
50 static constexpr int MaxColsAtCompileTime = Size_;
51 // Deliberately no NestByRefBit: transpose(), conjugate() and adjoint() return
52 // owning temporaries, so Product must nest the operator by value for a
53 // delayed-evaluated product expression to keep its left factor alive. The copy
54 // is O(n), negligible against the O(n log n) product evaluation.
55 static constexpr int Flags = 0;
56};
57
58template <typename Scalar_, int Size_>
59struct evaluator_traits<Circulant<Scalar_, Size_>> {
60 using Kind = IndexBased;
61 using Shape = StructuredShape;
62};
63
64} // namespace internal
65
97template <typename Scalar_, int Size_>
98class Circulant : public EigenBase<Circulant<Scalar_, Size_>> {
99 public:
100 using Scalar = Scalar_;
101 using RealScalar = typename NumTraits<Scalar>::Real;
102 using StorageIndex = int;
103 using Complex = std::complex<RealScalar>;
104 using GeneratorType = Matrix<Scalar, Size_, 1>;
105 using ComplexVector = Matrix<Complex, Dynamic, 1>;
106 using RealVector = Matrix<RealScalar, Size_, 1>;
107 using ComplexMatrix = Matrix<Complex, Size_, Size_>;
108
109 static constexpr int RowsAtCompileTime = Size_;
110 static constexpr int ColsAtCompileTime = Size_;
111 static constexpr int MaxRowsAtCompileTime = Size_;
112 static constexpr int MaxColsAtCompileTime = Size_;
113 static constexpr int SizeAtCompileTime = internal::size_at_compile_time(Size_, Size_);
114 static constexpr int MaxSizeAtCompileTime = SizeAtCompileTime;
115 static constexpr bool IsRowMajor = false;
116 // Deliberately no IsVectorAtCompileTime: Ref<const Circulant>'s default StrideType
117 // argument reads it, so its absence makes internal::is_ref_compatible SFINAE to
118 // false and keeps the iterative solvers on their matrix-free path.
119
125 template <typename Derived>
126 explicit Circulant(const MatrixBase<Derived>& col) : m_col(col) {
127 EIGEN_STATIC_ASSERT_VECTOR_ONLY(Derived)
128 eigen_assert(m_col.size() > 0 && "Circulant generator must be non-empty");
129 if (m_col.size() > internal::structured_direct_threshold()) {
130 m_symbol = computeSymbol();
131 m_prodSymbol = computeProdSymbol(m_col);
132 }
133 }
134
135 EIGEN_DEVICE_FUNC Index rows() const { return m_col.size(); }
136 EIGEN_DEVICE_FUNC Index cols() const { return m_col.size(); }
137
139 const GeneratorType& column() const { return m_col; }
140
145 ComplexVector symbol() const { return m_symbol.size() > 0 ? m_symbol : computeSymbol(); }
146
148 Scalar coeff(Index row, Index col) const {
149 Index k = row - col;
150 if (k < 0) k += rows();
151 return m_col.coeff(k);
152 }
153
159 const Index n = rows();
160 GeneratorType col(n);
161 col[0] = m_col[0];
162 if (n > 1) col.tail(n - 1) = m_col.tail(n - 1).reverse();
163 return Circulant(col, internal::structured_reverse_symbol(m_symbol),
164 internal::structured_reverse_symbol(m_prodSymbol));
165 }
166
171 return Circulant(m_col.conjugate(), internal::structured_reverse_symbol(m_symbol).conjugate(),
172 internal::structured_reverse_symbol(m_prodSymbol).conjugate());
173 }
174
180 const Index n = rows();
181 GeneratorType col(n);
182 col[0] = numext::conj(m_col[0]);
183 if (n > 1) col.tail(n - 1) = m_col.tail(n - 1).reverse().conjugate();
184 return Circulant(col, m_symbol.conjugate(), m_prodSymbol.conjugate());
185 }
186
190 template <typename Dest>
191 void evalTo(Dest& dst) const {
192 const Index n = rows();
193 EIGEN_IF_CONSTEXPR (Dest::IsRowMajor) {
194 for (Index i = 0; i < n; ++i) {
195 dst.row(i).head(i + 1) = m_col.head(i + 1).reverse().transpose();
196 dst.row(i).tail(n - i - 1) = m_col.tail(n - i - 1).reverse().transpose();
197 }
198 return;
199 }
200 for (Index j = 0; j < n; ++j) {
201 dst.col(j).head(j) = m_col.tail(j);
202 dst.col(j).tail(n - j) = m_col.head(n - j);
203 }
204 }
205
207 template <typename Dest>
208 void addTo(Dest& dst) const {
209 const Index n = rows();
210 EIGEN_IF_CONSTEXPR (Dest::IsRowMajor) {
211 for (Index i = 0; i < n; ++i) {
212 dst.row(i).head(i + 1) += m_col.head(i + 1).reverse().transpose();
213 dst.row(i).tail(n - i - 1) += m_col.tail(n - i - 1).reverse().transpose();
214 }
215 return;
216 }
217 for (Index j = 0; j < n; ++j) {
218 dst.col(j).head(j) += m_col.tail(j);
219 dst.col(j).tail(n - j) += m_col.head(n - j);
220 }
221 }
222
224 template <typename Dest>
225 void subTo(Dest& dst) const {
226 const Index n = rows();
227 EIGEN_IF_CONSTEXPR (Dest::IsRowMajor) {
228 for (Index i = 0; i < n; ++i) {
229 dst.row(i).head(i + 1) -= m_col.head(i + 1).reverse().transpose();
230 dst.row(i).tail(n - i - 1) -= m_col.tail(n - i - 1).reverse().transpose();
231 }
232 return;
233 }
234 for (Index j = 0; j < n; ++j) {
235 dst.col(j).head(j) -= m_col.tail(j);
236 dst.col(j).tail(n - j) -= m_col.head(n - j);
237 }
238 }
239
244 template <typename Rhs>
246 EIGEN_STATIC_ASSERT(ColsAtCompileTime == Dynamic || Rhs::RowsAtCompileTime == Dynamic ||
247 int(ColsAtCompileTime) == int(Rhs::RowsAtCompileTime),
248 INVALID_MATRIX_PRODUCT)
249 eigen_assert(x.rows() == cols() && "invalid product: dimensions do not match");
250 return Product<Circulant, Rhs>(*this, x.derived());
251 }
252
259 template <typename Rhs>
261 EIGEN_STATIC_ASSERT(RowsAtCompileTime == Dynamic || Rhs::RowsAtCompileTime == Dynamic ||
262 int(RowsAtCompileTime) == int(Rhs::RowsAtCompileTime),
263 YOU_MIXED_MATRICES_OF_DIFFERENT_SIZES)
264 const Index n = rows();
265 eigen_assert(b.rows() == n && "right-hand side has the wrong number of rows");
266 const ComplexVector s = symbol();
267 const RealVector mods = s.cwiseAbs();
268 const ComplexVector sinv = internal::structured_pinv_symbol(s, mods, internal::structured_rank_threshold(s, mods));
270 x.setZero();
271 internal::structured_fft_apply(x, sinv, n, b.derived(), Scalar(1));
272 return x;
273 }
274
281 Index rank() const {
282 const ComplexVector s = symbol();
283 const RealVector mods = s.cwiseAbs();
284 return (!(mods.array() < internal::structured_rank_threshold(s, mods))).count(); // NaN entries count as non-zero
285 }
286
292 const Index n = rows();
293 const ComplexVector sinv = symbol().cwiseInverse();
294 GeneratorType col(n);
295 if (n == 1) {
296 col = internal::structured_scalar_part_impl<Scalar>::run(sinv);
297 } else {
298 auto&& fft = internal::structured_fft_engine<RealScalar>();
299 ComplexVector ct(n);
300 fft.inv(ct, sinv, n);
301 col = internal::structured_scalar_part_impl<Scalar>::run(ct);
302 }
303 return Circulant(col, n > internal::structured_direct_threshold() ? sinv : ComplexVector(),
304 n > internal::structured_direct_threshold() ? computeProdSymbol(col) : ComplexVector());
305 }
306
315 Scalar determinant() const {
316 const ComplexVector s = symbol();
317 Complex det(1);
318 Index exponent = 0;
319 for (Index k = 0; k < s.size(); ++k)
320 det = internal::structured_balance(Complex(det * internal::structured_balance(s[k], exponent)), exponent);
321 return internal::structured_scalar_part_impl<Scalar>::run_scalar(internal::structured_ldexp_clamped(det, exponent));
322 }
323
328 ComplexVector eigenvalues() const { return symbol(); }
329
334 ComplexMatrix eigenvectors() const {
335 const Index n = rows();
336 const ComplexVector roots = fourierRoots();
337 ComplexMatrix F(n, n);
338 for (Index k = 0; k < n; ++k) fourierColumn(F, roots, k, k);
339 return F;
340 }
341
347 RealVector singularValues() const {
348 ComplexVector s;
349 RealVector mods;
350 const std::vector<Index> perm = svdOrdering(s, mods);
351 RealVector sv(s.size());
352 for (Index t = 0; t < s.size(); ++t) sv[t] = mods[perm[t]];
353 return sv;
354 }
355
359 ComplexMatrix matrixU() const {
360 const Index n = rows();
361 ComplexVector s;
362 RealVector mods;
363 const std::vector<Index> perm = svdOrdering(s, mods);
364 const ComplexVector roots = fourierRoots();
365 ComplexMatrix U(n, n);
366 for (Index t = 0; t < n; ++t) {
367 fourierColumn(U, roots, perm[t], t);
368 const RealScalar a = mods[perm[t]];
369 if (a > RealScalar(0)) U.col(t) *= s[perm[t]] / a;
370 }
371 return U;
372 }
373
377 ComplexMatrix matrixV() const {
378 const Index n = rows();
379 ComplexVector s;
380 RealVector mods;
381 const std::vector<Index> perm = svdOrdering(s, mods);
382 const ComplexVector roots = fourierRoots();
383 ComplexMatrix V(n, n);
384 for (Index t = 0; t < n; ++t) fourierColumn(V, roots, perm[t], t);
385 return V;
386 }
387
391 template <typename Dest, typename Rhs, typename ProductScalar>
392 void addProduct(Dest& dst, const Rhs& rhs, const ProductScalar& alpha) const {
393 const Index n = rows();
394 eigen_assert(rhs.rows() == n && "invalid product: dimensions do not match");
395 if (n <= internal::structured_direct_threshold()) {
396 directProduct(dst, rhs, alpha);
397 return;
398 }
399 // Products use the padded embedding symbol when the operator size is not
400 // 5-smooth (see computeProdSymbol()); the exact-size transform of such a
401 // size runs through kissfft's quadratic generic butterfly.
402 const ComplexVector& s = m_prodSymbol.size() > 0 ? m_prodSymbol : m_symbol;
403 internal::structured_fft_apply(dst, s, n, rhs, alpha);
404 }
405
406 private:
412 Circulant(const GeneratorType& col, const ComplexVector& symbol, const ComplexVector& prodSymbol)
413 : m_col(col), m_symbol(symbol), m_prodSymbol(prodSymbol) {}
414
425 static ComplexVector computeProdSymbol(const GeneratorType& col) {
426 const Index n = col.size();
427 if (internal::fft_next_good_size(n) == n) return ComplexVector();
428 const Index p = internal::fft_next_good_size(2 * n - 1);
429 ComplexVector embedding = ComplexVector::Zero(p);
430 embedding.head(n) = col.template cast<Complex>();
431 embedding.tail(n - 1) = col.tail(n - 1).template cast<Complex>();
432 ComplexVector symbol(p);
433 auto&& fft = internal::structured_fft_engine<RealScalar>();
434 fft.fwd(symbol, embedding, p);
435 return symbol;
436 }
437
441 template <typename Dest, typename Rhs, typename ProductScalar>
442 void directProductColumn(Dest& dst, const Rhs& rhs, Index k, const ProductScalar& alpha) const {
443 const Index n = rows();
444 // A unit alpha must not multiply: even the identity complex scalar (1,0)
445 // pollutes an (Inf,0) value with NaN through the 0*Inf cross term.
446 const bool unitAlpha = alpha == ProductScalar(1);
447 if (n <= internal::structured_scalar_threshold()) {
448 // Tiny sizes: a plain scalar loop beats the segment-based path below, whose
449 // per-segment setup dominates when segments hold only a few entries.
450 for (Index i = 0; i < n; ++i) {
451 ProductScalar acc(0);
452 for (Index j = 0; j < n; ++j) acc += coeff(i, j) * rhs.coeff(j, k);
453 dst.coeffRef(i, k) += unitAlpha ? acc : ProductScalar(alpha * acc);
454 }
455 return;
456 }
457
458 // Column j is c rotated downward by j; split it into two contiguous segments.
459 auto dstCol = dst.col(k);
460 for (Index j = 0; j < n; ++j) {
461 const ProductScalar xj = unitAlpha ? ProductScalar(rhs.coeff(j, k)) : ProductScalar(alpha * rhs.coeff(j, k));
462 dstCol.head(j) += xj * m_col.tail(j);
463 dstCol.tail(n - j) += xj * m_col.head(n - j);
464 }
465 }
466
469 template <typename Dest, typename Rhs, typename ProductScalar>
470 void directProduct(Dest& dst, const Rhs& rhs, const ProductScalar& alpha) const {
471 for (Index k = 0; k < rhs.cols(); ++k) directProductColumn(dst, rhs, k, alpha);
472 }
473
490 std::vector<Index> svdOrdering(ComplexVector& s, RealVector& mods) const {
491 const Index n = rows();
492 s = symbol();
493 EIGEN_IF_CONSTEXPR (!NumTraits<Scalar>::IsComplex) {
494 const Index pairs = (n - 1) / 2;
495 mods.resize(n);
496 mods.head(n - pairs) = s.head(n - pairs).cwiseAbs();
497 mods.tail(pairs) = mods.segment(1, pairs).reverse();
498 } else {
499 mods = s.cwiseAbs();
500 }
501 return internal::structured_svd_permutation(mods);
502 }
503
504 // Cache the n distinct roots once per Fourier matrix, preserving polar()'s
505 // bounded arguments and rounding while avoiding n^2 trigonometric evaluations.
506 ComplexVector fourierRoots() const {
507 const Index n = rows();
508 const RealScalar scale = RealScalar(1) / numext::sqrt(RealScalar(n));
509 ComplexVector roots(n);
510 for (Index j = 0; j < n; ++j)
511 roots[j] = std::polar(scale, RealScalar(2 * EIGEN_PI) * RealScalar(j) / RealScalar(n));
512 return roots;
513 }
514
515 // Column k uses roots[j*k mod n]; incremental modular indexing avoids j*k overflow.
516 void fourierColumn(ComplexMatrix& F, const ComplexVector& roots, Index k, Index dstCol) const {
517 const Index n = rows();
518 Index jk = 0;
519 for (Index j = 0; j < n; ++j) {
520 F(j, dstCol) = roots[jk];
521 jk += k;
522 if (jk >= n) jk -= n;
523 }
524 }
525
527 ComplexVector computeSymbol() const {
528 const Index n = m_col.size();
529 const ComplexVector cc = m_col.template cast<Complex>();
530 if (n == 1) return cc; // the DFT of a single sample is the identity
531 ComplexVector symbol(n);
532 auto&& fft = internal::structured_fft_engine<RealScalar>();
533 fft.fwd(symbol, cc, n);
534 return symbol;
535 }
536
537 GeneratorType m_col;
538 ComplexVector m_symbol;
539 // The padded embedding symbol products use when the size is not 5-smooth;
540 // empty otherwise. See computeProdSymbol().
541 ComplexVector m_prodSymbol;
542};
543
547template <typename Derived>
551
552namespace internal {
553
554// StructuredShape avoids ambiguity with the stock dense product specializations.
555template <typename Scalar_, int Size_, typename Rhs, int ProductTag>
556struct generic_product_impl<Circulant<Scalar_, Size_>, Rhs, StructuredShape, DenseShape, ProductTag>
557 : structured_product_impl<Circulant<Scalar_, Size_>, Rhs> {};
558
559} // namespace internal
560
561} // namespace Eigen
562
563#endif // EIGEN_STRUCTURED_CIRCULANT_H
An n x n circulant matrix represented by its first column.
Definition Circulant.h:98
Circulant(const MatrixBase< Derived > &col)
Definition Circulant.h:126
ComplexVector eigenvalues() const
Definition Circulant.h:328
const GeneratorType & column() const
Definition Circulant.h:139
Circulant transpose() const
Definition Circulant.h:158
ComplexMatrix matrixV() const
Definition Circulant.h:377
Index rank() const
Definition Circulant.h:281
Circulant conjugate() const
Definition Circulant.h:170
Circulant inverse() const
Definition Circulant.h:291
ComplexVector symbol() const
Definition Circulant.h:145
Circulant adjoint() const
Definition Circulant.h:179
Scalar coeff(Index row, Index col) const
Definition Circulant.h:148
ComplexMatrix eigenvectors() const
Definition Circulant.h:334
ComplexMatrix matrixU() const
Definition Circulant.h:359
Matrix< Scalar, Size_, Rhs::ColsAtCompileTime > solve(const MatrixBase< Rhs > &b) const
Definition Circulant.h:260
Scalar determinant() const
Definition Circulant.h:315
Product< Circulant, Rhs > operator*(const MatrixBase< Rhs > &x) const
Definition Circulant.h:245
RealVector singularValues() const
Definition Circulant.h:347
Derived & setZero(Index rows, Index cols)
Circulant< typename Derived::Scalar, Derived::SizeAtCompileTime > makeCirculant(const MatrixBase< Derived > &col)
Definition Circulant.h:548
Namespace containing all symbols from the Eigen library.