Skip to content

Commit 06e91e6

Browse files
committed
Add a sparse Jacobian with Enzyme.jl
1 parent 58eb3e7 commit 06e91e6

File tree

6 files changed

+132
-31
lines changed

6 files changed

+132
-31
lines changed

docs/src/sparse.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ x = rand(T, 2)
3131
H = hess(nlp, x)
3232
```
3333

34-
The backends available for sparse derivatives (`SparseADJacobian`, `SparseADHessian`, and `SparseReverseADHessian`) allow for customization through keyword arguments such as `detector` and `coloring_algorithm`.
34+
The backends available for sparse derivatives (`SparseADJacobian`, `SparseEnzymeADJacobian`, `SparseADHessian`, and `SparseReverseADHessian`) allow for customization through keyword arguments such as `detector` and `coloring_algorithm`.
3535
These arguments specify the sparsity pattern detector and the coloring algorithm, respectively.
3636

3737
- A **`detector`** must be of type `ADTypes.AbstractSparsityDetector`.
3838
The default detector is `TracerSparsityDetector()` from the package `SparseConnectivityTracer.jl`.
3939
Prior to version 0.8.0, the default was `SymbolicSparsityDetector()` from `Symbolics.jl`.
4040

4141
- A **`coloring_algorithm`** must be of type `SparseMatrixColorings.GreedyColoringAlgorithm`.
42-
The default algorithm is `GreedyColoringAlgorithm{:direct}()` for `SparseADJacobian` and `SparseADHessian`, while it is `GreedyColoringAlgorithm{:substitution}()` for `SparseReverseADHessian`.
42+
The default algorithm is `GreedyColoringAlgorithm{:direct}()` for `SparseADJacobian`, `SparseEnzymeADJacobian` and `SparseADHessian`, while it is `GreedyColoringAlgorithm{:substitution}()` for `SparseReverseADHessian`.
4343
These algorithms are provided by the package `SparseMatrixColorings.jl`.
4444

4545
The `GreedyColoringAlgorithm{:direct}()` performs column coloring for Jacobians and star coloring for Hessians.

src/enzyme.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ function EnzymeReverseADJprod(
8181
end
8282

8383
function Jprod!(b::EnzymeReverseADJprod, Jv, c!, x, v, ::Val)
84-
Enzyme.autodiff(Enzyme.Forward, Const(c!), Duplicated(b.x,Jv), Duplicated(x, v))
84+
Enzyme.autodiff(Enzyme.Forward, Const(c!), Duplicated(b.x, Jv), Duplicated(x, v))
8585
return Jv
8686
end
8787

src/sparse_jacobian.jl

Lines changed: 111 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -64,25 +64,6 @@ function SparseADJacobian(
6464
)
6565
end
6666

67-
function get_nln_nnzj(b::SparseADJacobian, nvar, ncon)
68-
length(b.rowval)
69-
end
70-
71-
function NLPModels.jac_structure!(
72-
b::SparseADJacobian,
73-
nlp::ADModel,
74-
rows::AbstractVector{<:Integer},
75-
cols::AbstractVector{<:Integer},
76-
)
77-
rows .= b.rowval
78-
for i = 1:(nlp.meta.nvar)
79-
for j = b.colptr[i]:(b.colptr[i + 1] - 1)
80-
cols[j] = i
81-
end
82-
end
83-
return rows, cols
84-
end
85-
8667
function sparse_jac_coord!(
8768
ℓ!::Function,
8869
b::SparseADJacobian{Tag},
@@ -111,8 +92,116 @@ function sparse_jac_coord!(
11192
return vals
11293
end
11394

95+
struct SparseEnzymeADJacobian{R, C, S} <: ADBackend
96+
nvar::Int
97+
ncon::Int
98+
rowval::Vector{Int}
99+
colptr::Vector{Int}
100+
nzval::Vector{R}
101+
result_coloring::C
102+
compressed_jacobian::S
103+
v::Vector{R}
104+
buffer::Vector{R}
105+
end
106+
107+
function SparseEnzymeADJacobian(
108+
nvar,
109+
f,
110+
ncon,
111+
c!;
112+
x0::AbstractVector = rand(nvar),
113+
coloring_algorithm::AbstractColoringAlgorithm = GreedyColoringAlgorithm{:direct}(),
114+
detector::AbstractSparsityDetector = TracerSparsityDetector(),
115+
kwargs...,
116+
)
117+
output = similar(x0, ncon)
118+
J = compute_jacobian_sparsity(c!, output, x0, detector = detector)
119+
SparseEnzymeADJacobian(nvar, f, ncon, c!, J; x0, coloring_algorithm, kwargs...)
120+
end
121+
122+
function SparseEnzymeADJacobian(
123+
nvar,
124+
f,
125+
ncon,
126+
c!,
127+
J::SparseMatrixCSC{Bool, Int};
128+
x0::AbstractVector{T} = rand(nvar),
129+
coloring_algorithm::AbstractColoringAlgorithm = GreedyColoringAlgorithm{:direct}(),
130+
kwargs...,
131+
) where {T}
132+
# We should support :row and :bidirectional in the future
133+
problem = ColoringProblem{:nonsymmetric, :column}()
134+
result_coloring = coloring(J, problem, coloring_algorithm, decompression_eltype = T)
135+
136+
rowval = J.rowval
137+
colptr = J.colptr
138+
nzval = T.(J.nzval)
139+
compressed_jacobian = similar(x0, ncon)
140+
v = similar(x0)
141+
buffer = zeros(T, ncon)
142+
143+
SparseEnzymeADJacobian(
144+
nvar,
145+
ncon,
146+
rowval,
147+
colptr,
148+
nzval,
149+
result_coloring,
150+
compressed_jacobian,
151+
v,
152+
buffer,
153+
)
154+
end
155+
156+
function sparse_jac_coord!(
157+
c!::Function,
158+
b::SparseEnzymeADJacobian,
159+
x::AbstractVector,
160+
vals::AbstractVector,
161+
)
162+
# SparseMatrixColorings.jl requires a SparseMatrixCSC for the decompression
163+
A = SparseMatrixCSC(b.ncon, b.nvar, b.colptr, b.rowval, b.nzval)
164+
165+
groups = column_groups(b.result_coloring)
166+
for (icol, cols) in enumerate(groups)
167+
# Update the seed
168+
b.v .= 0
169+
for col in cols
170+
b.v[col] = 1
171+
end
172+
173+
# b.compressed_jacobian is just a vector Jv here
174+
# We don't use the vector mode
175+
Enzyme.autodiff(Enzyme.Forward, Const(c!), Duplicated(b.buffer, b.compressed_jacobian), Duplicated(x, b.v))
176+
177+
# Update the columns of the Jacobian that have the color `icol`
178+
decompress_single_color!(A, b.compressed_jacobian, icol, b.result_coloring)
179+
end
180+
vals .= b.nzval
181+
return vals
182+
end
183+
184+
function get_nln_nnzj(b::Union{SparseADJacobian, SparseEnzymeADJacobian}, nvar, ncon)
185+
length(b.rowval)
186+
end
187+
188+
function NLPModels.jac_structure!(
189+
b::Union{SparseADJacobian, SparseEnzymeADJacobian},
190+
nlp::ADModel,
191+
rows::AbstractVector{<:Integer},
192+
cols::AbstractVector{<:Integer},
193+
)
194+
rows .= b.rowval
195+
for i = 1:(nlp.meta.nvar)
196+
for j = b.colptr[i]:(b.colptr[i + 1] - 1)
197+
cols[j] = i
198+
end
199+
end
200+
return rows, cols
201+
end
202+
114203
function NLPModels.jac_coord!(
115-
b::SparseADJacobian,
204+
b::Union{SparseADJacobian, SparseEnzymeADJacobian},
116205
nlp::ADModel,
117206
x::AbstractVector,
118207
vals::AbstractVector,
@@ -122,7 +211,7 @@ function NLPModels.jac_coord!(
122211
end
123212

124213
function NLPModels.jac_structure_residual!(
125-
b::SparseADJacobian,
214+
b::Union{SparseADJacobian, SparseEnzymeADJacobian},
126215
nls::AbstractADNLSModel,
127216
rows::AbstractVector{<:Integer},
128217
cols::AbstractVector{<:Integer},
@@ -137,7 +226,7 @@ function NLPModels.jac_structure_residual!(
137226
end
138227

139228
function NLPModels.jac_coord_residual!(
140-
b::SparseADJacobian,
229+
b::Union{SparseADJacobian, SparseEnzymeADJacobian},
141230
nls::AbstractADNLSModel,
142231
x::AbstractVector,
143232
vals::AbstractVector,

src/sparsity_pattern.jl

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ end
7979

8080
function get_sparsity_pattern(model::ADModel, ::Val{:jacobian})
8181
backend = model.adbackend.jacobian_backend
82-
validate_sparse_backend(backend, SparseADJacobian, "Jacobian")
82+
validate_sparse_backend(
83+
backend,
84+
Union{SparseADJacobian, SparseEnzymeADJacobian},
85+
"Jacobian",
86+
)
8387
m = model.meta.ncon
8488
n = model.meta.nvar
8589
colptr = backend.colptr
@@ -102,7 +106,11 @@ end
102106

103107
function get_sparsity_pattern(model::AbstractADNLSModel, ::Val{:jacobian_residual})
104108
backend = model.adbackend.jacobian_residual_backend
105-
validate_sparse_backend(backend, SparseADJacobian, "Jacobian of the residual")
109+
validate_sparse_backend(
110+
backend,
111+
Union{SparseADJacobian, SparseEnzymeADJacobian},
112+
"Jacobian of the residual",
113+
)
106114
m = model.nls_meta.nequ
107115
n = model.meta.nvar
108116
colptr = backend.colptr

test/sparse_jacobian.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
list_sparse_jac_backend =
2-
((ADNLPModels.SparseADJacobian, Dict()), (ADNLPModels.ForwardDiffADJacobian, Dict()))
2+
((ADNLPModels.SparseADJacobian, Dict()),
3+
(ADNLPModels.SparseEnzymeADJacobian, Dict()),
4+
(ADNLPModels.ForwardDiffADJacobian, Dict()))
35

46
dt = (Float32, Float64)
57

@@ -51,7 +53,7 @@ dt = (Float32, Float64)
5153
0 1
5254
]
5355

54-
if backend == ADNLPModels.SparseADJacobian
56+
if backend != ADNLPModels.ForwardDiffADJacobian
5557
J_sp = get_sparsity_pattern(nlp, :jacobian)
5658
@test J_sp == SparseMatrixCSC{Bool, Int}([
5759
1 0

test/sparse_jacobian_nls.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
list_sparse_jac_backend =
2-
((ADNLPModels.SparseADJacobian, Dict()), (ADNLPModels.ForwardDiffADJacobian, Dict()))
2+
((ADNLPModels.SparseADJacobian, Dict()),
3+
(ADNLPModels.SparseEnzymeADJacobian, Dict()),
4+
(ADNLPModels.ForwardDiffADJacobian, Dict()))
35

46
dt = (Float32, Float64)
57

@@ -43,7 +45,7 @@ dt = (Float32, Float64)
4345
0 1
4446
]
4547

46-
if backend == ADNLPModels.SparseADJacobian
48+
if backend != ADNLPModels.ForwardDiffADJacobian
4749
J_sp = get_sparsity_pattern(nls, :jacobian_residual)
4850
@test J_sp == SparseMatrixCSC{Bool, Int}([
4951
1 0

0 commit comments

Comments
 (0)