Eigen  5.0.1
 
Loading...
Searching...
No Matches
TriangularInPlace.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2026 Rasmus Munk Larsen <rmlarsen@gmail.com>
5//
6// This Source Code Form is subject to the terms of the Mozilla
7// Public License v. 2.0. If a copy of the MPL was not distributed
8// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
9// SPDX-License-Identifier: MPL-2.0
10
11#ifndef EIGEN_TRIANGULARINPLACE_H
12#define EIGEN_TRIANGULARINPLACE_H
13
14// IWYU pragma: private
15#include "./InternalHeaderCheck.h"
16
17namespace Eigen {
18
19namespace internal {
20
21// In-place counterparts of LAPACK xTRTRI and xLAUUM. Each overwrites its operand; the only scratch is
22// one block-sized panel, at most 128x128 elements whatever the matrix size, through which the in-place
23// TRMM steps run Eigen's blocked kernel, the same way optimized BLAS implement xTRMM.
24
25// Only the lower forms exist. An upper U runs the lower kernel on Transpose<MatrixType>: with
26// V = U^T, V^-1 = (U^-1)^T and V^* V = conj(U U^*) = (U U^*)^T since U U^* is self-adjoint, so the
27// lower triangle of the transposed view is the upper triangle of the original in both cases.
28
29// Both kernels have the panel-times-trailing-block shape of the Cholesky factorization, so the block
30// size tracks llt_inplace::blocked(), but each has its own floor and its own size below which the
31// unblocked form still wins: TRTRI's blocked step is a TRMM plus a TRSM against a bs-wide panel, and
32// its unblocked form is already a sequence of TRMVs, while LAUUM's is a TRMM plus a HERK and its
33// unblocked form is a sequence of GEMVs against a strided row. Measured on Zen 4 at n = 32..512.
34constexpr Index kTriangularInverseMinBlocked = 64;
35constexpr Index kTriangularInverseMinBlockSize = 24;
36constexpr Index kAdjointSquareMinBlocked = 128;
37constexpr Index kAdjointSquareMinBlockSize = 48;
38
39EIGEN_DEVICE_FUNC inline Index triangular_in_place_block_size(Index size, Index min_block_size) {
40 const Index block_size = ((size / 8) / 8) * 8;
41 return numext::mini(numext::maxi(block_size, min_block_size), Index(128));
42}
43
44template <typename MatrixType>
45struct triangular_in_place_workspace {
46 using type = Matrix<typename MatrixType::Scalar, Dynamic, Dynamic,
47 (int(traits<MatrixType>::Flags) & RowMajorBit) ? RowMajor : ColMajor>;
48};
49
50// Unblocked lower triangular inverse, columns bottom-up so that the trailing block already holds
51// X22 = L22^-1 when column j is reached: then X(j+1:, j) = -X22 L(j+1:, j) / L(j, j), one TRMV per
52// column rather than the chain of AXPYs a forward substitution per column would run. \a tmp is
53// scratch of at least n - 1 entries, since the TRMV's destination is its own operand.
54template <unsigned int Mode, typename MatrixType, typename VectorType>
55EIGEN_DEVICE_FUNC void triangular_inverse_unblocked(MatrixType& mat, VectorType& tmp) {
56 using Scalar = typename MatrixType::Scalar;
57 constexpr bool kUnitDiag = (Mode & UnitDiag) != 0;
58 const Index n = mat.rows();
59 eigen_internal_assert(n < 2 || tmp.size() >= n - 1);
60 for (Index j = n - 1; j >= 0; --j) {
61 Scalar xj = Scalar(1);
62 EIGEN_IF_CONSTEXPR (!kUnitDiag) {
63 xj = Scalar(1) / mat.coeff(j, j);
64 mat.coeffRef(j, j) = xj;
65 }
66 const Index rs = n - j - 1;
67 if (rs == 0) continue;
68 tmp.head(rs).noalias() = mat.block(j + 1, j + 1, rs, rs).template triangularView<Mode>() * mat.col(j).tail(rs);
69 mat.col(j).tail(rs) = -xj * tmp.head(rs);
70 }
71}
72
73// Blocked lower triangular inverse, block columns bottom-up as in LAPACK xTRTRI. With
74// L = [[L11, 0], [L21, L22]] the inverse is [[L11^-1, 0], [-L22^-1 L21 L11^-1, L22^-1]]; walking upwards
75// makes X22 = L22^-1 available first, so the large operation is the TRMM X22 L21 rather than a solve
76// against L22, and Eigen's TRMM runs closer to GEMM rate than its TRSM does on a 128-column operand.
77template <unsigned int Mode, typename MatrixType>
78EIGEN_DEVICE_FUNC void triangular_inverse_lower(MatrixType& mat) {
79 eigen_assert(mat.rows() == mat.cols());
80 const Index n = mat.rows();
81 if (n < kTriangularInverseMinBlocked) {
82 using Scalar = typename MatrixType::Scalar;
83 ei_declare_aligned_stack_constructed_variable(Scalar, tmp_data, numext::maxi(n, Index(1)), 0);
84 Map<Matrix<Scalar, Dynamic, 1> > tmp(tmp_data, n);
85 triangular_inverse_unblocked<Mode>(mat, tmp);
86 return;
87 }
88 const Index block_size = triangular_in_place_block_size(n, kTriangularInverseMinBlockSize);
89 // One block-sized panel, whose first column doubles as the TRMV scratch of the unblocked kernel:
90 // that kernel runs on L11 only after the panel's other uses in the same iteration are finished.
91 typename triangular_in_place_workspace<MatrixType>::type work(block_size, block_size);
92 for (Index k = ((n - 1) / block_size) * block_size; k >= 0; k -= block_size) {
93 const Index bs = numext::mini(block_size, n - k);
94 const Index rs = n - k - bs;
95 Block<MatrixType, Dynamic, Dynamic> L11(mat, k, k, bs, bs);
96 if (rs > 0) {
97 Block<MatrixType, Dynamic, Dynamic> L21(mat, k + bs, k, rs, bs);
98 Block<MatrixType, Dynamic, Dynamic> X22(mat, k + bs, k + bs, rs, rs);
99 // L21 <- X22 L21. Row panel r of the product reads only rows <= r of L21, so going upwards
100 // leaves every row a later panel still needs untouched.
101 for (Index r_end = rs; r_end > 0; r_end -= block_size) {
102 const Index r = numext::maxi(Index(0), r_end - block_size);
103 const Index h = r_end - r;
104 work.topLeftCorner(h, bs) = L21.middleRows(r, h);
105 L21.middleRows(r, h).noalias() =
106 X22.block(r, r, h, h).template triangularView<Mode>() * work.topLeftCorner(h, bs);
107 if (r > 0) L21.middleRows(r, h).noalias() += X22.block(r, 0, h, r) * L21.topRows(r);
108 }
109 L11.template triangularView<Mode>().template solveInPlace<OnTheRight>(L21);
110 L21 = -L21;
111 }
112 auto tmp = work.col(0);
113 triangular_inverse_unblocked<Mode>(L11, tmp);
114 }
115}
116
117template <unsigned int Mode, bool IsLower = (int(Mode) & int(Lower)) != 0>
118struct triangular_inverse_selector {
119 template <typename MatrixType>
120 EIGEN_DEVICE_FUNC static void run(MatrixType& mat) {
121 triangular_inverse_lower<Mode>(mat);
122 }
123};
124
125template <unsigned int Mode>
126struct triangular_inverse_selector<Mode, false> {
127 template <typename MatrixType>
128 EIGEN_DEVICE_FUNC static void run(MatrixType& mat) {
129 Transpose<MatrixType> matt(mat);
130 triangular_inverse_lower<(int(Mode) & int(UnitDiag)) | int(Lower)>(matt);
131 }
132};
133
134// Unblocked lower self-adjoint square: mat <- lower(L^* L). Row i of the result reads column i of L
135// below the diagonal and rows > i of the columns left of it, none of which row i's own update writes.
136template <typename MatrixType>
137void triangular_adjoint_square_unblocked(MatrixType& mat) {
138 using Scalar = typename MatrixType::Scalar;
139 const Index n = mat.rows();
140 for (Index i = 0; i < n; ++i) {
141 const Scalar lii = mat.coeff(i, i);
142 const Index rs = n - i - 1;
143 mat.coeffRef(i, i) = Scalar(mat.col(i).tail(n - i).squaredNorm());
144 if (i > 0) {
145 mat.row(i).head(i) *= numext::conj(lii);
146 if (rs > 0) mat.row(i).head(i).noalias() += mat.col(i).tail(rs).adjoint() * mat.bottomLeftCorner(rs, i);
147 }
148 }
149}
150
151// Blocked lower self-adjoint square, left-looking as in LAPACK xLAUUM: block row k of the result is
152// completed against the columns to its left before the trailing block rows consume the factor.
153template <typename MatrixType>
154void triangular_adjoint_square_lower(MatrixType& mat) {
155 eigen_assert(mat.rows() == mat.cols());
156 const Index n = mat.rows();
157 if (n < kAdjointSquareMinBlocked) {
158 triangular_adjoint_square_unblocked(mat);
159 return;
160 }
161 const Index block_size = triangular_in_place_block_size(n, kAdjointSquareMinBlockSize);
162 typename triangular_in_place_workspace<MatrixType>::type work(block_size, block_size);
163 for (Index k = 0; k < n; k += block_size) {
164 const Index bs = numext::mini(block_size, n - k);
165 const Index rs = n - k - bs;
166 Block<MatrixType, Dynamic, Dynamic> L11(mat, k, k, bs, bs);
167 if (k > 0) {
168 // A(k:k+bs, 0:k) = sum over rows m >= k of L(m, k:k+bs)^* L(m, 0:k), split at m = k+bs. The
169 // first part is the TRMM L11^* B in place, done by column panels through the workspace.
170 Block<MatrixType, Dynamic, Dynamic> B(mat, k, 0, bs, k);
171 for (Index c = 0; c < k; c += block_size) {
172 const Index w = numext::mini(block_size, k - c);
173 work.topLeftCorner(bs, w) = B.middleCols(c, w);
174 B.middleCols(c, w).noalias() = L11.adjoint().template triangularView<Upper>() * work.topLeftCorner(bs, w);
175 }
176 if (rs > 0) B.noalias() += mat.block(k + bs, k, rs, bs).adjoint() * mat.block(k + bs, 0, rs, k);
177 }
178 triangular_adjoint_square_unblocked(L11);
179 if (rs > 0) {
180 L11.template selfadjointView<Lower>().rankUpdate(mat.block(k + bs, k, rs, bs).adjoint());
181 // Fused complex products can leave a rounding residual in the imaginary diagonal.
182 EIGEN_IF_CONSTEXPR (NumTraits<typename MatrixType::Scalar>::IsComplex) {
183 L11.diagonal() = L11.diagonal().real().template cast<typename MatrixType::Scalar>();
184 }
185 }
186 }
187}
188
189template <int UpLo, bool IsLower = (int(UpLo) & int(Lower)) != 0>
190struct triangular_adjoint_square_selector {
191 template <typename MatrixType>
192 static void run(MatrixType& mat) {
193 triangular_adjoint_square_lower(mat);
194 }
195};
196
197template <int UpLo>
198struct triangular_adjoint_square_selector<UpLo, false> {
199 template <typename MatrixType>
200 static void run(MatrixType& mat) {
201 Transpose<MatrixType> matt(mat);
202 triangular_adjoint_square_lower(matt);
203 }
204};
205
209template <int UpLo, typename MatrixType>
210void triangular_adjoint_square_in_place(MatrixType& mat) {
211 triangular_adjoint_square_selector<UpLo>::run(mat);
212}
213
214} // end namespace internal
215
216#ifndef EIGEN_PARSED_BY_DOXYGEN
217template <typename MatrixType, unsigned int Mode>
218EIGEN_DEVICE_FUNC void TriangularViewImpl<MatrixType, Mode, Dense>::inverseInPlace() {
219 EIGEN_STATIC_ASSERT_LVALUE(MatrixType)
220 EIGEN_STATIC_ASSERT((int(Mode) & int(Upper | Lower)) != 0 && (int(Mode) & int(ZeroDiag)) == 0, PROGRAMMING_ERROR)
221 eigen_assert(derived().rows() == derived().cols());
222 internal::triangular_inverse_selector<Mode>::run(derived().nestedExpression());
223}
224#endif
225
226} // end namespace Eigen
227
228#endif // EIGEN_TRIANGULARINPLACE_H
@ UnitDiag
Definition Constants.h:216
@ ZeroDiag
Definition Constants.h:218
@ Lower
Definition Constants.h:212
@ Upper
Definition Constants.h:214
@ ColMajor
Definition Constants.h:319
@ RowMajor
Definition Constants.h:321
constexpr unsigned int RowMajorBit
Definition Constants.h:71