Skip to content

Decouple PDMat's matrix and Cholesky factor types (#237)#238

Merged
devmotion merged 8 commits into
masterfrom
dmw/pdmat
Jun 27, 2026
Merged

Decouple PDMat's matrix and Cholesky factor types (#237)#238
devmotion merged 8 commits into
masterfrom
dmw/pdmat

Conversation

@devmotion

@devmotion devmotion commented Jun 25, 2026

Copy link
Copy Markdown
Member

Note

Builds on #239 (now merged). This PR was stacked on top of it and has been retargeted to master, so the diff shown here is only this PR's own changes.

Why #239 was necessary: decoupling the matrix from the Cholesky factor type means PDMat now propagates the matrix's own type — e.g. a Symmetric wrapper — into .mat, instead of densifying it to a plain Matrix. Addition then forwards .mat straight into _adddiag (e.g. +(a::PDMat, b::PDiagMat) = PDMat(_adddiag(a.mat, b.diag))), which previously was only defined for Union{Matrix, SparseMatrixCSC} and would now hit a MethodError. #239 generalized _adddiag/_scaleadddiag to any AbstractMatrix, so this PR relies on it to keep +/pdadd working for the newly-preserved wrapper types.

Closes #237.

Problem

PDMat{T, S} forced the stored matrix mat::S and its Cholesky factorization chol::Cholesky{T, S} to share one array type S. Constructing a PDMat from a dense matrix and a Cholesky whose factors have a different array type therefore failed, because the matrix was coerced to the factor type:

julia> PDMat(mat, chol)   # chol.factors isa Fill
ERROR: Input array contains both 0.0 and 2.0. Cannot convert to Fill

This broke dense MvNormal/PDMat construction over covariances produced by SparseConnectivityTracer (≥ 1.2.2), which returns Cholesky{T, <:Fill}.

Change

Add a third type parameter C <: Cholesky{T} so the factorization's storage type is independent of the matrix's:

struct PDMat{T <: Real, S <: AbstractMatrix{T}, C <: Cholesky{T}} <: AbstractPDMat{T}
    mat::S
    chol::C
end

The two-argument constructor keeps both arguments as-is when their element types match (a fast path that preserves identity, isbits-ness, and allocation behavior) and promotes them otherwise — instead of the previous lossy "take the element type from chol" coercion.

PDMat{T, S} and PDMat{T} remain valid partial (UnionAll) types, so existing dispatch, construction, conversion, and the deprecations continue to work unchanged.

Behavior change

Because the matrix is no longer coerced to the factor type, PDMat now keeps the matrix's own type. For example PDMat(Symmetric(A)) keeps the Symmetric wrapper (the data is not densified), where previously .mat was the bare underlying matrix. The specialarrays.jl banded-matrix test is updated accordingly.

Tests / misc

  • Regression test pairing a dense matrix with a Fill-factored Cholesky, looping over mat/chol element-type combinations across PDMat, PDMat{T}, and PDMat{T, S}. FillArrays is added as a test dependency.
  • Refreshed a stale doctest repr in generics.jl.

Full test suite passes locally.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.66%. Comparing base (eab0192) to head (676b5be).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #238      +/-   ##
==========================================
- Coverage   94.84%   94.66%   -0.18%     
==========================================
  Files          11       11              
  Lines         814      825      +11     
==========================================
+ Hits          772      781       +9     
- Misses         42       44       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

devmotion and others added 4 commits June 25, 2026 10:42
`+` and `pdadd` of a `PDMat`/`PDiagMat`/`ScalMat` failed when the
underlying storage was a static array (e.g. `SMatrix`/`SVector`,
issue #167), because `_adddiag`/`_adddiag!` were only defined for
`Union{Matrix, SparseMatrixCSC}` and mutated in place — which is
impossible for immutable arrays.

Replace the non-mutating `_adddiag` wrappers with generic
`AbstractMatrix` methods that construct the result (`a + Diagonal(v)`,
`a + v*I`, `a .+ c.*Diagonal(v)`); these match the previous
single-allocation cost for dense matrices, stay sparse for sparse
inputs, and preserve the static container otherwise. Add
`_scaleadddiag` for the matrix-scaled `pdadd` paths (`c*a + diagonal`),
keeping an in-place fast path for mutable storage and falling back to
`muladd` elsewhere. The in-place `_adddiag!` is retained for the truly
in-place `pdadd!`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a third type parameter `C <: Cholesky{T}` to `PDMat` so the stored
matrix and its Cholesky factorization no longer have to share one array
type. This lets one pair, e.g., a dense matrix with a `Cholesky` whose
factors are a `Fill` (as returned by SparseConnectivityTracer), which
previously errored because the matrix was coerced to the factor type.

The two-argument constructor now keeps both arguments as-is when their
element types match and promotes them otherwise, rather than coercing the
matrix to the factor's array type. As a consequence `PDMat` preserves the
matrix's own type (e.g. `PDMat(Symmetric(A))` keeps the `Symmetric` wrapper).

`PDMat{T, S}` and `PDMat{T}` remain valid partial types, so existing
dispatch, construction, and conversion continue to work.

Add a regression test using a `Fill`-factored `Cholesky` (`FillArrays`
becomes a test dependency), relax the brittle Apple-Silicon allocation
checks in `chol.jl` to `skip=`, and refresh a stale doctest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@devmotion devmotion changed the base branch from master to dmw/adddiag June 25, 2026 09:04
@devmotion

Copy link
Copy Markdown
Member Author

I think this PR is ready for review now.

IMO the remaining 3 test failures in the Distributions tests are not practically relevant, they test whether some dense MvNormal distributions are subtypes of the FullMvNormal type aliases which relies on the type parameters of PDMat which arguably are not public API. This doesn't seem to affect anything in practice though apart from less compact printing: https://github.com/search?q=repo%3AJuliaStats%2FDistributions.jl+FullNormal&type=code

PDMat gained a third type parameter `C <: Cholesky{T}` so the Cholesky
factor's storage type is independent of the matrix type, and now stores
`chol::C` (xref #237). Update the documented struct definition to match,
and fix the pre-existing `S<:AbstractMatrix` to `S<:AbstractMatrix{T}`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Base automatically changed from dmw/adddiag to master June 25, 2026 11:21
@devmotion devmotion marked this pull request as ready for review June 25, 2026 11:29
@devmotion devmotion requested a review from andreasnoack June 25, 2026 11:29
@andreasnoack

Copy link
Copy Markdown
Member

Wouldn't it be better to adjust the Distributions tests first to avoid breaking CI there?

@devmotion

Copy link
Copy Markdown
Member Author

Initially I thought it wouldn't be possible (unless we'd remove the tests) but I got an idea, let me check it.

@devmotion

Copy link
Copy Markdown
Member Author

CI shows that the Distributions downstream tests in this PR pass with JuliaStats/Distributions.jl#2073 (for which Distributions tests with the latest PDMats release pass as well)

@devmotion devmotion merged commit a28bff0 into master Jun 27, 2026
17 of 18 checks passed
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.

PDMat cannot pair a dense mat with a structured/lazy Cholesky factor (shared type parameter S)

2 participants