blob: d886e159d64123dbb1ec088c6c7d80c5d5642524 [file]
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2008 Gael Guennebaud <gael.guennebaud@inria.fr>
//
// 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_SPARSETRIANGULARSOLVER_H
#define EIGEN_SPARSETRIANGULARSOLVER_H
// IWYU pragma: private
#include "./InternalHeaderCheck.h"
namespace Eigen {
namespace internal {
template <typename Lhs, typename Rhs, int Mode,
int UpLo = (Mode & Lower) ? Lower
: (Mode & Upper) ? Upper
: -1,
int StorageOrder = int(traits<Lhs>::Flags) & RowMajorBit>
struct sparse_solve_triangular_selector;
// forward substitution, row-major
template <typename Lhs, typename Rhs, int Mode>
struct sparse_solve_triangular_selector<Lhs, Rhs, Mode, Lower, RowMajor> {
using Scalar = typename Rhs::Scalar;
using LhsEval = evaluator<Lhs>;
using LhsIterator = typename evaluator<Lhs>::InnerIterator;
static void run(const Lhs& lhs, Rhs& other) {
LhsEval lhsEval(lhs);
for (Index col = 0; col < other.cols(); ++col) {
for (Index i = 0; i < lhs.rows(); ++i) {
Scalar tmp = other.coeff(i, col);
Scalar lastVal(0);
Index lastIndex = 0;
for (LhsIterator it(lhsEval, i); it; ++it) {
lastVal = it.value();
lastIndex = it.index();
if (lastIndex == i) break;
tmp = numext::madd<Scalar>(-lastVal, other.coeff(lastIndex, col), tmp);
}
EIGEN_IF_CONSTEXPR (Mode & UnitDiag)
other.coeffRef(i, col) = tmp;
else {
eigen_assert(lastIndex == i);
other.coeffRef(i, col) = tmp / lastVal;
}
}
}
}
};
// backward substitution, row-major
template <typename Lhs, typename Rhs, int Mode>
struct sparse_solve_triangular_selector<Lhs, Rhs, Mode, Upper, RowMajor> {
using Scalar = typename Rhs::Scalar;
using LhsEval = evaluator<Lhs>;
using LhsIterator = typename evaluator<Lhs>::InnerIterator;
static void run(const Lhs& lhs, Rhs& other) {
LhsEval lhsEval(lhs);
for (Index col = 0; col < other.cols(); ++col) {
for (Index i = lhs.rows() - 1; i >= 0; --i) {
Scalar tmp = other.coeff(i, col);
Scalar l_ii(0);
LhsIterator it(lhsEval, i);
while (it && it.index() < i) ++it;
EIGEN_IF_CONSTEXPR (!(Mode & UnitDiag)) {
eigen_assert(it && it.index() == i);
l_ii = it.value();
++it;
} else if (it && it.index() == i)
++it;
for (; it; ++it) {
tmp = numext::madd<Scalar>(-it.value(), other.coeff(it.index(), col), tmp);
}
EIGEN_IF_CONSTEXPR (Mode & UnitDiag)
other.coeffRef(i, col) = tmp;
else
other.coeffRef(i, col) = tmp / l_ii;
}
}
}
};
// forward substitution, col-major
template <typename Lhs, typename Rhs, int Mode>
struct sparse_solve_triangular_selector<Lhs, Rhs, Mode, Lower, ColMajor> {
using Scalar = typename Rhs::Scalar;
using LhsEval = evaluator<Lhs>;
using LhsIterator = typename evaluator<Lhs>::InnerIterator;
static void run(const Lhs& lhs, Rhs& other) {
LhsEval lhsEval(lhs);
for (Index col = 0; col < other.cols(); ++col) {
for (Index i = 0; i < lhs.cols(); ++i) {
Scalar& tmp = other.coeffRef(i, col);
if (!numext::is_exactly_zero(tmp)) // optimization when other is actually sparse
{
LhsIterator it(lhsEval, i);
while (it && it.index() < i) ++it;
EIGEN_IF_CONSTEXPR (!(Mode & UnitDiag)) {
eigen_assert(it && it.index() == i);
tmp /= it.value();
}
if (it && it.index() == i) ++it;
for (; it; ++it) {
other.coeffRef(it.index(), col) = numext::madd<Scalar>(-tmp, it.value(), other.coeffRef(it.index(), col));
}
}
}
}
}
};
// backward substitution, col-major
template <typename Lhs, typename Rhs, int Mode>
struct sparse_solve_triangular_selector<Lhs, Rhs, Mode, Upper, ColMajor> {
using Scalar = typename Rhs::Scalar;
using LhsEval = evaluator<Lhs>;
using LhsIterator = typename evaluator<Lhs>::InnerIterator;
static void run(const Lhs& lhs, Rhs& other) {
LhsEval lhsEval(lhs);
for (Index col = 0; col < other.cols(); ++col) {
for (Index i = lhs.cols() - 1; i >= 0; --i) {
Scalar& tmp = other.coeffRef(i, col);
if (!numext::is_exactly_zero(tmp)) // optimization when other is actually sparse
{
EIGEN_IF_CONSTEXPR (!(Mode & UnitDiag)) {
// TODO: replace this with a binary search. make sure the binary search is safe for partially sorted
// elements
LhsIterator it(lhsEval, i);
while (it && it.index() != i) ++it;
eigen_assert(it && it.index() == i);
other.coeffRef(i, col) /= it.value();
}
LhsIterator it(lhsEval, i);
for (; it && it.index() < i; ++it) {
other.coeffRef(it.index(), col) = numext::madd<Scalar>(-tmp, it.value(), other.coeffRef(it.index(), col));
}
}
}
}
}
};
} // end namespace internal
#ifndef EIGEN_PARSED_BY_DOXYGEN
template <typename ExpressionType, unsigned int Mode>
template <typename OtherDerived>
void TriangularViewImpl<ExpressionType, Mode, Sparse>::solveInPlace(MatrixBase<OtherDerived>& other) const {
eigen_assert(derived().cols() == derived().rows() && derived().cols() == other.rows());
eigen_assert((!(Mode & ZeroDiag)) && bool(Mode & (Upper | Lower)));
enum { copy = internal::traits<OtherDerived>::Flags & RowMajorBit };
using OtherCopy =
std::conditional_t<copy, typename internal::plain_matrix_type_column_major<OtherDerived>::type, OtherDerived&>;
OtherCopy otherCopy(other.derived());
internal::sparse_solve_triangular_selector<ExpressionType, std::remove_reference_t<OtherCopy>, Mode>::run(
derived().nestedExpression(), otherCopy);
if (copy) other = otherCopy;
}
#endif
// pure sparse path
namespace internal {
template <typename Lhs, typename Rhs, int Mode,
int UpLo = (Mode & Lower) ? Lower
: (Mode & Upper) ? Upper
: -1,
int StorageOrder = int(Lhs::Flags) & RowMajorBit>
struct sparse_solve_triangular_sparse_selector;
// True when the rhs exposes raw CSC storage with a StorageIndex matching the lhs, so a
// column's stored index slice can serve as the reach roots directly (no bIdx copy). A
// SparseVector qualifies too -- it is a single compressed column, handled below via the
// null-outerIndexPtr guard (its outerIndexPtr() is null since it has no outer array).
template <typename Lhs, typename Rhs>
using rhs_matching_slice = std::integral_constant<
bool, has_compressed_access<Rhs>::value &&
std::is_same<typename traits<Rhs>::StorageIndex, typename traits<Lhs>::StorageIndex>::value>;
// The reach for a column arrives in one of three compile-time-known orders, but the
// output column must store ascending inner index. reach_reorder encapsulates the
// per-order fix-up via partial specialization: an unordered reach (Ordered == false,
// the pointer/DFS path -- Upper irrelevant) is sorted; the iterator reach is already
// in solve order -- ascending for lower (no-op), descending for upper (reverse).
template <bool Ordered, bool Upper, typename StorageIndex>
struct reach_reorder { // Ordered == false: unordered pointer/DFS reach
static void run(StorageIndex* first, StorageIndex* last) { std::sort(first, last); }
};
template <typename StorageIndex>
struct reach_reorder<true, false, StorageIndex> { // iterator reach, lower: already ascending
static void run(StorageIndex* /*first*/, StorageIndex* /*last*/) {}
};
template <typename StorageIndex>
struct reach_reorder<true, true, StorageIndex> { // iterator reach, upper: descending
static void run(StorageIndex* first, StorageIndex* last) { std::reverse(first, last); }
};
// Common per-column finish: reorder the reach xi[top..n) to ascending inner index,
// insert reading values from xwork, and clear xwork and mark for the next column.
// The reach is a structural bound, not a numeric one: a reached coefficient can be
// exactly zero (a zero rhs entry, or numerical cancellation), so skip exact zeros at
// insertion. This matches the AmbiVector path, which pruned zeros, and keeps a zero rhs
// from materializing O(|reach|) stored zeros. xwork and mark are cleared regardless.
template <bool Ordered, bool Upper, typename Res, typename StorageIndex, typename Scalar>
void reach_insert_column(Res& res, Index col, StorageIndex* xi, Index top, Index n, Scalar* xwork, uint8_t* mark) {
reach_reorder<Ordered, Upper, StorageIndex>::run(xi + top, xi + n);
for (Index k = top; k < n; ++k) {
StorageIndex j = xi[k];
if (!numext::is_exactly_zero(xwork[j])) res.insert(j, col) = xwork[j];
xwork[j] = Scalar(0);
mark[j] = 0;
}
}
// Column loop, fast path: the rhs matches (see rhs_matching_slice), so each column's
// stored index slice is the reach root list and the value slice is scattered directly
// -- no bIdx copy, so iwork is just 2n (xi | pstack).
template <bool Upper, bool UnitDiag, typename Lhs, typename Rhs, typename Res, typename Scalar,
std::enable_if_t<rhs_matching_slice<Lhs, Rhs>::value, int> = 0>
void reach_solve_columns(const Lhs& lhs, const Rhs& other, Res& res, uint8_t* mark, Scalar* xwork, Index n) {
using StorageIndex = typename traits<Lhs>::StorageIndex;
Matrix<StorageIndex, Dynamic, 1> iwork(2 * n); // xi | pstack
StorageIndex* xi = iwork.data();
for (Index col = 0; col < other.cols(); ++col) {
const StorageIndex* outer = other.outerIndexPtr(); // null for a SparseVector (single column)
const StorageIndex* nnz = other.innerNonZeroPtr(); // null when compressed
Index p = outer ? outer[col] : 0;
Index bCount = outer ? (nnz ? Index(nnz[col]) : Index(outer[col + 1]) - p) : other.nonZeros();
const StorageIndex* roots = other.innerIndexPtr() + p; // the column's stored indices
const Scalar* vals = other.valuePtr() + p;
// Roots are the rhs slice directly (no bIdx copy, so iwork stays 2n). An exact-zero
// stored rhs entry is seeded harmlessly -- it propagates zeros and is dropped at
// insertion; filtering it here would cost a compacted root buffer (the 3n path).
for (Index r = 0; r < bCount; ++r) xwork[roots[r]] = vals[r];
Index top = reach_solve_dense<Upper, UnitDiag>(lhs, roots, bCount, xi, mark, xwork);
reach_insert_column<!has_compressed_access<Lhs>::value, Upper>(res, col, xi, top, n, xwork, mark);
}
}
// Column loop, fallback: read each column through the InnerIterator, copying indices
// into the bIdx third of a 3n iwork. For a rhs without raw storage or with a
// mismatched index type.
template <bool Upper, bool UnitDiag, typename Lhs, typename Rhs, typename Res, typename Scalar,
std::enable_if_t<!rhs_matching_slice<Lhs, Rhs>::value, int> = 0>
void reach_solve_columns(const Lhs& lhs, const Rhs& other, Res& res, uint8_t* mark, Scalar* xwork, Index n) {
using StorageIndex = typename traits<Lhs>::StorageIndex;
Matrix<StorageIndex, Dynamic, 1> iwork(3 * n); // xi | pstack | bIdx
StorageIndex* xi = iwork.data();
StorageIndex* bIdx = iwork.data() + 2 * n;
for (Index col = 0; col < other.cols(); ++col) {
Index bCount = 0;
for (typename Rhs::InnerIterator it(other, col); it; ++it) {
if (numext::is_exactly_zero(it.value())) continue; // a zero root seeds nothing; xwork stays clear there
bIdx[bCount] = StorageIndex(it.index());
xwork[it.index()] = it.value();
++bCount;
}
Index top = reach_solve_dense<Upper, UnitDiag>(lhs, bIdx, bCount, xi, mark, xwork);
reach_insert_column<!has_compressed_access<Lhs>::value, Upper>(res, col, xi, top, n, xwork, mark);
}
}
// Reach-based (Gilbert-Peierls) sparse triangular solve, col-major, for lower OR
// upper. Only the columns reachable from each rhs column's pattern are touched, so
// the cost is O(|reach| + flops) per column instead of a dense O(n)-per-column sweep
// (which also pays a coeff(i,i) binary search per row in the upper case). It is
// the sole col-major sparse-sparse selector, dispatching lower/upper via the UpLo
// template argument. reach_solve_dense leaves the solution values in
// xwork and the reached indices in iwork[top..n); reach_solve_columns (slice or
// fallback, selected on the rhs storage) scatters each column and solves, and
// reach_insert_column reads the values out and restores mark/xwork. Only mark and
// xwork need zeroing -- iwork is entirely written before read.
template <bool Upper, typename Lhs, typename Rhs, int Mode>
void run_sparse_reach_triangular_solve(const Lhs& lhs, Rhs& other) {
using Scalar = typename Rhs::Scalar;
Index n = lhs.rows();
Matrix<uint8_t, Dynamic, 1> mark = Matrix<uint8_t, Dynamic, 1>::Zero(n);
Matrix<Scalar, Dynamic, 1> xwork = Matrix<Scalar, Dynamic, 1>::Zero(n);
Rhs res(other.rows(), other.cols());
res.reserve(other.nonZeros());
reach_solve_columns<Upper, bool(Mode & UnitDiag)>(lhs, other, res, mark.data(), xwork.data(), n);
res.finalize();
other = res.markAsRValue();
}
// forward and backward substitution, col-major
template <typename Lhs, typename Rhs, int Mode, int UpLo>
struct sparse_solve_triangular_sparse_selector<Lhs, Rhs, Mode, UpLo, ColMajor> {
static void run(const Lhs& lhs, Rhs& other) {
run_sparse_reach_triangular_solve<UpLo == Upper, Lhs, Rhs, Mode>(lhs, other);
}
};
} // end namespace internal
#ifndef EIGEN_PARSED_BY_DOXYGEN
template <typename ExpressionType, unsigned int Mode>
template <typename OtherDerived>
void TriangularViewImpl<ExpressionType, Mode, Sparse>::solveInPlace(SparseMatrixBase<OtherDerived>& other) const {
eigen_assert(derived().cols() == derived().rows() && derived().cols() == other.rows());
eigen_assert((!(Mode & ZeroDiag)) && bool(Mode & (Upper | Lower)));
internal::sparse_solve_triangular_sparse_selector<ExpressionType, OtherDerived, Mode>::run(
derived().nestedExpression(), other.derived());
}
#endif
} // end namespace Eigen
#endif // EIGEN_SPARSETRIANGULARSOLVER_H