Skip to content

feat: Add Bunch–Kaufman LDL Decomposition#1591

Open
awinick wants to merge 2 commits intodimforge:mainfrom
awinick:ldl-pivot
Open

feat: Add Bunch–Kaufman LDL Decomposition#1591
awinick wants to merge 2 commits intodimforge:mainfrom
awinick:ldl-pivot

Conversation

@awinick
Copy link

@awinick awinick commented Mar 16, 2026

This PR adds an LDL decomposition for Hermitian matrices using the Bunch–Kaufman symmetric pivoting algorithm.

The factorization computed is

Pᵀ A P = L D Lᴴ

where

  • P is a permutation matrix from symmetric pivoting
  • L is unit lower triangular
  • D is Hermitian block diagonal containing 1×1 and 2×2 blocks

This corresponds to the class of algorithms implemented in LAPACK’s *HETRF family.

Implementation notes

The factorization is stored in-place in a single matrix following the standard LAPACK layout:

  • the strictly lower triangle stores L
  • the diagonal and first subdiagonal store D
  • pivot information is stored separately

Tests

The tests are intentionally compact but exercise a wide variety of pivot behaviors.

During development I inspected pivot structures and found that matrices constructed as

A = U D Uᴴ

where U is Haar-random unitary and D has alternating signs consistently generate rich mixtures of 1×1 and 2×2 pivot blocks.

Two spectra are used.

Alternating ±1 spectrum

D = diag(1, -1, 1, -1, ...)

This produces a diverse set of pivot patterns across matrix sizes and verifies

  • reconstruction accuracy
  • determinant correctness
  • linear solve correctness

Alternating geometric spectrum

D = diag(1, -10, 100, -1000, ...)

This stresses the algorithm under extreme scaling, which is particularly important for

  • pivot selection behavior
  • numerical stability
  • determinant accuracy

Empirically this spectrum produces a wide variety of pivot configurations while remaining numerically well-behaved.

Determinant accuracy

During testing it was observed that computing determinants through this LDL factorization produces significantly more accurate real-valued results for Hermitian matrices than the current generic approach.

Because the decomposition explicitly respects Hermitian structure, the determinant is obtained as the product of real block determinants, which reduces complex roundoff artifacts.

Benchmarking

Benchmarks were performed comparing this implementation against LAPACK (zhetrf) and the existing QR decomposition in nalgebra.

All results below correspond to 1000 factorizations.

Size LAPACK zhetrf (Accelerate) LAPACK zhetrf (OpenBLAS) This implementation nalgebra QR
100×100 ~136 ms ~117 ms ~179 ms ~529 ms
200×200 ~590 ms ~877 ms ~1274 ms ~4249 ms

The pure Rust implementation is typically within roughly a factor of two of hardware-accelerated LAPACK. Given that LAPACK implementations rely on highly tuned BLAS kernels and architecture-specific optimizations, this level of performance is reasonable for a portable pure-Rust implementation.

For comparison, QR factorization, the current best available method, is substantially slower. QR does not exploit Hermitian structure and therefore performs significantly more work than an LDL factorization.

@awinick
Copy link
Author

awinick commented Mar 16, 2026

This work was inspired in part by the implementation efforts in #1515 by @ajefweiss.

For the problem class I was working on, I needed clear and reliable handling of indefinite Hermitian matrices, which motivated implementing the classical Bunch–Kaufman LDL factorization.

While there is overlap in goals, this PR focuses specifically on the symmetric-indefinite Hermitian case with explicit pivot handling and block-diagonal D structure.

@geo-ant
Copy link
Collaborator

geo-ant commented Mar 16, 2026

I'll try to take a look soon ish. I've kicked off the checks and they show a few problems in the derive macros. Could you fix them if you have time?

@geo-ant geo-ant self-requested a review March 16, 2026 18:21
Copy link
Collaborator

@geo-ant geo-ant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, I'm sorry that I didn't have time to take a super in-depth look yet. I need to brush up on the Bunch-Kaufman factorization.

Did you use LAPACKS zhetrf function as your reference? If not, what other references should I use to review the implementation. I'm aware of:

Let me know what would be a good reference for me to check your implementation. I see you've added tests which is already fantastic, I'd just like to be able to review your implementation as well.

I've made some comments in the code for some API related things. If you want to take care of them, could you also run cargo fmt on your code so it'll pass the lint?

///
/// This uses the LAPACK `ipiv` convention:
/// a positive entry denotes a 1x1 pivot block, while a repeated negative entry
/// denotes a 2x2 pivot block. The stored pivot indices are 1-based.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like LAPACK as much as the next guy, but to my mind this isn't going to feel intuitive for Rust users.

  • Is there a reason to expose this API at all?
  • If so, would you mind abstracting it into a Pivot structure? This could expose an API for applying P A P^T, either as a function or using lazily overloaded operators depending how overengineered you'd like it to be... 😅
  • I'd much prefer 0-based indices, which is what nalgebra uses everywhere else.

///
/// This follows the LAPACK convention and is therefore 1-based.
/// A value of `None` means no zero pivot was detected.
#[inline]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as above, this could better be abstracted into a Pivot structure, if it's needed at all...

///
/// P A P^T = L * D * L^H
///
/// where L is a product of permutation and unit lower triangular matrices, U^H is the
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where L is a product of permutation and unit lower triangular matrices, U^H is the

I think this is copied from LAPACK's ?{he,sy}trf family of functions, but lapack uses the form A = L D L^H, whereas you give P A P^T = L D L^H. In your case L should indeed be unit lower triangular, right?

self.zero_pivot
}

/// The permutation-aware factor of this decomposition.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this essentially P^T L? If so could you just state that in the comment?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants