Repository navigation
Conversation
None of these packages is part of the sysimage anymore, so loading them unconditionally slows down loading ChainRules for all dependents. Their rules only apply once the respective package is loaded anyway. This commit only moves the rules; SparseInverseSubset (which loads SparseArrays) is still a dependency and is removed in the next commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rrules for `det`, `logdet` and `logabsdet` of `SparseMatrixCSC` only used SparseInverseSubset for the entries of the inverse on the sparsity pattern of the Cholesky factor. Its implementation scans all rows below the diagonal with sparse indexing for each entry; computing Takahashi's recurrence directly on the pattern of the factor is about 100x faster for n = 10^4 and removes a dependency that loads SparseArrays. Additionally: - factorize once in the forward pass and reuse the factorization in the pullback (previously `logdet` and `det` used an LU in the forward pass and a Cholesky factorization in the pullback) - restrict the cotangent to the sparsity pattern of the input - support matrices whose stored pattern is asymmetric due to explicit zeros - do not throw in the forward pass of `logabsdet` for matrices that are not positive definite; pullbacks of non-positive-definite inputs throw an informative `ArgumentError` - test that loading ChainRules does not load SparseArrays, Statistics or Distributed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Random rules are self-contained as well. Random is currently part of the sysimage and loaded at startup, so the extension is loaded together with ChainRules and the change is not observable; it avoids loading Random should that change in the future. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
||
| import ChainRulesCore: rrule | ||
|
|
||
| function rrule(::typeof(sparse), I::AbstractVector, J::AbstractVector, V::AbstractVector, m, n, combine::typeof(+)) |
There was a problem hiding this comment.
[JuliaFormatter] reported by reviewdog 🐶
| function rrule(::typeof(sparse), I::AbstractVector, J::AbstractVector, V::AbstractVector, m, n, combine::typeof(+)) | |
| function rrule( | |
| ::typeof(sparse), | |
| I::AbstractVector, | |
| J::AbstractVector, | |
| V::AbstractVector, | |
| m, | |
| n, | |
| combine::typeof(+), | |
| ) |
|
|
||
| function rrule(::typeof(sparse), I::AbstractVector, J::AbstractVector, V::AbstractVector, m, n, combine::typeof(+)) | ||
| project_V = ProjectTo(V) | ||
|
|
There was a problem hiding this comment.
[JuliaFormatter] reported by reviewdog 🐶
| function sparse_pullback(Ω̄) | ||
| ΔΩ = unthunk(Ω̄) | ||
| ΔV = project_V(ΔΩ[I .+ m .* (J .- 1)]) | ||
| return NoTangent(), NoTangent(), NoTangent(), ΔV, NoTangent(), NoTangent(), NoTangent() |
There was a problem hiding this comment.
[JuliaFormatter] reported by reviewdog 🐶
| return NoTangent(), NoTangent(), NoTangent(), ΔV, NoTangent(), NoTangent(), NoTangent() | |
| return NoTangent(), | |
| NoTangent(), NoTangent(), ΔV, NoTangent(), NoTangent(), | |
| NoTangent() |
| return sparse(I, J, V, m, n, combine), sparse_pullback | ||
| end | ||
|
|
||
| function rrule(::Type{T}, A::AbstractMatrix) where T <: AbstractSparseMatrix |
There was a problem hiding this comment.
[JuliaFormatter] reported by reviewdog 🐶
| function rrule(::Type{T}, A::AbstractMatrix) where T <: AbstractSparseMatrix | |
| function rrule(::Type{T}, A::AbstractMatrix) where {T<:AbstractSparseMatrix} |
| return T(A), sparse_pullback | ||
| end | ||
|
|
||
| function rrule(::Type{T}, v::AbstractVector) where T <: AbstractSparseVector |
There was a problem hiding this comment.
[JuliaFormatter] reported by reviewdog 🐶
| function rrule(::Type{T}, v::AbstractVector) where T <: AbstractSparseVector | |
| function rrule(::Type{T}, v::AbstractVector) where {T<:AbstractSparseVector} |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #852 +/- ##
==========================================
+ Coverage 96.57% 96.64% +0.06%
==========================================
Files 28 28
Lines 3618 3692 +74
==========================================
+ Hits 3494 3568 +74
Misses 124 124 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
On recent SparseArrays, CHOLMOD supports single precision, so the rrules for
`det`, `logdet` and `logabsdet` of a `SparseMatrixCSC{Float32}` returned
`Float32` primals (and single-precision gradients) whereas the primals,
computed with UMFPACK, are `Float64`.
Also reuse the unit vector for columns outside of the pattern of the
Cholesky factor, and add tests for complex Hermitian and `Int`/`Float32`
matrices.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| success || throw(ArgumentError( | ||
| "rrules for `det`, `logdet` and `logabsdet` of a `SparseMatrixCSC` only support " * | ||
| "Hermitian positive definite matrices" | ||
| )) |
There was a problem hiding this comment.
[JuliaFormatter] reported by reviewdog 🐶
| success || throw(ArgumentError( | |
| "rrules for `det`, `logdet` and `logabsdet` of a `SparseMatrixCSC` only support " * | |
| "Hermitian positive definite matrices" | |
| )) | |
| success || throw( | |
| ArgumentError( | |
| "rrules for `det`, `logdet` and `logabsdet` of a `SparseMatrixCSC` only support " * | |
| "Hermitian positive definite matrices", | |
| ), | |
| ) |
| end | ||
| end | ||
| end | ||
| return SparseMatrixCSC(size(x)..., copy(SparseArrays.getcolptr(x)), copy(xrows), vals) |
There was a problem hiding this comment.
[JuliaFormatter] reported by reviewdog 🐶
| return SparseMatrixCSC(size(x)..., copy(SparseArrays.getcolptr(x)), copy(xrows), vals) | |
| return SparseMatrixCSC( | |
| size(x)..., copy(SparseArrays.getcolptr(x)), copy(xrows), vals | |
| ) |
| end | ||
| return Ω, det_pullback | ||
| end | ||
|
|
There was a problem hiding this comment.
[JuliaFormatter] reported by reviewdog 🐶
| function rrule(::typeof(spdiagm), m::Integer, n::Integer, kv::Pair{<:Integer,<:AbstractVector}...) | ||
|
|
There was a problem hiding this comment.
[JuliaFormatter] reported by reviewdog 🐶
| function rrule(::typeof(spdiagm), m::Integer, n::Integer, kv::Pair{<:Integer,<:AbstractVector}...) | |
| function rrule( | |
| ::typeof(spdiagm), m::Integer, n::Integer, kv::Pair{<:Integer,<:AbstractVector}... | |
| ) |
| A = sparse([1, 3, 4, 2, 3, 4, 1, 2, 3, 1, 2, 4], [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4], | ||
| [1.0, 1, 1, 1, 1, -1, 1, 1, 3, 1, -1, 3]) |
There was a problem hiding this comment.
[JuliaFormatter] reported by reviewdog 🐶
| A = sparse([1, 3, 4, 2, 3, 4, 1, 2, 3, 1, 2, 4], [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4], | |
| [1.0, 1, 1, 1, 1, -1, 1, 1, 3, 1, -1, 3]) | |
| A = sparse( | |
| [1, 3, 4, 2, 3, 4, 1, 2, 3, 1, 2, 4], | |
| [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4], | |
| [1.0, 1, 1, 1, 1, -1, 1, 1, 3, 1, -1, 3], | |
| ) |
…pe directly Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SparseArrays, Statistics and Distributed are no longer part of the sysimage, but ChainRules loads all of them (SparseArrays also through SparseInverseSubset) for every package that depends on it. Their rules only apply once the respective package is loaded anyway, so this PR moves every rule set that can be disentangled to a package extension:
SparseArraysExt,StatisticsExt,DistributedExtandRandomExt. LinearAlgebra, StructArrays, Adapt and GPUArraysCore stay hard dependencies since they are used throughout the other rules and the broadcasting machinery.The PR consists of three commits, best reviewed separately:
det/logdet/logabsdetofSparseMatrixCSConly used SparseInverseSubset for the entries of the inverse on the pattern of the Cholesky factor. Its implementation scans all rows below the diagonal with sparse indexing for each entry. Computing Takahashi's recurrence directly on the pattern of the factor is about 100× faster (n = 10⁴,B'B + IwithB = sprandn(n, n, 3/n): 23 s vs 2470 s, max. difference 5e-15) and removes the dependency. Additionally:logdetanddetused an LU in the forward pass and a Cholesky factorization in the pullback)L + L')choleskythrows for these unless wrapped inHermitian)logabsdetno longer throws for matrices that are not positive definite; pullbacks for non-positive-definite inputs throw an informativeArgumentErrorRandomExtis loaded together with ChainRules (checked on Julia 1.10 and 1.13, withoutusing Random) and the change is not observable today; it avoids loading Random should that change.Tests: comparisons of the selected inverse with the dense inverse (real SPD, complex Hermitian, explicit zeros, exact cancellation in the fill-in),
test_rruleon a tridiagonal matrix with dense inverse, non-positive-definite inputs, and a check thatusing ChainRulesdoes not load SparseArrays, Statistics or Distributed.Notes:
detis now computed asexp(logdet(F))(asdetof a CHOLMOD factorization does), with relative error growing with|logdet|(at most ~1.6e-13 before overflow).🤖 Generated with Claude Code