Skip to content

Commit 0ee4cac

Browse files
Add LDLt factorization for HMatrix and use it in the local DDM solvers
Bunch-Kaufman LDLt for symmetric (real or complex) and Hermitian matrices, dense and hierarchical, selected by a symmetry of 'S' or 'H' (real 'H' falls back to the symmetric routines). Dense (matrix/linalg/factorization.hpp): - ldlt_factorization/ldlt_solve wrapping sytrf/sytrs and hetrf/hetrs. - triangular_ldlt_matrix_matrix_solve: solve with the unit L/U, Bunch-Kaufman permutations included, transa in N/T/C like trsm. - apply_ldlt_diagonal: D^-1 on the left or right, the 2x2 inverse in sytrs/hetrs's scaled form. A full solve is L^-1, D^-1, L^-T. - Blas::geru/gerc (BLAS has no plain complex ger). HMatrix: - Block-recursive ldlt_factorization storing the unit L in place and D at the dense diagonal leaves: W = A21 L11^-T, L21 = W D1^-1, A22 -= L21 W^T. UPLO='U' eliminates the last cluster first. - ldlt_solve, internal_triangular_ldlt_hmatrix_{hmatrix,matrix,lrmat}_solve and apply_ldlt_diagonal.hpp (keeps the compression of the blocks D is applied to, densifying with a warning only below the dense leaves). Local DDM solvers: - is_positive_definite option (default false) in LocalHMatrixSolver, LocalHMatrixPlusOverlapSolver, make_DDM_solver_w_custom_local_solver and DDMSolverBuilder. Symmetric/Hermitian local HMatrices now use LDLt unless declared positive definite, since Cholesky silently gave a wrong factorization for indefinite matrices. Complex symmetric always uses LDLt. Fixes: - HMatrix cholesky_factorization's symmetry check compared UPLO and could never trigger; LU/Cholesky checks now live in the public functions dispatching on the execution policy. - potrf and sytrf/hetrf failures (info > 0) are now reported. Tests: dense and HMatrix LDLt factorization and triangular solves, including 2x2 pivots, and Test_local_hmatrix_solvers checking both local solvers against the assembled system. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 46f0ad3 commit 0ee4cac

27 files changed

Lines changed: 2089 additions & 81 deletions

‎CHANGELOG.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ All notable changes to this project will be documented in this file.
3030

3131
- JOSS badge and update `CITATION.cff`.
3232
- Opt-in `tidy` CMake target running clang-tidy (`bugprone-*`/`performance-*`) over `examples/` and `tests/`, not part of the default build. Configured via `.clang-tidy`: findings are errors except `branch-clone`, `exception-escape` and `implicit-widening-of-multiplication-result`; the macro-generated `wrapper_blas.hpp`/`wrapper_lapack.hpp` are excluded from findings (needs clang-tidy >= 19). Run in CI on the Ubuntu clang/OpenMPI job.
33+
- LDLt factorization (Bunch-Kaufman) for symmetric, complex-symmetric and Hermitian matrices, selected by a `symmetry` of `'S'` or `'H'`:
34+
- dense: `ldlt_factorization`/`ldlt_solve` (`sytrf`/`sytrs`, `hetrf`/`hetrs`), `triangular_ldlt_matrix_matrix_solve` (unit L/U with `transa` in N/T/C) and `apply_ldlt_diagonal` (D^-1), plus `Blas::geru`/`Blas::gerc` (BLAS has no plain complex `ger`).
35+
- HMatrix: `ldlt_factorization`/`sequential_ldlt_factorization` and `ldlt_solve`, also on a full-storage HMatrix (symmetry `'N'`) like `cholesky_factorization`, with `internal_triangular_ldlt_hmatrix_{hmatrix,matrix,lrmat}_solve` and `internal_apply_ldlt_diagonal`.
36+
- `is_positive_definite` option (default `false`) in `LocalHMatrixSolver`, `LocalHMatrixPlusOverlapSolver`, `make_DDM_solver_w_custom_local_solver` and `DDMSolverBuilder`: a symmetric/Hermitian local HMatrix is factorized with Cholesky only when it is set, and with LDLt otherwise.
3337

3438
### Changed
3539

@@ -39,6 +43,8 @@ All notable changes to this project will be documented in this file.
3943
- Replace `std::endl`/`endl` with `'\n'` across `include/`, `tests/` and `examples/`.
4044
- Fix unnecessary-value-param findings from clang-tidy: move sink parameters, take pure reads by const reference.
4145
- Add explicit `default` cases to `DDM`'s `schwarz_method`/`krylov_method` switches (behavior unchanged).
46+
- Symmetric/Hermitian local HMatrix solvers (`DDMSolverBuilder`) now use LDLt by default instead of Cholesky, which silently gave a wrong factorization for indefinite matrices; pass `is_positive_definite=true` to keep Cholesky.
47+
- Checks of `lu_factorization`/`cholesky_factorization` on HMatrix moved to the public functions taking an execution policy, so they run once for the sequential and task-based implementations.
4248

4349
### Removed
4450

@@ -56,6 +62,9 @@ All notable changes to this project will be documented in this file.
5662
- Fix `Matrix`'s int-overflow risk in allocation sizes (rows*cols computed in `int` then used as a `new[]`/read/write size), and a zero-length-array leak in `resize()`.
5763
- Fix real overflow/mistyping bugs surfaced by clang-tidy's widening/narrowing-conversion checks in `matrix/utils/output.hpp` and `hmatrix_output.hpp`.
5864
- Compute the partition-level power of the number of children as an exact integer instead of via `std::pow` in cluster tree building.
65+
- Fix HMatrix `cholesky_factorization` symmetry check, which compared `UPLO` instead of the symmetry and could never trigger: it now rejects a complex symmetric (non-Hermitian) HMatrix.
66+
- Fix complex-symmetric (non-Hermitian) local HMatrix solvers, which were factorized as Hermitian with Cholesky.
67+
- Report a failed dense `cholesky_factorization` (`potrf`'s `info`), for a matrix that is not positive definite.
5968

6069
## [1.0.2] - 2026-02-14
6170

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#ifndef HTOOL_HMATRIX_LINALG_APPLY_LDLT_DIAGONAL_HPP
2+
#define HTOOL_HMATRIX_LINALG_APPLY_LDLT_DIAGONAL_HPP
3+
4+
#include "../../matrix/linalg/factorization.hpp"
5+
#include "../../matrix/matrix.hpp"
6+
#include "../../misc/logger.hpp"
7+
#include "../hmatrix.hpp"
8+
#include <algorithm>
9+
#include <memory>
10+
#include <vector>
11+
12+
namespace htool {
13+
// B := D^-1 B (side='L') or B D^-1 (side='R'), D being the block-diagonal factor of an LDLt
14+
// factorization held at A's dense diagonal leaves (see apply_ldlt_diagonal). B's rows (side='L')
15+
// or columns (side='R') are numbered from offset.
16+
template <typename CoefficientPrecision, typename CoordinatePrecision>
17+
void internal_apply_ldlt_diagonal(char symmetry, char side, char UPLO, const HMatrix<CoefficientPrecision, CoordinatePrecision> &A, Matrix<CoefficientPrecision> &B, int offset) {
18+
if (A.is_hierarchical()) {
19+
for (auto &child : A.get_children()) {
20+
if (child->get_target_cluster() == child->get_source_cluster()) {
21+
internal_apply_ldlt_diagonal(symmetry, side, UPLO, *child, B, offset);
22+
}
23+
}
24+
} else {
25+
int local_offset = A.get_target_cluster().get_offset() - offset;
26+
int size = A.get_target_cluster().get_size();
27+
if (side == 'R') {
28+
Matrix<CoefficientPrecision> B_columns;
29+
B_columns.assign(B.nb_rows(), size, B.data() + B.nb_rows() * local_offset, false);
30+
apply_ldlt_diagonal(symmetry, 'R', UPLO, *A.get_dense_data(), B_columns);
31+
} else {
32+
Matrix<CoefficientPrecision> B_rows(size, B.nb_cols());
33+
for (int j = 0; j < B.nb_cols(); j++) {
34+
std::copy_n(B.data() + local_offset + j * B.nb_rows(), size, B_rows.data() + j * size);
35+
}
36+
apply_ldlt_diagonal(symmetry, 'L', UPLO, *A.get_dense_data(), B_rows);
37+
for (int j = 0; j < B.nb_cols(); j++) {
38+
std::copy_n(B_rows.data() + j * size, size, B.data() + local_offset + j * B.nb_rows());
39+
}
40+
}
41+
}
42+
}
43+
44+
// Same with an HMatrix B, following B's own block structure so that its blocks keep their compression.
45+
template <typename CoefficientPrecision, typename CoordinatePrecision>
46+
void internal_apply_ldlt_diagonal(char symmetry, char side, char UPLO, const HMatrix<CoefficientPrecision, CoordinatePrecision> &A, HMatrix<CoefficientPrecision, CoordinatePrecision> &B) {
47+
int offset = side == 'L' ? B.get_target_cluster().get_offset() : B.get_source_cluster().get_offset();
48+
if (B.is_dense()) {
49+
internal_apply_ldlt_diagonal(symmetry, side, UPLO, A, *B.get_dense_data(), offset);
50+
} else if (B.is_low_rank()) {
51+
internal_apply_ldlt_diagonal(symmetry, side, UPLO, A, side == 'L' ? B.get_low_rank_data()->get_U() : B.get_low_rank_data()->get_V(), offset);
52+
} else {
53+
std::vector<const HMatrix<CoefficientPrecision, CoordinatePrecision> *> A_children;
54+
for (auto &child : B.get_children()) {
55+
const Cluster<CoordinatePrecision> &cluster = side == 'L' ? child->get_target_cluster() : child->get_source_cluster();
56+
A_children.push_back(A.get_sub_hmatrix(cluster, cluster));
57+
}
58+
if (std::find(A_children.begin(), A_children.end(), nullptr) == A_children.end()) {
59+
for (std::size_t c = 0; c < A_children.size(); c++) {
60+
internal_apply_ldlt_diagonal(symmetry, side, UPLO, *A_children[c], *B.get_children()[c]);
61+
}
62+
return;
63+
}
64+
// B is refined below A's dense leaves, where a 2x2 pivot block of D may straddle B's children.
65+
htool::Logger::get_instance().log(LogLevel::WARNING, "In internal_apply_ldlt_diagonal, B is refined below the dense diagonal leaves holding D and is densified, losing its compression.");
66+
auto B_dense = std::make_unique<Matrix<CoefficientPrecision>>(B.nb_rows(), B.nb_cols());
67+
copy_to_dense(B, B_dense->data());
68+
internal_apply_ldlt_diagonal(symmetry, side, UPLO, A, *B_dense, offset);
69+
B.set_dense_data(std::move(B_dense));
70+
}
71+
}
72+
} // namespace htool
73+
#endif

0 commit comments

Comments
 (0)