Skip to content

Commit 49c186f

Browse files
authored
Merge pull request #245 from JuliaMath/teh/fix_#244
Avoid numerical-precision failures of bounds checking (fixes #244)
2 parents 659215c + b771d67 commit 49c186f

File tree

12 files changed

+140
-191
lines changed

12 files changed

+140
-191
lines changed

src/Interpolations.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,18 @@ count_interp_dims(it::Type{IT}, n) where IT<:Tuple{Vararg{InterpolationType,N}}
129129
_count_interp_dims(c + count_interp_dims(IT1), args...)
130130
_count_interp_dims(c) = c
131131

132+
"""
133+
BoundsCheckStyle(itp)
134+
135+
A trait to determine dispatch of bounds-checking for `itp`.
136+
Can return `NeedsCheck()`, in which case bounds-checking is performed, or `CheckWillPass()`
137+
in which case the check will return `true`.
138+
"""
139+
abstract type BoundsCheckStyle end
140+
struct NeedsCheck <: BoundsCheckStyle end
141+
struct CheckWillPass <: BoundsCheckStyle end
142+
143+
BoundsCheckStyle(itp) = NeedsCheck()
132144

133145
"""
134146
wi = WeightedIndex(indexes, weights)
@@ -386,6 +398,23 @@ import Base: getindex
386398
itp(i)
387399
end
388400

401+
@inline checkbounds(::Type{Bool}, itp::AbstractInterpolation, x::Vararg{ExpandedIndexTypes,N}) where N =
402+
_checkbounds(BoundsCheckStyle(itp), itp, x)
403+
404+
_checkbounds(::CheckWillPass, itp, x) = true
405+
_checkbounds(::NeedsCheck, itp, x) = checklubounds(lbounds(itp), ubounds(itp), x)
406+
407+
checklubounds(ls, us, xs) = _checklubounds(true, ls, us, xs)
408+
_checklubounds(tf::Bool, ls, us, xs::Tuple{Number, Vararg{Any}}) =
409+
_checklubounds(tf & (ls[1] <= xs[1] <= us[1]), Base.tail(ls), Base.tail(us), Base.tail(xs))
410+
_checklubounds(tf::Bool, ls, us, xs::Tuple{AbstractVector, Vararg{Any}}) =
411+
_checklubounds(tf & all(ls[1] .<= xs[1] .<= us[1]), Base.tail(ls), Base.tail(us), Base.tail(xs))
412+
_checklubounds(tf::Bool, ::Tuple{}, ::Tuple{}, ::Tuple{}) = tf
413+
414+
maybe_clamp(itp, xs) = maybe_clamp(BoundsCheckStyle(itp), itp, xs)
415+
maybe_clamp(::NeedsCheck, itp, xs) = clamp.(xs, lbounds(itp), ubounds(itp))
416+
maybe_clamp(::CheckWillPass, itp, xs) = xs
417+
389418
include("nointerp/nointerp.jl")
390419
include("b-splines/b-splines.jl")
391420
include("gridded/gridded.jl")

src/b-splines/indexing.jl

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ end
2222
reshape(ret, shape(wis...))
2323
end
2424

25-
@inline function gradient(itp::BSplineInterpolation{T,N}, x::Vararg{Number,N}) where {T,N}
25+
@propagate_inbounds function gradient(itp::BSplineInterpolation{T,N}, x::Vararg{Number,N}) where {T,N}
2626
@boundscheck checkbounds(Bool, itp, x...) || Base.throw_boundserror(itp, x)
2727
wis = weightedindexes((value_weights, gradient_weights), itpinfo(itp)..., x)
2828
SVector(map(inds->itp.coefs[inds...], wis))
@@ -31,7 +31,7 @@ end
3131
dest .= gradient(itp, x...)
3232
end
3333

34-
@inline function hessian(itp::BSplineInterpolation{T,N}, x::Vararg{Number,N}) where {T,N}
34+
@propagate_inbounds function hessian(itp::BSplineInterpolation{T,N}, x::Vararg{Number,N}) where {T,N}
3535
@boundscheck checkbounds(Bool, itp, x...) || Base.throw_boundserror(itp, x)
3636
wis = weightedindexes((value_weights, gradient_weights, hessian_weights), itpinfo(itp)..., x)
3737
symmatrix(map(inds->itp.coefs[inds...], wis))
@@ -40,16 +40,6 @@ end
4040
dest .= hessian(itp, x...)
4141
end
4242

43-
checkbounds(::Type{Bool}, itp::AbstractInterpolation, x::Vararg{ExpandedIndexTypes,N}) where N =
44-
checklubounds(lbounds(itp), ubounds(itp), x)
45-
46-
checklubounds(ls, us, xs) = _checklubounds(true, ls, us, xs)
47-
_checklubounds(tf::Bool, ls, us, xs::Tuple{Number, Vararg{Any}}) =
48-
_checklubounds(tf & (ls[1] <= xs[1] <= us[1]), Base.tail(ls), Base.tail(us), Base.tail(xs))
49-
_checklubounds(tf::Bool, ls, us, xs::Tuple{AbstractVector, Vararg{Any}}) =
50-
_checklubounds(tf & all(ls[1] .<= xs[1] .<= us[1]), Base.tail(ls), Base.tail(us), Base.tail(xs))
51-
_checklubounds(tf::Bool, ::Tuple{}, ::Tuple{}, ::Tuple{}) = tf
52-
5343
# Leftovers from AbstractInterpolation
5444
@inline function (itp::BSplineInterpolation)(x::Vararg{UnexpandedIndexTypes})
5545
itp(to_indices(itp, x)...)

src/extrapolation/extrapolation.jl

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const ExtrapDimSpec = Union{BoundaryCondition,Tuple{Vararg{Union{BoundaryConditi
1515
etptype(::Extrapolation{T,N,ITPT,IT,ET}) where {T,N,ITPT,IT,ET} = ET
1616
etpflag(etp::Extrapolation{T,N,ITPT,IT,ET}) where {T,N,ITPT,IT,ET} = etp.et
1717

18+
BoundsCheckStyle(etp::AbstractExtrapolation) = CheckWillPass()
19+
1820
"""
1921
`extrapolate(itp, scheme)` adds extrapolation behavior to an interpolation object, according to the provided scheme.
2022
@@ -35,11 +37,12 @@ extrapolate(itp::AbstractInterpolation{T,N,IT}, et::ET) where {T,N,IT,ET<:Extrap
3537

3638
count_interp_dims(::Type{<:Extrapolation{T,N,ITPT}}, n) where {T,N,ITPT} = count_interp_dims(ITPT, n)
3739

38-
@inline function (etp::Extrapolation{T,N})(x::Vararg{Number,N}) where {T,N}
40+
@propagate_inbounds function (etp::Extrapolation{T,N})(x::Vararg{Number,N}) where {T,N}
3941
itp = parent(etp)
4042
eflag = etpflag(etp)
4143
xs = inbounds_position(eflag, bounds(itp), x, etp, x)
42-
extrapolate_value(eflag, skip_flagged_nointerp(itp, x), skip_flagged_nointerp(itp, xs), Tuple(gradient(itp, xs...)), itp(xs...))
44+
g = @inbounds gradient(itp, xs...)
45+
extrapolate_value(eflag, skip_flagged_nointerp(itp, x), skip_flagged_nointerp(itp, xs), Tuple(g), @inbounds(itp(xs...)))
4346
end
4447
@inline function (etp::Extrapolation{T,N})(x::Vararg{Union{Number,AbstractVector},N}) where {T,N}
4548
itp = parent(etp)
@@ -58,7 +61,7 @@ end
5861
else
5962
eflag = tcollect(etpflag, etp)
6063
xs = inbounds_position(eflag, bounds(itp), x, etp, x)
61-
g = gradient(itp, xs...)
64+
g = @inbounds gradient(itp, xs...)
6265
skipni = t->skip_flagged_nointerp(itp, t)
6366
SVector(extrapolate_gradient.(skipni(eflag), skipni(x), skipni(xs), Tuple(g)))
6467
end
@@ -127,7 +130,7 @@ end
127130
periodic(y, l, u) = mod(y-l, u-l) + l
128131

129132

130-
function extrapolate_value(eflag, x, xs, g, val)
133+
@inline function extrapolate_value(eflag, x, xs, g, val)
131134
val = extrapolate_axis(getfirst(eflag), x[1], xs[1], g[1], val)
132135
extrapolate_value(getrest(eflag), Base.tail(x), Base.tail(xs), Base.tail(g), val)
133136
end

src/gridded/constant.jl

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

src/gridded/gridded.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ end
6969

7070
lbounds(itp::GriddedInterpolation) = first.(itp.knots)
7171
ubounds(itp::GriddedInterpolation) = last.(itp.knots)
72+
lbound(ax::AbstractVector, gr::Gridded) = lbound(ax, degree(gr))
73+
ubound(ax::AbstractVector, gr::Gridded) = ubound(ax, degree(gr))
7274

73-
include("constant.jl")
74-
include("linear.jl")
7575
include("indexing.jl")

src/gridded/linear.jl

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

src/scaling/scaling.jl

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ end
99

1010
Base.parent(A::ScaledInterpolation) = A.itp
1111
count_interp_dims(::Type{<:ScaledInterpolation{T,N,ITPT}}, n) where {T,N,ITPT} = count_interp_dims(ITPT, n)
12+
BoundsCheckStyle(sitp::ScaledInterpolation) = BoundsCheckStyle(sitp.itp)
1213

1314
"""
1415
`scale(itp, xs, ys, ...)` scales an existing interpolation object to allow for indexing using other coordinate axes than unit ranges, by wrapping the interpolation object and transforming the indices from the provided axes onto unit ranges upon indexing.
@@ -58,9 +59,10 @@ lbound(ax::AbstractRange, ::DegreeBC, ::OnGrid) = first(ax)
5859
ubound(ax::AbstractRange, ::DegreeBC, ::OnGrid) = last(ax)
5960

6061
# For (), we scale the evaluation point
61-
function (sitp::ScaledInterpolation{T,N})(xs::Vararg{Number,N}) where {T,N}
62-
xl = coordslookup(itpflag(sitp.itp), sitp.ranges, xs)
63-
sitp.itp(xl...)
62+
@propagate_inbounds function (sitp::ScaledInterpolation{T,N})(xs::Vararg{Number,N}) where {T,N}
63+
@boundscheck (checkbounds(Bool, sitp, xs...) || Base.throw_boundserror(sitp, xs))
64+
xl = maybe_clamp(sitp.itp, coordslookup(itpflag(sitp.itp), sitp.ranges, xs))
65+
@inbounds sitp.itp(xl...)
6466
end
6567
@inline function (sitp::ScaledInterpolation)(x::Vararg{UnexpandedIndexTypes})
6668
xis = to_indices(sitp, x)
@@ -71,7 +73,6 @@ end
7173
(sitp::ScaledInterpolation{T,1}, x::Number, y::Int) where {T} = y == 1 ? sitp(x) : Base.throw_boundserror(sitp, (x, y))
7274

7375
@inline function (itp::ScaledInterpolation{T,N})(x::Vararg{Union{Number,AbstractVector},N}) where {T,N}
74-
# @boundscheck (checkbounds(Bool, itp, x...) || Base.throw_boundserror(itp, x))
7576
[itp(i...) for i in Iterators.product(x...)]
7677
end
7778

@@ -96,8 +97,9 @@ basetype(::Type{ScaledInterpolation{T,N,ITPT,IT,RT}}) where {T,N,ITPT,IT,RT} = I
9697
basetype(sitp::ScaledInterpolation) = basetype(typeof(sitp))
9798

9899

99-
function gradient(sitp::ScaledInterpolation{T,N}, xs::Vararg{Number,N}) where {T,N}
100-
xl = coordslookup(itpflag(sitp.itp), sitp.ranges, xs)
100+
@propagate_inbounds function gradient(sitp::ScaledInterpolation{T,N}, xs::Vararg{Number,N}) where {T,N}
101+
@boundscheck (checkbounds(Bool, sitp, xs...) || Base.throw_boundserror(sitp, xs))
102+
xl = maybe_clamp(sitp.itp, coordslookup(itpflag(sitp.itp), sitp.ranges, xs))
101103
g = gradient(sitp.itp, xl...)
102104
SVector(rescale_gradient_components(itpflag(sitp.itp), sitp.ranges, Tuple(g)))
103105
end

test/extrapolation/non1.jl

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
1-
module ExtrapNon1
2-
31
using Test, Interpolations, OffsetArrays
42

5-
f(x) = sin((x-3)*2pi/9 - 1)
6-
xinds = -3:6
7-
A = OffsetArray(Float64[f(x) for x in xinds], xinds)
8-
itpg = interpolate(A, BSpline(Linear()))
3+
@testset "ExtrapNon1" begin
94

10-
schemes = (
11-
Flat,
12-
Line,
13-
Reflect,
14-
Periodic
15-
)
5+
f(x) = sin((x-3)*2pi/9 - 1)
6+
xinds = -3:6
7+
A = OffsetArray(Float64[f(x) for x in xinds], xinds)
8+
itpg = interpolate(A, BSpline(Linear()))
169

17-
for etp in map(E -> extrapolate(itpg, E()), schemes), x in xinds
18-
@test parent(etp) === itpg
19-
@test @inferred(getindex(etp, x)) A[x]
20-
end
10+
schemes = (
11+
Flat,
12+
Line,
13+
Reflect,
14+
Periodic
15+
)
2116

22-
g(y) = (y/100)^3
23-
yinds = 2:5
24-
A = OffsetArray(Float64[f(x)*g(y) for x in xinds, y in yinds], xinds, yinds)
25-
itp2 = interpolate(A, BSpline(Linear()))
17+
for etp in map(E -> extrapolate(itpg, E()), schemes), x in xinds
18+
@test parent(etp) === itpg
19+
@test @inferred(getindex(etp, x)) A[x]
20+
end
2621

27-
for (etp2,E) in map(E -> (extrapolate(itp2, E()), E), schemes)
28-
@test parent(etp2) === itp2
29-
E == Periodic && continue # g isn't periodic
30-
for y in yinds, x in xinds
31-
@test @inferred(getindex(etp2, x, y)) A[x, y]
22+
g(y) = (y/100)^3
23+
yinds = 2:5
24+
A = OffsetArray(Float64[f(x)*g(y) for x in xinds, y in yinds], xinds, yinds)
25+
itp2 = interpolate(A, BSpline(Linear()))
26+
27+
for (etp2,E) in map(E -> (extrapolate(itp2, E()), E), schemes)
28+
@test parent(etp2) === itp2
29+
E == Periodic && continue # g isn't periodic
30+
for y in yinds, x in xinds
31+
@test @inferred(getindex(etp2, x, y)) A[x, y]
32+
end
3233
end
33-
end
3434

3535
end

test/extrapolation/runtests.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ using Test
9595
targeterr = ArgumentError("cannot create a filled extrapolation with a type Line, use a value of this type instead (e.g., Line())")
9696
@test_throws targeterr extrapolate(itp, Line)
9797

98+
# Issue #244
99+
xs = range(1e-2, stop = 8.3, length = 3)
100+
ys = sort(rand(3))
101+
itp = LinearInterpolation(xs, ys, extrapolation_bc = Flat())
102+
@test itp(8.3) ys[end]
103+
98104
include("type-stability.jl")
99105
include("non1.jl")
100106
end

0 commit comments

Comments
 (0)