Skip to content

Commit 89bd481

Browse files
Merge pull request #320 from SciML/ap/approx_sparsity
Use approximate sparsity detection by default
2 parents d7ef4af + 540ef5b commit 89bd481

File tree

11 files changed

+165
-14
lines changed

11 files changed

+165
-14
lines changed

Project.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "NonlinearSolve"
22
uuid = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
33
authors = ["SciML"]
4-
version = "3.0.1"
4+
version = "3.0.2"
55

66
[deps]
77
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
@@ -35,6 +35,7 @@ FastLevenbergMarquardt = "7a0df574-e128-4d35-8cbd-3d84502bf7ce"
3535
LeastSquaresOptim = "0fc2ff8b-aaa3-5acd-a817-1944a5e08891"
3636
MINPACK = "4854310b-de5a-5eb6-a2a5-c1dee2bd17f9"
3737
NLsolve = "2774e3e8-f4cf-5e23-947b-6d7e65073b56"
38+
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
3839
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
3940

4041
[extensions]
@@ -43,6 +44,7 @@ NonlinearSolveFastLevenbergMarquardtExt = "FastLevenbergMarquardt"
4344
NonlinearSolveLeastSquaresOptimExt = "LeastSquaresOptim"
4445
NonlinearSolveMINPACKExt = "MINPACK"
4546
NonlinearSolveNLsolveExt = "NLsolve"
47+
NonlinearSolveSymbolicsExt = "Symbolics"
4648
NonlinearSolveZygoteExt = "Zygote"
4749

4850
[compat]

docs/make.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ include("pages.jl")
1010

1111
makedocs(; sitename = "NonlinearSolve.jl",
1212
authors = "Chris Rackauckas",
13-
modules = [NonlinearSolve, SciMLBase, DiffEqBase, SimpleNonlinearSolve, Sundials,
14-
SteadyStateDiffEq],
13+
modules = [NonlinearSolve, SimpleNonlinearSolve, SteadyStateDiffEq, Sundials,
14+
DiffEqBase, SciMLBase],
1515
clean = true, doctest = false, linkcheck = true,
1616
linkcheck_ignore = ["https://twitter.com/ChrisRackauckas/status/1544743542094020615"],
17-
warnonly = [:missing_docs, :cross_references],
17+
warnonly = [:cross_references], checkdocs = :export,
1818
format = Documenter.HTML(assets = ["assets/favicon.ico"],
1919
canonical = "https://docs.sciml.ai/NonlinearSolve/stable/"),
2020
pages)

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/api/nonlinearsolve.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ PseudoTransient
1010
DFSane
1111
Broyden
1212
Klement
13+
LimitedMemoryBroyden
1314
```
1415

1516
## Nonlinear Least Squares Solvers
@@ -50,4 +51,6 @@ RadiusUpdateSchemes.Hei
5051
RadiusUpdateSchemes.Yuan
5152
RadiusUpdateSchemes.Bastin
5253
RadiusUpdateSchemes.Fan
54+
RadiusUpdateSchemes.NLsolve
55+
RadiusUpdateSchemes.NocedalWright
5356
```

docs/src/api/simplenonlinearsolve.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ i.e. `IntervalNonlinearProblem`.
1111

1212
```@docs
1313
ITP
14+
Alefeld
1415
Bisection
1516
Falsi
1617
Ridder

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: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,14 @@ broadcast). Use `dx=dy=1/32`.
6060
The resulting `NonlinearProblem` definition is:
6161

6262
```@example ill_conditioned_nlprob
63-
using NonlinearSolve, LinearAlgebra, SparseArrays, LinearSolve, Symbolics
63+
using NonlinearSolve, LinearAlgebra, SparseArrays, LinearSolve, SparseDiffTools
6464
6565
const N = 32
6666
const xyd_brusselator = range(0, stop = 1, length = N)
67+
6768
brusselator_f(x, y) = (((x - 0.3)^2 + (y - 0.6)^2) <= 0.1^2) * 5.0
6869
limit(a, N) = a == N + 1 ? 1 : a == 0 ? N : a
70+
6971
function brusselator_2d_loop(du, u, p)
7072
A, B, alpha, dx = p
7173
alpha = alpha / dx^2
@@ -96,8 +98,10 @@ function init_brusselator_2d(xyd)
9698
end
9799
u
98100
end
101+
99102
u0 = init_brusselator_2d(xyd_brusselator)
100-
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)
101105
```
102106

103107
## Choosing Jacobian Types
@@ -120,6 +124,27 @@ include:
120124
- BandedMatrix ([BandedMatrices.jl](https://github.com/JuliaLinearAlgebra/BandedMatrices.jl))
121125
- BlockBandedMatrix ([BlockBandedMatrices.jl](https://github.com/JuliaLinearAlgebra/BlockBandedMatrices.jl))
122126

127+
## Approximate Sparsity Detection & Sparse Jacobians
128+
129+
In the next section, we will discuss how to declare a sparse Jacobian and how to use
130+
[Symbolics.jl](https://github.com/JuliaSymbolics/Symbolics.jl), to compute exact sparse
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.
135+
136+
```@example ill_conditioned_nlprob
137+
using BenchmarkTools # for @btime
138+
139+
@btime solve(prob_brusselator_2d, NewtonRaphson());
140+
@btime solve(prob_brusselator_2d, NewtonRaphson(; autodiff = AutoSparseForwardDiff()));
141+
@btime solve(prob_brusselator_2d,
142+
NewtonRaphson(; autodiff = AutoSparseForwardDiff(), linsolve = KLUFactorization()));
143+
@btime solve(prob_brusselator_2d,
144+
NewtonRaphson(; autodiff = AutoSparseForwardDiff(), linsolve = KrylovJL_GMRES()));
145+
nothing # hide
146+
```
147+
123148
## Declaring a Sparse Jacobian with Automatic Sparsity Detection
124149

125150
Jacobian sparsity is declared by the `jac_prototype` argument in the `NonlinearFunction`.
@@ -156,7 +181,6 @@ prob_brusselator_2d_sparse = NonlinearProblem(ff, u0, p)
156181
Now let's see how the version with sparsity compares to the version without:
157182

158183
```@example ill_conditioned_nlprob
159-
using BenchmarkTools # for @btime
160184
@btime solve(prob_brusselator_2d, NewtonRaphson());
161185
@btime solve(prob_brusselator_2d_sparse, NewtonRaphson());
162186
@btime solve(prob_brusselator_2d_sparse, NewtonRaphson(linsolve = KLUFactorization()));
@@ -202,6 +226,7 @@ used in the solution of the ODE. An example of this with using
202226

203227
```@example ill_conditioned_nlprob
204228
using IncompleteLU
229+
205230
function incompletelu(W, du, u, p, t, newW, Plprev, Prprev, solverdata)
206231
if newW === nothing || newW
207232
Pl = ilu(W, τ = 50.0)
@@ -236,6 +261,7 @@ which is more automatic. The setup is very similar to before:
236261

237262
```@example ill_conditioned_nlprob
238263
using AlgebraicMultigrid
264+
239265
function algebraicmultigrid(W, du, u, p, t, newW, Plprev, Prprev, solverdata)
240266
if newW === nothing || newW
241267
Pl = aspreconditioner(ruge_stuben(convert(AbstractMatrix, W)))
@@ -258,10 +284,8 @@ function algebraicmultigrid2(W, du, u, p, t, newW, Plprev, Prprev, solverdata)
258284
if newW === nothing || newW
259285
A = convert(AbstractMatrix, W)
260286
Pl = AlgebraicMultigrid.aspreconditioner(AlgebraicMultigrid.ruge_stuben(A,
261-
presmoother = AlgebraicMultigrid.Jacobi(rand(size(A,
262-
1))),
263-
postsmoother = AlgebraicMultigrid.Jacobi(rand(size(A,
264-
1)))))
287+
presmoother = AlgebraicMultigrid.Jacobi(rand(size(A, 1))),
288+
postsmoother = AlgebraicMultigrid.Jacobi(rand(size(A, 1)))))
265289
else
266290
Pl = Plprev
267291
end
@@ -274,5 +298,22 @@ end
274298
nothing # hide
275299
```
276300

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+
277318
For more information on the preconditioner interface, see the
278319
[linear solver documentation](https://docs.sciml.ai/LinearSolve/stable/basics/Preconditioners/).

ext/NonlinearSolveSymbolicsExt.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module NonlinearSolveSymbolicsExt
2+
3+
import NonlinearSolve, Symbolics
4+
5+
NonlinearSolve.is_extension_loaded(::Val{:Symbolics}) = true
6+
7+
end

src/jacobian.jl

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,30 @@ sparsity_detection_alg(_, _) = NoSparsityDetection()
1212
function sparsity_detection_alg(f, ad::AbstractSparseADType)
1313
if f.sparsity === nothing
1414
if f.jac_prototype === nothing
15-
return SymbolicsSparsityDetection()
15+
if is_extension_loaded(Val(:Symbolics))
16+
return SymbolicsSparsityDetection()
17+
else
18+
return ApproximateJacobianSparsity()
19+
end
1620
else
1721
jac_prototype = f.jac_prototype
1822
end
19-
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
2030
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`.")
2139
end
2240

2341
if SciMLBase.has_colorvec(f)

src/lbroyden.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ An implementation of `LimitedMemoryBroyden` with resetting and line search.
1414
- `linesearch`: the line search algorithm to use. Defaults to [`LineSearch()`](@ref),
1515
which means that no line search is performed. Algorithms from `LineSearches.jl` can be
1616
used here directly, and they will be converted to the correct `LineSearch`. It is
17-
recommended to use [`LiFukushimaLineSearchCache`](@ref) -- a derivative free linesearch
17+
recommended to use [`LiFukushimaLineSearch`](@ref) -- a derivative free linesearch
1818
specifically designed for Broyden's method.
1919
"""
2020
@concrete struct LimitedMemoryBroyden{threshold} <: AbstractNewtonAlgorithm{false, Nothing}

0 commit comments

Comments
 (0)