| // This file is part of Eigen, a lightweight C++ template library |
| // for linear algebra. |
| // |
| // Copyright (C) 2026 Rasmus Munk Larsen <rmlarsen@gmail.com> |
| // |
| // This Source Code Form is subject to the terms of the Mozilla |
| // Public License v. 2.0. If a copy of the MPL was not distributed |
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| #ifndef EIGEN_TRIANGULARINPLACE_H |
| #define EIGEN_TRIANGULARINPLACE_H |
| |
| // IWYU pragma: private |
| #include "./InternalHeaderCheck.h" |
| |
| namespace Eigen { |
| |
| namespace internal { |
| |
| // In-place counterparts of LAPACK xTRTRI and xLAUUM. Each overwrites its operand; the only scratch is |
| // one block-sized panel, at most 128x128 elements whatever the matrix size, through which the in-place |
| // TRMM steps run Eigen's blocked kernel, the same way optimized BLAS implement xTRMM. |
| |
| // Only the lower forms exist. An upper U runs the lower kernel on Transpose<MatrixType>: with |
| // 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 |
| // lower triangle of the transposed view is the upper triangle of the original in both cases. |
| |
| // Both kernels have the panel-times-trailing-block shape of the Cholesky factorization, so the block |
| // size tracks llt_inplace::blocked(), but each has its own floor and its own size below which the |
| // unblocked form still wins: TRTRI's blocked step is a TRMM plus a TRSM against a bs-wide panel, and |
| // its unblocked form is already a sequence of TRMVs, while LAUUM's is a TRMM plus a HERK and its |
| // unblocked form is a sequence of GEMVs against a strided row. Measured on Zen 4 at n = 32..512. |
| constexpr Index kTriangularInverseMinBlocked = 64; |
| constexpr Index kTriangularInverseMinBlockSize = 24; |
| constexpr Index kAdjointSquareMinBlocked = 128; |
| constexpr Index kAdjointSquareMinBlockSize = 48; |
| |
| EIGEN_DEVICE_FUNC inline Index triangular_in_place_block_size(Index size, Index min_block_size) { |
| const Index block_size = ((size / 8) / 8) * 8; |
| return numext::mini(numext::maxi(block_size, min_block_size), Index(128)); |
| } |
| |
| template <typename MatrixType> |
| struct triangular_in_place_workspace { |
| using type = Matrix<typename MatrixType::Scalar, Dynamic, Dynamic, |
| (int(traits<MatrixType>::Flags) & RowMajorBit) ? RowMajor : ColMajor>; |
| }; |
| |
| // Unblocked lower triangular inverse, columns bottom-up so that the trailing block already holds |
| // X22 = L22^-1 when column j is reached: then X(j+1:, j) = -X22 L(j+1:, j) / L(j, j), one TRMV per |
| // column rather than the chain of AXPYs a forward substitution per column would run. \a tmp is |
| // scratch of at least n - 1 entries, since the TRMV's destination is its own operand. |
| template <unsigned int Mode, typename MatrixType, typename VectorType> |
| EIGEN_DEVICE_FUNC void triangular_inverse_unblocked(MatrixType& mat, VectorType& tmp) { |
| using Scalar = typename MatrixType::Scalar; |
| constexpr bool kUnitDiag = (Mode & UnitDiag) != 0; |
| const Index n = mat.rows(); |
| eigen_internal_assert(n < 2 || tmp.size() >= n - 1); |
| for (Index j = n - 1; j >= 0; --j) { |
| Scalar xj = Scalar(1); |
| EIGEN_IF_CONSTEXPR (!kUnitDiag) { |
| xj = Scalar(1) / mat.coeff(j, j); |
| mat.coeffRef(j, j) = xj; |
| } |
| const Index rs = n - j - 1; |
| if (rs == 0) continue; |
| tmp.head(rs).noalias() = mat.block(j + 1, j + 1, rs, rs).template triangularView<Mode>() * mat.col(j).tail(rs); |
| mat.col(j).tail(rs) = -xj * tmp.head(rs); |
| } |
| } |
| |
| // Blocked lower triangular inverse, block columns bottom-up as in LAPACK xTRTRI. With |
| // L = [[L11, 0], [L21, L22]] the inverse is [[L11^-1, 0], [-L22^-1 L21 L11^-1, L22^-1]]; walking upwards |
| // makes X22 = L22^-1 available first, so the large operation is the TRMM X22 L21 rather than a solve |
| // against L22, and Eigen's TRMM runs closer to GEMM rate than its TRSM does on a 128-column operand. |
| template <unsigned int Mode, typename MatrixType> |
| EIGEN_DEVICE_FUNC void triangular_inverse_lower(MatrixType& mat) { |
| eigen_assert(mat.rows() == mat.cols()); |
| const Index n = mat.rows(); |
| if (n < kTriangularInverseMinBlocked) { |
| using Scalar = typename MatrixType::Scalar; |
| ei_declare_aligned_stack_constructed_variable(Scalar, tmp_data, numext::maxi(n, Index(1)), 0); |
| Map<Matrix<Scalar, Dynamic, 1> > tmp(tmp_data, n); |
| triangular_inverse_unblocked<Mode>(mat, tmp); |
| return; |
| } |
| const Index block_size = triangular_in_place_block_size(n, kTriangularInverseMinBlockSize); |
| // One block-sized panel, whose first column doubles as the TRMV scratch of the unblocked kernel: |
| // that kernel runs on L11 only after the panel's other uses in the same iteration are finished. |
| typename triangular_in_place_workspace<MatrixType>::type work(block_size, block_size); |
| for (Index k = ((n - 1) / block_size) * block_size; k >= 0; k -= block_size) { |
| const Index bs = numext::mini(block_size, n - k); |
| const Index rs = n - k - bs; |
| Block<MatrixType, Dynamic, Dynamic> L11(mat, k, k, bs, bs); |
| if (rs > 0) { |
| Block<MatrixType, Dynamic, Dynamic> L21(mat, k + bs, k, rs, bs); |
| Block<MatrixType, Dynamic, Dynamic> X22(mat, k + bs, k + bs, rs, rs); |
| // L21 <- X22 L21. Row panel r of the product reads only rows <= r of L21, so going upwards |
| // leaves every row a later panel still needs untouched. |
| for (Index r_end = rs; r_end > 0; r_end -= block_size) { |
| const Index r = numext::maxi(Index(0), r_end - block_size); |
| const Index h = r_end - r; |
| work.topLeftCorner(h, bs) = L21.middleRows(r, h); |
| L21.middleRows(r, h).noalias() = |
| X22.block(r, r, h, h).template triangularView<Mode>() * work.topLeftCorner(h, bs); |
| if (r > 0) L21.middleRows(r, h).noalias() += X22.block(r, 0, h, r) * L21.topRows(r); |
| } |
| L11.template triangularView<Mode>().template solveInPlace<OnTheRight>(L21); |
| L21 = -L21; |
| } |
| auto tmp = work.col(0); |
| triangular_inverse_unblocked<Mode>(L11, tmp); |
| } |
| } |
| |
| template <unsigned int Mode, bool IsLower = (int(Mode) & int(Lower)) != 0> |
| struct triangular_inverse_selector { |
| template <typename MatrixType> |
| EIGEN_DEVICE_FUNC static void run(MatrixType& mat) { |
| triangular_inverse_lower<Mode>(mat); |
| } |
| }; |
| |
| template <unsigned int Mode> |
| struct triangular_inverse_selector<Mode, false> { |
| template <typename MatrixType> |
| EIGEN_DEVICE_FUNC static void run(MatrixType& mat) { |
| Transpose<MatrixType> matt(mat); |
| triangular_inverse_lower<(int(Mode) & int(UnitDiag)) | int(Lower)>(matt); |
| } |
| }; |
| |
| // Unblocked lower self-adjoint square: mat <- lower(L^* L). Row i of the result reads column i of L |
| // below the diagonal and rows > i of the columns left of it, none of which row i's own update writes. |
| template <typename MatrixType> |
| void triangular_adjoint_square_unblocked(MatrixType& mat) { |
| using Scalar = typename MatrixType::Scalar; |
| const Index n = mat.rows(); |
| for (Index i = 0; i < n; ++i) { |
| const Scalar lii = mat.coeff(i, i); |
| const Index rs = n - i - 1; |
| mat.coeffRef(i, i) = Scalar(mat.col(i).tail(n - i).squaredNorm()); |
| if (i > 0) { |
| mat.row(i).head(i) *= numext::conj(lii); |
| if (rs > 0) mat.row(i).head(i).noalias() += mat.col(i).tail(rs).adjoint() * mat.bottomLeftCorner(rs, i); |
| } |
| } |
| } |
| |
| // Blocked lower self-adjoint square, left-looking as in LAPACK xLAUUM: block row k of the result is |
| // completed against the columns to its left before the trailing block rows consume the factor. |
| template <typename MatrixType> |
| void triangular_adjoint_square_lower(MatrixType& mat) { |
| eigen_assert(mat.rows() == mat.cols()); |
| const Index n = mat.rows(); |
| if (n < kAdjointSquareMinBlocked) { |
| triangular_adjoint_square_unblocked(mat); |
| return; |
| } |
| const Index block_size = triangular_in_place_block_size(n, kAdjointSquareMinBlockSize); |
| typename triangular_in_place_workspace<MatrixType>::type work(block_size, block_size); |
| for (Index k = 0; k < n; k += block_size) { |
| const Index bs = numext::mini(block_size, n - k); |
| const Index rs = n - k - bs; |
| Block<MatrixType, Dynamic, Dynamic> L11(mat, k, k, bs, bs); |
| if (k > 0) { |
| // 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 |
| // first part is the TRMM L11^* B in place, done by column panels through the workspace. |
| Block<MatrixType, Dynamic, Dynamic> B(mat, k, 0, bs, k); |
| for (Index c = 0; c < k; c += block_size) { |
| const Index w = numext::mini(block_size, k - c); |
| work.topLeftCorner(bs, w) = B.middleCols(c, w); |
| B.middleCols(c, w).noalias() = L11.adjoint().template triangularView<Upper>() * work.topLeftCorner(bs, w); |
| } |
| if (rs > 0) B.noalias() += mat.block(k + bs, k, rs, bs).adjoint() * mat.block(k + bs, 0, rs, k); |
| } |
| triangular_adjoint_square_unblocked(L11); |
| if (rs > 0) L11.template selfadjointView<Lower>().rankUpdate(mat.block(k + bs, k, rs, bs).adjoint()); |
| } |
| } |
| |
| template <int UpLo, bool IsLower = (int(UpLo) & int(Lower)) != 0> |
| struct triangular_adjoint_square_selector { |
| template <typename MatrixType> |
| static void run(MatrixType& mat) { |
| triangular_adjoint_square_lower(mat); |
| } |
| }; |
| |
| template <int UpLo> |
| struct triangular_adjoint_square_selector<UpLo, false> { |
| template <typename MatrixType> |
| static void run(MatrixType& mat) { |
| Transpose<MatrixType> matt(mat); |
| triangular_adjoint_square_lower(matt); |
| } |
| }; |
| |
| /** \internal Replaces the \a UpLo triangle of \a mat, holding a triangular factor T, by the same |
| * triangle of the self-adjoint product T^* T (\c Lower) or T T^* (\c Upper). The opposite triangle is |
| * neither read nor written. This is LAPACK's xLAUUM. */ |
| template <int UpLo, typename MatrixType> |
| void triangular_adjoint_square_in_place(MatrixType& mat) { |
| triangular_adjoint_square_selector<UpLo>::run(mat); |
| } |
| |
| } // end namespace internal |
| |
| #ifndef EIGEN_PARSED_BY_DOXYGEN |
| template <typename MatrixType, unsigned int Mode> |
| EIGEN_DEVICE_FUNC void TriangularViewImpl<MatrixType, Mode, Dense>::inverseInPlace() { |
| EIGEN_STATIC_ASSERT_LVALUE(MatrixType) |
| EIGEN_STATIC_ASSERT((int(Mode) & int(Upper | Lower)) != 0 && (int(Mode) & int(ZeroDiag)) == 0, PROGRAMMING_ERROR) |
| eigen_assert(derived().rows() == derived().cols()); |
| internal::triangular_inverse_selector<Mode>::run(derived().nestedExpression()); |
| } |
| #endif |
| |
| } // end namespace Eigen |
| |
| #endif // EIGEN_TRIANGULARINPLACE_H |