Skip to content

Commit 87fad10

Browse files
committed
feat: SimpleBroyden implementation
1 parent 9c160ef commit 87fad10

File tree

4 files changed

+106
-3
lines changed

4 files changed

+106
-3
lines changed

lib/SimpleNonlinearSolve/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ DifferentiationInterface = "a0c0ee7d-e4b9-4e03-894e-1c5f64a51d63"
1313
FastClosures = "9aa1b823-49e4-5ca5-8b0f-3971ec8bab6a"
1414
FiniteDiff = "6a86dc24-6348-571c-b903-95158fe2bd41"
1515
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
16+
LineSearch = "87fe0de2-c867-4266-b59a-2f0a94fc965b"
1617
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1718
MaybeInplace = "bb5d69b7-63fc-4a16-80bd-7e42200c7bdb"
1819
NonlinearSolveBase = "be0214bd-f91f-a760-ac4e-3421ce2b2da0"

lib/SimpleNonlinearSolve/src/SimpleNonlinearSolve.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module SimpleNonlinearSolve
33
using CommonSolve: CommonSolve, solve
44
using ConcreteStructs: @concrete
55
using FastClosures: @closure
6+
using LineSearch: LiFukushimaLineSearch
67
using LinearAlgebra: dot
78
using MaybeInplace: @bb
89
using PrecompileTools: @compile_workload, @setup_workload
@@ -79,6 +80,7 @@ function solve_adjoint_internal end
7980
prob_oop = NonlinearProblem{false}((u, p) -> u .* u .- p, ones(T, 3), T(2))
8081

8182
algs = [
83+
SimpleBroyden(),
8284
SimpleKlement(),
8385
SimpleNewtonRaphson(),
8486
SimpleTrustRegion()
@@ -100,7 +102,7 @@ export AutoFiniteDiff, AutoForwardDiff, AutoPolyesterForwardDiff
100102

101103
export Alefeld, Bisection, Brent, Falsi, ITP, Ridder
102104

103-
export SimpleKlement
105+
export SimpleBroyden, SimpleKlement
104106
export SimpleGaussNewton, SimpleNewtonRaphson, SimpleTrustRegion
105107

106108
end
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,101 @@
1+
"""
2+
SimpleBroyden(; linesearch = Val(false), alpha = nothing)
13
4+
A low-overhead implementation of Broyden. This method is non-allocating on scalar and static
5+
array problems.
6+
7+
### Keyword Arguments
8+
9+
- `linesearch`: If `linesearch` is `Val(true)`, then we use the `LiFukushimaLineSearch`
10+
line search else no line search is used. For advanced customization of the line search,
11+
use `Broyden` from `NonlinearSolve.jl`.
12+
- `alpha`: Scale the initial jacobian initialization with `alpha`. If it is `nothing`, we
13+
will compute the scaling using `2 * norm(fu) / max(norm(u), true)`.
14+
"""
15+
@concrete struct SimpleBroyden <: AbstractSimpleNonlinearSolveAlgorithm
16+
linesearch <: Union{Val{false}, Val{true}}
17+
alpha
18+
end
19+
20+
function SimpleBroyden(;
21+
linesearch::Union{Bool, Val{true}, Val{false}} = Val(false), alpha = nothing)
22+
linesearch = linesearch isa Bool ? Val(linesearch) : linesearch
23+
return SimpleBroyden(linesearch, alpha)
24+
end
25+
26+
function SciMLBase.__solve(prob::ImmutableNonlinearProblem, alg::SimpleBroyden, args...;
27+
abstol = nothing, reltol = nothing, maxiters = 1000,
28+
alias_u0 = false, termination_condition = nothing, kwargs...)
29+
x = Utils.maybe_unaliased(prob.u0, alias_u0)
30+
fx = Utils.get_fx(prob, x)
31+
fx = Utils.eval_f(prob, fx, x)
32+
T = promote_type(eltype(fx), eltype(x))
33+
34+
iszero(fx) &&
35+
return SciMLBase.build_solution(prob, alg, x, fx; retcode = ReturnCode.Success)
36+
37+
@bb xo = copy(x)
38+
@bb δx = similar(x)
39+
@bb δf = copy(fx)
40+
@bb fprev = copy(fx)
41+
42+
if alg.alpha === nothing
43+
fx_norm = L2_NORM(fx)
44+
x_norm = L2_NORM(x)
45+
init_α = ifelse(fx_norm 1e-5, max(x_norm, T(true)) / (2 * fx_norm), T(true))
46+
else
47+
init_α = inv(alg.alpha)
48+
end
49+
50+
J⁻¹ = Utils.identity_jacobian(fx, x, init_α)
51+
@bb J⁻¹δf = copy(x)
52+
@bb xᵀJ⁻¹ = copy(x)
53+
@bb δJ⁻¹n = copy(x)
54+
@bb δJ⁻¹ = copy(J⁻¹)
55+
56+
abstol, reltol, tc_cache = NonlinearSolveBase.init_termination_cache(
57+
prob, abstol, reltol, fx, x, termination_condition, Val(:simple))
58+
59+
if alg.linesearch === Val(true)
60+
ls_alg = LiFukushimaLineSearch(; nan_maxiters = nothing)
61+
ls_cache = init(prob, ls_alg, fx, x)
62+
else
63+
ls_cache = nothing
64+
end
65+
66+
for _ in 1:maxiters
67+
@bb δx = J⁻¹ × vec(fprev)
68+
@bb δx .*= -1
69+
70+
if ls_cache === nothing
71+
α = true
72+
else
73+
ls_sol = solve!(ls_cache, xo, δx)
74+
α = ls_sol.step_size # Ignores the return code for now
75+
end
76+
77+
@bb @. x = xo + α * δx
78+
fx = Utils.eval_f(prob, fx, x)
79+
@bb @. δf = fx - fprev
80+
81+
# Termination Checks
82+
solved, retcode, fx_sol, x_sol = Utils.check_termination(tc_cache, fx, x, xo, prob)
83+
solved && return SciMLBase.build_solution(prob, alg, x_sol, fx_sol; retcode)
84+
85+
@bb J⁻¹δf = J⁻¹ × vec(δf)
86+
d = dot(δx, J⁻¹δf)
87+
@bb xᵀJ⁻¹ = transpose(J⁻¹) × vec(δx)
88+
89+
@bb @. δJ⁻¹n = (δx - J⁻¹δf) / d
90+
91+
δJ⁻¹n_ = Utils.safe_vec(δJ⁻¹n)
92+
xᵀJ⁻¹_ = Utils.safe_vec(xᵀJ⁻¹)
93+
@bb δJ⁻¹ = δJ⁻¹n_ × transpose(xᵀJ⁻¹_)
94+
@bb J⁻¹ .+= δJ⁻¹
95+
96+
@bb copyto!(xo, x)
97+
@bb copyto!(fprev, fx)
98+
end
99+
100+
return SciMLBase.build_solution(prob, alg, x, fx; retcode = ReturnCode.MaxIters)
101+
end

lib/SimpleNonlinearSolve/src/utils.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ function identity_jacobian(u::Number, fu::Number, α = true)
6262
return convert(promote_type(eltype(u), eltype(fu)), α)
6363
end
6464
function identity_jacobian(u, fu, α = true)
65-
J = safe_similar(u, promote_type(eltype(u), eltype(fu)))
65+
J = safe_similar(u, promote_type(eltype(u), eltype(fu)), length(fu), length(u))
6666
fill!(J, zero(eltype(J)))
6767
J[diagind(J)] .= eltype(J)(α)
6868
return J
6969
end
7070
function identity_jacobian(u::StaticArray, fu, α = true)
71-
return SMatrix{length(fu), length(u), eltype(u)}(I * α)
71+
return SMatrix{length(fu), length(u), promote_type(eltype(fu), eltype(u))}(I * α)
7272
end
7373

7474
identity_jacobian!!(J::Number) = one(J)

0 commit comments

Comments
 (0)