Eigen  5.0.1
 
Loading...
Searching...
No Matches
RandColPivHouseholderQR.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#ifndef EIGEN_RANDCOLPIVOTINGHOUSEHOLDERQR_H
11#define EIGEN_RANDCOLPIVOTINGHOUSEHOLDERQR_H
12
13#include <random>
14
15// IWYU pragma: private
16#include "./InternalHeaderCheck.h"
17
18namespace Eigen {
19
20namespace internal {
21
22template <typename MatrixType_, typename PermutationIndex_>
23struct traits<RandColPivHouseholderQR<MatrixType_, PermutationIndex_>> : traits<MatrixType_> {
24 using XprKind = MatrixXpr;
25 using StorageKind = SolverStorage;
26 using PermutationIndex = PermutationIndex_;
27 enum { Flags = 0 };
28};
29
30// Fill `mat` with iid samples drawn from a real standard normal distribution.
31template <typename Derived, typename Engine>
32EIGEN_STRONG_INLINE std::enable_if_t<!NumTraits<typename Derived::Scalar>::IsComplex> fill_gaussian(
33 MatrixBase<Derived>& mat, Engine& engine) {
34 using Scalar = typename Derived::Scalar;
35 std::normal_distribution<Scalar> dist(Scalar(0), Scalar(1));
36 for (Index j = 0; j < mat.cols(); ++j)
37 for (Index i = 0; i < mat.rows(); ++i) mat.coeffRef(i, j) = dist(engine);
38}
39
40// Fill `mat` with iid samples drawn from a complex standard normal
41// distribution (real and imaginary parts sampled independently).
42template <typename Derived, typename Engine>
43EIGEN_STRONG_INLINE std::enable_if_t<NumTraits<typename Derived::Scalar>::IsComplex> fill_gaussian(
44 MatrixBase<Derived>& mat, Engine& engine) {
45 using Scalar = typename Derived::Scalar;
46 using RealScalar = typename NumTraits<Scalar>::Real;
47 std::normal_distribution<RealScalar> dist(RealScalar(0), RealScalar(1));
48 for (Index j = 0; j < mat.cols(); ++j)
49 for (Index i = 0; i < mat.rows(); ++i) mat.coeffRef(i, j) = Scalar(dist(engine), dist(engine));
50}
51
52// Stable LAWN-176 column-norm downdate after a Householder reflector has
53// been applied. `pivot_entry` is the entry of the just-pivoted row above
54// column `j`; `tail_norm_fn` recomputes the trailing-column norm from
55// scratch when the running estimate has lost too much precision (see
56// http://www.netlib.org/lapack/lawnspdf/lawn176.pdf).
57template <typename RealScalar, typename Scalar, typename RecomputeFn>
58EIGEN_STRONG_INLINE void lawn176_norm_downdate(RealScalar& norm_updated, RealScalar& norm_direct, Scalar pivot_entry,
59 RealScalar downdate_threshold, RecomputeFn&& tail_norm_fn) {
60 using std::abs;
61 if (numext::is_exactly_zero(norm_updated)) return;
62 RealScalar t = abs(pivot_entry) / norm_updated;
63 t = (RealScalar(1) + t) * (RealScalar(1) - t);
64 if (t < RealScalar(0)) t = RealScalar(0);
65 RealScalar t2 = t * numext::abs2<RealScalar>(norm_updated / norm_direct);
66 if (t2 <= downdate_threshold) {
67 norm_direct = tail_norm_fn();
68 norm_updated = norm_direct;
69 } else {
70 norm_updated *= numext::sqrt(t);
71 }
72}
73
74} // end namespace internal
75
132template <typename MatrixType_, typename PermutationIndex_>
133class RandColPivHouseholderQR : public SolverBase<RandColPivHouseholderQR<MatrixType_, PermutationIndex_>>,
134 public RankRevealingBase<RandColPivHouseholderQR<MatrixType_, PermutationIndex_>> {
135 public:
136 using MatrixType = MatrixType_;
138 using RankRevealingBase_ = RankRevealingBase<RandColPivHouseholderQR>;
140 friend class RankRevealingBase<RandColPivHouseholderQR>;
150 using PermutationIndex = PermutationIndex_;
151 EIGEN_GENERIC_PUBLIC_INTERFACE(RandColPivHouseholderQR)
152
153 enum {
154 MaxRowsAtCompileTime = MatrixType::MaxRowsAtCompileTime,
155 MaxColsAtCompileTime = MatrixType::MaxColsAtCompileTime
156 };
157 using HCoeffsType = typename internal::plain_diag_type<MatrixType>::type;
159 using IntRowVectorType = typename internal::plain_row_type<MatrixType, PermutationIndex>::type;
160 using RowVectorType = typename internal::plain_row_type<MatrixType>::type;
161 using RealRowVectorType = typename internal::plain_row_type<MatrixType, RealScalar>::type;
162 using HouseholderSequenceType =
164 using PlainObject = typename MatrixType::PlainObject;
165
166 private:
167 // Default `m_blockSize == 0` means: let computeInPlace pick a size
168 // tuned to the input dimensions and the host cache hierarchy.
169 static constexpr Index kAutoBlockSize = 0;
170
171 // SIMD-kernel efficiency floor; smaller blocks degrade the panel-QR
172 // and trailing-GEMM kernels without speeding up the sketch.
173 static constexpr Index kKernelFloor = 48;
174 // Hard upper bound on the auto block size.
175 static constexpr Index kBlockCeiling = 1024;
176
177 // Minimum min(rows, cols) at which auto-block enters the blocked path.
178 // Below this, the per-compute sketch overhead (G fill, G*A, per-panel
179 // LU on the sketch) is not amortized by the trailing-GEMM savings vs.
180 // classical Businger-Golub pivoting. Empirically calibrated (Apple M4,
181 // double): n ~= 128 loses by ~1.5x, n >= 256 wins. Set above the
182 // kernel-efficiency floor so that `can_block` (size >= 2*b) cannot
183 // fire below it. A user who calls `setBlockSize(b > 0)` explicitly
184 // bypasses this gate.
185 static constexpr Index kAutoBlockedPathMinSize = 192;
186
187 // L2 cache size in bytes, from Eigen's shared cache-sizes singleton
188 // (which handles cross-platform detection plus user overrides via
189 // setCpuCacheSizes).
190 static Index defaultL2Bytes() {
191 std::ptrdiff_t l1, l2, l3;
192 internal::manage_caching_sizes(GetAction, &l1, &l2, &l3);
193 return Index(l2);
194 }
195
196 // Roofline-derived auto block size for `m_blockSize == kAutoBlockSize`.
197 // `size = min(rows, cols)` is passed in to avoid recomputing it.
198 //
199 // The dominant per-iteration cost is the trailing-update GEMM
200 // (compact-WY apply: C := C - V*T*V^T*C). With panel V resident in
201 // cache, the arithmetic intensity is
202 // AI = 2*b / sizeof(Scalar) [FLOPS / byte]
203 // which crosses the roofline ceiling AI_crit = peak_FLOPS / cache_BW
204 // at b roughly 2-3 on current hardware. That bound is dominated by the
205 // SIMD-kernel efficiency floor (~48); the roofline therefore gives no
206 // useful lower bound on b.
207 //
208 // The binding constraint is the cache-fit invariant: V must stay
209 // resident across the trailing sweep, otherwise the streamed re-reads
210 // drop us off the compute roof onto the DRAM-bandwidth side. That
211 // yields the upper bound
212 // b * rows * sizeof(Scalar) <= L2 / 2 (half of L2; remainder
213 // for the streamed C tile)
214 // BQRRP's recommended b ~= n/32 (paper Section 3.2) sits inside this
215 // window for matrices up to roughly 16 * L2 / (rows * sizeof(Scalar));
216 // beyond that, the cache bound binds and clamps b.
217 static Index computeAutoBlockSize(Index rows, Index size) {
218 // Degenerate empty inputs (0 rows or 0 cols): there is no work, and
219 // the cache-bound calculation below would divide by `rows`. Return 0
220 // so the caller's can_block check (b > 0) routes to the unblocked
221 // path, which handles empty matrices correctly.
222 if (rows <= 0 || size <= 0) return Index(0);
223 const Index scalar_bytes = Index(sizeof(Scalar));
224 const Index b_cache = numext::maxi(Index(1), (defaultL2Bytes() / Index(2)) / (rows * scalar_bytes));
225 const Index b_bqrrp = size / Index(32);
226
227 Index b = numext::maxi(Index(kKernelFloor), b_bqrrp);
228 b = numext::mini(b, numext::maxi(Index(kKernelFloor), b_cache));
229 b = numext::mini(b, Index(kBlockCeiling));
230 return b;
231 }
232
233 void init(Index rows, Index cols) {
234 Index diag = numext::mini(rows, cols);
235 m_hCoeffs.resize(diag);
236 m_colsPermutation.resize(cols);
237 m_temp.resize(cols);
238 m_isInitialized = false;
239 }
240
241 public:
244
246 RandColPivHouseholderQR(Index rows, Index cols) : m_qr(rows, cols) { init(rows, cols); }
247
249 template <typename InputType>
250 explicit RandColPivHouseholderQR(const EigenBase<InputType>& matrix) : m_qr(matrix.rows(), matrix.cols()) {
251 init(matrix.rows(), matrix.cols());
252 compute(matrix.derived());
253 }
254
256 template <typename InputType>
257 explicit RandColPivHouseholderQR(EigenBase<InputType>& matrix) : m_qr(matrix.derived()) {
258 init(matrix.rows(), matrix.cols());
259 computeInPlace();
260 }
261
262#ifdef EIGEN_PARSED_BY_DOXYGEN
263 template <typename Rhs>
264 inline Solve<RandColPivHouseholderQR, Rhs> solve(const MatrixBase<Rhs>& b) const;
265#endif
266
267 HouseholderSequenceType householderQ() const;
268 HouseholderSequenceType matrixQ() const { return householderQ(); }
269
270 const MatrixType& matrixQR() const {
271 eigen_assert(m_isInitialized && "RandColPivHouseholderQR is not initialized.");
272 return m_qr;
273 }
274
275 const MatrixType& matrixR() const {
276 eigen_assert(m_isInitialized && "RandColPivHouseholderQR is not initialized.");
277 return m_qr;
278 }
279
280 template <typename InputType>
281 RandColPivHouseholderQR& compute(const EigenBase<InputType>& matrix);
282
283 const PermutationType& colsPermutation() const {
284 eigen_assert(m_isInitialized && "RandColPivHouseholderQR is not initialized.");
285 return m_colsPermutation;
286 }
287
288 typename MatrixType::Scalar determinant() const;
289 typename MatrixType::RealScalar absDeterminant() const;
290 typename MatrixType::RealScalar logAbsDeterminant() const;
291 typename MatrixType::Scalar signDeterminant() const;
292
293 RealScalar pivotCoeff(Index i) const {
294 using std::abs;
295 return abs(m_qr.coeff(i, i));
296 }
297
298 inline Inverse<RandColPivHouseholderQR> inverse() const {
299 eigen_assert(m_isInitialized && "RandColPivHouseholderQR is not initialized.");
300 return Inverse<RandColPivHouseholderQR>(*this);
301 }
302
303 inline Index rows() const { return m_qr.rows(); }
304 inline Index cols() const { return m_qr.cols(); }
305
306 const HCoeffsType& hCoeffs() const { return m_hCoeffs; }
307
308 ComputationInfo info() const {
309 eigen_assert(m_isInitialized && "Decomposition is not initialized.");
310 return Success;
311 }
312
325 eigen_assert(b >= 0 && "Block size must be non-negative.");
326 m_blockSize = b;
327 return *this;
328 }
329
339
346 m_seed = seed;
347 m_seedSet = true;
348 return *this;
349 }
350
355 Index blockSize() const { return m_blockSize; }
356
358 Index oversampling() const { return Index(0); }
359
360#ifndef EIGEN_PARSED_BY_DOXYGEN
361 template <typename RhsType, typename DstType>
362 void _solve_impl(const RhsType& rhs, DstType& dst) const;
363
364 template <bool Conjugate, typename RhsType, typename DstType>
365 void _solve_impl_transposed(const RhsType& rhs, DstType& dst) const;
366#endif
367
368 protected:
369 friend class internal::CompleteOrthogonalDecompositionImpl<MatrixType, PermutationIndex, RandColPivHouseholderQR>;
370
371 EIGEN_STATIC_ASSERT_NON_INTEGER(Scalar)
372
373 void computeInPlace();
374
375 // Unblocked column-pivoted Householder QR on rows [row0, m) and columns
376 // [col0, col0 + ncols), using full-column swaps. Updates m_hCoeffs in
377 // [col0, col0 + ncols), m_colsPermutation, and the maxpivot tracker.
378 // Returns the number of column transpositions applied. Used both as
379 // the small-matrix fallback path and as the rank-deficient tail of the
380 // blocked path.
381 Index unblocked_pivoted_qr(Index row0, Index col0, Index ncols, RealScalar threshold_helper);
382
383 // Scan the diagonal of m_qr and set m_nonzero_pivots / m_maxpivot using
384 // the LAWN-176 threshold (same scale as ColPivHouseholderQR).
385 void finalize_rank(RealScalar threshold_helper);
386
387 MatrixType m_qr;
388 HCoeffsType m_hCoeffs;
389 PermutationType m_colsPermutation;
390 RowVectorType m_temp;
391 Index m_blockSize = kAutoBlockSize;
392 uint64_t m_seed = 0;
393 bool m_seedSet = false;
394 bool m_isInitialized = false;
395 Index m_det_p = 1;
396};
397
398template <typename MatrixType, typename PermutationIndex>
399typename MatrixType::Scalar RandColPivHouseholderQR<MatrixType, PermutationIndex>::determinant() const {
400 eigen_assert(m_isInitialized && "RandColPivHouseholderQR is not initialized.");
401 eigen_assert(m_qr.rows() == m_qr.cols() && "You can't take the determinant of a non-square matrix!");
402 Scalar detQ;
403 internal::householder_determinant<HCoeffsType, Scalar, NumTraits<Scalar>::IsComplex>::run(m_hCoeffs, detQ);
404 return isInjective() ? (detQ * Scalar(m_det_p)) * m_qr.diagonal().prod() : Scalar(0);
405}
406
407template <typename MatrixType, typename PermutationIndex>
408typename MatrixType::RealScalar RandColPivHouseholderQR<MatrixType, PermutationIndex>::absDeterminant() const {
409 using std::abs;
410 eigen_assert(m_isInitialized && "RandColPivHouseholderQR is not initialized.");
411 eigen_assert(m_qr.rows() == m_qr.cols() && "You can't take the determinant of a non-square matrix!");
412 return isInjective() ? abs(m_qr.diagonal().prod()) : RealScalar(0);
413}
414
415template <typename MatrixType, typename PermutationIndex>
416typename MatrixType::RealScalar RandColPivHouseholderQR<MatrixType, PermutationIndex>::logAbsDeterminant() const {
417 eigen_assert(m_isInitialized && "RandColPivHouseholderQR is not initialized.");
418 eigen_assert(m_qr.rows() == m_qr.cols() && "You can't take the determinant of a non-square matrix!");
419 return isInjective() ? m_qr.diagonal().cwiseAbs().array().log().sum() : -NumTraits<RealScalar>::infinity();
420}
421
422template <typename MatrixType, typename PermutationIndex>
423typename MatrixType::Scalar RandColPivHouseholderQR<MatrixType, PermutationIndex>::signDeterminant() const {
424 eigen_assert(m_isInitialized && "RandColPivHouseholderQR is not initialized.");
425 eigen_assert(m_qr.rows() == m_qr.cols() && "You can't take the determinant of a non-square matrix!");
426 Scalar detQ;
427 internal::householder_determinant<HCoeffsType, Scalar, NumTraits<Scalar>::IsComplex>::run(m_hCoeffs, detQ);
428 return isInjective() ? (detQ * Scalar(m_det_p)) * m_qr.diagonal().array().sign().prod() : Scalar(0);
429}
430
431template <typename MatrixType, typename PermutationIndex>
432template <typename InputType>
433RandColPivHouseholderQR<MatrixType, PermutationIndex>& RandColPivHouseholderQR<MatrixType, PermutationIndex>::compute(
434 const EigenBase<InputType>& matrix) {
435 m_qr = matrix.derived();
436 computeInPlace();
437 return *this;
438}
439
440template <typename MatrixType, typename PermutationIndex>
441Index RandColPivHouseholderQR<MatrixType, PermutationIndex>::unblocked_pivoted_qr(Index row0, Index col0, Index ncols,
442 RealScalar threshold_helper) {
443 using std::abs;
444 const Index rows = m_qr.rows();
445 const Index col_end = col0 + ncols;
446 const Index sub_rows = rows - row0;
447 Index num_transpositions = 0;
448
449 Matrix<RealScalar, 1, Dynamic> norms_direct = m_qr.block(row0, col0, sub_rows, ncols).colwise().norm();
450 Matrix<RealScalar, 1, Dynamic> norms_updated = norms_direct;
451 const RealScalar downdate_threshold = numext::sqrt(NumTraits<RealScalar>::epsilon());
452
453 const Index size = (std::min)(sub_rows, ncols);
454 for (Index k = 0; k < size; ++k) {
455 Index biggest;
456 RealScalar biggest_sq = numext::abs2(norms_updated.tail(ncols - k).maxCoeff(&biggest));
457 biggest += k;
458
459 if (this->m_nonzero_pivots == m_qr.diagonalSize() && biggest_sq < threshold_helper * RealScalar(rows - row0 - k))
460 this->m_nonzero_pivots = col0 + k;
461
462 if (k != biggest) {
463 m_qr.col(col0 + k).swap(m_qr.col(col0 + biggest));
464 std::swap(norms_updated.coeffRef(k), norms_updated.coeffRef(biggest));
465 std::swap(norms_direct.coeffRef(k), norms_direct.coeffRef(biggest));
466 m_colsPermutation.applyTranspositionOnTheRight(col0 + k, col0 + biggest);
467 ++num_transpositions;
468 }
469
470 RealScalar beta;
471 m_qr.col(col0 + k).tail(sub_rows - k).makeHouseholderInPlace(m_hCoeffs.coeffRef(col0 + k), beta);
472 m_qr.coeffRef(row0 + k, col0 + k) = beta;
473 if (abs(beta) > this->m_maxpivot) this->m_maxpivot = abs(beta);
474
475 // Apply the reflector only to the remaining panel columns. Columns at
476 // indices >= col_end are owned by the outer caller and updated via a
477 // BLAS-3 trailing update; in the tail/fallback path col_end equals
478 // m_qr.cols(), so this naturally degrades to a full apply.
479 const Index trail_cols = col_end - col0 - k - 1;
480 if (trail_cols > 0) {
481 m_qr.block(row0 + k, col0 + k + 1, sub_rows - k, trail_cols)
482 .applyHouseholderOnTheLeft(m_qr.col(col0 + k).tail(sub_rows - k - 1), m_hCoeffs.coeff(col0 + k),
483 &m_temp.coeffRef(col0 + k + 1));
484 }
485
486 for (Index j = k + 1; j < ncols; ++j) {
487 internal::lawn176_norm_downdate(norms_updated.coeffRef(j), norms_direct.coeffRef(j),
488 m_qr.coeff(row0 + k, col0 + j), downdate_threshold,
489 [&] { return m_qr.col(col0 + j).tail(sub_rows - k - 1).norm(); });
490 }
491 }
492 return num_transpositions;
493}
494
495template <typename MatrixType, typename PermutationIndex>
496void RandColPivHouseholderQR<MatrixType, PermutationIndex>::finalize_rank(RealScalar threshold_helper) {
497 using std::abs;
498 const Index rows = m_qr.rows();
499 const Index size = (std::min)(rows, m_qr.cols());
500 // m_maxpivot is already up-to-date: the blocked path tracks it per
501 // panel, and unblocked_pivoted_qr tracks it per step.
502 // If neither the blocked path nor the unblocked tail tightened the
503 // rank cap, fall back to the LAWN-176 first-below-threshold scan.
504 // This handles the full-rank-blocked-path case where every panel
505 // was full rank and the only "small" diagonal entries are in the
506 // unblocked tail.
507 if (this->m_nonzero_pivots == size) {
508 for (Index i = 0; i < size; ++i) {
509 RealScalar a = abs(m_qr.coeff(i, i));
510 if (numext::abs2(a) < threshold_helper * RealScalar(rows - i)) {
511 this->m_nonzero_pivots = i;
512 break;
513 }
514 }
515 }
516}
517
518template <typename MatrixType, typename PermutationIndex>
519void RandColPivHouseholderQR<MatrixType, PermutationIndex>::computeInPlace() {
520 eigen_assert(m_qr.cols() <= NumTraits<PermutationIndex>::highest());
521
522 const Index rows = m_qr.rows();
523 const Index cols = m_qr.cols();
524 const Index size = (std::min)(rows, cols);
525
526 m_hCoeffs.resize(size);
527 m_temp.resize(cols);
528 m_colsPermutation.resize(cols);
529 m_colsPermutation.setIdentity();
530 this->m_nonzero_pivots = size;
531 this->m_maxpivot = RealScalar(0);
532 Index num_transpositions = 0;
533
534 // Global rank-detection scale, derived from the original column norms
535 // exactly as ColPivHouseholderQR does (LAWN-176). All panel calls share
536 // this threshold so rank revelation is consistent across blocks.
537 RealScalar max_initial_norm = RealScalar(0);
538 for (Index j = 0; j < cols; ++j) max_initial_norm = numext::maxi(max_initial_norm, m_qr.col(j).norm());
539 const RealScalar threshold_helper =
540 cols == 0 ? RealScalar(0)
541 : numext::abs2<RealScalar>(max_initial_norm * NumTraits<RealScalar>::epsilon()) / RealScalar(rows);
542
543 const bool auto_block = (m_blockSize == kAutoBlockSize);
544 const Index requested_b = auto_block ? computeAutoBlockSize(rows, size) : m_blockSize;
545 const Index b = (std::min)(requested_b, size);
546
547 // Gates for entering the blocked path:
548 // - `b > 0` and `rows > b` for the trailing update to have work,
549 // - `size >= 2*b` so at least two full blocks fit (one block + tail
550 // is handled more efficiently by the unblocked path),
551 // - in the auto-block path, an additional size threshold so the
552 // sketch overhead is amortized by trailing-GEMM savings (see
553 // kAutoBlockedPathMinSize). Users who pin a block size via
554 // setBlockSize() bypass this — useful for tests that need to
555 // exercise the blocked path on small inputs.
556 const bool can_block = (b > 0) && (size >= 2 * b) && (rows > b) && (!auto_block || size >= kAutoBlockedPathMinSize);
557 if (!can_block) {
558 num_transpositions += unblocked_pivoted_qr(0, 0, cols, threshold_helper);
559 m_det_p = (num_transpositions % 2) ? -1 : 1;
560 finalize_rank(threshold_helper);
561 m_isInitialized = true;
562 return;
563 }
564
566 using WorkVector = Matrix<Scalar, Dynamic, 1>;
567 // Dynamic-sized Ref types so that a fixed-size MatrixType (e.g.
568 // Matrix<float, 8, 10>) and its run-time-sized blocks can both be
569 // wrapped without tripping Ref's compile-time-size check.
570 using WorkMatrixRef = Ref<WorkMatrix, 0, OuterStride<>>;
571 // Panels of m_qr carry MatrixType's storage order, which need not be the workspaces'.
573 using QrPanelRef = Ref<QrMatrix, 0, OuterStride<>>;
574 using HCoeffsRef = Ref<WorkVector>;
576
577 // Hoisted workspaces — allocated once per compute() call. Worst-case
578 // sizes are computed from the first iteration (n_remain = cols,
579 // trail_cols = cols - b); subsequent iterations use shrinking
580 // sub-blocks via leftCols/topRows.
581 WorkMatrix Y(b, cols);
582 WorkMatrix YT(cols, b);
583 WorkMatrix sketch_R(b, b);
584 IpivType ipiv(b);
585 WorkVector y_hcoeffs(b);
586 WorkVector y_temp(cols);
587
588 // Initialize the sketch Y = G * A. We don't keep G around; the
589 // Duersch-Gu update (step 24) maintains Y deterministically from
590 // here on.
591 {
592 WorkMatrix G(b, rows);
593 uint64_t seed = m_seed;
594 if (!m_seedSet) {
595 std::random_device rd;
596 seed = (uint64_t(rd()) << 32) | uint64_t(rd());
597 }
598 std::mt19937_64 engine(seed);
599 internal::fill_gaussian(G, engine);
600 Y.noalias() = G * m_qr;
601 }
602
603 Index k = 0;
604 bool blocked_terminated_early = false;
605 while (k + b <= size && cols - k > b) {
606 const Index n_remain = cols - k; // columns from k to end
607 const Index trail_cols = n_remain - b; // columns to the right of the panel
608 const Index sub_rows = rows - k; // rows from k to end
609
610 // === Step 7 (Algorithm 2): "crazy man's QRCP" on the sketch.
611 // Form Y_T = Y(:, k:)^T and run partial-pivoted LU on it. The IPIV
612 // vector tells us which b columns of Y(:, k:) (and thus which
613 // columns of m_qr.middleCols(k, n_remain)) to bring to the front.
614 auto Y_curr = Y.middleCols(k, n_remain);
615 auto Y_T = YT.topRows(n_remain);
616 Y_T = Y_curr.transpose();
617
618 typename IpivType::StorageIndex nb_lu_transp = 0;
619 internal::partial_lu_inplace(Y_T, ipiv, nb_lu_transp);
620
621 // === Steps 9-11: apply IPIV to columns of m_qr and Y starting at
622 // absolute column k (LAPACK-style serial swaps; equivalent to
623 // applying Algorithm 4 of the BQRRP paper to the converted pivot
624 // vector).
625 for (Index i = 0; i < b; ++i) {
626 Index dst = static_cast<Index>(ipiv.coeff(i));
627 if (dst != i) {
628 m_qr.col(k + i).swap(m_qr.col(k + dst));
629 Y.col(k + i).swap(Y.col(k + dst));
630 m_colsPermutation.applyTranspositionOnTheRight(k + i, k + dst);
631 ++num_transpositions;
632 }
633 }
634
635 // Materialize R_sk in the upper triangle of Y(:, k:) via unpivoted
636 // blocked Householder QR. We discard the resulting Householder
637 // vectors and tau; only R_sk feeds the step-24 sketch update.
638 // Wrap in `Ref` so householder_qr_inplace_blocked sees a flat
639 // MatrixQR — its internal `Block<MatrixQR, ...>` typedef does not
640 // compose with Block-of-Block expressions.
641 {
642 WorkMatrixRef Y_curr_ref(Y_curr);
643 Ref<WorkVector> y_hc_ref(y_hcoeffs);
644 internal::householder_qr_inplace_blocked<WorkMatrixRef, Ref<WorkVector>>::run(
645 Y_curr_ref, y_hc_ref, /*maxBlockSize=*/(std::min)(b, Index(48)), y_temp.data());
646 }
647
648 // Save R_sk_11 = upper-triangular portion of Y(:, k:k+b) before the
649 // panel QR overwrites the corresponding columns of m_qr (via the
650 // trailing update); we need it for step 24.
651 sketch_R = Y.block(0, k, b, b);
652 // The step-24 update below consumes R_sk_11 as a plain matrix, so clear
653 // the Householder vectors the sketch QR left below its diagonal.
654 sketch_R.template triangularView<StrictlyLower>().setZero();
655
656 // === Step 12: tall unpivoted Householder QR on the panel.
657 auto panel = m_qr.block(k, k, sub_rows, b);
658 auto hCoeffsSegment = m_hCoeffs.segment(k, b);
659 {
660 QrPanelRef panel_ref(panel);
661 HCoeffsRef hc_ref(hCoeffsSegment);
662 internal::householder_qr_inplace_blocked<QrPanelRef, HCoeffsRef>::run(
663 panel_ref, hc_ref, /*maxBlockSize=*/(std::min)(b, Index(48)), m_temp.data());
664 }
665
666 this->m_maxpivot = (std::max)(this->m_maxpivot, m_qr.diagonal().segment(k, b).cwiseAbs().maxCoeff());
667
668 // Detect rank deficiency by scanning the panel's diagonal against
669 // a relative threshold (max pivot seen so far times the default
670 // RankRevealingBase tolerance). The LAWN-176 threshold used by
671 // ColPivHouseholderQR assumes a strictly monotonic diagonal —
672 // BQRRP's unpivoted panel QR breaks that assumption for matrices
673 // with closely-spaced singular values (e.g. partial isometries),
674 // so we use the same relative tolerance that RankRevealingBase::
675 // rank() applies to the final diagonal.
676 Index panel_rank = b;
677 {
678 using std::abs;
679 const RealScalar relative_cutoff = this->m_maxpivot * NumTraits<RealScalar>::epsilon() * RealScalar(4 * size);
680 for (Index i = 0; i < b; ++i) {
681 RealScalar a = abs(m_qr.coeff(k + i, k + i));
682 if (a <= relative_cutoff) {
683 panel_rank = i;
684 break;
685 }
686 }
687 }
688
689 // === Step 17: apply Q^H from the panel to the trailing block.
690 // The loop guard `cols - k > b` guarantees `trail_cols > 0`. We
691 // pass the un-conjugated coeffs because apply_block_householder_on_the_left
692 // conjugates internally when forward=false (matching how HouseholderQR
693 // drives this same routine).
694 auto trailing = m_qr.block(k, k + b, sub_rows, trail_cols);
695 internal::apply_block_householder_on_the_left(trailing, panel, hCoeffsSegment,
696 /*forward=*/false);
697
698 if (panel_rank < b) {
699 // Pin the rank cap and break out; the unblocked tail will
700 // process the remaining columns. The tail's m_nonzero_pivots
701 // write is gated on it still being unset (== diagonalSize),
702 // so a tighter cap from here survives.
703 if (this->m_nonzero_pivots == size) {
704 this->m_nonzero_pivots = k + panel_rank;
705 }
706 k += b;
707 blocked_terminated_early = true;
708 break;
709 }
710
711 // === Step 24: Duersch-Gu sketch update.
712 // Y(:, k+b:) := R_sk_12 - R_sk_11 * R_11^{-1} * R_12
713 // R_sk_12 currently sits in Y.middleCols(k+b, trail_cols) (the QR of
714 // the sketch above placed it there). R_sk_11 we saved into sketch_R.
715 // R_11 and R_12 are the corresponding blocks of m_qr after the
716 // panel QR + trailing update.
717 // Associating as (R_sk_11 * R_11^{-1}) * R_12 keeps the triangular solve at
718 // b x b and leaves a single GEMM reading R_12 straight out of m_qr: no
719 // b x trail_cols workspace, and no wide triangular solve/product (both of
720 // which run well below GEMM throughput).
721 {
722 m_qr.block(k, k, b, b).template triangularView<Upper>().template solveInPlace<OnTheRight>(sketch_R);
723 Y.middleCols(k + b, trail_cols).noalias() -= sketch_R * m_qr.block(k, k + b, b, trail_cols);
724 }
725
726 k += b;
727 }
728
729 // Tail loop: any remaining columns (last partial block, or the rank-
730 // deficient remainder after early termination) handled by the
731 // unblocked pivoted QR. This routine also updates m_nonzero_pivots
732 // along its diagonal scan, which finalize_rank below cross-checks.
733 if (k < cols && (k < size || blocked_terminated_early)) {
734 num_transpositions += unblocked_pivoted_qr(k, k, cols - k, threshold_helper);
735 }
736
737 m_det_p = (num_transpositions % 2) ? -1 : 1;
738
739 // Final rank determination: scan the entire diagonal with the LAWN-176
740 // threshold. This refines whatever the unblocked tail set, and covers
741 // the full-rank blocked path where m_nonzero_pivots was never touched.
742 finalize_rank(threshold_helper);
743
744 m_isInitialized = true;
745}
746
747#ifndef EIGEN_PARSED_BY_DOXYGEN
748template <typename MatrixType_, typename PermutationIndex_>
749template <typename RhsType, typename DstType>
750void RandColPivHouseholderQR<MatrixType_, PermutationIndex_>::_solve_impl(const RhsType& rhs, DstType& dst) const {
751 const Index nonzero_pivots = nonzeroPivots();
752
753 if (nonzero_pivots == 0) {
754 dst.setZero();
755 return;
756 }
757
758 typename RhsType::PlainObject c(rhs);
759
760 c.applyOnTheLeft(householderQ().setLength(nonzero_pivots).adjoint());
761
762 m_qr.topLeftCorner(nonzero_pivots, nonzero_pivots)
763 .template triangularView<Upper>()
764 .solveInPlace(c.topRows(nonzero_pivots));
765
766 for (Index i = 0; i < nonzero_pivots; ++i) dst.row(m_colsPermutation.indices().coeff(i)) = c.row(i);
767 for (Index i = nonzero_pivots; i < cols(); ++i) dst.row(m_colsPermutation.indices().coeff(i)).setZero();
768}
769
770template <typename MatrixType_, typename PermutationIndex_>
771template <bool Conjugate, typename RhsType, typename DstType>
773 DstType& dst) const {
774 const Index nonzero_pivots = nonzeroPivots();
775
776 if (nonzero_pivots == 0) {
777 dst.setZero();
778 return;
779 }
780
781 typename RhsType::PlainObject c(m_colsPermutation.transpose() * rhs);
782
783 m_qr.topLeftCorner(nonzero_pivots, nonzero_pivots)
784 .template triangularView<Upper>()
785 .transpose()
786 .template conjugateIf<Conjugate>()
787 .solveInPlace(c.topRows(nonzero_pivots));
788
789 dst.topRows(nonzero_pivots) = c.topRows(nonzero_pivots);
790 dst.bottomRows(rows() - nonzero_pivots).setZero();
791
792 dst.applyOnTheLeft(householderQ().setLength(nonzero_pivots).template conjugateIf<!Conjugate>());
793}
794#endif
795
796namespace internal {
797
798template <typename DstXprType, typename MatrixType, typename PermutationIndex>
799struct Assignment<DstXprType, Inverse<RandColPivHouseholderQR<MatrixType, PermutationIndex>>,
800 internal::assign_op<typename DstXprType::Scalar,
801 typename RandColPivHouseholderQR<MatrixType, PermutationIndex>::Scalar>,
802 Dense2Dense> {
803 using QrType = RandColPivHouseholderQR<MatrixType, PermutationIndex>;
804 using SrcXprType = Inverse<QrType>;
805 static void run(DstXprType& dst, const SrcXprType& src,
806 const internal::assign_op<typename DstXprType::Scalar, typename QrType::Scalar>&) {
807 dst = src.nestedExpression().solve(MatrixType::Identity(src.rows(), src.cols()));
808 }
809};
810
811} // end namespace internal
812
813template <typename MatrixType, typename PermutationIndex>
814typename RandColPivHouseholderQR<MatrixType, PermutationIndex>::HouseholderSequenceType
815RandColPivHouseholderQR<MatrixType, PermutationIndex>::householderQ() const {
816 eigen_assert(m_isInitialized && "RandColPivHouseholderQR is not initialized.");
817 return HouseholderSequenceType(m_qr, m_hCoeffs.conjugate());
818}
819
824template <typename Derived>
825template <typename PermutationIndexType>
827MatrixBase<Derived>::randColPivHouseholderQr() const {
829}
830
831} // end namespace Eigen
832
833#endif // EIGEN_RANDCOLPIVOTINGHOUSEHOLDERQR_H
EvalReturnType eval() const
Definition DenseBase.h:385
Sequence of Householder reflections acting on subspaces with decreasing size.
Definition HouseholderSequence.h:140
Base class for all dense matrices, vectors, and expressions.
Definition MatrixBase.h:53
The matrix class, also used for vectors and row-vectors.
Definition Matrix.h:188
Permutation matrix.
Definition PermutationMatrix.h:346
Randomized blocked Householder rank-revealing QR with column pivoting.
Definition RandColPivHouseholderQR.h:134
RandColPivHouseholderQR(Index rows, Index cols)
Constructor with memory preallocation.
Definition RandColPivHouseholderQR.h:246
RandColPivHouseholderQR & setBlockSize(Index b)
Sets the panel block size b.
Definition RandColPivHouseholderQR.h:324
RandColPivHouseholderQR & setOversampling(Index)
Sets the oversampling parameter p.
Definition RandColPivHouseholderQR.h:338
Index oversampling() const
Definition RandColPivHouseholderQR.h:358
Index blockSize() const
Returns the user-set panel block size, or 0 if the algorithm should pick automatically....
Definition RandColPivHouseholderQR.h:355
RandColPivHouseholderQR()=default
Default constructor.
RandColPivHouseholderQR(EigenBase< InputType > &matrix)
Inplace constructor: takes a Ref and decomposes in place.
Definition RandColPivHouseholderQR.h:257
RandColPivHouseholderQR(const EigenBase< InputType > &matrix)
Constructs and computes a QR factorization from matrix.
Definition RandColPivHouseholderQR.h:250
RandColPivHouseholderQR & setSeed(uint64_t seed)
Fixes the seed of the internal RNG for reproducible factorization.
Definition RandColPivHouseholderQR.h:345
Index dimensionOfKernel() const
Definition RankRevealingBase.h:110
bool isInjective() const
Definition RankRevealingBase.h:122
bool isSurjective() const
Definition RankRevealingBase.h:134
RandColPivHouseholderQR & setThreshold(const RealScalar &threshold)
Definition RankRevealingBase.h:57
Index nonzeroPivots() const
Definition RankRevealingBase.h:157
RealScalar threshold() const
Definition RankRevealingBase.h:80
RealScalar maxPivot() const
Definition RankRevealingBase.h:165
Index rank() const
Definition RankRevealingBase.h:95
bool isInvertible() const
Definition RankRevealingBase.h:145
A matrix or vector expression mapping an existing expression.
Definition Ref.h:262
Pseudo expression representing a solving operation.
Definition Solve.h:63
constexpr Derived & derived()
Definition EigenBase.h:50
Represents a sequence of transpositions (row/column interchange)
Definition Transpositions.h:144
ComputationInfo
Definition Constants.h:455
@ Success
Definition Constants.h:457
Definition EigenBase.h:34
constexpr Index cols() const noexcept
Definition EigenBase.h:62
constexpr Derived & derived()
Definition EigenBase.h:50
constexpr Index rows() const noexcept
Definition EigenBase.h:60
constexpr Index size() const noexcept
Definition EigenBase.h:65
Eigen::Index Index
The interface type of indices.
Definition EigenBase.h:44
Holds information about the various numeric (i.e. scalar) types allowed by Eigen.
Definition NumTraits.h:233