Eigen-Contrib  5.0.1
 
Loading...
Searching...
No Matches
Bccb.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
12// circulant and block circulant matrices by the DFT; the closed-form
13// eigenstructure used by eigenvalues()/eigenvectors(), and the SVD and
14// pseudo-inverse below, follow from it by taking moduli/phases of the
15// eigenvalues.
16// [2] R. H. Chan and X.-Q. Jin, "An Introduction to Iterative Toeplitz
17// Solvers", SIAM, 2007. BCCB matrices are diagonalized by the 2-D DFT
18// F_{n1} (x) F_{n2}; the FFT-based products and solves below, and the use
19// of BCCB operators as preconditioners for two-level Toeplitz (BTTB)
20// systems, follow this reference.
21// [3] G. H. Golub and C. F. Van Loan, "Matrix Computations", 4th ed., Johns
22// Hopkins University Press, 2013, chapter 5.4 (numerical rank conventions).
23// [4] J. J. Dongarra, J. R. Bunch, C. B. Moler and G. W. Stewart, "LINPACK
24// Users' Guide", SIAM, 1979. determinant()'s balanced accumulation follows
25// the convention of its xGEDI routines, which return determinants as a
26// (fraction, exponent) pair to avoid spurious overflow/underflow.
27// [5] P. H. Sterbenz, "Floating-Point Computation", Prentice-Hall, 1974.
28// Scaling by a power of two is exact, the property the balanced
29// accumulation relies on.
30
31#ifndef EIGEN_STRUCTURED_BCCB_H
32#define EIGEN_STRUCTURED_BCCB_H
33
34// IWYU pragma: private
35#include "./InternalHeaderCheck.h"
36
37namespace Eigen {
38
39template <typename Scalar_, int BlockSize_ = Dynamic, int NumBlocks_ = Dynamic>
40class Bccb;
41
42namespace internal {
43
45constexpr int bccb_dim(int a, int b) { return (a == Dynamic || b == Dynamic) ? Dynamic : a * b; }
46
47template <typename Scalar_, int BlockSize_, int NumBlocks_>
48struct traits<Bccb<Scalar_, BlockSize_, NumBlocks_>> {
49 using Scalar = Scalar_;
50 using StorageKind = Dense;
51 using XprKind = MatrixXpr;
52 using StorageIndex = int;
53 static constexpr int RowsAtCompileTime = bccb_dim(BlockSize_, NumBlocks_);
54 static constexpr int ColsAtCompileTime = RowsAtCompileTime;
55 static constexpr int MaxRowsAtCompileTime = RowsAtCompileTime;
56 static constexpr int MaxColsAtCompileTime = RowsAtCompileTime;
57 // Deliberately no NestByRefBit: transpose(), conjugate() and adjoint() return
58 // owning temporaries, so Product must nest the operator by value for a
59 // delayed-evaluated product expression to keep its left factor alive. The copy
60 // is O(N), negligible against the O(N log N) product evaluation.
61 static constexpr unsigned int Flags = 0;
62};
63
64template <typename Scalar_, int BlockSize_, int NumBlocks_>
65struct evaluator_traits<Bccb<Scalar_, BlockSize_, NumBlocks_>> {
66 using Kind = IndexBased;
67 using Shape = StructuredShape;
68};
69
70} // namespace internal
71
125template <typename Scalar_, int BlockSize_, int NumBlocks_>
126class Bccb : public EigenBase<Bccb<Scalar_, BlockSize_, NumBlocks_>> {
127 enum DenseAssignment { SetAssignment, AddAssignment, SubAssignment };
128
129 public:
130 using Scalar = Scalar_;
131 using RealScalar = typename NumTraits<Scalar>::Real;
132 using StorageIndex = int;
133 using Complex = std::complex<RealScalar>;
134 // The two-level structure keys on the *column-major* flattening of the n2 x n1
135 // generating array and symbol (entry (k2, k1) is flat index k1*n2 + k2), so
136 // every internal 2-D type is pinned to ColMajor explicitly: the semantics must
137 // not change under EIGEN_DEFAULT_TO_ROW_MAJOR. The single-row special case
138 // only satisfies Eigen's rule that 1 x n matrices be row-major; with one row
139 // the two orders coincide.
140 using GeneratorType =
141 Matrix<Scalar, BlockSize_, NumBlocks_, (BlockSize_ == 1 && NumBlocks_ != 1) ? int(RowMajor) : int(ColMajor)>;
143 using ComplexVector = Matrix<Complex, Dynamic, 1>;
144 using RealVector = Matrix<RealScalar, Dynamic, 1>;
147
148 static constexpr int RowsAtCompileTime = internal::bccb_dim(BlockSize_, NumBlocks_);
149 static constexpr int ColsAtCompileTime = RowsAtCompileTime;
150 static constexpr int MaxRowsAtCompileTime = RowsAtCompileTime;
151 static constexpr int MaxColsAtCompileTime = RowsAtCompileTime;
152 static constexpr int SizeAtCompileTime = internal::size_at_compile_time(RowsAtCompileTime, ColsAtCompileTime);
153 static constexpr int MaxSizeAtCompileTime = SizeAtCompileTime;
154 static constexpr bool IsRowMajor = false;
155 // Deliberately no IsVectorAtCompileTime: Ref<const Bccb>'s default StrideType
156 // argument reads it, so its absence makes internal::is_ref_compatible SFINAE to
157 // false and keeps the iterative solvers on their matrix-free path.
158
166 template <typename Derived>
167 explicit Bccb(const MatrixBase<Derived>& generator) : m_g(generator) {
168 eigen_assert(m_g.rows() > 0 && m_g.cols() > 0 && "Bccb generator must be non-empty");
169 if (m_g.size() > internal::structured_direct_threshold()) {
170 m_symbol = computeSymbol();
171 m_prodSymbol = computeProdSymbol(m_g);
172 }
173 }
174
175 EIGEN_DEVICE_FUNC Index rows() const { return m_g.size(); }
176 EIGEN_DEVICE_FUNC Index cols() const { return m_g.size(); }
177
179 Index blockSize() const { return m_g.rows(); }
181 Index numBlocks() const { return m_g.cols(); }
183 const GeneratorType& generator() const { return m_g; }
184
190 ComplexArray symbol() const { return m_symbol.size() > 0 ? m_symbol : computeSymbol(); }
191
193 Scalar coeff(Index row, Index col) const {
194 const Index n2 = blockSize();
195 Index k2 = row % n2 - col % n2;
196 if (k2 < 0) k2 += n2;
197 Index k1 = row / n2 - col / n2;
198 if (k1 < 0) k1 += numBlocks();
199 return m_g.coeff(k2, k1);
200 }
201
208 Bccb transpose() const { return Bccb(reverse2(m_g), reverse2(m_symbol), reverse2(m_prodSymbol)); }
209
213 Bccb conjugate() const {
214 return Bccb(m_g.conjugate(), reverse2(m_symbol).conjugate(), reverse2(m_prodSymbol).conjugate());
215 }
216
221 Bccb adjoint() const { return Bccb(reverse2(m_g).conjugate(), m_symbol.conjugate(), m_prodSymbol.conjugate()); }
222
229 template <typename Rhs>
231 EIGEN_STATIC_ASSERT(RowsAtCompileTime == Dynamic || Rhs::RowsAtCompileTime == Dynamic ||
232 int(RowsAtCompileTime) == int(Rhs::RowsAtCompileTime),
233 YOU_MIXED_MATRICES_OF_DIFFERENT_SIZES)
234 const Index N = rows();
235 eigen_assert(b.rows() == N && "right-hand side has the wrong number of rows");
236 const ComplexArray s = symbol();
237 const RealArray mods = s.cwiseAbs();
238 const ComplexArray sinv = internal::structured_pinv_symbol(s, mods, internal::structured_rank_threshold(s, mods));
240 applySymbol(x, sinv, b.derived(), Scalar(1), /*accumulate=*/false);
241 return x;
242 }
243
250 Index rank() const {
251 const ComplexArray s = symbol();
252 const RealArray mods = s.cwiseAbs();
253 return (!(mods.array() < internal::structured_rank_threshold(s, mods))).count(); // NaN entries count as non-zero
254 }
255
260 Bccb inverse() const {
261 ComplexArray sinv = symbol().cwiseInverse();
262 const GeneratorType ginv = generatorFromSymbol(sinv);
263 const bool cache = rows() > internal::structured_direct_threshold();
264 return Bccb(ginv, cache ? sinv : ComplexArray(), cache ? computeProdSymbol(ginv) : ComplexArray());
265 }
266
275 Scalar determinant() const {
276 const ComplexArray s = symbol();
277 Complex det(1);
278 Index exponent = 0;
279 for (Index k1 = 0; k1 < s.cols(); ++k1)
280 for (Index k2 = 0; k2 < s.rows(); ++k2)
281 det = internal::structured_balance(Complex(det * internal::structured_balance(s(k2, k1), exponent)), exponent);
282 return toScalar(internal::structured_ldexp_clamped(det, exponent), std::is_same<RealScalar, Scalar>());
283 }
284
290 ComplexVector eigenvalues() const { return symbol().reshaped(); }
291
296 ComplexMatrix eigenvectors() const {
297 const Index n2 = blockSize(), n1 = numBlocks(), N = rows();
298 ComplexMatrix F(N, N);
299 for (Index f1 = 0; f1 < n1; ++f1)
300 for (Index f2 = 0; f2 < n2; ++f2) fourierColumn(F, f1, f2, f1 * n2 + f2);
301 return F;
302 }
303
307 RealVector singularValues() const {
308 const ComplexVector s = eigenvalues();
309 const RealVector mods = s.cwiseAbs();
310 const std::vector<Index> perm = internal::structured_svd_permutation(mods);
311 RealVector sv(s.size());
312 for (Index t = 0; t < s.size(); ++t) sv[t] = mods[perm[t]];
313 return sv;
314 }
315
319 ComplexMatrix matrixU() const {
320 const Index n2 = blockSize(), N = rows();
321 const ComplexVector s = eigenvalues();
322 const RealVector mods = s.cwiseAbs();
323 const std::vector<Index> perm = internal::structured_svd_permutation(mods);
324 ComplexMatrix U(N, N);
325 for (Index t = 0; t < N; ++t) {
326 fourierColumn(U, perm[t] / n2, perm[t] % n2, t);
327 const RealScalar a = mods[perm[t]];
328 if (a > RealScalar(0)) U.col(t) *= s[perm[t]] / a;
329 }
330 return U;
331 }
332
336 ComplexMatrix matrixV() const {
337 const Index n2 = blockSize(), N = rows();
338 const ComplexVector s = eigenvalues();
339 const std::vector<Index> perm = internal::structured_svd_permutation(RealVector(s.cwiseAbs()));
340 ComplexMatrix V(N, N);
341 for (Index t = 0; t < N; ++t) fourierColumn(V, perm[t] / n2, perm[t] % n2, t);
342 return V;
343 }
344
349 template <typename Dest>
350 void evalTo(Dest& dst) const {
351 assignTo<SetAssignment>(dst);
352 }
353
355 template <typename Dest>
356 void addTo(Dest& dst) const {
357 assignTo<AddAssignment>(dst);
358 }
359
361 template <typename Dest>
362 void subTo(Dest& dst) const {
363 assignTo<SubAssignment>(dst);
364 }
365
371 template <typename Rhs>
373 EIGEN_STATIC_ASSERT(ColsAtCompileTime == Dynamic || Rhs::RowsAtCompileTime == Dynamic ||
374 int(ColsAtCompileTime) == int(Rhs::RowsAtCompileTime),
375 INVALID_MATRIX_PRODUCT)
376 eigen_assert(x.rows() == cols() && "invalid product: dimensions do not match");
377 return Product<Bccb, Rhs>(*this, x.derived());
378 }
379
383 template <typename Dest, typename Rhs, typename ProductScalar>
384 void addProduct(Dest& dst, const Rhs& rhs, const ProductScalar& alpha) const {
385 const Index N = rows();
386 eigen_assert(rhs.rows() == N && "invalid product: dimensions do not match");
387 if (N <= internal::structured_direct_threshold()) {
388 directProduct(dst, rhs, alpha);
389 return;
390 }
391 // Products use the padded embedding symbol when a block dimension is not
392 // 5-smooth (see computeProdSymbol()); the exact-size transform of such a
393 // dimension runs through kissfft's quadratic generic butterfly.
394 const ComplexArray& s = m_prodSymbol.size() > 0 ? m_prodSymbol : m_symbol;
395 applySymbol(dst, s, rhs, alpha, /*accumulate=*/true);
396 }
397
398 private:
401 template <DenseAssignment Assignment, typename Dest>
402 void assignTo(Dest& dst) const {
403 const Index n2 = blockSize(), n1 = numBlocks();
404 for (Index c1 = 0; c1 < n1; ++c1)
405 for (Index j2 = 0; j2 < n2; ++j2) {
406 auto col = dst.col(c1 * n2 + j2);
407 for (Index b1 = 0; b1 < n1; ++b1) {
408 Index k1 = b1 - c1;
409 if (k1 < 0) k1 += n1;
410 EIGEN_IF_CONSTEXPR (Assignment == SetAssignment) {
411 col.segment(b1 * n2, j2) = m_g.col(k1).tail(j2);
412 col.segment(b1 * n2 + j2, n2 - j2) = m_g.col(k1).head(n2 - j2);
413 } else EIGEN_IF_CONSTEXPR (Assignment == AddAssignment) {
414 col.segment(b1 * n2, j2) += m_g.col(k1).tail(j2);
415 col.segment(b1 * n2 + j2, n2 - j2) += m_g.col(k1).head(n2 - j2);
416 } else {
417 col.segment(b1 * n2, j2) -= m_g.col(k1).tail(j2);
418 col.segment(b1 * n2 + j2, n2 - j2) -= m_g.col(k1).head(n2 - j2);
419 }
420 }
421 }
422 }
423
430 Bccb(const GeneratorType& g, const ComplexArray& symbol, const ComplexArray& prodSymbol)
431 : m_g(g), m_symbol(symbol), m_prodSymbol(prodSymbol) {}
432
451 ComplexArray computeProdSymbol(const GeneratorType& g) const {
452 const Index n2 = g.rows(), n1 = g.cols();
453 const Index p2 = internal::fft_next_good_size(n2) == n2 ? n2 : internal::fft_next_good_size(2 * n2 - 1);
454 const Index p1 = internal::fft_next_good_size(n1) == n1 ? n1 : internal::fft_next_good_size(2 * n1 - 1);
455 if (p2 == n2 && p1 == n1) return ComplexArray();
456 ComplexArray Ge = ComplexArray::Zero(p2, p1);
457 Ge.topLeftCorner(n2, n1) = g.template cast<Complex>();
458 if (p2 != n2) Ge.bottomLeftCorner(n2 - 1, n1) = g.bottomRows(n2 - 1).template cast<Complex>();
459 if (p1 != n1) Ge.topRightCorner(n2, n1 - 1) = g.rightCols(n1 - 1).template cast<Complex>();
460 if (p2 != n2 && p1 != n1)
461 Ge.bottomRightCorner(n2 - 1, n1 - 1) = g.bottomRightCorner(n2 - 1, n1 - 1).template cast<Complex>();
462 fft2(Ge);
463 return Ge;
464 }
465
471 template <typename Dest, typename Rhs, typename ProductScalar>
472 void directProductColumn(Dest& dst, const Rhs& rhs, Index k, const ProductScalar& alpha) const {
473 const Index N = rows();
474 // A unit alpha must not multiply: even the identity complex scalar (1,0)
475 // pollutes an (Inf,0) value with NaN through the 0*Inf cross term.
476 const bool unitAlpha = alpha == ProductScalar(1);
477 for (Index i = 0; i < N; ++i) {
478 ProductScalar acc(0);
479 for (Index j = 0; j < N; ++j) acc += coeff(i, j) * rhs.coeff(j, k);
480 dst.coeffRef(i, k) += unitAlpha ? acc : ProductScalar(alpha * acc);
481 }
482 }
483
486 template <typename Dest, typename Rhs, typename ProductScalar>
487 void directProduct(Dest& dst, const Rhs& rhs, const ProductScalar& alpha) const {
488 for (Index k = 0; k < rhs.cols(); ++k) directProductColumn(dst, rhs, k, alpha);
489 }
490
500 template <typename Dest, typename Rhs, typename ProductScalar>
501 void applySymbol(Dest& dst, const ComplexArray& s, const Rhs& rhs, const ProductScalar& alpha,
502 bool accumulate) const {
503 const Index n2 = blockSize(), n1 = numBlocks();
504 const Index p2 = s.rows(), p1 = s.cols(); // the symbol's transform grid: (n2, n1) or the padded product grid
505 const bool padded = p2 != n2 || p1 != n1;
506 ComplexVector xc(rows());
507 ComplexArray X(p2, p1), Xn; // Xn: the leading-block extraction of a padded grid
508 for (Index k = 0; k < rhs.cols(); ++k) {
509 // reshaped() defaults to column-major traversal, the flattening the
510 // two-level structure keys on, regardless of EIGEN_DEFAULT_TO_ROW_MAJOR.
511 // Plain objects keep both reshapes linear.
512 xc = rhs.col(k).template cast<Complex>();
513 if (padded) {
514 X.setZero();
515 X.topLeftCorner(n2, n1) = xc.reshaped(n2, n1);
516 } else {
517 X = xc.reshaped(n2, n1);
518 }
519 fft2(X);
520 X.array() *= s.array();
521 ifft2(X);
522 if (padded) Xn = X.topLeftCorner(n2, n1);
523 const auto out = internal::structured_scalar_part_impl<ProductScalar>::run((padded ? Xn : X).reshaped());
524 if (accumulate)
525 dst.col(k) += alpha * out;
526 else
527 dst.col(k) = alpha * out;
528 }
529 }
530
535 template <bool Inverse>
536 void transform2(ComplexArray& X) const {
537 auto&& fft = internal::structured_fft_engine<RealScalar>();
538 const Index n2 = X.rows(), n1 = X.cols();
539 // Both passes share one output buffer (resized by the engine) and one packed
540 // input buffer, allocated at most twice for the whole transform. A column of
541 // the column-major grid is already packed and goes to the engine as is; a row
542 // is strided, and the engine would pack it into a fresh temporary per call.
543 ComplexVector tmp, rowv;
544 if (n2 > 1) {
545 for (Index k1 = 0; k1 < n1; ++k1) {
546 EIGEN_IF_CONSTEXPR (Inverse) {
547 fft.inv(tmp, X.col(k1), n2);
548 } else {
549 fft.fwd(tmp, X.col(k1), n2);
550 }
551 X.col(k1) = tmp;
552 }
553 }
554 if (n1 > 1) {
555 for (Index k2 = 0; k2 < n2; ++k2) {
556 rowv = X.row(k2).transpose();
557 EIGEN_IF_CONSTEXPR (Inverse) {
558 fft.inv(tmp, rowv, n1);
559 } else {
560 fft.fwd(tmp, rowv, n1);
561 }
562 X.row(k2) = tmp.transpose();
563 }
564 }
565 }
566
568 void fft2(ComplexArray& X) const { transform2<false>(X); }
569
571 void ifft2(ComplexArray& X) const { transform2<true>(X); }
572
574 GeneratorType generatorFromSymbol(ComplexArray symbol) const {
575 ifft2(symbol);
576 GeneratorType generator = internal::structured_scalar_part_impl<Scalar>::run(symbol);
577 return generator;
578 }
579
581 ComplexArray computeSymbol() const {
582 ComplexArray s = m_g.template cast<Complex>();
583 fft2(s);
584 return s;
585 }
586
590 template <typename MatType>
591 static MatType reverse2(const MatType& M) {
592 const Index r = M.rows(), c = M.cols();
593 MatType R(r, c);
594 if (M.size() == 0) return R;
595 R(0, 0) = M(0, 0);
596 if (c > 1) R.row(0).tail(c - 1) = M.row(0).tail(c - 1).reverse();
597 if (r > 1) R.col(0).tail(r - 1) = M.col(0).tail(r - 1).reverse();
598 if (r > 1 && c > 1) R.bottomRightCorner(r - 1, c - 1) = M.bottomRightCorner(r - 1, c - 1).reverse();
599 return R;
600 }
601
607 void fourierColumn(ComplexMatrix& F, Index f1, Index f2, Index dstCol) const {
608 const Index n2 = blockSize(), n1 = numBlocks();
609 const RealScalar scale = RealScalar(1) / numext::sqrt(RealScalar(rows()));
610 ComplexVector w2(n2);
611 Index jf = 0; // i2 * f2 mod n2
612 for (Index i2 = 0; i2 < n2; ++i2) {
613 w2[i2] = std::polar(scale, RealScalar(2 * EIGEN_PI) * RealScalar(jf) / RealScalar(n2));
614 jf += f2;
615 if (jf >= n2) jf -= n2;
616 }
617 Index bf = 0; // b1 * f1 mod n1
618 for (Index b1 = 0; b1 < n1; ++b1) {
619 const Complex w1 = std::polar(RealScalar(1), RealScalar(2 * EIGEN_PI) * RealScalar(bf) / RealScalar(n1));
620 F.col(dstCol).segment(b1 * n2, n2) = w1 * w2;
621 bf += f1;
622 if (bf >= n1) bf -= n1;
623 }
624 }
625
627 static Scalar toScalar(const Complex& z, std::true_type /*scalar_is_real*/) { return numext::real(z); }
628 static Scalar toScalar(const Complex& z, std::false_type /*scalar_is_real*/) { return z; }
629
630 GeneratorType m_g;
631 ComplexArray m_symbol;
632 // The padded embedding symbol products use when a block dimension is not
633 // 5-smooth; empty otherwise. See computeProdSymbol().
634 ComplexArray m_prodSymbol;
635};
636
640template <typename Derived>
645
646namespace internal {
647
648template <typename Scalar_, int BlockSize_, int NumBlocks_, typename Rhs, int ProductTag>
649struct generic_product_impl<Bccb<Scalar_, BlockSize_, NumBlocks_>, Rhs, StructuredShape, DenseShape, ProductTag>
650 : structured_product_impl<Bccb<Scalar_, BlockSize_, NumBlocks_>, Rhs> {};
651
652} // namespace internal
653
654} // namespace Eigen
655
656#endif // EIGEN_STRUCTURED_BCCB_H
A block circulant matrix with circulant blocks (BCCB), the matrix of a two-dimensional circular convo...
Definition Bccb.h:126
ComplexVector eigenvalues() const
Definition Bccb.h:290
Matrix< Scalar, RowsAtCompileTime, Rhs::ColsAtCompileTime > solve(const MatrixBase< Rhs > &b) const
Definition Bccb.h:230
ComplexMatrix eigenvectors() const
Definition Bccb.h:296
Index numBlocks() const
Definition Bccb.h:181
Scalar coeff(Index row, Index col) const
Definition Bccb.h:193
Index blockSize() const
Definition Bccb.h:179
Bccb transpose() const
Definition Bccb.h:208
ComplexMatrix matrixU() const
Definition Bccb.h:319
Index rank() const
Definition Bccb.h:250
Product< Bccb, Rhs > operator*(const MatrixBase< Rhs > &x) const
Definition Bccb.h:372
Bccb conjugate() const
Definition Bccb.h:213
Bccb adjoint() const
Definition Bccb.h:221
ComplexMatrix matrixV() const
Definition Bccb.h:336
Bccb(const MatrixBase< Derived > &generator)
Definition Bccb.h:167
const GeneratorType & generator() const
Definition Bccb.h:183
RealVector singularValues() const
Definition Bccb.h:307
Bccb inverse() const
Definition Bccb.h:260
Scalar determinant() const
Definition Bccb.h:275
ComplexArray symbol() const
Definition Bccb.h:190
Bccb< typename Derived::Scalar, Derived::RowsAtCompileTime, Derived::ColsAtCompileTime > makeBccb(const MatrixBase< Derived > &generator)
Definition Bccb.h:641
Namespace containing all symbols from the Eigen library.