From 29d63c559d983c715b50ad6d4f9b8fd10438b8ca Mon Sep 17 00:00:00 2001 From: Fabian Gittins Date: Tue, 9 Apr 2024 20:23:27 +0100 Subject: [PATCH 1/6] Added Muller's method --- src/SimpleNonlinearSolve.jl | 3 +- src/nlsolve/muller.jl | 48 +++++++++++++++++++++++++++++++ test/core/muller_tests.jl | 56 +++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/nlsolve/muller.jl create mode 100644 test/core/muller_tests.jl diff --git a/src/SimpleNonlinearSolve.jl b/src/SimpleNonlinearSolve.jl index 3a4e5eb..b1bea88 100644 --- a/src/SimpleNonlinearSolve.jl +++ b/src/SimpleNonlinearSolve.jl @@ -37,6 +37,7 @@ include("nlsolve/klement.jl") include("nlsolve/trustRegion.jl") include("nlsolve/halley.jl") include("nlsolve/dfsane.jl") +include("nlsolve/muller.jl") ## Interval Nonlinear Solvers include("bracketing/bisection.jl") @@ -115,7 +116,7 @@ end end export SimpleBroyden, SimpleDFSane, SimpleGaussNewton, SimpleHalley, SimpleKlement, - SimpleLimitedMemoryBroyden, SimpleNewtonRaphson, SimpleTrustRegion + SimpleLimitedMemoryBroyden, SimpleNewtonRaphson, SimpleTrustRegion, Muller export Alefeld, Bisection, Brent, Falsi, ITP, Ridder end # module diff --git a/src/nlsolve/muller.jl b/src/nlsolve/muller.jl new file mode 100644 index 0000000..dac6749 --- /dev/null +++ b/src/nlsolve/muller.jl @@ -0,0 +1,48 @@ +""" + Muller() + +Muller's method. +""" +struct Muller <: AbstractSimpleNonlinearSolveAlgorithm end + +function SciMLBase.solve(prob::NonlinearProblem, alg::Muller, args...; + abstol = nothing, maxiters = 1000, kwargs...) + @assert !isinplace(prob) "`Muller` only supports OOP problems." + xᵢ₋₂, xᵢ₋₁, xᵢ = prob.u0 + @assert xᵢ₋₂ ≠ xᵢ₋₁ ≠ xᵢ ≠ xᵢ₋₂ + f = Base.Fix2(prob.f, prob.p) + fxᵢ₋₂, fxᵢ₋₁, fxᵢ = f(xᵢ₋₂), f(xᵢ₋₁), f(xᵢ) + + abstol = __get_tolerance(nothing, abstol, + promote_type(eltype(fxᵢ₋₂), eltype(xᵢ₋₂))) + + for _ ∈ 1:maxiters + q = (xᵢ - xᵢ₋₁)/(xᵢ₋₁ - xᵢ₋₂) + A = q*fxᵢ - q*(1 + q)*fxᵢ₋₁ + q^2*fxᵢ₋₂ + B = (2*q + 1)*fxᵢ - (1 + q)^2*fxᵢ₋₁ + q^2*fxᵢ₋₂ + C = (1 + q)*fxᵢ + + denom₊ = B + √(B^2 - 4*A*C) + denom₋ = B - √(B^2 - 4*A*C) + + if abs(denom₊) ≥ abs(denom₋) + xᵢ₊₁ = xᵢ - (xᵢ - xᵢ₋₁)*2*C/denom₊ + else + xᵢ₊₁ = xᵢ - (xᵢ - xᵢ₋₁)*2*C/denom₋ + end + + fxᵢ₊₁ = f(xᵢ₊₁) + + # Termination Check + if abstol ≥ abs(fxᵢ₊₁) + return build_solution(prob, alg, xᵢ₊₁, fxᵢ₊₁; + retcode = ReturnCode.Success) + end + + xᵢ₋₂, xᵢ₋₁, xᵢ = xᵢ₋₁, xᵢ, xᵢ₊₁ + fxᵢ₋₂, fxᵢ₋₁, fxᵢ = fxᵢ₋₁, fxᵢ, fxᵢ₊₁ + end + + return build_solution(prob, alg, xᵢ₊₁, fxᵢ₊₁; + retcode = ReturnCode.MaxIters) +end diff --git a/test/core/muller_tests.jl b/test/core/muller_tests.jl new file mode 100644 index 0000000..3f43094 --- /dev/null +++ b/test/core/muller_tests.jl @@ -0,0 +1,56 @@ +@testitem "Muller" begin + @testset "Quadratic function" begin + f(u, p) = u^2 - p + + u0 = (10, 20, 30) + p = 612 + prob = NonlinearProblem{false}(f, u0, p) + sol = solve(prob, Muller()) + + @test sol.u ≈ √612 + + u0 = (-10, -20, -30) + prob = NonlinearProblem{false}(f, u0, p) + sol = solve(prob, Muller()) + + @test sol.u ≈ -√612 + end + + @testset "Sine function" begin + f(u, p) = sin(u) + + u0 = (1, 2, 3) + prob = NonlinearProblem{false}(f, u0) + sol = solve(prob, Muller()) + + @test sol.u ≈ π + + u0 = (2, 4, 6) + prob = NonlinearProblem{false}(f, u0) + sol = solve(prob, Muller()) + + @test sol.u ≈ 2*π + end + + @testset "Exponential-sine function" begin + f(u, p) = exp(-u)*sin(u) + + u0 = (-2, -3, -4) + prob = NonlinearProblem{false}(f, u0) + sol = solve(prob, Muller()) + + @test sol.u ≈ -π + + u0 = (-1, 0, 1/2) + prob = NonlinearProblem{false}(f, u0) + sol = solve(prob, Muller()) + + @test sol.u ≈ 0 + + u0 = (-1, 0, 1) + prob = NonlinearProblem{false}(f, u0) + sol = solve(prob, Muller()) + + @test sol.u ≈ π + end +end From 5ddd166f07f279831982c6eb2ed79601ee8e702b Mon Sep 17 00:00:00 2001 From: Fabian Gittins Date: Sat, 20 Apr 2024 21:59:39 +0100 Subject: [PATCH 2/6] Changed name of algorithm to SimpleMuller --- src/SimpleNonlinearSolve.jl | 2 +- src/nlsolve/muller.jl | 8 ++++---- test/core/muller_tests.jl | 16 ++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/SimpleNonlinearSolve.jl b/src/SimpleNonlinearSolve.jl index b1bea88..a9cc09f 100644 --- a/src/SimpleNonlinearSolve.jl +++ b/src/SimpleNonlinearSolve.jl @@ -116,7 +116,7 @@ end end export SimpleBroyden, SimpleDFSane, SimpleGaussNewton, SimpleHalley, SimpleKlement, - SimpleLimitedMemoryBroyden, SimpleNewtonRaphson, SimpleTrustRegion, Muller + SimpleLimitedMemoryBroyden, SimpleNewtonRaphson, SimpleTrustRegion, SimpleMuller export Alefeld, Bisection, Brent, Falsi, ITP, Ridder end # module diff --git a/src/nlsolve/muller.jl b/src/nlsolve/muller.jl index dac6749..00c4400 100644 --- a/src/nlsolve/muller.jl +++ b/src/nlsolve/muller.jl @@ -1,13 +1,13 @@ """ - Muller() + SimpleMuller() Muller's method. """ -struct Muller <: AbstractSimpleNonlinearSolveAlgorithm end +struct SimpleMuller <: AbstractSimpleNonlinearSolveAlgorithm end -function SciMLBase.solve(prob::NonlinearProblem, alg::Muller, args...; +function SciMLBase.solve(prob::NonlinearProblem, alg::SimpleMuller, args...; abstol = nothing, maxiters = 1000, kwargs...) - @assert !isinplace(prob) "`Muller` only supports OOP problems." + @assert !isinplace(prob) "`SimpleMuller` only supports OOP problems." xᵢ₋₂, xᵢ₋₁, xᵢ = prob.u0 @assert xᵢ₋₂ ≠ xᵢ₋₁ ≠ xᵢ ≠ xᵢ₋₂ f = Base.Fix2(prob.f, prob.p) diff --git a/test/core/muller_tests.jl b/test/core/muller_tests.jl index 3f43094..a950456 100644 --- a/test/core/muller_tests.jl +++ b/test/core/muller_tests.jl @@ -1,17 +1,17 @@ -@testitem "Muller" begin +@testitem "SimpleMuller" begin @testset "Quadratic function" begin f(u, p) = u^2 - p u0 = (10, 20, 30) p = 612 prob = NonlinearProblem{false}(f, u0, p) - sol = solve(prob, Muller()) + sol = solve(prob, SimpleMuller()) @test sol.u ≈ √612 u0 = (-10, -20, -30) prob = NonlinearProblem{false}(f, u0, p) - sol = solve(prob, Muller()) + sol = solve(prob, SimpleMuller()) @test sol.u ≈ -√612 end @@ -21,13 +21,13 @@ u0 = (1, 2, 3) prob = NonlinearProblem{false}(f, u0) - sol = solve(prob, Muller()) + sol = solve(prob, SimpleMuller()) @test sol.u ≈ π u0 = (2, 4, 6) prob = NonlinearProblem{false}(f, u0) - sol = solve(prob, Muller()) + sol = solve(prob, SimpleMuller()) @test sol.u ≈ 2*π end @@ -37,19 +37,19 @@ u0 = (-2, -3, -4) prob = NonlinearProblem{false}(f, u0) - sol = solve(prob, Muller()) + sol = solve(prob, SimpleMuller()) @test sol.u ≈ -π u0 = (-1, 0, 1/2) prob = NonlinearProblem{false}(f, u0) - sol = solve(prob, Muller()) + sol = solve(prob, SimpleMuller()) @test sol.u ≈ 0 u0 = (-1, 0, 1) prob = NonlinearProblem{false}(f, u0) - sol = solve(prob, Muller()) + sol = solve(prob, SimpleMuller()) @test sol.u ≈ π end From bad6a774d30df45d44ec3d040323093e4aacf6ee Mon Sep 17 00:00:00 2001 From: Fabian Gittins Date: Sun, 21 Apr 2024 09:04:04 +0100 Subject: [PATCH 3/6] Fixed tests using Floats --- test/core/muller_tests.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/core/muller_tests.jl b/test/core/muller_tests.jl index a950456..fab2fbd 100644 --- a/test/core/muller_tests.jl +++ b/test/core/muller_tests.jl @@ -2,14 +2,14 @@ @testset "Quadratic function" begin f(u, p) = u^2 - p - u0 = (10, 20, 30) - p = 612 + u0 = (10.0, 20.0, 30.0) + p = 612.0 prob = NonlinearProblem{false}(f, u0, p) sol = solve(prob, SimpleMuller()) @test sol.u ≈ √612 - u0 = (-10, -20, -30) + u0 = (-10.0, -20.0, -30.0) prob = NonlinearProblem{false}(f, u0, p) sol = solve(prob, SimpleMuller()) @@ -19,13 +19,13 @@ @testset "Sine function" begin f(u, p) = sin(u) - u0 = (1, 2, 3) + u0 = (1.0, 2.0, 3.0) prob = NonlinearProblem{false}(f, u0) sol = solve(prob, SimpleMuller()) @test sol.u ≈ π - u0 = (2, 4, 6) + u0 = (2.0, 4.0, 6.0) prob = NonlinearProblem{false}(f, u0) sol = solve(prob, SimpleMuller()) @@ -35,19 +35,19 @@ @testset "Exponential-sine function" begin f(u, p) = exp(-u)*sin(u) - u0 = (-2, -3, -4) + u0 = (-2.0, -3.0, -4.0) prob = NonlinearProblem{false}(f, u0) sol = solve(prob, SimpleMuller()) @test sol.u ≈ -π - u0 = (-1, 0, 1/2) + u0 = (-1.0, 0.0, 1/2) prob = NonlinearProblem{false}(f, u0) sol = solve(prob, SimpleMuller()) @test sol.u ≈ 0 - u0 = (-1, 0, 1) + u0 = (-1.0, 0.0, 1.0) prob = NonlinearProblem{false}(f, u0) sol = solve(prob, SimpleMuller()) From 873a6d898985dc339e7f4763d8a9a7d05ba946a9 Mon Sep 17 00:00:00 2001 From: Fabian Gittins Date: Mon, 27 May 2024 17:15:48 +0300 Subject: [PATCH 4/6] Make docstring more detailed --- src/nlsolve/muller.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nlsolve/muller.jl b/src/nlsolve/muller.jl index 00c4400..22b3061 100644 --- a/src/nlsolve/muller.jl +++ b/src/nlsolve/muller.jl @@ -1,7 +1,10 @@ """ SimpleMuller() -Muller's method. +Muller's method for determining a root of a univariate, scalar function. The +algorithm, described in Sec. 9.5.2 of +[Press et al. (2007)](https://numerical.recipes/book.html), requires three +initial guesses `(xᵢ₋₂, xᵢ₋₁, xᵢ)` for the root. """ struct SimpleMuller <: AbstractSimpleNonlinearSolveAlgorithm end From 5d5f6ee0b5dba0e5742abb752268c0415ec233d7 Mon Sep 17 00:00:00 2001 From: Fabian Gittins Date: Mon, 27 May 2024 17:26:35 +0300 Subject: [PATCH 5/6] Add check for three guesses --- src/nlsolve/muller.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nlsolve/muller.jl b/src/nlsolve/muller.jl index 22b3061..d1bf039 100644 --- a/src/nlsolve/muller.jl +++ b/src/nlsolve/muller.jl @@ -11,6 +11,7 @@ struct SimpleMuller <: AbstractSimpleNonlinearSolveAlgorithm end function SciMLBase.solve(prob::NonlinearProblem, alg::SimpleMuller, args...; abstol = nothing, maxiters = 1000, kwargs...) @assert !isinplace(prob) "`SimpleMuller` only supports OOP problems." + @assert length(prob.u0) == 3 "`SimpleMuller` requires three initial guesses." xᵢ₋₂, xᵢ₋₁, xᵢ = prob.u0 @assert xᵢ₋₂ ≠ xᵢ₋₁ ≠ xᵢ ≠ xᵢ₋₂ f = Base.Fix2(prob.f, prob.p) From 9bfaee4f5bcfed852bbec55b8c6c177affa826f8 Mon Sep 17 00:00:00 2001 From: Fabian Gittins Date: Mon, 3 Jun 2024 10:35:10 +0100 Subject: [PATCH 6/6] Fix type-instability --- src/nlsolve/muller.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nlsolve/muller.jl b/src/nlsolve/muller.jl index d1bf039..b05b5f4 100644 --- a/src/nlsolve/muller.jl +++ b/src/nlsolve/muller.jl @@ -13,6 +13,7 @@ function SciMLBase.solve(prob::NonlinearProblem, alg::SimpleMuller, args...; @assert !isinplace(prob) "`SimpleMuller` only supports OOP problems." @assert length(prob.u0) == 3 "`SimpleMuller` requires three initial guesses." xᵢ₋₂, xᵢ₋₁, xᵢ = prob.u0 + xᵢ₋₂, xᵢ₋₁, xᵢ = promote(xᵢ₋₂, xᵢ₋₁, xᵢ) @assert xᵢ₋₂ ≠ xᵢ₋₁ ≠ xᵢ ≠ xᵢ₋₂ f = Base.Fix2(prob.f, prob.p) fxᵢ₋₂, fxᵢ₋₁, fxᵢ = f(xᵢ₋₂), f(xᵢ₋₁), f(xᵢ) @@ -20,6 +21,8 @@ function SciMLBase.solve(prob::NonlinearProblem, alg::SimpleMuller, args...; abstol = __get_tolerance(nothing, abstol, promote_type(eltype(fxᵢ₋₂), eltype(xᵢ₋₂))) + xᵢ₊₁, fxᵢ₊₁ = xᵢ₋₂, fxᵢ₋₂ + for _ ∈ 1:maxiters q = (xᵢ - xᵢ₋₁)/(xᵢ₋₁ - xᵢ₋₂) A = q*fxᵢ - q*(1 + q)*fxᵢ₋₁ + q^2*fxᵢ₋₂