Skip to content

Commit 1c5bb59

Browse files
committed
Start moving termination conditions to solve kwargs
1 parent 2763d65 commit 1c5bb59

File tree

12 files changed

+109
-51
lines changed

12 files changed

+109
-51
lines changed

docs/pages.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
pages = ["index.md",
44
"Getting Started with Nonlinear Rootfinding in Julia" => "tutorials/getting_started.md",
5-
"Tutorials" => Any[
6-
"Code Optimization for Small Nonlinear Systems" => "tutorials/code_optimization.md",
5+
"Tutorials" => Any["Code Optimization for Small Nonlinear Systems" => "tutorials/code_optimization.md",
76
"Handling Large Ill-Conditioned and Sparse Systems" => "tutorials/large_systems.md",
87
"Symbolic System Definition and Acceleration via ModelingToolkit" => "tutorials/modelingtoolkit.md",
98
"tutorials/small_compile.md",

docs/src/tutorials/code_optimization.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Take for example a prototypical small nonlinear solver code in its out-of-place
3434
```@example small_opt
3535
using NonlinearSolve
3636
37-
function f(u, p)
37+
function f(u, p)
3838
u .* u .- p
3939
end
4040
u0 = [1.0, 1.0]
@@ -54,7 +54,7 @@ using BenchmarkTools
5454
Note that this way of writing the function is a shorthand for:
5555

5656
```@example small_opt
57-
function f(u, p)
57+
function f(u, p)
5858
[u[1] * u[1] - p, u[2] * u[2] - p]
5959
end
6060
```
@@ -69,25 +69,25 @@ In order to avoid this issue, we can use a non-allocating "in-place" approach. W
6969
this looks like:
7070

7171
```@example small_opt
72-
function f(du, u, p)
72+
function f(du, u, p)
7373
du[1] = u[1] * u[1] - p
7474
du[2] = u[2] * u[2] - p
7575
nothing
7676
end
7777
7878
prob = NonlinearProblem(f, u0, p)
79-
@btime sol = solve(prob, NewtonRaphson())
79+
@btime sol = solve(prob, NewtonRaphson())
8080
```
8181

8282
Notice how much faster this already runs! We can make this code even simpler by using
8383
the `.=` in-place broadcasting.
8484

8585
```@example small_opt
86-
function f(du, u, p)
86+
function f(du, u, p)
8787
du .= u .* u .- p
8888
end
8989
90-
@btime sol = solve(prob, NewtonRaphson())
90+
@btime sol = solve(prob, NewtonRaphson())
9191
```
9292

9393
## Further Optimizations for Small Nonlinear Solves with Static Arrays and SimpleNonlinearSolve
@@ -140,7 +140,7 @@ want to use the out-of-place allocating form, but this time we want to output
140140
a static array. Doing it with broadcasting looks like:
141141

142142
```@example small_opt
143-
function f_SA(u, p)
143+
function f_SA(u, p)
144144
u .* u .- p
145145
end
146146
u0 = SA[1.0, 1.0]
@@ -153,7 +153,7 @@ Note that only change here is that `u0` is made into a StaticArray! If we needed
153153
for a more complex nonlinear case, then we'd simply do the following:
154154

155155
```@example small_opt
156-
function f_SA(u, p)
156+
function f_SA(u, p)
157157
SA[u[1] * u[1] - p, u[2] * u[2] - p]
158158
end
159159
@@ -170,4 +170,4 @@ which are designed for these small-scale static examples. Let's now use `SimpleN
170170
@btime solve(prob, SimpleNewtonRaphson())
171171
```
172172

173-
And there we go, around 100ns from our starting point of almost 6μs!
173+
And there we go, around 100ns from our starting point of almost 6μs!

docs/src/tutorials/large_systems.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# [Efficiently Solving Large Sparse Ill-Conditioned Nonlinear Systems in Julia](@id large_systems)
22

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

88
## Definition of the Brusselator Equation
@@ -182,8 +182,8 @@ nothing # hide
182182

183183
Notice that this acceleration does not require the definition of a sparsity
184184
pattern, and can thus be an easier way to scale for large problems. For more
185-
information on linear solver choices, see the
186-
[linear solver documentation](https://docs.sciml.ai/DiffEqDocs/stable/features/linear_nonlinear/#linear_nonlinear).
185+
information on linear solver choices, see the
186+
[linear solver documentation](https://docs.sciml.ai/DiffEqDocs/stable/features/linear_nonlinear/#linear_nonlinear).
187187
`linsolve` choices are any valid [LinearSolve.jl](https://linearsolve.sciml.ai/dev/) solver.
188188

189189
!!! note

docs/src/tutorials/modelingtoolkit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,4 @@ sol[u5]
120120

121121
If you're interested in building models in a component or block based form, such as seen in systems like Simulink or Modelica,
122122
take a deeper look at [ModelingToolkit.jl's documentation](https://docs.sciml.ai/ModelingToolkit/stable/) which goes into
123-
detail on such features.
123+
detail on such features.

docs/src/tutorials/small_compile.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@ sol = solve(prob, SimpleNewtonRaphson())
1919

2020
However, there are a few downsides to SimpleNonlinearSolve's `SimpleX` style algorithms to note:
2121

22-
1. SimpleNonlinearSolve.jl's methods are not hooked into the LinearSolve.jl system, and thus do not have
23-
the ability to specify linear solvers, use sparse matrices, preconditioners, and all of the other features
24-
which are required to scale for very large systems of equations.
25-
2. SimpleNonlinearSolve.jl's methods have less robust error handling and termination conditions, and thus
26-
these methods are missing some flexibility and give worse hints for debugging.
27-
3. SimpleNonlinearSolve.jl's methods are focused on out-of-place support. There is some in-place support,
28-
but it's designed for simple smaller systems and so some optimizations are missing.
22+
1. SimpleNonlinearSolve.jl's methods are not hooked into the LinearSolve.jl system, and thus do not have
23+
the ability to specify linear solvers, use sparse matrices, preconditioners, and all of the other features
24+
which are required to scale for very large systems of equations.
25+
2. SimpleNonlinearSolve.jl's methods have less robust error handling and termination conditions, and thus
26+
these methods are missing some flexibility and give worse hints for debugging.
27+
3. SimpleNonlinearSolve.jl's methods are focused on out-of-place support. There is some in-place support,
28+
but it's designed for simple smaller systems and so some optimizations are missing.
2929

3030
However, the major upsides of SimpleNonlinearSolve.jl are:
3131

32-
1. The methods are optimized and non-allocating on StaticArrays
33-
2. The methods are minimal in compilation
32+
1. The methods are optimized and non-allocating on StaticArrays
33+
2. The methods are minimal in compilation
3434

3535
As such, you can use the code as shown above to have very low startup with good methods, but for more scaling and debuggability
3636
we recommend the full NonlinearSolve.jl. But that said,
@@ -51,4 +51,4 @@ is not only sufficient but optimal.
5151

5252
Julia has tools for building small binaries via static compilation with [StaticCompiler.jl](https://github.com/tshort/StaticCompiler.jl).
5353
However, these tools are currently limited to type-stable non-allocating functions. That said, SimpleNonlinearSolve.jl's solvers are
54-
precisely the subset of NonlinearSolve.jl which are compatible with static compilation.
54+
precisely the subset of NonlinearSolve.jl which are compatible with static compilation.

src/NonlinearSolve.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const AbstractSparseADType = Union{ADTypes.AbstractSparseFiniteDifferences,
2626
ADTypes.AbstractSparseForwardMode, ADTypes.AbstractSparseReverseMode}
2727

2828
abstract type AbstractNonlinearSolveAlgorithm <: AbstractNonlinearAlgorithm end
29-
abstract type AbstractNewtonAlgorithm{CJ, AD, TC} <: AbstractNonlinearSolveAlgorithm end
29+
abstract type AbstractNewtonAlgorithm{CJ, AD} <: AbstractNonlinearSolveAlgorithm end
3030

3131
abstract type AbstractNonlinearSolveCache{iip} end
3232

src/gaussnewton.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ for large-scale and numerically-difficult nonlinear least squares problems.
3636
Jacobian-Free version of `GaussNewton` doesn't work yet, and it forces jacobian
3737
construction. This will be fixed in the near future.
3838
"""
39-
@concrete struct GaussNewton{CJ, AD, TC} <: AbstractNewtonAlgorithm{CJ, AD, TC}
39+
@concrete struct GaussNewton{CJ, AD, TC} <: AbstractNewtonAlgorithm{CJ, AD}
4040
ad::AD
4141
linsolve
4242
precs

src/levenberg.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ numerically-difficult nonlinear systems.
7575
`DᵀD` to prevent the damping from being too small. Defaults to `1e-8`.
7676
"""
7777
@concrete struct LevenbergMarquardt{CJ, AD, T, TC <: NLSolveTerminationCondition} <:
78-
AbstractNewtonAlgorithm{CJ, AD, TC}
78+
AbstractNewtonAlgorithm{CJ, AD}
7979
ad::AD
8080
linsolve
8181
precs
@@ -157,7 +157,7 @@ end
157157
end
158158

159159
function SciMLBase.__init(prob::Union{NonlinearProblem{uType, iip},
160-
NonlinearLeastSquaresProblem{uType, iip}}, alg_::LevenbergMarquardt,
160+
NonlinearLeastSquaresProblem{uType, iip}}, alg_::LevenbergMarquardt,
161161
args...; alias_u0 = false, maxiters = 1000, abstol = nothing, reltol = nothing,
162162
internalnorm = DEFAULT_NORM,
163163
linsolve_kwargs = (;), kwargs...) where {uType, iip}

src/raphson.jl

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,31 +30,26 @@ for large-scale and numerically-difficult nonlinear systems.
3030
which means that no line search is performed. Algorithms from `LineSearches.jl` can be
3131
used here directly, and they will be converted to the correct `LineSearch`.
3232
"""
33-
@concrete struct NewtonRaphson{CJ, AD, TC <: NLSolveTerminationCondition} <:
34-
AbstractNewtonAlgorithm{CJ, AD, TC}
33+
@concrete struct NewtonRaphson{CJ, AD} <:
34+
AbstractNewtonAlgorithm{CJ, AD}
3535
ad::AD
3636
linsolve
3737
precs
3838
linesearch
39-
termination_condition::TC
4039
end
4140

4241
function set_ad(alg::NewtonRaphson{CJ}, ad) where {CJ}
4342
return NewtonRaphson{CJ}(ad, alg.linsolve, alg.precs, alg.linesearch)
4443
end
4544

4645
function NewtonRaphson(; concrete_jac = nothing, linsolve = nothing,
47-
linesearch = LineSearch(), precs = DEFAULT_PRECS,
48-
termination_condition = NLSolveTerminationCondition(NLSolveTerminationMode.NLSolveDefault;
49-
abstol = nothing,
50-
reltol = nothing), adkwargs...)
46+
linesearch = LineSearch(), precs = DEFAULT_PRECS, adkwargs...)
5147
ad = default_adargs_to_adtype(; adkwargs...)
5248
linesearch = linesearch isa LineSearch ? linesearch : LineSearch(; method = linesearch)
5349
return NewtonRaphson{_unwrap_val(concrete_jac)}(ad,
5450
linsolve,
5551
precs,
56-
linesearch,
57-
termination_condition)
52+
linesearch)
5853
end
5954

6055
@concrete mutable struct NewtonRaphsonCache{iip} <: AbstractNonlinearSolveCache{iip}
@@ -79,11 +74,13 @@ end
7974
prob
8075
stats::NLStats
8176
lscache
77+
termination_condition
8278
tc_storage
8379
end
8480

8581
function SciMLBase.__init(prob::NonlinearProblem{uType, iip}, alg_::NewtonRaphson, args...;
8682
alias_u0 = false, maxiters = 1000, abstol = nothing, reltol = nothing,
83+
termination_condition = nothing,
8784
internalnorm = DEFAULT_NORM,
8885
linsolve_kwargs = (;), kwargs...) where {uType, iip}
8986
alg = get_concrete_algorithm(alg_, prob)
@@ -93,27 +90,28 @@ function SciMLBase.__init(prob::NonlinearProblem{uType, iip}, alg_::NewtonRaphso
9390
uf, linsolve, J, fu2, jac_cache, du = jacobian_caches(alg, f, u, p, Val(iip);
9491
linsolve_kwargs)
9592

96-
tc = alg.termination_condition
97-
mode = DiffEqBase.get_termination_mode(tc)
93+
abstol, reltol, termination_condition = _init_termination_elements(abstol,
94+
reltol,
95+
termination_condition,
96+
eltype(u))
9897

99-
atol = _get_tolerance(abstol, tc.abstol, eltype(u))
100-
rtol = _get_tolerance(reltol, tc.reltol, eltype(u))
98+
mode = DiffEqBase.get_termination_mode(termination_condition)
10199

102100
storage = mode DiffEqBase.SAFE_TERMINATION_MODES ? NLSolveSafeTerminationResult() :
103101
nothing
104102

105103
return NewtonRaphsonCache{iip}(f, alg, u, copy(u), fu1, fu2, du, p, uf, linsolve, J,
106-
jac_cache, false, maxiters, internalnorm, ReturnCode.Default, atol, rtol, prob,
104+
jac_cache, false, maxiters, internalnorm, ReturnCode.Default, abstol, reltol, prob,
107105
NLStats(1, 0, 0, 0, 0), LineSearchCache(alg.linesearch, f, u, p, fu1, Val(iip)),
108-
storage)
106+
termination_condition, storage)
109107
end
110108

111109
function perform_step!(cache::NewtonRaphsonCache{true})
112110
@unpack u, u_prev, fu1, f, p, alg, J, linsolve, du = cache
113111
jacobian!!(J, cache)
114112

115113
tc_storage = cache.tc_storage
116-
termination_condition = cache.alg.termination_condition(tc_storage)
114+
termination_condition = cache.termination_condition(tc_storage)
117115

118116
# u = u - J \ fu
119117
linres = dolinsolve(alg.precs, linsolve; A = J, b = _vec(fu1), linu = _vec(du),
@@ -140,7 +138,7 @@ function perform_step!(cache::NewtonRaphsonCache{false})
140138
@unpack u, u_prev, fu1, f, p, alg, linsolve = cache
141139

142140
tc_storage = cache.tc_storage
143-
termination_condition = cache.alg.termination_condition(tc_storage)
141+
termination_condition = cache.termination_condition(tc_storage)
144142

145143
cache.J = jacobian!!(cache.J, cache)
146144
# u = u - J \ fu
@@ -169,7 +167,9 @@ function perform_step!(cache::NewtonRaphsonCache{false})
169167
end
170168

171169
function SciMLBase.reinit!(cache::NewtonRaphsonCache{iip}, u0 = cache.u; p = cache.p,
172-
abstol = cache.abstol, maxiters = cache.maxiters) where {iip}
170+
abstol = cache.abstol, reltol = cache.reltol,
171+
termination_condition = cache.termination_condition,
172+
maxiters = cache.maxiters) where {iip}
173173
cache.p = p
174174
if iip
175175
recursivecopy!(cache.u, u0)
@@ -179,7 +179,14 @@ function SciMLBase.reinit!(cache::NewtonRaphsonCache{iip}, u0 = cache.u; p = cac
179179
cache.u = u0
180180
cache.fu1 = cache.f(cache.u, p)
181181
end
182+
183+
termination_condition = _get_reinit_termination_condition(cache,
184+
abstol,
185+
reltol,
186+
termination_condition)
182187
cache.abstol = abstol
188+
cache.reltol = reltol
189+
cache.termination_condition = termination_condition
183190
cache.maxiters = maxiters
184191
cache.stats.nf = 1
185192
cache.stats.nsteps = 1

src/trustRegion.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ for large-scale and numerically-difficult nonlinear systems.
149149
Support for the OOP version is planned!
150150
"""
151151
@concrete struct TrustRegion{CJ, AD, MTR, TC <: NLSolveTerminationCondition} <:
152-
AbstractNewtonAlgorithm{CJ, AD, TC}
152+
AbstractNewtonAlgorithm{CJ, AD}
153153
ad::AD
154154
linsolve
155155
precs

0 commit comments

Comments
 (0)