Skip to content
This repository was archived by the owner on Apr 23, 2025. It is now read-only.

Commit 84de29e

Browse files
Merge pull request #276 from avik-pal/ap/improve_immutables
Non allocating versions for StaticArrays
2 parents 93b7789 + 7a8170f commit 84de29e

File tree

8 files changed

+113
-22
lines changed

8 files changed

+113
-22
lines changed

Project.toml

+4-4
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 <pankajmishra1511@gmail.com>", "Chris Rackauckas <contact@chrisrackauckas.com>"]
4-
version = "2.13.0"
4+
version = "2.14.0"
55

66
[deps]
77
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
@@ -45,13 +45,13 @@ Enzyme = "0.11"
4545
FiniteDiff = "2.8.1"
4646
ForwardDiff = "0.10"
4747
Graphs = "1"
48-
LinearAlgebra = "1.6"
48+
LinearAlgebra = "<0.0.1, 1"
4949
PackageExtensionCompat = "1"
50-
Random = "1.6"
50+
Random = "<0.0.1, 1"
5151
Reexport = "1"
5252
SciMLOperators = "0.3.7"
5353
Setfield = "1"
54-
SparseArrays = "1.6"
54+
SparseArrays = "<0.0.1, 1"
5555
StaticArrayInterface = "1.3"
5656
StaticArrays = "1"
5757
Symbolics = "5.5"

src/SparseDiffTools.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import ForwardDiff: Dual, jacobian, partials, DEFAULT_CHUNK_THRESHOLD
1616
using ArrayInterface, SparseArrays
1717
import ArrayInterface: matrix_colors
1818
import StaticArrays
19-
import StaticArrays: StaticArray
19+
import StaticArrays: StaticArray, SArray, MArray, Size
2020
# Others
2121
using SciMLOperators, LinearAlgebra, Random
2222
import DataStructures: DisjointSets, find_root!, union!

src/highlevel/common.jl

+56-12
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,13 @@ A cache for computing the Jacobian of type `AbstractMaybeSparseJacobianCache`.
173173
"""
174174
function sparse_jacobian_cache end
175175

176+
function sparse_jacobian_static_array(ad, cache, f, x::SArray)
177+
# Not the most performant fallback
178+
J = init_jacobian(cache)
179+
sparse_jacobian!(J, ad, cache, f, MArray(x))
180+
return J
181+
end
182+
176183
"""
177184
sparse_jacobian(ad::AbstractADType, sd::AbstractMaybeSparsityDetection, f, x; fx=nothing)
178185
sparse_jacobian(ad::AbstractADType, sd::AbstractMaybeSparsityDetection, f!, fx, x)
@@ -181,6 +188,9 @@ Sequentially calls `sparse_jacobian_cache` and `sparse_jacobian!` to compute the
181188
`f` at `x`. Use this if the jacobian for `f` is computed exactly once. In all other
182189
cases, use `sparse_jacobian_cache` once to generate the cache and use `sparse_jacobian!`
183190
with the same cache to compute the jacobian.
191+
192+
If `x` is a StaticArray, then this function tries to use a non-allocating implementation for
193+
the jacobian computation. This is possible only for a limited backends currently.
184194
"""
185195
function sparse_jacobian(ad::AbstractADType, sd::AbstractMaybeSparsityDetection, args...;
186196
kwargs...)
@@ -189,20 +199,32 @@ function sparse_jacobian(ad::AbstractADType, sd::AbstractMaybeSparsityDetection,
189199
sparse_jacobian!(J, ad, cache, args...)
190200
return J
191201
end
202+
function sparse_jacobian(ad::AbstractADType, sd::AbstractMaybeSparsityDetection, f,
203+
x::SArray; kwargs...)
204+
cache = sparse_jacobian_cache(ad, sd, f, x; kwargs...)
205+
return sparse_jacobian_static_array(ad, cache, f, x)
206+
end
192207

193208
"""
194209
sparse_jacobian(ad::AbstractADType, cache::AbstractMaybeSparseJacobianCache, f, x)
195210
sparse_jacobian(ad::AbstractADType, cache::AbstractMaybeSparseJacobianCache, f!, fx, x)
196211
197212
Use the sparsity detection `cache` for computing the sparse Jacobian. This allocates a new
198-
Jacobian at every function call
213+
Jacobian at every function call.
214+
215+
If `x` is a StaticArray, then this function tries to use a non-allocating implementation for
216+
the jacobian computation. This is possible only for a limited backends currently.
199217
"""
200218
function sparse_jacobian(ad::AbstractADType, cache::AbstractMaybeSparseJacobianCache,
201219
args...)
202220
J = init_jacobian(cache)
203221
sparse_jacobian!(J, ad, cache, args...)
204222
return J
205223
end
224+
function sparse_jacobian(ad::AbstractADType, cache::AbstractMaybeSparseJacobianCache, f,
225+
x::SArray)
226+
return sparse_jacobian_static_array(ad, cache, f, x)
227+
end
206228

207229
"""
208230
sparse_jacobian!(J::AbstractMatrix, ad::AbstractADType, sd::AbstractSparsityDetection,
@@ -247,14 +269,18 @@ function __chunksize(::Union{AutoSparseForwardDiff{C}, AutoForwardDiff{C}}, x) w
247269
C isa ForwardDiff.Chunk && return C
248270
return __chunksize(Val(C), x)
249271
end
250-
__chunksize(::Val{nothing}, x) = ForwardDiff.Chunk(x)
272+
__chunksize(::Val{nothing}, x) = __chunksize(x)
251273
function __chunksize(::Val{C}, x) where {C}
252274
if C isa Integer && !(C isa Bool)
253-
return C 0 ? ForwardDiff.Chunk(x) : ForwardDiff.Chunk{C}()
275+
return C 0 ? __chunksize(x) : ForwardDiff.Chunk{C}()
254276
else
255277
error("$(C)::$(typeof(C)) is not a valid chunksize!")
256278
end
257279
end
280+
281+
__chunksize(x) = ForwardDiff.Chunk(x)
282+
__chunksize(x::StaticArray) = ForwardDiff.Chunk{ForwardDiff.pickchunksize(prod(Size(x)))}()
283+
258284
function __chunksize(::Union{AutoSparseForwardDiff{C}, AutoForwardDiff{C}}) where {C}
259285
C === nothing && return nothing
260286
C isa Integer && !(C isa Bool) && return C 0 ? nothing : Val(C)
@@ -273,18 +299,36 @@ end
273299
return :(nothing)
274300
end
275301

276-
function init_jacobian(c::AbstractMaybeSparseJacobianCache)
302+
"""
303+
init_jacobian(cache::AbstractMaybeSparseJacobianCache;
304+
preserve_immutable::Val = Val(false))
305+
306+
Initialize the Jacobian based on the cache. Uses sparse jacobians if possible.
307+
308+
If `preserve_immutable` is `true`, then the Jacobian returned might be immutable, this is
309+
relevant if the inputs are immutable like `StaticArrays`.
310+
"""
311+
function init_jacobian(c::AbstractMaybeSparseJacobianCache;
312+
preserve_immutable::Val = Val(false))
277313
T = promote_type(eltype(c.fx), eltype(c.x))
278-
return init_jacobian(__getfield(c, Val(:jac_prototype)), T, c.fx, c.x)
314+
return init_jacobian(__getfield(c, Val(:jac_prototype)), T, c.fx, c.x;
315+
preserve_immutable)
279316
end
280-
init_jacobian(::Nothing, ::Type{T}, fx, x) where {T} = similar(fx, T, length(fx), length(x))
281-
function init_jacobian(::Nothing, ::Type{T}, fx::StaticArray, x::StaticArray) where {T}
282-
# We want to construct a MArray to preserve types
283-
J = StaticArrays.MArray{Tuple{length(fx), length(x)}, T}(undef)
284-
return J
317+
function init_jacobian(::Nothing, ::Type{T}, fx, x; kwargs...) where {T}
318+
return similar(fx, T, length(fx), length(x))
319+
end
320+
function init_jacobian(::Nothing, ::Type{T}, fx::StaticArray, x::StaticArray;
321+
preserve_immutable::Val{PI} = Val(true)) where {T, PI}
322+
if PI
323+
return StaticArrays.SArray{Tuple{length(fx), length(x)}, T}(I)
324+
else
325+
return StaticArrays.MArray{Tuple{length(fx), length(x)}, T}(undef)
326+
end
327+
end
328+
function init_jacobian(J, ::Type{T}, fx, x; kwargs...) where {T}
329+
return similar(J, T, size(J, 1), size(J, 2))
285330
end
286-
init_jacobian(J, ::Type{T}, _, _) where {T} = similar(J, T, size(J, 1), size(J, 2))
287-
init_jacobian(J::SparseMatrixCSC, ::Type{T}, _, _) where {T} = T.(J)
331+
init_jacobian(J::SparseMatrixCSC, ::Type{T}, fx, x; kwargs...) where {T} = T.(J)
288332

289333
__maybe_copy_x(_, x) = x
290334
__maybe_copy_x(_, ::Nothing) = nothing

src/highlevel/finite_diff.jl

+4
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,7 @@ function sparse_jacobian!(J::AbstractMatrix, _, cache::FiniteDiffJacobianCache,
4848
FiniteDiff.finite_difference_jacobian!(J, f!, x, cache.cache)
4949
return J
5050
end
51+
52+
function sparse_jacobian_static_array(_, cache::FiniteDiffJacobianCache, f, x::SArray)
53+
return FiniteDiff.finite_difference_jacobian(f, x, cache.cache)
54+
end

src/highlevel/forward_mode.jl

+8
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,11 @@ function sparse_jacobian!(J::AbstractMatrix, _, cache::ForwardDiffJacobianCache,
7171
end
7272
return J
7373
end
74+
75+
function sparse_jacobian_static_array(_, cache::ForwardDiffJacobianCache, f, x::SArray)
76+
if cache.cache isa ForwardColorJacCache
77+
return forwarddiff_color_jacobian(f, x, cache.cache)
78+
else
79+
return ForwardDiff.jacobian(f, x, cache.cache)
80+
end
81+
end

test/allocs/Project.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[deps]
2+
AllocCheck = "9b6a8646-10ed-4001-bbdc-1d2f46dfbb1a"

test/runtests.jl

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ const GROUP = get(ENV, "GROUP", "All")
55
const is_APPVEYOR = (Sys.iswindows() && haskey(ENV, "APPVEYOR"))
66
const is_TRAVIS = haskey(ENV, "TRAVIS")
77

8-
function activate_gpu_env()
9-
Pkg.activate("gpu")
8+
function activate_env(env)
9+
Pkg.activate(env)
1010
Pkg.develop(PackageSpec(path = dirname(@__DIR__)))
1111
Pkg.instantiate()
1212
end
@@ -42,6 +42,7 @@ if GROUP == "Core" || GROUP == "All"
4242
end
4343

4444
if GROUP == "InterfaceI" || GROUP == "All"
45+
VERSION v"1.9" && activate_env("allocs")
4546
@time @safetestset "Jac Vecs and Hes Vecs" begin
4647
include("test_jaches_products.jl")
4748
end
@@ -54,7 +55,7 @@ if GROUP == "InterfaceI" || GROUP == "All"
5455
end
5556

5657
if GROUP == "GPU"
57-
activate_gpu_env()
58+
activate_env("gpu")
5859
@time @safetestset "GPU AD" begin
5960
include("test_gpu_ad.jl")
6061
end

test/test_sparse_jacobian.jl

+34-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## Sparse Jacobian tests
2-
using SparseDiffTools, Symbolics, ForwardDiff, LinearAlgebra, SparseArrays, Zygote, Enzyme
3-
using Test
2+
using SparseDiffTools,
3+
Symbolics, ForwardDiff, LinearAlgebra, SparseArrays, Zygote, Enzyme, Test, StaticArrays
44

55
@views function fdiff(y, x) # in-place
66
L = length(x)
@@ -163,3 +163,35 @@ SPARSITY_DETECTION_ALGS = [JacPrototypeSparsityDetection(; jac_prototype = J_spa
163163
end
164164
end
165165
end
166+
167+
@static if VERSION v"1.9"
168+
using AllocCheck
169+
end
170+
171+
@static if VERSION v"1.9"
172+
# Testing that the non-sparse jacobian's are non-allocating.
173+
fvcat(x) = vcat(x, x)
174+
175+
x_sa = @SVector randn(Float32, 10)
176+
177+
J_true_sa = ForwardDiff.jacobian(fvcat, x_sa)
178+
179+
AllocCheck.@check_allocs function __sparse_jacobian_no_allocs(ad, sd, f::F, x) where {F}
180+
return sparse_jacobian(ad, sd, f, x)
181+
end
182+
183+
@testset "Static Arrays" begin
184+
@testset "No Allocations: $(difftype)" for difftype in (AutoSparseForwardDiff(),
185+
AutoForwardDiff())
186+
J = __sparse_jacobian_no_allocs(difftype, NoSparsityDetection(), fvcat, x_sa)
187+
@test J J_true_sa
188+
end
189+
190+
@testset "Other Backends: $(difftype)" for difftype in (AutoSparseZygote(),
191+
AutoZygote(), AutoSparseEnzyme(), AutoEnzyme(), AutoSparseFiniteDiff(),
192+
AutoFiniteDiff())
193+
J = sparse_jacobian(difftype, NoSparsityDetection(), fvcat, x_sa)
194+
@test J J_true_sa
195+
end
196+
end
197+
end

0 commit comments

Comments
 (0)