Skip to content

Commit 001a75f

Browse files
Merge pull request #277 from avik-pal/ap/approx_sparsity
Path to use FiniteDiff for ApproximateSparsityDetection
2 parents 84de29e + b6553b8 commit 001a75f

File tree

6 files changed

+82
-16
lines changed

6 files changed

+82
-16
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "SparseDiffTools"
22
uuid = "47a9eef4-7e08-11e9-0b38-333d64bd3804"
33
authors = ["Pankaj Mishra <[email protected]>", "Chris Rackauckas <[email protected]>"]
4-
version = "2.14.0"
4+
version = "2.15.0"
55

66
[deps]
77
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"

ext/SparseDiffToolsEnzymeExt.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
module SparseDiffToolsEnzymeExt
22

33
import ArrayInterface: fast_scalar_indexing
4-
import SparseDiffTools: __f̂,
5-
__maybe_copy_x, __jacobian!, __gradient, __gradient!, AutoSparseEnzyme
4+
import SparseDiffTools: __f̂, __maybe_copy_x, __jacobian!, __gradient, __gradient!,
5+
AutoSparseEnzyme, __test_backend_loaded
66
# FIXME: For Enzyme we currently assume reverse mode
77
import ADTypes: AutoEnzyme
88
using Enzyme
99

1010
using ForwardDiff
1111

12+
@inline __test_backend_loaded(::Union{AutoSparseEnzyme, AutoEnzyme}) = nothing
13+
1214
## Satisfying High-Level Interface for Sparse Jacobians
1315
function __gradient(::Union{AutoSparseEnzyme, AutoEnzyme}, f, x, cols)
1416
dx = zero(x)

ext/SparseDiffToolsZygoteExt.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module SparseDiffToolsZygoteExt
22

33
using ADTypes, LinearAlgebra, Zygote
4-
import SparseDiffTools: SparseDiffTools, DeivVecTag, AutoDiffVJP
4+
import SparseDiffTools: SparseDiffTools, DeivVecTag, AutoDiffVJP, __test_backend_loaded
55
import ForwardDiff: ForwardDiff, Dual, partials
66
import SciMLOperators: update_coefficients, update_coefficients!
77
import Setfield: @set!
@@ -12,6 +12,8 @@ import SparseDiffTools: numback_hesvec!,
1212
import SparseDiffTools: __f̂, __jacobian!, __gradient, __gradient!
1313
import ADTypes: AutoZygote, AutoSparseZygote
1414

15+
@inline __test_backend_loaded(::Union{AutoSparseZygote, AutoZygote}) = nothing
16+
1517
## Satisfying High-Level Interface for Sparse Jacobians
1618
function __gradient(::Union{AutoSparseZygote, AutoZygote}, f::F, x, cols) where {F}
1719
_, ∂x, _ = Zygote.gradient(__f̂, f, x, cols)

src/highlevel/coloring.jl

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,28 +34,72 @@ end
3434
## Right now we hardcode it to use `ForwardDiff`
3535
function (alg::ApproximateJacobianSparsity)(ad::AbstractSparseADType, f::F, x; fx = nothing,
3636
kwargs...) where {F}
37+
if !(ad isa AutoSparseForwardDiff)
38+
@warn "$(ad) support for approximate jacobian not implemented. Using ForwardDiff instead." maxlog=1
39+
end
3740
@unpack ntrials, rng = alg
3841
fx = fx === nothing ? f(x) : fx
39-
J = fill!(similar(fx, length(fx), length(x)), 0)
4042
cfg = ForwardDiff.JacobianConfig(f, x)
43+
J = fill!(similar(fx, length(fx), length(x)), 0)
44+
J_cache = similar(J)
45+
x_ = similar(x)
46+
for _ in 1:ntrials
47+
randn!(rng, x_)
48+
ForwardDiff.jacobian!(J_cache, f, x_, cfg)
49+
@. J += abs(J_cache)
50+
end
51+
return (JacPrototypeSparsityDetection(; jac_prototype = sparse(J), alg.alg))(ad, f, x;
52+
fx, kwargs...)
53+
end
54+
55+
function (alg::ApproximateJacobianSparsity)(ad::AbstractSparseADType, f::F, fx, x;
56+
kwargs...) where {F}
57+
if !(ad isa AutoSparseForwardDiff)
58+
@warn "$(ad) support for approximate jacobian not implemented. Using ForwardDiff instead." maxlog=1
59+
end
60+
@unpack ntrials, rng = alg
61+
cfg = ForwardDiff.JacobianConfig(f, fx, x)
62+
J = fill!(similar(fx, length(fx), length(x)), 0)
63+
J_cache = similar(J)
64+
x_ = similar(x)
65+
for _ in 1:ntrials
66+
randn!(rng, x_)
67+
ForwardDiff.jacobian!(J_cache, f, fx, x_, cfg)
68+
@. J += abs(J_cache)
69+
end
70+
return (JacPrototypeSparsityDetection(; jac_prototype = sparse(J), alg.alg))(ad, f, x;
71+
fx, kwargs...)
72+
end
73+
74+
function (alg::ApproximateJacobianSparsity)(ad::AutoSparseFiniteDiff, f::F, x; fx = nothing,
75+
kwargs...) where {F}
76+
@unpack ntrials, rng = alg
77+
fx = fx === nothing ? f(x) : fx
78+
cache = FiniteDiff.JacobianCache(x, fx)
79+
J = fill!(similar(fx, length(fx), length(x)), 0)
80+
x_ = similar(x)
81+
ε = ifelse(alg.epsilon === nothing, eps(eltype(x)) * 100, alg.epsilon)
4182
for _ in 1:ntrials
42-
x_ = similar(x)
4383
randn!(rng, x_)
44-
J .+= abs.(ForwardDiff.jacobian(f, x_, cfg))
84+
J_cache = FiniteDiff.finite_difference_jacobian(f, x, cache)
85+
@. J += (abs(J_cache) .≥ ε) # hedge against numerical issues
4586
end
4687
return (JacPrototypeSparsityDetection(; jac_prototype = sparse(J), alg.alg))(ad, f, x;
4788
fx, kwargs...)
4889
end
4990

50-
function (alg::ApproximateJacobianSparsity)(ad::AbstractSparseADType, f!::F, fx, x;
91+
function (alg::ApproximateJacobianSparsity)(ad::AutoSparseFiniteDiff, f!::F, fx, x;
5192
kwargs...) where {F}
5293
@unpack ntrials, rng = alg
53-
cfg = ForwardDiff.JacobianConfig(f!, fx, x)
94+
cache = FiniteDiff.JacobianCache(x, fx)
5495
J = fill!(similar(fx, length(fx), length(x)), 0)
96+
J_cache = similar(J)
97+
x_ = similar(x)
98+
ε = ifelse(alg.epsilon === nothing, eps(eltype(x)) * 100, alg.epsilon)
5599
for _ in 1:ntrials
56-
x_ = similar(x)
57100
randn!(rng, x_)
58-
J .+= abs.(ForwardDiff.jacobian(f!, fx, x_, cfg))
101+
FiniteDiff.finite_difference_jacobian!(J_cache, f!, x_, cache)
102+
@. J += (abs(J_cache) .≥ ε) # hedge against numerical issues
59103
end
60104
return (JacPrototypeSparsityDetection(; jac_prototype = sparse(J), alg.alg))(ad, f!, fx,
61105
x; kwargs...)

src/highlevel/common.jl

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ end
114114

115115
"""
116116
ApproximateJacobianSparsity(; ntrials = 5, rng = Random.default_rng(),
117-
alg = GreedyD1Color())
117+
epsilon = nothing, alg = GreedyD1Color())
118118
119119
Use `ntrials` random vectors to compute the sparsity pattern of the Jacobian. This is an
120120
approximate method and the sparsity pattern may not be exact.
@@ -124,17 +124,21 @@ approximate method and the sparsity pattern may not be exact.
124124
- `ntrials`: The number of random vectors to use for computing the sparsity pattern
125125
- `rng`: The random number generator used for generating the random vectors
126126
- `alg`: The algorithm used for computing the matrix colors
127+
- `epsilon`: For Finite Differencing based Jacobian Approximations, any number smaller
128+
than `epsilon` is considered to be zero. If `nothing` is specified, then this value
129+
is calculated as `100 * eps(eltype(x))`
127130
"""
128-
struct ApproximateJacobianSparsity{R <: AbstractRNG,
129-
A <: ArrayInterface.ColoringAlgorithm} <: AbstractSparsityDetection
131+
struct ApproximateJacobianSparsity{R <: AbstractRNG, A <: ArrayInterface.ColoringAlgorithm,
132+
E} <: AbstractSparsityDetection
130133
ntrials::Int
131134
rng::R
132135
alg::A
136+
epsilon::E
133137
end
134138

135-
function ApproximateJacobianSparsity(; ntrials::Int = 3,
139+
function ApproximateJacobianSparsity(; ntrials::Int = 3, epsilon = nothing,
136140
rng::AbstractRNG = Random.default_rng(), alg = GreedyD1Color())
137-
return ApproximateJacobianSparsity(ntrials, rng, alg)
141+
return ApproximateJacobianSparsity(ntrials, rng, alg, epsilon)
138142
end
139143

140144
# No one should be using this currently
@@ -332,3 +336,15 @@ init_jacobian(J::SparseMatrixCSC, ::Type{T}, fx, x; kwargs...) where {T} = T.(J)
332336

333337
__maybe_copy_x(_, x) = x
334338
__maybe_copy_x(_, ::Nothing) = nothing
339+
340+
# Check Backend has been loaded
341+
## We pay a small compile time cost for this, but it avoids cryptic error messages
342+
@inline function __test_backend_loaded(ad::AbstractADType)
343+
error("$(ad) requires $(__backend(ad)).jl to be loaded. Please load it.")
344+
end
345+
346+
@inline __backend(ad) = nothing
347+
@inline __backend(::Union{AutoEnzyme, AutoSparseEnzyme}) = :Enzyme
348+
@inline __backend(::Union{AutoZygote, AutoSparseZygote}) = :Zygote
349+
@inline __backend(::Union{AutoForwardDiff, AutoSparseForwardDiff}) = :ForwardDiff
350+
@inline __backend(::Union{AutoFiniteDiff, AutoSparseFiniteDiff}) = :FiniteDiff

src/highlevel/reverse_mode.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ end
2828

2929
function sparse_jacobian!(J::AbstractMatrix, ad, cache::ReverseModeJacobianCache, args...)
3030
if cache.coloring isa NoMatrixColoring
31+
__test_backend_loaded(ad)
3132
return __jacobian!(J, ad, args...)
3233
else
3334
return __sparse_jacobian_reverse_impl!(J, ad, cache.idx_vec, cache.coloring,
@@ -43,6 +44,7 @@ end
4344
function __sparse_jacobian_reverse_impl!(J::AbstractMatrix, ad, idx_vec,
4445
cache::MatrixColoringResult, f::F, fx, x) where {F}
4546
# If `fx` is `nothing` then assume `f` is not in-place
47+
__test_backend_loaded(ad)
4648
x_ = __maybe_copy_x(ad, x)
4749
fx_ = __maybe_copy_x(ad, fx)
4850
@unpack colorvec, nz_rows, nz_cols = cache

0 commit comments

Comments
 (0)