Skip to content

Commit 8b99e6a

Browse files
authored
close #454; some guard on runaway roots (#435)
* close #454; some guard on runaway roots * fix doc
1 parent 4263c76 commit 8b99e6a

File tree

5 files changed

+31
-10
lines changed

5 files changed

+31
-10
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ f (generic function with 1 method)
206206
julia> x0 = 0.1147
207207
0.1147
208208

209-
julia> find_zero(f, x0, Roots.Order1()) 5.075844588445206 # stopped as |f(xₙ)| ≤ |xₙ|ϵ
209+
julia> find_zero(f, x0, Roots.Order5()) 5.936596662527689 # stopped as |f(xₙ)| ≤ |xₙ|ϵ
210210
true
211211

212212
julia> find_zero(f, x0, Roots.Order1(), atol=0.0, rtol=0.0) # error as no check on `|f(xn)|`

src/convergence.jl

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,18 @@ function iszero_Δx(
199199
isapprox(a, b, atol=δₐ, rtol=δᵣ)
200200
end
201201

202+
# test when fconverged to ensure not runawa
203+
function is_small_Δx(M::AbstractUnivariateZeroMethod,
204+
state::AbstractUnivariateZeroState,
205+
options)
206+
δ = abs(state.xn1 - state.xn0)
207+
δₐ, δᵣ = options.xabstol, options.xreltol
208+
Δₓ = max(_unitless(δₐ), _unitless(abs(state.xn1)) * δᵣ)
209+
Δₓ = sqrt(sqrt(sqrt((abs(_unitless(Δₓ)))))) # faster than x^(1/8)
210+
return δ Δₓ
211+
end
212+
213+
202214
isnan_f(M::AbstractBracketingMethod, state) = isnan(state.fxn1) || isnan(state.fxn0)
203215
isnan_f(M::AbstractNonBracketingMethod, state) = isnan(state.fxn1)
204216

@@ -279,6 +291,7 @@ function decide_convergence(
279291
) where {T,S}
280292
xn0, xn1 = state.xn0, state.xn1
281293
fxn1 = state.fxn1
294+
282295
val (:f_converged, :exact_zero, :converged) && return xn1
283296

284297
## XXX this could be problematic
@@ -299,7 +312,8 @@ function decide_convergence(
299312
elseif val == :not_converged
300313
# this is the case where runaway can happen
301314
## XXX Need a good heuristic to catch that
302-
is_approx_zero_f(M, state, options, :relaxed) && return xn1
315+
is_approx_zero_f(M, state, options, :relaxed) &&
316+
is_small_Δx(M, state, options) && return xn1
303317
end
304318
end
305319

src/find_zeros.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ end
107107

108108
# adjust what we mean by x1 ~ x2 for purposes of adding a new zero
109109
function approx_close(z1, z2, xatol, xrtol)
110-
tol = max(sqrt(xatol), max(abs(z1), abs(z2)) * sqrt(xrtol))
111-
abs(z1 - z2) < tol
110+
z₁,z₂,δ,ϵ = _unitless.((z1, z2, xatol, xrtol))
111+
return isapprox(z₁, z₂; atol=sqrt(δ), rtol=sqrt(ϵ))
112112
end
113113

114114
# is proposed not near xs? (false and we add proposed)

test/test_bracketing.jl

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -292,20 +292,23 @@ end
292292
Roots.Bisection(),
293293
]
294294
results = [run_tests((f, b) -> find_zero(f, b, M), name="$M") for M in Ms]
295-
maxfailures = maximum([length(result.failures) for result in results])
296-
maxresidual = maximum([result.maxresidual for result in results])
295+
maxfailures = maximum(length(result.failures) for result in results)
296+
maxresidual = maximum(result.maxresidual for result in results)
297297
cnts = [result.evalcount for result in results]
298+
298299
@test maxfailures == 0
299300
@test maxresidual <= 5e-13
300301
@test avg(cnts) <= 4700
301302

302-
## False position has larger residuals (and failures until maxsteps is increased)
303+
## False position has larger residuals
304+
## Fn #13 fails on numbers 2 and 4 until maxsteps is increased; 100 works
303305
Ms = [Roots.FalsePosition(i) for i in 1:12]
304306
results = [run_tests((f, b) -> find_zero(f, b, M), name="$M") for M in Ms]
305-
maxfailures = maximum([length(result.failures) for result in results])
306-
maxresidual = maximum([result.maxresidual for result in results])
307+
maxfailures = maximum(length(result.failures) for result in results)
308+
maxresidual = maximum(result.maxresidual for result in results)
307309
cnts = [result.evalcount for result in results]
308-
@test maxfailures <= 0
310+
311+
@test maxfailures <= 1
309312
@test maxresidual <= 1e-5
310313
@test avg(cnts) <= 3000
311314

test/test_composable.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ using ForwardDiff
4040
xrts = find_zeros(y, 0s, 10s)
4141
@test length(xrts) == 1
4242
@test xrts[1] 1.886053370668014s
43+
44+
# issue #434
45+
xzs1=find_zeros(x -> cos(x / 1u"m"), -1.6u"m",2u"m")
46+
@test length(xzs1) == 2 && maximum(xzs1) 1.5707963267948966 * u"m"
4347
end
4448

4549
# Polynomials

0 commit comments

Comments
 (0)