Skip to content

Commit bc25e2c

Browse files
authored
Fix bounds errors in default x_abschange and x_relchange (#1222)
* Fix bounds errors in default `x_abschange` and `x_relchange` * Add code comment
1 parent 3e3d33e commit bc25e2c

File tree

4 files changed

+45
-32
lines changed

4 files changed

+45
-32
lines changed

src/Optim.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ include("multivariate/precon.jl") # preconditioning functionality
146146

147147
# utilities
148148
include("utilities/generic.jl") # generic utilities
149-
include("utilities/maxdiff.jl") # find largest difference
150149
include("utilities/update.jl") # trace code
151150

152151
# Unconstrained optimization

src/utilities/assess_convergence.jl

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,26 @@ f_relchange(state::AbstractOptimizerState) = f_relchange(state.f_x, state.f_x_pr
44
f_relchange(f_x, f_x_previous) = abs(f_x - f_x_previous) / abs(f_x)
55

66
x_abschange(state::AbstractOptimizerState) = x_abschange(state.x, state.x_previous)
7-
x_abschange(x, x_previous) = maxdiff(x, x_previous)
7+
x_abschange(x::AbstractArray{<:Number}, x_previous::AbstractArray{<:Number}) = Linfdist(x, x_previous)
88
x_relchange(state::AbstractOptimizerState) = x_relchange(state.x, state.x_previous)
9-
x_relchange(x, x_previous) = maxdiff(x, x_previous) / Base.maximum(abs, x) # Base.maximum !== maximum
9+
x_relchange(x::AbstractArray{<:Number}, x_previous::AbstractArray{<:Number}) = Linfdist(x, x_previous) / Base.maximum(abs, x) # Base.maximum !== maximum
10+
11+
# Copied and adapted from https://github.com/JuliaStats/StatsBase.jl/blob/d70c4a203177c79d851c1cdca450bc6dbd2a4683/src/deviation.jl#L100-L110
12+
# Linfdist(a, b)
13+
#
14+
# Compute the L∞ distance, also called the Chebyshev distance, between
15+
# two arrays: ``\\max_{1≤i≤n} |a_i - b_i|``.
16+
# Efficient equivalent of `maximum(abs, a - b)`.
17+
function Linfdist(x::AbstractArray{<:Number}, y::AbstractArray{<:Number})
18+
n = length(x)
19+
length(y) == n || throw(DimensionMismatch("Inconsistent array lengths."))
20+
if iszero(n)
21+
return zero(abs(zero(eltype(x)) - zero(eltype(y))))
22+
else
23+
broadcasted = Broadcast.broadcasted((xi, yi) -> abs(xi - yi), vec(x), vec(y))
24+
return Base.maximum(Broadcast.instantiate(broadcasted)) # Base.maximum !== maximum
25+
end
26+
end
1027

1128
g_residual(state::NelderMeadState) = state.nm_x
1229
g_residual(state::ZerothOrderState) = oftype(state.f_x, NaN)

src/utilities/maxdiff.jl

Lines changed: 0 additions & 16 deletions
This file was deleted.

test/general/convergence.jl

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
mutable struct DummyState <: Optim.AbstractOptimizerState
2-
x::Any
3-
x_previous::Any
4-
f_x::Any
5-
f_x_previous::Any
6-
g_x::Any
1+
mutable struct DummyState{TX,TF,TG} <: Optim.AbstractOptimizerState
2+
x::TX
3+
x_previous::TX
4+
f_x::TF
5+
f_x_previous::TF
6+
g_x::TG
77
end
88

9-
mutable struct DummyStateZeroth <: Optim.ZerothOrderState
10-
x::Any
11-
x_previous::Any
12-
f_x::Any
13-
f_x_previous::Any
14-
g_x::Any
9+
mutable struct DummyStateZeroth{TX,TF} <: Optim.ZerothOrderState
10+
x::TX
11+
x_previous::TX
12+
f_x::TF
13+
f_x_previous::TF
1514
end
1615

1716
mutable struct DummyOptions
@@ -81,7 +80,7 @@ mutable struct DummyMethodZeroth <: Optim.ZerothOrderOptimizer end
8180
@test Optim.initial_convergence(ds, opt) == (true, false)
8281

8382
# Zeroth order methods have no gradient -> returns false by default
84-
ds = DummyStateZeroth(x1, x0, f1, f0, g)
83+
ds = DummyStateZeroth(x1, x0, f1, f0)
8584
dm = DummyMethodZeroth()
8685

8786
x = ones(2)
@@ -92,4 +91,18 @@ mutable struct DummyMethodZeroth <: Optim.ZerothOrderOptimizer end
9291

9392
# should check all other methods as well
9493

94+
for T in (Float32, Float64)
95+
ds = DummyState(T[-1.3, 2.5, -4.1], T[-1.1, 2.8, -4.0], f_x, f0, zeros(3))
96+
@test @inferred(Optim.x_abschange(ds))::T 0.3
97+
@test iszero((s -> @allocated(Optim.x_abschange(s)))(ds))
98+
@test @inferred(Optim.x_relchange(ds))::T 0.3 / 4.1
99+
@test iszero((s -> @allocated(Optim.x_relchange(s)))(ds))
100+
101+
# Special case: Empty state
102+
ds = DummyState(T[], T[], f_x, f0, empty(g_x))
103+
@test iszero(@inferred(Optim.x_abschange(ds))::T)
104+
@test iszero((s -> @allocated(Optim.x_abschange(s)))(ds))
105+
@test isnan(@inferred(Optim.x_relchange(ds))::T)
106+
@test iszero((s -> @allocated(Optim.x_relchange(s)))(ds))
107+
end
95108
end

0 commit comments

Comments
 (0)