Skip to content

Commit 540ef5b

Browse files
committed
Handle Approximate Sparsity detection more gracefully
1 parent b569244 commit 540ef5b

File tree

5 files changed

+120
-9
lines changed

5 files changed

+120
-9
lines changed

docs/pages.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pages = ["index.md",
1414
"basics/NonlinearSolution.md",
1515
"basics/TerminationCondition.md",
1616
"basics/Logging.md",
17+
"basics/SparsityDetection.md",
1718
"basics/FAQ.md"],
1819
"Solver Summaries and Recommendations" => Any["solvers/NonlinearSystemSolvers.md",
1920
"solvers/BracketingSolvers.md",

docs/src/basics/SparsityDetection.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# [(Semi-)Automatic Sparsity Detection](@id sparsity-detection)
2+
3+
This section describes how to enable Sparsity Detection. For a detailed tutorial on how
4+
to use this in an actual problem, see
5+
[this tutorial on Efficiently Solving Large Sparse Ill-Conditioned Nonlinear Systems](@ref large_systems).
6+
7+
Notation wise we are trying to solve for `x` such that `nlfunc(x) = 0`.
8+
9+
## Case I: Sparse Jacobian Prototype is Provided
10+
11+
Let's say you have a Sparse Jacobian Prototype `jac_prototype`, in this case you can
12+
create your problem as follows:
13+
14+
```julia
15+
prob = NonlinearProblem(NonlinearFunction(nlfunc; sparsity = jac_prototype), x0)
16+
# OR
17+
prob = NonlinearProblem(NonlinearFunction(nlfunc; jac_prototype = jac_prototype), x0)
18+
```
19+
20+
NonlinearSolve will automatically perform matrix coloring and use sparse differentiation.
21+
22+
Now you can help the solver further by providing the color vector. This can be done by
23+
24+
```julia
25+
prob = NonlinearProblem(NonlinearFunction(nlfunc; sparsity = jac_prototype,
26+
colorvec = colorvec), x0)
27+
# OR
28+
prob = NonlinearProblem(NonlinearFunction(nlfunc; jac_prototype = jac_prototype,
29+
colorvec = colorvec), x0)
30+
```
31+
32+
!!! note
33+
34+
One thing to be careful about in this case is that `colorvec` is dependent on the
35+
autodiff backend used. Forward Mode and Finite Differencing will assume that the
36+
colorvec is the column colorvec, while Reverse Mode will assume that the colorvec is the
37+
row colorvec.
38+
39+
## Case II: Sparsity Detection algorithm is provided
40+
41+
If you don't have a Sparse Jacobian Prototype, but you know the which sparsity detection
42+
algorithm you want to use, then you can create your problem as follows:
43+
44+
```julia
45+
prob = NonlinearProblem(NonlinearFunction(nlfunc; sparsity = SymbolicsSparsityDetection()),
46+
x0) # Remember to have Symbolics.jl loaded
47+
# OR
48+
prob = NonlinearProblem(NonlinearFunction(nlfunc; sparsity = ApproximateJacobianSparsity()),
49+
x0)
50+
```
51+
52+
These Detection Algorithms are from [SparseDiffTools.jl](https://github.com/JuliaDiff/SparseDiffTools.jl),
53+
refer to the documentation there for more details.
54+
55+
## Case III: Sparse AD Type is being Used
56+
57+
If you constructed a Nonlinear Solver with a sparse AD type, for example
58+
59+
```julia
60+
NewtonRaphson(; autodiff = AutoSparseForwardDiff())
61+
# OR
62+
TrustRegion(; autodiff = AutoSparseZygote())
63+
```
64+
65+
then NonlinearSolve will automatically perform matrix coloring and use sparse
66+
differentiation if none of `sparsity` or `jac_prototype` is provided. If `Symbolics.jl` is
67+
loaded, we default to using `SymbolicsSparsityDetection()`, else we default to using
68+
`ApproximateJacobianSparsity()`.
69+
70+
`Case I/II` take precedence for sparsity detection and we perform sparse AD based on those
71+
options if those are provided.
72+
73+
!!! warning
74+
75+
If you provide a non-sparse AD, and provide a `sparsity` or `jac_prototype` then
76+
we will use dense AD. This is because, if you provide a specific AD type, we assume
77+
that you know what you are doing and want to override the default choice of `nothing`.

docs/src/tutorials/large_systems.md

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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
63+
using NonlinearSolve, LinearAlgebra, SparseArrays, LinearSolve, SparseDiffTools
6464
6565
const N = 32
6666
const xyd_brusselator = range(0, stop = 1, length = N)
@@ -100,7 +100,8 @@ function init_brusselator_2d(xyd)
100100
end
101101
102102
u0 = init_brusselator_2d(xyd_brusselator)
103-
prob_brusselator_2d = NonlinearProblem(brusselator_2d_loop, u0, p)
103+
prob_brusselator_2d = NonlinearProblem(brusselator_2d_loop, u0, p; abstol = 1e-10,
104+
reltol = 1e-10)
104105
```
105106

106107
## Choosing Jacobian Types
@@ -127,8 +128,10 @@ include:
127128

128129
In the next section, we will discuss how to declare a sparse Jacobian and how to use
129130
[Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl), to compute exact sparse
130-
jacobians. For this to work it is important to not load `Symbolics.jl`. This is triggered
131-
if you pass in a sparse autodiff type such as `AutoSparseForwardDiff()`.
131+
jacobians. This is triggered if you pass in a sparse autodiff type such as
132+
`AutoSparseForwardDiff()`. If `Symbolics.jl` is loaded, then the default changes to
133+
Symbolic Sparsity Detection. See the manual entry on
134+
[Sparsity Detection](@ref sparsity-detection) for more details on the default.
132135

133136
```@example ill_conditioned_nlprob
134137
using BenchmarkTools # for @btime
@@ -223,6 +226,7 @@ used in the solution of the ODE. An example of this with using
223226

224227
```@example ill_conditioned_nlprob
225228
using IncompleteLU
229+
226230
function incompletelu(W, du, u, p, t, newW, Plprev, Prprev, solverdata)
227231
if newW === nothing || newW
228232
Pl = ilu(W, τ = 50.0)
@@ -257,6 +261,7 @@ which is more automatic. The setup is very similar to before:
257261

258262
```@example ill_conditioned_nlprob
259263
using AlgebraicMultigrid
264+
260265
function algebraicmultigrid(W, du, u, p, t, newW, Plprev, Prprev, solverdata)
261266
if newW === nothing || newW
262267
Pl = aspreconditioner(ruge_stuben(convert(AbstractMatrix, W)))
@@ -293,5 +298,22 @@ end
293298
nothing # hide
294299
```
295300

301+
## Let's compare the Sparsity Detection Methods
302+
303+
We benchmarked the solvers before with approximate and exact sparsity detection. However,
304+
for the exact sparsity detection case, we left out the time it takes to perform exact
305+
sparsity detection. Let's compare the two by setting the sparsity detection algorithms.
306+
307+
```@example ill_conditioned_nlprob
308+
prob_brusselator_2d_exact = NonlinearProblem(NonlinearFunction(brusselator_2d_loop;
309+
sparsity = SymbolicsSparsityDetection()), u0, p; abstol = 1e-10, reltol = 1e-10)
310+
prob_brusselator_2d_approx = NonlinearProblem(NonlinearFunction(brusselator_2d_loop;
311+
sparsity = ApproximateJacobianSparsity()), u0, p; abstol = 1e-10, reltol = 1e-10)
312+
313+
@btime solve(prob_brusselator_2d_exact, NewtonRaphson());
314+
@btime solve(prob_brusselator_2d_approx, NewtonRaphson());
315+
nothing # hide
316+
```
317+
296318
For more information on the preconditioner interface, see the
297319
[linear solver documentation](https://docs.sciml.ai/LinearSolve/stable/basics/Preconditioners/).

src/jacobian.jl

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,27 @@ function sparsity_detection_alg(f, ad::AbstractSparseADType)
1515
if is_extension_loaded(Val(:Symbolics))
1616
return SymbolicsSparsityDetection()
1717
else
18-
@warn "Symbolics.jl is not loaded and sparse AD mode $(ad) is being used. \
19-
Using approximate sparsity detection using ForwardDiff. This can \
20-
potentially fail or generate incorrect sparsity pattern for \
21-
complicated problems. Use with caution." maxlog=1
2218
return ApproximateJacobianSparsity()
2319
end
2420
else
2521
jac_prototype = f.jac_prototype
2622
end
27-
else
23+
elseif f.sparsity isa SparseDiffTools.AbstractSparsityDetection
24+
if f.jac_prototype === nothing
25+
return f.sparsity
26+
else
27+
jac_prototype = f.jac_prototype
28+
end
29+
elseif f.sparsity isa AbstractMatrix
2830
jac_prototype = f.sparsity
31+
elseif f.jac_prototype isa AbstractMatrix
32+
jac_prototype = f.jac_prototype
33+
else
34+
error("`sparsity::typeof($(typeof(f.sparsity)))` & \
35+
`jac_prototype::typeof($(typeof(f.jac_prototype)))` is not supported. \
36+
Use `sparsity::AbstractMatrix` or `sparsity::AbstractSparsityDetection` or \
37+
set to `nothing`. `jac_prototype` can be set to `nothing` or an \
38+
`AbstractMatrix`.")
2939
end
3040

3141
if SciMLBase.has_colorvec(f)

src/utils.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ LazyArrays.applied_axes(::typeof(__zero), x) = axes(x)
384384
end
385385
return pinv(A)
386386
end
387+
@inline __safe_inv(A::SparseMatrixCSC) = __safe_inv(Matrix(A))
387388

388389
LazyArrays.applied_eltype(::typeof(__safe_inv), x) = eltype(x)
389390
LazyArrays.applied_ndims(::typeof(__safe_inv), x) = ndims(x)

0 commit comments

Comments
 (0)