Skip to content

Commit fc7fcdb

Browse files
committed
feat: use DI for sparse jacobians
1 parent 78e19a9 commit fc7fcdb

File tree

7 files changed

+247
-116
lines changed

7 files changed

+247
-116
lines changed

Project.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
2525
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
2626
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
2727
SciMLJacobianOperators = "19f34311-ddf3-4b8b-af20-060888a46c0e"
28+
SciMLOperators = "c0aeaf25-5076-4817-a8d5-81caf7dfa961"
29+
Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46"
2830
SimpleNonlinearSolve = "727e6d20-b764-4bd8-a329-72de5adea6c7"
2931
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
3032
SparseConnectivityTracer = "9f842d2f-2579-4b1d-911e-f412cf18a3f5"
@@ -102,6 +104,8 @@ Reexport = "1.2"
102104
SIAMFANLEquations = "1.0.1"
103105
SciMLBase = "2.54.0"
104106
SciMLJacobianOperators = "0.1"
107+
SciMLOperators = "0.3.10"
108+
Setfield = "1.1.1"
105109
SimpleNonlinearSolve = "1.12.3"
106110
SparseArrays = "1.10"
107111
SparseConnectivityTracer = "0.6.5"

docs/Project.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ AlgebraicMultigrid = "2169fc97-5a83-5252-b627-83903c6c433c"
33
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
44
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
55
DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
6+
DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63"
67
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
78
DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244"
89
IncompleteLU = "40713840-3770-5561-ab4c-a76e7d0d7895"
@@ -16,6 +17,7 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1617
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
1718
SciMLJacobianOperators = "19f34311-ddf3-4b8b-af20-060888a46c0e"
1819
SimpleNonlinearSolve = "727e6d20-b764-4bd8-a329-72de5adea6c7"
20+
SparseConnectivityTracer = "9f842d2f-2579-4b1d-911e-f412cf18a3f5"
1921
SparseDiffTools = "47a9eef4-7e08-11e9-0b38-333d64bd3804"
2022
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
2123
SteadyStateDiffEq = "9672c7b4-1e72-59bd-8a11-6ac3964bc41f"
@@ -27,6 +29,7 @@ AlgebraicMultigrid = "0.5, 0.6"
2729
ArrayInterface = "6, 7"
2830
BenchmarkTools = "1"
2931
DiffEqBase = "6.136"
32+
DifferentiationInterface = "0.6"
3033
Documenter = "1"
3134
DocumenterCitations = "1"
3235
IncompleteLU = "0.2"
@@ -40,6 +43,7 @@ Random = "<0.0.1, 1"
4043
SciMLBase = "2.4"
4144
SciMLJacobianOperators = "0.1"
4245
SimpleNonlinearSolve = "1"
46+
SparseConnectivityTracer = "0.6.5"
4347
SparseDiffTools = "2.14"
4448
StaticArrays = "1"
4549
SteadyStateDiffEq = "2"

docs/src/basics/sparsity_detection.md

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ Let's say you have a Sparse Jacobian Prototype `jac_prototype`, in this case you
1212
create your problem as follows:
1313

1414
```julia
15-
prob = NonlinearProblem(NonlinearFunction(nlfunc; sparsity = jac_prototype), x0)
16-
# OR
1715
prob = NonlinearProblem(NonlinearFunction(nlfunc; jac_prototype = jac_prototype), x0)
1816
```
1917

@@ -22,9 +20,6 @@ NonlinearSolve will automatically perform matrix coloring and use sparse differe
2220
Now you can help the solver further by providing the color vector. This can be done by
2321

2422
```julia
25-
prob = NonlinearProblem(
26-
NonlinearFunction(nlfunc; sparsity = jac_prototype, colorvec = colorvec), x0)
27-
# OR
2823
prob = NonlinearProblem(
2924
NonlinearFunction(nlfunc; jac_prototype = jac_prototype, colorvec = colorvec), x0)
3025
```
@@ -34,10 +29,16 @@ If the `colorvec` is not provided, then it is computed on demand.
3429
!!! note
3530

3631
One thing to be careful about in this case is that `colorvec` is dependent on the
37-
autodiff backend used. Forward Mode and Finite Differencing will assume that the
38-
colorvec is the column colorvec, while Reverse Mode will assume that the colorvec is the
32+
autodiff backend used. `ADTypes.mode(ad) isa ADTypes.ForwardMode` will assume that the
33+
colorvec is the column colorvec, otherwise we will assume that the colorvec is the
3934
row colorvec.
4035

36+
!!! warning
37+
38+
Previously you could provide a `sparsity` argument to `NonlinearFunction` to specify
39+
the jacobian prototype. However, to avoid confusion, this is now deprecated. Instead,
40+
use the `jac_prototype` argument.
41+
4142
## Case II: Sparsity Detection algorithm is provided
4243

4344
If you don't have a Sparse Jacobian Prototype, but you know the which sparsity detection
@@ -48,14 +49,18 @@ prob = NonlinearProblem(
4849
NonlinearFunction(nlfunc; sparsity = SymbolicsSparsityDetection()), x0) # Remember to have Symbolics.jl loaded
4950
# OR
5051
prob = NonlinearProblem(
51-
NonlinearFunction(nlfunc; sparsity = ApproximateJacobianSparsity()), x0)
52+
NonlinearFunction(nlfunc; sparsity = TracerSparsityDetector()), x0) # From SparseConnectivityTracer.jl
5253
```
5354

54-
These Detection Algorithms are from [SparseDiffTools.jl](https://github.com/JuliaDiff/SparseDiffTools.jl),
55-
refer to the documentation there for more details.
55+
Refer to the documentation of DifferentiationInterface.jl for more information on
56+
sparsity detection algorithms.
5657

5758
## Case III: Sparse AD Type is being Used
5859

60+
!!! warning
61+
62+
This is now deprecated. Please use the previous two cases instead.
63+
5964
If you constructed a Nonlinear Solver with a sparse AD type, for example
6065

6166
```julia
@@ -65,15 +70,6 @@ TrustRegion(; autodiff = AutoSparse(AutoZygote()))
6570
```
6671

6772
then NonlinearSolve will automatically perform matrix coloring and use sparse
68-
differentiation if none of `sparsity` or `jac_prototype` is provided. If `Symbolics.jl` is
69-
loaded, we default to using `SymbolicsSparsityDetection()`, else we default to using
70-
`ApproximateJacobianSparsity()`.
71-
72-
`Case I/II` take precedence for sparsity detection and we perform sparse AD based on those
73-
options if those are provided.
74-
75-
!!! warning
76-
77-
If you provide a non-sparse AD, and provide a `sparsity` or `jac_prototype` then
78-
we will use dense AD. This is because, if you provide a specific AD type, we assume
79-
that you know what you are doing and want to override the default choice of `nothing`.
73+
differentiation if none of `sparsity` or `jac_prototype` is provided. We default to using
74+
`TracerSparsityDetector()`. `Case I/II` take precedence for sparsity detection and we
75+
perform sparse AD based on those options if those are provided.

docs/src/tutorials/large_systems.md

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22

33
This tutorial is for getting into the extra features of using NonlinearSolve.jl. Solving
44
ill-conditioned nonlinear systems requires specializing the linear solver on properties of
5-
the Jacobian in order to cut down on the ``\mathcal{O}(n^3)`` linear solve and the
6-
``\mathcal{O}(n^2)`` back-solves. This tutorial is designed to explain the advanced usage of
5+
the Jacobian in order to cut down on the `\mathcal{O}(n^3)` linear solve and the
6+
`\mathcal{O}(n^2)` back-solves. This tutorial is designed to explain the advanced usage of
77
NonlinearSolve.jl by solving the steady state stiff Brusselator partial differential
88
equation (BRUSS) using NonlinearSolve.jl.
99

1010
## Definition of the Brusselator Equation
1111

1212
!!! note
13-
13+
1414
Feel free to skip this section: it simply defines the example problem.
1515

1616
The Brusselator PDE is defined as follows:
@@ -60,7 +60,7 @@ broadcast). Use `dx=dy=1/32`.
6060
The resulting `NonlinearProblem` definition is:
6161

6262
```@example ill_conditioned_nlprob
63-
using NonlinearSolve, LinearAlgebra, SparseArrays, LinearSolve, SparseDiffTools
63+
using NonlinearSolve, LinearAlgebra, SparseArrays, LinearSolve
6464
6565
const N = 32
6666
const xyd_brusselator = range(0, stop = 1, length = N)
@@ -117,31 +117,37 @@ However, if you know the sparsity of your problem, then you can pass a different
117117
type. For example, a `SparseMatrixCSC` will give a sparse matrix. Other sparse matrix types
118118
include:
119119

120-
- Bidiagonal
121-
- Tridiagonal
122-
- SymTridiagonal
123-
- BandedMatrix ([BandedMatrices.jl](https://github.com/JuliaLinearAlgebra/BandedMatrices.jl))
124-
- BlockBandedMatrix ([BlockBandedMatrices.jl](https://github.com/JuliaLinearAlgebra/BlockBandedMatrices.jl))
120+
- Bidiagonal
121+
- Tridiagonal
122+
- SymTridiagonal
123+
- BandedMatrix ([BandedMatrices.jl](https://github.com/JuliaLinearAlgebra/BandedMatrices.jl))
124+
- BlockBandedMatrix ([BlockBandedMatrices.jl](https://github.com/JuliaLinearAlgebra/BlockBandedMatrices.jl))
125125

126126
## Approximate Sparsity Detection & Sparse Jacobians
127127

128-
In the next section, we will discuss how to declare a sparse Jacobian and how to use
129-
[Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl), to compute exact sparse
130-
jacobians. This is triggered if you pass in a sparse autodiff type such as
131-
`AutoSparse(AutoForwardDiff())`. If `Symbolics.jl` is loaded, then the default changes to
132-
Symbolic Sparsity Detection. See the manual entry on
133-
[Sparsity Detection](@ref sparsity-detection) for more details on the default.
128+
In the next section, we will show how to specify `sparsity` to trigger automatic sparsity
129+
detection.
134130

135131
```@example ill_conditioned_nlprob
136132
using BenchmarkTools # for @btime
137133
138134
@btime solve(prob_brusselator_2d, NewtonRaphson());
139-
@btime solve(prob_brusselator_2d,
140-
NewtonRaphson(; autodiff = AutoSparse(AutoForwardDiff(; chunksize = 32))));
141-
@btime solve(prob_brusselator_2d,
142-
NewtonRaphson(; autodiff = AutoSparse(AutoForwardDiff(; chunksize = 32)),
135+
nothing # hide
136+
```
137+
138+
```@example ill_conditioned_nlprob
139+
using SparseConnectivityTracer
140+
141+
prob_brusselator_2d_autosparse = NonlinearProblem(
142+
NonlinearFunction(brusselator_2d_loop; sparsity = TracerSparsityDetector()),
143+
u0, p; abstol = 1e-10, reltol = 1e-10)
144+
145+
@btime solve(prob_brusselator_2d_autosparse,
146+
NewtonRaphson(; autodiff = AutoForwardDiff(; chunksize = 32)));
147+
@btime solve(prob_brusselator_2d_autosparse,
148+
NewtonRaphson(; autodiff = AutoForwardDiff(; chunksize = 32),
143149
linsolve = KLUFactorization()));
144-
@btime solve(prob_brusselator_2d,
150+
@btime solve(prob_brusselator_2d_autosparse,
145151
NewtonRaphson(; autodiff = AutoForwardDiff(; chunksize = 32),
146152
linsolve = KrylovJL_GMRES()));
147153
nothing # hide
@@ -160,8 +166,14 @@ declaration of Jacobian sparsity types. To see this in action, we can give an ex
160166
and `u` and call `jacobian_sparsity` on our function with the example arguments, and it will
161167
kick out a sparse matrix with our pattern, that we can turn into our `jac_prototype`.
162168

169+
!!! tip
170+
171+
Alternatively you can use the `SparseConnectivityTracer.jl` package to automatically
172+
generate a sparse Jacobian.
173+
163174
```@example ill_conditioned_nlprob
164175
using Symbolics
176+
165177
du0 = copy(u0)
166178
jac_sparsity = Symbolics.jacobian_sparsity(
167179
(du, u) -> brusselator_2d_loop(du, u, p), du0, u0)
@@ -171,7 +183,7 @@ Notice that Julia gives a nice print out of the sparsity pattern. That's neat, a
171183
tedious to build by hand! Now we just pass it to the `NonlinearFunction` like as before:
172184

173185
```@example ill_conditioned_nlprob
174-
ff = NonlinearFunction(brusselator_2d_loop; sparsity = jac_sparsity)
186+
ff = NonlinearFunction(brusselator_2d_loop; jac_prototype = jac_sparsity)
175187
```
176188

177189
Build the `NonlinearProblem`:
@@ -212,7 +224,7 @@ choices, see the
212224
`linsolve` choices are any valid [LinearSolve.jl](https://linearsolve.sciml.ai/dev/) solver.
213225

214226
!!! note
215-
227+
216228
Switching to a Krylov linear solver will automatically change the nonlinear problem
217229
solver into Jacobian-free mode, dramatically reducing the memory required. This can be
218230
overridden by adding `concrete_jac=true` to the algorithm.
@@ -275,8 +287,8 @@ function algebraicmultigrid(W, du, u, p, t, newW, Plprev, Prprev, solverdata)
275287
end
276288
277289
@btime solve(prob_brusselator_2d_sparse,
278-
NewtonRaphson(
279-
linsolve = KrylovJL_GMRES(), precs = algebraicmultigrid, concrete_jac = true));
290+
NewtonRaphson(linsolve = KrylovJL_GMRES(), precs = algebraicmultigrid,
291+
concrete_jac = true));
280292
nothing # hide
281293
```
282294

@@ -296,8 +308,8 @@ function algebraicmultigrid2(W, du, u, p, t, newW, Plprev, Prprev, solverdata)
296308
end
297309
298310
@btime solve(prob_brusselator_2d_sparse,
299-
NewtonRaphson(
300-
linsolve = KrylovJL_GMRES(), precs = algebraicmultigrid2, concrete_jac = true));
311+
NewtonRaphson(linsolve = KrylovJL_GMRES(), precs = algebraicmultigrid2,
312+
concrete_jac = true));
301313
nothing # hide
302314
```
303315

@@ -308,15 +320,22 @@ for the exact sparsity detection case, we left out the time it takes to perform
308320
sparsity detection. Let's compare the two by setting the sparsity detection algorithms.
309321

310322
```@example ill_conditioned_nlprob
311-
prob_brusselator_2d_exact = NonlinearProblem(
312-
NonlinearFunction(brusselator_2d_loop; sparsity = SymbolicsSparsityDetection()),
323+
using DifferentiationInterface, SparseConnectivityTracer
324+
325+
prob_brusselator_2d_exact_symbolics = NonlinearProblem(
326+
NonlinearFunction(brusselator_2d_loop; sparsity = SymbolicsSparsityDetector()),
327+
u0, p; abstol = 1e-10, reltol = 1e-10)
328+
prob_brusselator_2d_exact_tracer = NonlinearProblem(
329+
NonlinearFunction(brusselator_2d_loop; sparsity = TracerSparsityDetector()),
313330
u0, p; abstol = 1e-10, reltol = 1e-10)
314-
prob_brusselator_2d_approx = NonlinearProblem(
315-
NonlinearFunction(brusselator_2d_loop; sparsity = ApproximateJacobianSparsity()),
331+
prob_brusselator_2d_approx_di = NonlinearProblem(
332+
NonlinearFunction(brusselator_2d_loop;
333+
sparsity = DenseSparsityDetector(AutoForwardDiff(); atol=1e-4)),
316334
u0, p; abstol = 1e-10, reltol = 1e-10)
317335
318-
@btime solve(prob_brusselator_2d_exact, NewtonRaphson());
319-
@btime solve(prob_brusselator_2d_approx, NewtonRaphson());
336+
@btime solve(prob_brusselator_2d_exact_symbolics, NewtonRaphson());
337+
@btime solve(prob_brusselator_2d_exact_tracer, NewtonRaphson());
338+
@btime solve(prob_brusselator_2d_approx_di, NewtonRaphson());
320339
nothing # hide
321340
```
322341

src/NonlinearSolve.jl

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,31 +31,33 @@ using Printf: @printf
3131
using Preferences: Preferences, @load_preference, @set_preferences!
3232
using RecursiveArrayTools: recursivecopy!
3333
using SciMLBase: AbstractNonlinearAlgorithm, JacobianWrapper, AbstractNonlinearProblem,
34-
AbstractSciMLOperator, _unwrap_val, isinplace, NLStats
35-
using SciMLJacobianOperators: AbstractJacobianOperator, JacobianOperator, VecJacOperator,
36-
JacVecOperator, StatefulJacobianOperator
37-
using SparseDiffTools: SparseDiffTools, AbstractSparsityDetection,
38-
ApproximateJacobianSparsity, JacPrototypeSparsityDetection,
39-
NoSparsityDetection, PrecomputedJacobianColorvec,
40-
init_jacobian, sparse_jacobian, sparse_jacobian!,
41-
sparse_jacobian_cache
34+
_unwrap_val, isinplace, NLStats
35+
using SciMLOperators: AbstractSciMLOperator
36+
using Setfield: @set!
4237
using StaticArraysCore: StaticArray, SVector, SArray, MArray, Size, SMatrix
4338
using SymbolicIndexingInterface: SymbolicIndexingInterface, ParameterIndexingProxy,
4439
symbolic_container, parameter_values, state_values, getu,
4540
setu
4641

4742
# AD Support
4843
using ADTypes: ADTypes, AbstractADType, AutoFiniteDiff, AutoForwardDiff,
49-
AutoPolyesterForwardDiff, AutoZygote, AutoEnzyme, AutoSparse
44+
AutoPolyesterForwardDiff, AutoZygote, AutoEnzyme, AutoSparse,
45+
NoSparsityDetector, KnownJacobianSparsityDetector
5046
using ADTypes: AutoSparseFiniteDiff, AutoSparseForwardDiff, AutoSparsePolyesterForwardDiff,
5147
AutoSparseZygote # FIXME: deprecated, remove in future
5248
using DifferentiationInterface: DifferentiationInterface, Constant
5349
using FiniteDiff: FiniteDiff
5450
using ForwardDiff: ForwardDiff, Dual
51+
using SciMLJacobianOperators: AbstractJacobianOperator, JacobianOperator, VecJacOperator,
52+
JacVecOperator, StatefulJacobianOperator
5553

5654
## Sparse AD Support
5755
using SparseArrays: AbstractSparseMatrix, SparseMatrixCSC
58-
using SparseConnectivityTracer: TracerSparsityDetector
56+
using SparseConnectivityTracer: TracerSparsityDetector # This can be dropped in the next release
57+
using SparseDiffTools: SparseDiffTools, JacPrototypeSparsityDetection,
58+
PrecomputedJacobianColorvec, init_jacobian, sparse_jacobian,
59+
sparse_jacobian!, sparse_jacobian_cache
60+
using SparseMatrixColorings: ConstantColoringAlgorithm, GreedyColoringAlgorithm
5961

6062
@reexport using SciMLBase, SimpleNonlinearSolve
6163

0 commit comments

Comments
 (0)