Skip to content

Commit fb7e767

Browse files
Merge pull request #105 from SciML/sparsedifftools_support
Setup with SparseDiffTools
2 parents 903b483 + edf3fbc commit fb7e767

File tree

8 files changed

+222
-62
lines changed

8 files changed

+222
-62
lines changed

Project.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
1515
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
1616
SimpleNonlinearSolve = "727e6d20-b764-4bd8-a329-72de5adea6c7"
1717
SnoopPrecompile = "66db9d55-30c0-4569-8b51-7e840670fc0c"
18+
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
19+
SparseDiffTools = "47a9eef4-7e08-11e9-0b38-333d64bd3804"
1820
StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"
1921
UnPack = "3a884ed6-31ef-47d7-9d2a-63182c4928ed"
2022

@@ -39,7 +41,8 @@ ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
3941
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
4042
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
4143
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
44+
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
4245
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
4346

4447
[targets]
45-
test = ["BenchmarkTools", "SafeTestsets", "Pkg", "Test", "ForwardDiff", "StaticArrays"]
48+
test = ["BenchmarkTools", "SafeTestsets", "Pkg", "Test", "ForwardDiff", "StaticArrays", "Symbolics"]

docs/src/solvers/NonlinearSystemSolvers.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,18 @@ then `NLSolveJL`'s `:anderson` can be a good choice.
2929

3030
These are the core solvers.
3131

32-
- `NewtonRaphson(;autodiff=true,chunk_size=12,diff_type=Val{:forward},linsolve=DEFAULT_LINSOLVE)`:
32+
- `NewtonRaphson()`:
3333
A Newton-Raphson method with swappable nonlinear solvers and autodiff methods
3434
for high performance on large and sparse systems.
3535

36+
#### Details on Controlling NonlinearSolve.jl Solvers
37+
38+
```julia
39+
NewtonRaphson(; chunk_size = Val{0}(), autodiff = Val{true}(),
40+
standardtag = Val{true}(), concrete_jac = nothing,
41+
diff_type = Val{:forward}, linsolve = nothing, precs = DEFAULT_PRECS)
42+
```
43+
3644
### SimpleNonlinearSolve.jl
3745

3846
These methods are included with NonlinearSolve.jl by default, though SimpleNonlinearSolve.jl

src/NonlinearSolve.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ using RecursiveArrayTools
1010
import ArrayInterfaceCore
1111
import LinearSolve
1212
using DiffEqBase
13+
using SparseDiffTools
1314

1415
@reexport using SciMLBase
1516
@reexport using SimpleNonlinearSolve

src/jacobian.jl

Lines changed: 118 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,144 @@
1-
mutable struct JacobianWrapper{fType, pType}
1+
struct JacobianWrapper{fType, pType}
22
f::fType
33
p::pType
44
end
55

66
(uf::JacobianWrapper)(u) = uf.f(u, uf.p)
77
(uf::JacobianWrapper)(res, u) = uf.f(res, u, uf.p)
88

9-
struct ImmutableJacobianWrapper{fType, pType}
10-
f::fType
11-
p::pType
12-
end
9+
struct NonlinearSolveTag end
1310

14-
(uf::ImmutableJacobianWrapper)(u) = uf.f(u, uf.p)
15-
16-
function calc_J(solver, cache)
17-
@unpack u, f, p, alg = solver
18-
@unpack uf = cache
19-
uf.f = f
20-
uf.p = p
21-
J = jacobian(uf, u, solver)
22-
return J
11+
function sparsity_colorvec(f, x)
12+
sparsity = f.sparsity
13+
colorvec = DiffEqBase.has_colorvec(f) ? f.colorvec :
14+
(isnothing(sparsity) ? (1:length(x)) : matrix_colors(sparsity))
15+
sparsity, colorvec
2316
end
2417

25-
function calc_J(solver, uf::ImmutableJacobianWrapper)
26-
@unpack u, f, p, alg = solver
27-
J = jacobian(uf, u, solver)
28-
return J
18+
function jacobian_finitediff_forward!(J, f, x, jac_config, forwardcache, cache)
19+
(FiniteDiff.finite_difference_jacobian!(J, f, x, jac_config, forwardcache);
20+
maximum(jac_config.colorvec))
21+
end
22+
function jacobian_finitediff!(J, f, x, jac_config, cache)
23+
(FiniteDiff.finite_difference_jacobian!(J, f, x, jac_config);
24+
2 * maximum(jac_config.colorvec))
2925
end
3026

31-
function jacobian(f, x::Number, solver)
32-
if alg_autodiff(solver.alg)
33-
J = ForwardDiff.derivative(f, x)
27+
function jacobian!(J::AbstractMatrix{<:Number}, cache)
28+
f = cache.f
29+
uf = cache.uf
30+
x = cache.u
31+
fx = cache.fu
32+
jac_config = cache.jac_config
33+
alg = cache.alg
34+
35+
if alg_autodiff(alg)
36+
forwarddiff_color_jacobian!(J, uf, x, jac_config)
37+
#cache.destats.nf += 1
3438
else
35-
J = FiniteDiff.finite_difference_derivative(f, x, alg_difftype(solver.alg),
36-
eltype(x))
39+
isforward = alg_difftype(alg) === Val{:forward}
40+
if isforward
41+
uf(fx, x)
42+
#cache.destats.nf += 1
43+
tmp = jacobian_finitediff_forward!(J, uf, x, jac_config, fx,
44+
cache)
45+
else # not forward difference
46+
tmp = jacobian_finitediff!(J, uf, x, jac_config, cache)
47+
end
48+
#cache.destats.nf += tmp
3749
end
38-
return J
50+
nothing
3951
end
4052

41-
function jacobian(f, x, solver)
42-
if alg_autodiff(solver.alg)
43-
J = ForwardDiff.jacobian(f, x)
53+
function build_jac_config(alg, f::F1, uf::F2, du1, u, tmp, du2) where {F1, F2}
54+
haslinsolve = hasfield(typeof(alg), :linsolve)
55+
56+
if !SciMLBase.has_jac(f) && # No Jacobian if has analytical solution
57+
((concrete_jac(alg) === nothing && (!haslinsolve || (haslinsolve && # No Jacobian if linsolve doesn't want it
58+
(alg.linsolve === nothing || LinearSolve.needs_concrete_A(alg.linsolve))))) ||
59+
(concrete_jac(alg) !== nothing && concrete_jac(alg))) # Jacobian if explicitly asked for
60+
jac_prototype = f.jac_prototype
61+
sparsity, colorvec = sparsity_colorvec(f, u)
62+
63+
if alg_autodiff(alg)
64+
_chunksize = get_chunksize(alg) === Val(0) ? nothing : get_chunksize(alg) # SparseDiffEq uses different convection...
65+
66+
T = if standardtag(alg)
67+
typeof(ForwardDiff.Tag(NonlinearSolveTag(), eltype(u)))
68+
else
69+
typeof(ForwardDiff.Tag(uf, eltype(u)))
70+
end
71+
jac_config = ForwardColorJacCache(uf, u, _chunksize; colorvec = colorvec,
72+
sparsity = sparsity, tag = T)
73+
else
74+
if alg_difftype(alg) !== Val{:complex}
75+
jac_config = FiniteDiff.JacobianCache(tmp, du1, du2, alg_difftype(alg),
76+
colorvec = colorvec,
77+
sparsity = sparsity)
78+
else
79+
jac_config = FiniteDiff.JacobianCache(Complex{eltype(tmp)}.(tmp),
80+
Complex{eltype(du1)}.(du1), nothing,
81+
alg_difftype(alg), eltype(u),
82+
colorvec = colorvec,
83+
sparsity = sparsity)
84+
end
85+
end
4486
else
45-
J = FiniteDiff.finite_difference_jacobian(f, x, alg_difftype(solver.alg), eltype(x))
87+
jac_config = nothing
4688
end
47-
return J
89+
jac_config
4890
end
4991

50-
function calc_J!(J, solver, cache)
51-
@unpack f, u, p, alg = solver
52-
@unpack du1, uf, jac_config = cache
92+
function get_chunksize(jac_config::ForwardDiff.JacobianConfig{T, V, N, D}) where {T, V, N, D
93+
}
94+
Val(N)
95+
end # don't degrade compile time information to runtime information
5396

54-
uf.f = f
55-
uf.p = p
56-
57-
jacobian!(J, uf, u, du1, solver, jac_config)
97+
function jacobian_finitediff(f, x, ::Type{diff_type}, dir, colorvec, sparsity,
98+
jac_prototype) where {diff_type}
99+
(FiniteDiff.finite_difference_derivative(f, x, diff_type, eltype(x), dir = dir), 2)
100+
end
101+
function jacobian_finitediff(f, x::AbstractArray, ::Type{diff_type}, dir, colorvec,
102+
sparsity, jac_prototype) where {diff_type}
103+
f_in = diff_type === Val{:forward} ? f(x) : similar(x)
104+
ret_eltype = eltype(f_in)
105+
J = FiniteDiff.finite_difference_jacobian(f, x, diff_type, ret_eltype, f_in,
106+
dir = dir, colorvec = colorvec,
107+
sparsity = sparsity,
108+
jac_prototype = jac_prototype)
109+
return J, _nfcount(maximum(colorvec), diff_type)
58110
end
111+
function jacobian(cache, f::F) where {F}
112+
x = cache.u
113+
alg = cache.alg
114+
uf = cache.uf
115+
local tmp
59116

60-
function jacobian!(J, f, x, fx, solver, jac_config)
61-
alg = solver.alg
62-
if alg_autodiff(alg)
63-
ForwardDiff.jacobian!(J, f, fx, x, jac_config)
117+
if DiffEqBase.has_jac(cache.f)
118+
J = f.jac(cache.u, cache.p)
119+
elseif alg_autodiff(alg)
120+
J, tmp = jacobian_autodiff(uf, x, cache.f, alg)
64121
else
65-
FiniteDiff.finite_difference_jacobian!(J, f, x, jac_config)
122+
jac_prototype = cache.f.jac_prototype
123+
sparsity, colorvec = sparsity_colorvec(cache.f, x)
124+
dir = true
125+
J, tmp = jacobian_finitediff(uf, x, alg_difftype(alg), dir, colorvec, sparsity,
126+
jac_prototype)
66127
end
67-
nothing
128+
J
129+
end
130+
131+
jacobian_autodiff(f, x, nonlinfun, alg) = (ForwardDiff.derivative(f, x), 1, alg)
132+
function jacobian_autodiff(f, x::AbstractArray, nonlinfun, alg)
133+
jac_prototype = nonlinfun.jac_prototype
134+
sparsity, colorvec = sparsity_colorvec(nonlinfun, x)
135+
maxcolor = maximum(colorvec)
136+
chunk_size = get_chunksize(alg) === Val(0) ? nothing : get_chunksize(alg)
137+
num_of_chunks = chunk_size === nothing ?
138+
Int(ceil(maxcolor /
139+
SparseDiffTools.getsize(ForwardDiff.pickchunksize(maxcolor)))) :
140+
Int(ceil(maxcolor / _unwrap_val(chunk_size)))
141+
(forwarddiff_color_jacobian(f, x, colorvec = colorvec, sparsity = sparsity,
142+
jac_prototype = jac_prototype, chunksize = chunk_size),
143+
num_of_chunks)
68144
end

src/raphson.jl

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,24 +66,15 @@ function jacobian_caches(alg::NewtonRaphson, f, u, p, ::Val{true})
6666
Pl = Pl, Pr = Pr)
6767

6868
du1 = zero(u)
69+
du2 = zero(u)
6970
tmp = zero(u)
70-
if alg_autodiff(alg)
71-
jac_config = ForwardDiff.JacobianConfig(uf, du1, u)
72-
else
73-
if alg.diff_type != Val{:complex}
74-
du2 = zero(u)
75-
jac_config = FiniteDiff.JacobianCache(tmp, du1, du2, alg.diff_type)
76-
else
77-
jac_config = FiniteDiff.JacobianCache(Complex{eltype(tmp)}.(tmp),
78-
Complex{eltype(du1)}.(du1), nothing,
79-
alg.diff_type, eltype(u))
80-
end
81-
end
71+
jac_config = build_jac_config(alg, f, uf, du1, u, tmp, du2)
72+
8273
uf, linsolve, J, du1, jac_config
8374
end
8475

8576
function jacobian_caches(alg::NewtonRaphson, f, u, p, ::Val{false})
86-
nothing, nothing, nothing, nothing, nothing
77+
JacobianWrapper(f, p), nothing, nothing, nothing, nothing
8778
end
8879

8980
function SciMLBase.__init(prob::NonlinearProblem{uType, iip}, alg::NewtonRaphson,
@@ -116,10 +107,10 @@ end
116107
function perform_step!(cache::NewtonRaphsonCache{true})
117108
@unpack u, fu, f, p, alg = cache
118109
@unpack J, linsolve, du1 = cache
119-
calc_J!(J, cache, cache)
110+
jacobian!(J, cache)
120111

121112
# u = u - J \ fu
122-
linres = dolinsolve(alg.precs, linsolve, A = J, b = fu, linu = du1,
113+
linres = dolinsolve(alg.precs, linsolve, A = J, b = _vec(fu), linu = _vec(du1),
123114
p = p, reltol = cache.abstol)
124115
cache.linsolve = linres.cache
125116
@. u = u - du1
@@ -133,10 +124,9 @@ end
133124

134125
function perform_step!(cache::NewtonRaphsonCache{false})
135126
@unpack u, fu, f, p = cache
136-
J = calc_J(cache, ImmutableJacobianWrapper(f, p))
127+
J = jacobian(cache, f)
137128
cache.u = u - J \ fu
138-
fu = f(cache.u, p)
139-
cache.fu = fu
129+
cache.fu = f(cache.u, p)
140130
if iszero(cache.fu) || cache.internalnorm(cache.fu) < cache.abstol
141131
cache.force_stop = true
142132
end

src/utils.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,22 @@ function alg_difftype(alg::AbstractNewtonAlgorithm{CS, AD, FDT, ST, CJ}) where {
4848
FDT
4949
end
5050

51+
function concrete_jac(alg::AbstractNewtonAlgorithm{CS, AD, FDT, ST, CJ}) where {CS, AD, FDT,
52+
ST, CJ}
53+
CJ
54+
end
55+
56+
function get_chunksize(alg::AbstractNewtonAlgorithm{CS, AD, FDT, ST, CJ}) where {CS, AD,
57+
FDT,
58+
ST, CJ}
59+
Val(CS)
60+
end
61+
62+
function standardtag(alg::AbstractNewtonAlgorithm{CS, AD, FDT, ST, CJ}) where {CS, AD, FDT,
63+
ST, CJ}
64+
ST
65+
end
66+
5167
DEFAULT_PRECS(W, du, u, p, t, newW, Plprev, Prprev, cachedata) = nothing, nothing
5268

5369
function dolinsolve(precs::P, linsolve; A = nothing, linu = nothing, b = nothing,
@@ -97,3 +113,14 @@ function wrapprecs(_Pl, _Pr, weight)
97113
end
98114
Pl, Pr
99115
end
116+
117+
function _nfcount(N, ::Type{diff_type}) where {diff_type}
118+
if diff_type === Val{:complex}
119+
tmp = N
120+
elseif diff_type === Val{:forward}
121+
tmp = N + 1
122+
else
123+
tmp = 2N
124+
end
125+
tmp
126+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ const is_APPVEYOR = Sys.iswindows() && haskey(ENV, "APPVEYOR")
99

1010
if GROUP == "All" || GROUP == "Core"
1111
@time @safetestset "Basic Tests + Some AD" begin include("basictests.jl") end
12+
@time @safetestset "Sparsity Tests" begin include("sparse.jl") end
1213
end end

test/sparse.jl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using NonlinearSolve, LinearAlgebra, SparseArrays, Symbolics
2+
3+
const N = 32
4+
const xyd_brusselator = range(0, stop = 1, length = N)
5+
brusselator_f(x, y) = (((x - 0.3)^2 + (y - 0.6)^2) <= 0.1^2) * 5.0
6+
limit(a, N) = a == N + 1 ? 1 : a == 0 ? N : a
7+
function brusselator_2d_loop(du, u, p)
8+
A, B, alpha, dx = p
9+
alpha = alpha / dx^2
10+
@inbounds for I in CartesianIndices((N, N))
11+
i, j = Tuple(I)
12+
x, y = xyd_brusselator[I[1]], xyd_brusselator[I[2]]
13+
ip1, im1, jp1, jm1 = limit(i + 1, N), limit(i - 1, N), limit(j + 1, N),
14+
limit(j - 1, N)
15+
du[i, j, 1] = alpha * (u[im1, j, 1] + u[ip1, j, 1] + u[i, jp1, 1] + u[i, jm1, 1] -
16+
4u[i, j, 1]) +
17+
B + u[i, j, 1]^2 * u[i, j, 2] - (A + 1) * u[i, j, 1] +
18+
brusselator_f(x, y)
19+
du[i, j, 2] = alpha * (u[im1, j, 2] + u[ip1, j, 2] + u[i, jp1, 2] + u[i, jm1, 2] -
20+
4u[i, j, 2]) +
21+
A * u[i, j, 1] - u[i, j, 1]^2 * u[i, j, 2]
22+
end
23+
end
24+
p = (3.4, 1.0, 10.0, step(xyd_brusselator))
25+
26+
function init_brusselator_2d(xyd)
27+
N = length(xyd)
28+
u = zeros(N, N, 2)
29+
for I in CartesianIndices((N, N))
30+
x = xyd[I[1]]
31+
y = xyd[I[2]]
32+
u[I, 1] = 22 * (y * (1 - y))^(3 / 2)
33+
u[I, 2] = 27 * (x * (1 - x))^(3 / 2)
34+
end
35+
u
36+
end
37+
u0 = init_brusselator_2d(xyd_brusselator)
38+
prob_brusselator_2d = NonlinearProblem(brusselator_2d_loop, u0, p)
39+
sol = solve(prob_brusselator_2d, NewtonRaphson())
40+
41+
du0 = copy(u0)
42+
jac_sparsity = Symbolics.jacobian_sparsity((du, u) -> brusselator_2d_loop(du, u, p), du0,
43+
u0)
44+
45+
ff = NonlinearFunction(brusselator_2d_loop; jac_prototype = float.(jac_sparsity))
46+
prob_brusselator_2d = NonlinearProblem(ff, u0, p)
47+
sol = solve(prob_brusselator_2d, NewtonRaphson())
48+
@test norm(sol.resid) < 1e-8
49+
50+
sol = solve(prob_brusselator_2d, NewtonRaphson(autodiff = false))
51+
@test norm(sol.resid) < 1e-6
52+
53+
cache = init(prob_brusselator_2d, NewtonRaphson())
54+
@test maximum(cache.jac_config.colorvec) == 12

0 commit comments

Comments
 (0)