Skip to content
This repository was archived by the owner on May 15, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/SimpleNonlinearSolve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ include("falsi.jl")
include("raphson.jl")
include("ad.jl")
include("broyden.jl")
include("klement.jl")

import SnoopPrecompile

Expand All @@ -46,6 +47,6 @@ SnoopPrecompile.@precompile_all_calls begin for T in (Float32, Float64)
end end

# DiffEq styled algorithms
export Bisection, Broyden, Falsi, SimpleNewtonRaphson
export Bisection, Broyden, Falsi, Klement, SimpleNewtonRaphson

end # module
62 changes: 62 additions & 0 deletions src/klement.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
```julia
Klement()
```

A low-overhead implementation of [Klement](https://jatm.com.br/jatm/article/view/373).
This method is non-allocating on scalar and static array problems.
"""
struct Klement <: AbstractSimpleNonlinearSolveAlgorithm end

function SciMLBase.solve(prob::NonlinearProblem,
alg::Klement, args...; abstol = nothing,
reltol = nothing,
maxiters = 1000, kwargs...)
f = Base.Fix2(prob.f, prob.p)
x = float(prob.u0)
fₙ = f(x)
T = eltype(x)
J = ArrayInterfaceCore.zeromatrix(x) + I
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a fallback. If x is mutable, then we could do J = ArrayInterfaceCore.zeromatrix(x); J[diagind(J)] .= one(eltype(x)).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open an issue.


if SciMLBase.isinplace(prob)
error("Klement currently only supports out-of-place nonlinear problems")
end

atol = abstol !== nothing ? abstol :
real(oneunit(eltype(T))) * (eps(real(one(eltype(T)))))^(4 // 5)
rtol = reltol !== nothing ? reltol : eps(real(one(eltype(T))))^(4 // 5)

xₙ = x
xₙ₋₁ = x
fₙ₋₁ = fₙ
for _ in 1:maxiters
xₙ = xₙ₋₁ - inv(J) * fₙ₋₁
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always use the matrix factorization object to solve.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed that. Instead, just \ here.

Copy link
Member

@YingboMa YingboMa Jan 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or F = lu(J), and the singularity check is then abs(F.factors[end, end]) < singular_tol.

fₙ = f(xₙ)
Δxₙ = xₙ - xₙ₋₁
Δfₙ = fₙ - fₙ₋₁

# Prevent division by 0
denominator = max.(J' .^ 2 * Δxₙ .^ 2, 1e-9)

k = (Δfₙ - J * Δxₙ) ./ denominator
J += (k * Δxₙ' .* J) * J

# Prevent inverting singular matrix
if det(J) ≈ 0
Copy link
Member

@YingboMa YingboMa Jan 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

det(J) ≈ 0 is a bad check for singularity. Consider det(0.1I(1000)).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, by default, x ≈ 0 is true iff x == 0.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this was left for a follow-up: #19

J = ArrayInterfaceCore.zeromatrix(x) + I
Comment on lines +45 to +46
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a little hacky. Is it part of the method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yeah, it's hacky...
I'll have a look at it tomorrow!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's merge and improve.

end

iszero(fₙ) &&
return SciMLBase.build_solution(prob, alg, xₙ, fₙ;
retcode = ReturnCode.Success)

if isapprox(xₙ, xₙ₋₁, atol = atol, rtol = rtol)
return SciMLBase.build_solution(prob, alg, xₙ, fₙ;
retcode = ReturnCode.Success)
end
xₙ₋₁ = xₙ
fₙ₋₁ = fₙ
end

return SciMLBase.build_solution(prob, alg, xₙ, fₙ; retcode = ReturnCode.MaxIters)
end
22 changes: 19 additions & 3 deletions test/basictests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,24 @@ sol = benchmark_scalar(sf, csu0)
@test sol.u * sol.u - 2 < 1e-9
@test (@ballocated benchmark_scalar(sf, csu0)) == 0

# Klement
function benchmark_scalar(f, u0)
probN = NonlinearProblem{false}(f, u0)
sol = (solve(probN, Klement()))
end

sol = benchmark_scalar(sf, csu0)
@test sol.retcode === ReturnCode.Success
@test sol.u * sol.u - 2 < 1e-9
@test (@ballocated benchmark_scalar(sf, csu0)) == 0

# AD Tests
using ForwardDiff

# Immutable
f, u0 = (u, p) -> u .* u .- p, @SVector[1.0, 1.0]

for alg in [SimpleNewtonRaphson(), Broyden()]
for alg in [SimpleNewtonRaphson(), Broyden(), Klement()]
g = function (p)
probN = NonlinearProblem{false}(f, csu0, p)
sol = solve(probN, alg, tol = 1e-9)
Expand All @@ -56,7 +67,7 @@ end

# Scalar
f, u0 = (u, p) -> u * u - p, 1.0
for alg in [SimpleNewtonRaphson(), Broyden()]
for alg in [SimpleNewtonRaphson(), Broyden(), Klement()]
g = function (p)
probN = NonlinearProblem{false}(f, oftype(p, u0), p)
sol = solve(probN, alg)
Expand Down Expand Up @@ -97,7 +108,7 @@ for alg in [Bisection(), Falsi()]
@test ForwardDiff.jacobian(g, p) ≈ ForwardDiff.jacobian(t, p)
end

for alg in [SimpleNewtonRaphson(), Broyden()]
for alg in [SimpleNewtonRaphson(), Broyden(), Klement()]
global g, p
g = function (p)
probN = NonlinearProblem{false}(f, 0.5, p)
Expand All @@ -120,6 +131,9 @@ probN = NonlinearProblem(f, u0)
@test solve(probN, Broyden()).u[end] ≈ sqrt(2.0)
@test solve(probN, Broyden(); immutable = false).u[end] ≈ sqrt(2.0)

@test solve(probN, Klement()).u[end] ≈ sqrt(2.0)
@test solve(probN, Klement(); immutable = false).u[end] ≈ sqrt(2.0)

for u0 in [1.0, [1, 1.0]]
local f, probN, sol
f = (u, p) -> u .* u .- 2.0
Expand All @@ -131,6 +145,8 @@ for u0 in [1.0, [1, 1.0]]
@test solve(probN, SimpleNewtonRaphson(; autodiff = false)).u ≈ sol

@test solve(probN, Broyden()).u ≈ sol

@test solve(probN, Klement()).u ≈ sol
end

# Bisection Tests
Expand Down