Skip to content

Commit 7d49259

Browse files
authored
Merge branch 'master' into u/termination_condition
2 parents 4baf777 + 786e8d3 commit 7d49259

28 files changed

+1804
-150
lines changed

.github/workflows/CI.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ jobs:
1414
test:
1515
runs-on: ubuntu-latest
1616
strategy:
17+
fail-fast: false
1718
matrix:
1819
group:
1920
- All
2021
version:
2122
- '1'
23+
- '~1.10.0-0'
2224
steps:
2325
- uses: actions/checkout@v4
2426
- uses: julia-actions/setup-julia@v1

Project.toml

Lines changed: 5 additions & 4 deletions
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 = "2.3.0"
4+
version = "2.5.0"
55

66
[deps]
77
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
@@ -44,13 +44,13 @@ FiniteDiff = "2"
4444
ForwardDiff = "0.10.3"
4545
LeastSquaresOptim = "0.8"
4646
LineSearches = "7"
47-
LinearSolve = "2"
47+
LinearSolve = "2.12"
4848
NonlinearProblemLibrary = "0.1"
4949
PrecompileTools = "1"
5050
RecursiveArrayTools = "2"
5151
Reexport = "0.2, 1"
5252
SciMLBase = "2.4"
53-
SimpleNonlinearSolve = "0.1.22"
53+
SimpleNonlinearSolve = "0.1.23"
5454
SparseDiffTools = "2.6"
5555
StaticArraysCore = "1.4"
5656
UnPack = "1.0"
@@ -65,6 +65,7 @@ ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
6565
LeastSquaresOptim = "0fc2ff8b-aaa3-5acd-a817-1944a5e08891"
6666
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
6767
LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
68+
NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
6869
NonlinearProblemLibrary = "b7050fa9-e91f-4b37-bcee-a89a063da141"
6970
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
7071
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
@@ -77,4 +78,4 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
7778
DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
7879

7980
[targets]
80-
test = ["Enzyme", "BenchmarkTools", "SafeTestsets", "Pkg", "Test", "ForwardDiff", "StaticArrays", "Symbolics", "LinearSolve", "Random", "LinearAlgebra", "Zygote", "SparseDiffTools", "NonlinearProblemLibrary", "LeastSquaresOptim", "FastLevenbergMarquardt", "DiffEqBase"]
81+
test = ["Enzyme", "BenchmarkTools", "SafeTestsets", "Pkg", "Test", "ForwardDiff", "StaticArrays", "Symbolics", "LinearSolve", "Random", "LinearAlgebra", "Zygote", "SparseDiffTools", "NonlinearProblemLibrary", "LeastSquaresOptim", "FastLevenbergMarquardt", "NaNMath", "DiffEqBase"]

docs/pages.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ pages = ["index.md",
1717
"Solver Summaries and Recommendations" => Any["solvers/NonlinearSystemSolvers.md",
1818
"solvers/BracketingSolvers.md",
1919
"solvers/SteadyStateSolvers.md",
20-
"solvers/NonlinearLeastSquaresSolvers.md"],
20+
"solvers/NonlinearLeastSquaresSolvers.md",
21+
"solvers/LineSearch.md"],
2122
"Detailed Solver APIs" => Any["api/nonlinearsolve.md",
2223
"api/simplenonlinearsolve.md",
2324
"api/minpack.md",

docs/src/api/nonlinearsolve.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ These are the native solvers of NonlinearSolve.jl.
77
```@docs
88
NewtonRaphson
99
TrustRegion
10+
PseudoTransient
11+
DFSane
12+
GeneralBroyden
13+
GeneralKlement
1014
```
1115

1216
## Polyalgorithms

docs/src/solvers/LineSearch.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# [Line Search](@id linesearch)
2+
3+
A convenience wrapper over `LineSearches.jl` and some native Line Search methods, powered
4+
internally with fast automatic differentiation.
5+
6+
```@docs
7+
LineSearch
8+
```
9+
10+
## Native Line Search Methods
11+
12+
```@docs
13+
LiFukushimaLineSearch
14+
```

docs/src/solvers/NonlinearLeastSquaresSolvers.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,8 @@ Solves the nonlinear least squares problem defined by `prob` using the algorithm
1919
handling of sparse matrices via colored automatic differentiation and preconditioned
2020
linear solvers. Designed for large-scale and numerically-difficult nonlinear least squares
2121
problems.
22-
- `SimpleNewtonRaphson()`: Newton Raphson implementation that uses Linear Least Squares
23-
solution at every step to compute the descent direction. **WARNING**: This method is not
24-
a robust solver for nonlinear least squares problems. The computed delta step might not
25-
be the correct descent direction!
22+
- `SimpleNewtonRaphson()`: Simple Gauss Newton Implementation with `QRFactorization` to
23+
solve a linear least squares problem at each step!
2624

2725
## Example usage
2826

docs/src/solvers/NonlinearSystemSolvers.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,26 @@ features, but have a bit of overhead on very small problems.
5555
improvements suggested in the [paper](https://arxiv.org/abs/1201.5885) "Improvements to
5656
the Levenberg-Marquardt algorithm for nonlinear least-squares minimization". Designed for
5757
large-scale and numerically-difficult nonlinear systems.
58+
- `PseudoTransient()`: A pseudo-transient method which mixes the stability of Euler-type
59+
stepping with the convergence speed of a Newton method. Good for highly unstable
60+
systems.
5861
- `RobustMultiNewton()`: A polyalgorithm that mixes highly robust methods (line searches and
5962
trust regions) in order to be as robust as possible for difficult problems. If this method
6063
fails to converge, then one can be pretty certain that most (all?) other choices would
6164
likely fail.
62-
- `FastShortcutNonlinearPolyalg`: The default method. A polyalgorithm that mixes fast methods
65+
- `FastShortcutNonlinearPolyalg()`: The default method. A polyalgorithm that mixes fast methods
6366
with fallbacks to robust methods to allow for solving easy problems quickly without sacrificing
6467
robustnes on the hard problems.
68+
- `GeneralBroyden()`: Generalization of Broyden's Quasi-Newton Method with Line Search and
69+
Automatic Jacobian Resetting. This is a fast method but unstable when the condition number of
70+
the Jacobian matrix is sufficiently large.
71+
- `GeneralKlement()`: Generalization of Klement's Quasi-Newton Method with Line Search and
72+
Automatic Jacobian Resetting. This is a fast method but unstable when the condition number of
73+
the Jacobian matrix is sufficiently large.
74+
- `LimitedMemoryBroyden()`: An advanced version of `LBroyden` which uses a limited memory
75+
Broyden method. This is a fast method but unstable when the condition number of
76+
the Jacobian matrix is sufficiently large. It is recommended to use `GeneralBroyden` or
77+
`GeneralKlement` instead unless the memory usage is a concern.
6578

6679
### SimpleNonlinearSolve.jl
6780

src/NonlinearSolve.jl

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@max_m
55
end
66

77
using DiffEqBase, LinearAlgebra, LinearSolve, SparseArrays, SparseDiffTools
8+
import ArrayInterface: restructure
89
import ForwardDiff
910

1011
import ADTypes: AbstractFiniteDifferencesMode
11-
import ArrayInterface: undefmatrix, matrix_colors, parameterless_type, ismutable
12+
import ArrayInterface: undefmatrix, matrix_colors, parameterless_type, ismutable, issingular
1213
import ConcreteStructs: @concrete
1314
import EnumX: @enumx
1415
import ForwardDiff: Dual
@@ -25,6 +26,8 @@ import UnPack: @unpack
2526
const AbstractSparseADType = Union{ADTypes.AbstractSparseFiniteDifferences,
2627
ADTypes.AbstractSparseForwardMode, ADTypes.AbstractSparseReverseMode}
2728

29+
abstract type AbstractNonlinearSolveLineSearchAlgorithm end
30+
2831
abstract type AbstractNonlinearSolveAlgorithm <: AbstractNonlinearAlgorithm end
2932
abstract type AbstractNewtonAlgorithm{CJ, AD} <: AbstractNonlinearSolveAlgorithm end
3033

@@ -49,10 +52,13 @@ function SciMLBase.solve!(cache::AbstractNonlinearSolveCache)
4952
cache.stats.nsteps += 1
5053
end
5154

52-
if cache.stats.nsteps == cache.maxiters
53-
cache.retcode = ReturnCode.MaxIters
54-
else
55-
cache.retcode = ReturnCode.Success
55+
# The solver might have set a different `retcode`
56+
if cache.retcode == ReturnCode.Default
57+
if cache.stats.nsteps == cache.maxiters
58+
cache.retcode = ReturnCode.MaxIters
59+
else
60+
cache.retcode = ReturnCode.Success
61+
end
5662
end
5763

5864
return SciMLBase.build_solution(cache.prob, cache.alg, cache.u, get_fu(cache);
@@ -67,18 +73,23 @@ include("trustRegion.jl")
6773
include("levenberg.jl")
6874
include("gaussnewton.jl")
6975
include("dfsane.jl")
76+
include("pseudotransient.jl")
77+
include("broyden.jl")
78+
include("klement.jl")
79+
include("lbroyden.jl")
7080
include("jacobian.jl")
7181
include("ad.jl")
7282
include("default.jl")
7383

7484
import PrecompileTools
7585

76-
@static if VERSION >= v"1.10"
86+
@static if VERSION v"1.10-"
7787
PrecompileTools.@compile_workload begin
7888
for T in (Float32, Float64)
7989
prob = NonlinearProblem{false}((u, p) -> u .* u .- p, T(0.1), T(2))
8090

81-
precompile_algs = (NewtonRaphson(), TrustRegion(), LevenbergMarquardt())
91+
precompile_algs = (NewtonRaphson(), TrustRegion(), LevenbergMarquardt(),
92+
PseudoTransient(), GeneralBroyden(), GeneralKlement(), nothing)
8293

8394
for alg in precompile_algs
8495
solve(prob, alg, abstol = T(1e-2))
@@ -95,10 +106,11 @@ end
95106

96107
export RadiusUpdateSchemes
97108

98-
export NewtonRaphson, TrustRegion, LevenbergMarquardt, DFSane, GaussNewton
109+
export NewtonRaphson, TrustRegion, LevenbergMarquardt, DFSane, GaussNewton, PseudoTransient,
110+
GeneralBroyden, GeneralKlement, LimitedMemoryBroyden
99111
export LeastSquaresOptimJL, FastLevenbergMarquardtJL
100112
export RobustMultiNewton, FastShortcutNonlinearPolyalg
101113

102-
export LineSearch
114+
export LineSearch, LiFukushimaLineSearch
103115

104116
end # module

src/broyden.jl

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Sadly `Broyden` is taken up by SimpleNonlinearSolve.jl
2+
"""
3+
GeneralBroyden(; max_resets = 3, linesearch = LineSearch(), reset_tolerance = nothing)
4+
5+
An implementation of `Broyden` with reseting and line search.
6+
7+
## Arguments
8+
9+
- `max_resets`: the maximum number of resets to perform. Defaults to `3`.
10+
- `reset_tolerance`: the tolerance for the reset check. Defaults to
11+
`sqrt(eps(eltype(u)))`.
12+
- `linesearch`: the line search algorithm to use. Defaults to [`LineSearch()`](@ref),
13+
which means that no line search is performed. Algorithms from `LineSearches.jl` can be
14+
used here directly, and they will be converted to the correct `LineSearch`. It is
15+
recommended to use [LiFukushimaLineSearchCache](@ref) -- a derivative free linesearch
16+
specifically designed for Broyden's method.
17+
"""
18+
@concrete struct GeneralBroyden <: AbstractNewtonAlgorithm{false, Nothing}
19+
max_resets::Int
20+
reset_tolerance
21+
linesearch
22+
end
23+
24+
function GeneralBroyden(; max_resets = 3, linesearch = LineSearch(),
25+
reset_tolerance = nothing)
26+
linesearch = linesearch isa LineSearch ? linesearch : LineSearch(; method = linesearch)
27+
return GeneralBroyden(max_resets, reset_tolerance, linesearch)
28+
end
29+
30+
@concrete mutable struct GeneralBroydenCache{iip} <: AbstractNonlinearSolveCache{iip}
31+
f
32+
alg
33+
u
34+
du
35+
fu
36+
fu2
37+
dfu
38+
p
39+
J⁻¹
40+
J⁻¹₂
41+
J⁻¹df
42+
force_stop::Bool
43+
resets::Int
44+
max_resets::Int
45+
maxiters::Int
46+
internalnorm
47+
retcode::ReturnCode.T
48+
abstol
49+
reset_tolerance
50+
reset_check
51+
prob
52+
stats::NLStats
53+
lscache
54+
end
55+
56+
get_fu(cache::GeneralBroydenCache) = cache.fu
57+
58+
function SciMLBase.__init(prob::NonlinearProblem{uType, iip}, alg::GeneralBroyden, args...;
59+
alias_u0 = false, maxiters = 1000, abstol = 1e-6, internalnorm = DEFAULT_NORM,
60+
kwargs...) where {uType, iip}
61+
@unpack f, u0, p = prob
62+
u = alias_u0 ? u0 : deepcopy(u0)
63+
fu = evaluate_f(prob, u)
64+
J⁻¹ = __init_identity_jacobian(u, fu)
65+
reset_tolerance = alg.reset_tolerance === nothing ? sqrt(eps(eltype(u))) :
66+
alg.reset_tolerance
67+
reset_check = x -> abs(x) reset_tolerance
68+
return GeneralBroydenCache{iip}(f, alg, u, _mutable_zero(u), fu, zero(fu),
69+
zero(fu), p, J⁻¹, zero(_reshape(fu, 1, :)), _mutable_zero(u), false, 0,
70+
alg.max_resets, maxiters, internalnorm, ReturnCode.Default, abstol, reset_tolerance,
71+
reset_check, prob, NLStats(1, 0, 0, 0, 0),
72+
init_linesearch_cache(alg.linesearch, f, u, p, fu, Val(iip)))
73+
end
74+
75+
function perform_step!(cache::GeneralBroydenCache{true})
76+
@unpack f, p, du, fu, fu2, dfu, u, J⁻¹, J⁻¹df, J⁻¹₂ = cache
77+
T = eltype(u)
78+
79+
mul!(_vec(du), J⁻¹, -_vec(fu))
80+
α = perform_linesearch!(cache.lscache, u, du)
81+
_axpy!(α, du, u)
82+
f(fu2, u, p)
83+
84+
cache.internalnorm(fu2) < cache.abstol && (cache.force_stop = true)
85+
cache.stats.nf += 1
86+
87+
cache.force_stop && return nothing
88+
89+
# Update the inverse jacobian
90+
dfu .= fu2 .- fu
91+
92+
if all(cache.reset_check, du) || all(cache.reset_check, dfu)
93+
if cache.resets cache.max_resets
94+
cache.retcode = ReturnCode.Unstable
95+
cache.force_stop = true
96+
return nothing
97+
end
98+
fill!(J⁻¹, 0)
99+
J⁻¹[diagind(J⁻¹)] .= T(1)
100+
cache.resets += 1
101+
else
102+
mul!(_vec(J⁻¹df), J⁻¹, _vec(dfu))
103+
mul!(J⁻¹₂, _vec(du)', J⁻¹)
104+
denom = dot(du, J⁻¹df)
105+
du .= (du .- J⁻¹df) ./ ifelse(iszero(denom), T(1e-5), denom)
106+
mul!(J⁻¹, _vec(du), J⁻¹₂, 1, 1)
107+
end
108+
fu .= fu2
109+
110+
return nothing
111+
end
112+
113+
function perform_step!(cache::GeneralBroydenCache{false})
114+
@unpack f, p = cache
115+
T = eltype(cache.u)
116+
117+
cache.du = _restructure(cache.du, cache.J⁻¹ * -_vec(cache.fu))
118+
α = perform_linesearch!(cache.lscache, cache.u, cache.du)
119+
cache.u = cache.u .+ α * cache.du
120+
cache.fu2 = f(cache.u, p)
121+
122+
cache.internalnorm(cache.fu2) < cache.abstol && (cache.force_stop = true)
123+
cache.stats.nf += 1
124+
125+
cache.force_stop && return nothing
126+
127+
# Update the inverse jacobian
128+
cache.dfu = cache.fu2 .- cache.fu
129+
if all(cache.reset_check, cache.du) || all(cache.reset_check, cache.dfu)
130+
if cache.resets cache.max_resets
131+
cache.retcode = ReturnCode.Unstable
132+
cache.force_stop = true
133+
return nothing
134+
end
135+
cache.J⁻¹ = __init_identity_jacobian(cache.u, cache.fu)
136+
cache.resets += 1
137+
else
138+
cache.J⁻¹df = _restructure(cache.J⁻¹df, cache.J⁻¹ * _vec(cache.dfu))
139+
cache.J⁻¹₂ = _vec(cache.du)' * cache.J⁻¹
140+
denom = dot(cache.du, cache.J⁻¹df)
141+
cache.du = (cache.du .- cache.J⁻¹df) ./ ifelse(iszero(denom), T(1e-5), denom)
142+
cache.J⁻¹ = cache.J⁻¹ .+ _vec(cache.du) * cache.J⁻¹₂
143+
end
144+
cache.fu = cache.fu2
145+
146+
return nothing
147+
end
148+
149+
function SciMLBase.reinit!(cache::GeneralBroydenCache{iip}, u0 = cache.u; p = cache.p,
150+
abstol = cache.abstol, maxiters = cache.maxiters) where {iip}
151+
cache.p = p
152+
if iip
153+
recursivecopy!(cache.u, u0)
154+
cache.f(cache.fu, cache.u, p)
155+
else
156+
# don't have alias_u0 but cache.u is never mutated for OOP problems so it doesn't matter
157+
cache.u = u0
158+
cache.fu = cache.f(cache.u, p)
159+
end
160+
cache.abstol = abstol
161+
cache.maxiters = maxiters
162+
cache.stats.nf = 1
163+
cache.stats.nsteps = 1
164+
cache.resets = 0
165+
cache.force_stop = false
166+
cache.retcode = ReturnCode.Default
167+
return cache
168+
end

src/default.jl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,8 @@ end
159159
]
160160
else
161161
[
162-
# FIXME: Broyden and Klement are type unstable
163-
# (upstream SimpleNonlinearSolve.jl issue)
164-
!iip ? :(Klement()) : nothing, # Klement not yet implemented for IIP
165-
!iip ? :(Broyden()) : nothing, # Broyden not yet implemented for IIP
162+
:(GeneralKlement()),
163+
:(GeneralBroyden()),
166164
:(NewtonRaphson(; linsolve, precs, adkwargs...)),
167165
:(NewtonRaphson(; linsolve, precs, linesearch = BackTracking(), adkwargs...)),
168166
:(TrustRegion(; linsolve, precs, adkwargs...)),

0 commit comments

Comments
 (0)