Skip to content

Commit e6a0f7d

Browse files
committed
Implement the new scheme for BSpline (and partial for NoInterp)
This makes several major changes: - it eliminates the generated functions and switches emphasis to the value domain rather than the type domain - it uses OffsetArrays to handle the padding. When using interpolate!, the axes may be narrowed to account for the boundary conditions. - now, the axes of the output correspond to the region where we can guarantee that we reconstruct the original array values - switches to the bounds-checking framework in base (may not be working yet)
1 parent 00c34ee commit e6a0f7d

27 files changed

+969
-1020
lines changed

REQUIRE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ WoodburyMatrices 0.1.5
44
Ratios
55
AxisAlgorithms 0.3.0
66
OffsetArrays
7+
StaticArrays

src/Interpolations.jl

Lines changed: 158 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ export
3030
# scaling/scaling.jl
3131

3232
using LinearAlgebra, SparseArrays
33-
using WoodburyMatrices, Ratios, AxisAlgorithms, OffsetArrays
33+
using StaticArrays, WoodburyMatrices, Ratios, AxisAlgorithms, OffsetArrays
3434

35-
import Base: convert, size, axes, getindex, promote_rule,
36-
ndims, eltype, checkbounds
35+
using Base: @propagate_inbounds
36+
import Base: convert, size, axes, promote_rule, ndims, eltype, checkbounds
3737

3838
abstract type Flag end
3939
abstract type InterpolationType <: Flag end
@@ -48,57 +48,183 @@ abstract type AbstractInterpolation{T,N,IT<:DimSpec{InterpolationType},GT<:DimSp
4848
abstract type AbstractInterpolationWrapper{T,N,ITPT,IT,GT} <: AbstractInterpolation{T,N,IT,GT} end
4949
abstract type AbstractExtrapolation{T,N,ITPT,IT,GT} <: AbstractInterpolationWrapper{T,N,ITPT,IT,GT} end
5050

51-
struct Throw <: Flag end
52-
struct Flat <: Flag end
53-
struct Line <: Flag end
54-
struct Free <: Flag end
55-
struct Periodic <: Flag end
56-
struct Reflect <: Flag end
57-
struct InPlace <: Flag end
51+
"""
52+
BoundaryCondition
53+
54+
An abstract type with one of the following values (see the help for each for details):
55+
56+
- `Throw()`
57+
- `Flat()`
58+
- `Line()`
59+
- `Free()`
60+
- `Periodic()`
61+
- `Reflect()`
62+
- `InPlace()`
63+
- `InPlaceQ()`
64+
"""
65+
abstract type BoundaryCondition <: Flag end
66+
struct Throw <: BoundaryCondition end
67+
struct Flat <: BoundaryCondition end
68+
struct Line <: BoundaryCondition end
69+
struct Free <: BoundaryCondition end
70+
struct Periodic <: BoundaryCondition end
71+
struct Reflect <: BoundaryCondition end
72+
struct InPlace <: BoundaryCondition end
5873
# InPlaceQ is exact for an underlying quadratic. This is nice for ground-truth testing
5974
# of in-place (unpadded) interpolation.
60-
struct InPlaceQ <: Flag end
75+
struct InPlaceQ <: BoundaryCondition end
6176
const Natural = Line
6277

63-
@generated size(itp::AbstractInterpolation{T,N}) where {T, N} = Expr(:tuple, [:(size(itp, $i)) for i in 1:N]...)
64-
size(exp::AbstractExtrapolation, d) = size(exp.itp, d)
65-
bounds(itp::AbstractInterpolation{T,N}) where {T,N} = tuple(zip(lbounds(itp), ubounds(itp))...)
66-
bounds(itp::AbstractInterpolation{T,N}, d) where {T,N} = (lbound(itp,d),ubound(itp,d))
67-
@generated lbounds(itp::AbstractInterpolation{T,N}) where {T,N} = Expr(:tuple, [:(lbound(itp, $i)) for i in 1:N]...)
68-
@generated ubounds(itp::AbstractInterpolation{T,N}) where {T,N} = Expr(:tuple, [:(ubound(itp, $i)) for i in 1:N]...)
69-
lbound(itp::AbstractInterpolation{T,N}, d) where {T,N} = 1
70-
ubound(itp::AbstractInterpolation{T,N}, d) where {T,N} = size(itp, d)
78+
Base.IndexStyle(::Type{<:AbstractInterpolation}) = IndexCartesian()
79+
80+
size(itp::AbstractInterpolation) = map(length, itp.parentaxes)
81+
size(exp::AbstractExtrapolation) = size(exp.itp)
82+
axes(itp::AbstractInterpolation) = itp.parentaxes
83+
axes(exp::AbstractExtrapolation) = axes(exp.itp)
84+
85+
bounds(itp::AbstractInterpolation) = map(twotuple, lbounds(itp), ubounds(itp))
86+
twotuple(r::AbstractUnitRange) = (first(r), last(r))
87+
twotuple(x, y) = (x, y)
88+
bounds(itp::AbstractInterpolation, d) = bounds(itp)[d]
89+
lbounds(itp::AbstractInterpolation) = _lbounds(itp.parentaxes, itpflag(itp), gridflag(itp))
90+
ubounds(itp::AbstractInterpolation) = _ubounds(itp.parentaxes, itpflag(itp), gridflag(itp))
91+
_lbounds(axs::NTuple{N,Any}, itp, gt) where N = ntuple(d->lbound(axs[d], iextract(itp,d), iextract(gt,d)), Val(N))
92+
_ubounds(axs::NTuple{N,Any}, itp, gt) where N = ntuple(d->ubound(axs[d], iextract(itp,d), iextract(gt,d)), Val(N))
93+
7194
itptype(::Type{AbstractInterpolation{T,N,IT,GT}}) where {T,N,IT<:DimSpec{InterpolationType},GT<:DimSpec{GridType}} = IT
7295
itptype(::Type{ITP}) where {ITP<:AbstractInterpolation} = itptype(supertype(ITP))
73-
itptype(itp::AbstractInterpolation ) = itptype(typeof(itp))
96+
itptype(itp::AbstractInterpolation) = itptype(typeof(itp))
97+
itpflag(itp::AbstractInterpolation) = itp.it
7498
gridtype(::Type{AbstractInterpolation{T,N,IT,GT}}) where {T,N,IT<:DimSpec{InterpolationType},GT<:DimSpec{GridType}} = GT
7599
gridtype(::Type{ITP}) where {ITP<:AbstractInterpolation} = gridtype(supertype(ITP))
76100
gridtype(itp::AbstractInterpolation) = gridtype(typeof(itp))
101+
gridflag(itp::AbstractInterpolation) = itp.gt
77102
ndims(::Type{AbstractInterpolation{T,N,IT,GT}}) where {T,N,IT<:DimSpec{InterpolationType},GT<:DimSpec{GridType}} = N
78103
ndims(::Type{ITP}) where {ITP<:AbstractInterpolation} = ndims(supertype(ITP))
79104
ndims(itp::AbstractInterpolation) = ndims(typeof(itp))
80105
eltype(::Type{AbstractInterpolation{T,N,IT,GT}}) where {T,N,IT<:DimSpec{InterpolationType},GT<:DimSpec{GridType}} = T
81106
eltype(::Type{ITP}) where {ITP<:AbstractInterpolation} = eltype(supertype(ITP))
82107
eltype(itp::AbstractInterpolation) = eltype(typeof(itp))
83-
count_interp_dims(::Type{T}, N) where {T<:AbstractInterpolation} = N
84108

85-
# Generic indexing methods (fixes #183)
86-
Base.to_index(::AbstractInterpolation, i::Real) = i
109+
"""
110+
n = count_interp_dims(ITP)
111+
112+
Count the number of dimensions along which type `ITP` is interpolating.
113+
`NoInterp` dimensions do not contribute to the sum.
114+
"""
115+
count_interp_dims(::Type{ITP}) where {ITP<:AbstractInterpolation} = count_interp_dims(itptype(ITP), ndims(ITP))
116+
count_interp_dims(::Type{IT}, n) where {IT<:InterpolationType} = n * count_interp_dims(IT)
117+
count_interp_dims(it::Type{IT}, n) where IT<:Tuple{Vararg{InterpolationType,N}} where N =
118+
_count_interp_dims(0, it...)
119+
@inline _count_interp_dims(c, ::IT1, args...) where IT1 =
120+
_count_interp_dims(c + count_interp_dims(IT1), args...)
121+
_count_interp_dims(c) = c
122+
123+
"""
124+
w = value_weights(degree, δx)
125+
126+
Compute the weights for interpolation of the value at an offset `δx` from the "base" position.
127+
`degree` describes the interpolation scheme.
128+
129+
# Example
130+
131+
```jldoctest
132+
julia> Interpolations.value_weights(Linear(), 0.2)
133+
(0.8, 0.2)
134+
```
135+
136+
This corresponds to the fact that linear interpolation at `x + 0.2` is `0.8*y[x] + 0.2*y[x+1]`.
137+
"""
138+
function value_weights end
139+
140+
"""
141+
w = gradient_weights(degree, δx)
142+
143+
Compute the weights for interpolation of the gradient at an offset `δx` from the "base" position.
144+
`degree` describes the interpolation scheme.
145+
146+
# Example
147+
148+
```jldoctest
149+
julia> Interpolations.gradient_weights(Linear(), 0.2)
150+
(-1.0, 1.0)
151+
```
152+
153+
This defines the gradient of a linear interpolation at 3.2 as `y[4] - y[3]`.
154+
"""
155+
function gradient_weights end
156+
157+
"""
158+
w = hessian_weights(degree, δx)
159+
160+
Compute the weights for interpolation of the hessian at an offset `δx` from the "base" position.
161+
`degree` describes the interpolation scheme.
87162
88-
@inline function Base._getindex(::IndexCartesian, A::AbstractInterpolation{T,N}, I::Vararg{Int,N}) where {T,N} # ambiguity resolution
89-
@inbounds r = getindex(A, I...)
90-
r
163+
# Example
164+
165+
```jldoctest
166+
julia> Interpolations.hessian_weights(Linear(), 0.2)
167+
(0.0, 0.0)
168+
```
169+
170+
Linear interpolation uses straight line segments, so the second derivative is zero.
171+
"""
172+
function hessian_weights end
173+
174+
175+
gradient1(itp::AbstractInterpolation{T,1}, x) where {T} = gradient(itp, x)[1]
176+
hessian1(itp::AbstractInterpolation{T,1}, x) where {T} = hessian(itp, x)[1]
177+
178+
### Supporting expansion of CartesianIndex
179+
180+
const UnexpandedIndexTypes = Union{Number, AbstractVector, CartesianIndex}
181+
const ExpandedIndexTypes = Union{Number, AbstractVector}
182+
183+
Base.to_index(::AbstractInterpolation, x::Number) = x
184+
185+
# Commented out because you can't add methods to an abstract type.
186+
# @inline function (itp::AbstractInterpolation)(x::Vararg{UnexpandedIndexTypes})
187+
# itp(to_indices(itp, x))
188+
# end
189+
function gradient(itp::AbstractInterpolation, x::Vararg{UnexpandedIndexTypes})
190+
gradient(itp, to_indices(itp, x))
91191
end
92-
@inline function Base._getindex(::IndexCartesian, A::AbstractInterpolation{T,N}, I::Vararg{Real,N}) where {T,N}
93-
@inbounds r = getindex(A, I...)
94-
r
192+
function gradient!(dest, itp::AbstractInterpolation, x::Vararg{UnexpandedIndexTypes})
193+
gradient!(dest, itp, to_indices(itp, x))
95194
end
195+
function hessian(itp::AbstractInterpolation, x::Vararg{UnexpandedIndexTypes})
196+
hessian(itp, to_indices(itp, x))
197+
end
198+
function hessian!(dest, itp::AbstractInterpolation, x::Vararg{UnexpandedIndexTypes})
199+
hessian!(dest, itp, to_indices(itp, x))
200+
end
201+
202+
# @inline function (itp::AbstractInterpolation)(x::Vararg{ExpandedIndexTypes})
203+
# itp.(Iterators.product(x...))
204+
# end
205+
function gradient(itp::AbstractInterpolation, x::Vararg{ExpandedIndexTypes})
206+
map(y->gradient(itp, y), Iterators.product(x...))
207+
end
208+
function hessian(itp::AbstractInterpolation, x::Vararg{ExpandedIndexTypes})
209+
map(y->hessian(itp, y), Iterators.product(x...))
210+
end
211+
212+
# getindex is supported only for Integer indices (deliberately)
213+
import Base: getindex
214+
@propagate_inbounds getindex(itp::AbstractInterpolation{T,N}, i::Vararg{Integer,N}) where {T,N} = itp(i...)
215+
@inline function getindex(itp::AbstractInterpolation{T,1}, i::Integer, j::Integer) where T
216+
@boundscheck (j == 1 || Base.throw_boundserror(itp, (i, j)))
217+
@inbounds itp(i)
218+
end
219+
220+
# deprecate getindex for other numeric indices
221+
@deprecate getindex(itp::AbstractInterpolation{T,N}, i::Vararg{Number,N}) where {T,N} itp(i...)
96222

97223
include("nointerp/nointerp.jl")
98224
include("b-splines/b-splines.jl")
99-
include("gridded/gridded.jl")
100-
include("extrapolation/extrapolation.jl")
101-
include("scaling/scaling.jl")
225+
# include("gridded/gridded.jl")
226+
# include("extrapolation/extrapolation.jl")
227+
# include("scaling/scaling.jl")
102228
include("utils.jl")
103229
include("io.jl")
104230
include("convenience-constructors.jl")

src/b-splines/b-splines.jl

Lines changed: 34 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ struct BSpline{D<:Degree} <: InterpolationType end
1313
BSpline(::D) where {D<:Degree} = BSpline{D}()
1414

1515
bsplinetype(::Type{BSpline{D}}) where {D<:Degree} = D
16+
bsplinetype(::BS) where {BS<:BSpline} = bsplinetype(BS)
17+
18+
degree(::BSpline{D}) where D<:Degree = D()
19+
degree(::NoInterp) = NoInterp()
1620

1721
struct BSplineInterpolation{T,N,TCoefs<:AbstractArray,IT<:DimSpec{BSpline},GT<:DimSpec{GridType},Axs<:Tuple{Vararg{AbstractUnitRange,N}}} <: AbstractInterpolation{T,N,IT,GT}
1822
coefs::TCoefs
@@ -37,53 +41,44 @@ function BSplineInterpolation(::Type{TWeights}, A::AbstractArray{Tel,N}, it::IT,
3741
BSplineInterpolation{T,N,typeof(A),IT,GT,typeof(axs)}(A, fix_axis.(axs), it, gt)
3842
end
3943

40-
# Utilities for working either with scalars or tuples/tuple-types
41-
iextract(::Type{T}, d) where {T<:BSpline} = T
42-
iextract(t, d) = t.parameters[d]
43-
padding(::Type{BSplineInterpolation{T,N,TCoefs,IT,GT,pad}}) where {T,N,TCoefs,IT,GT,pad} = pad
44-
padding(itp::AbstractInterpolation) = padding(typeof(itp))
45-
padextract(pad::Integer, d) = pad
46-
padextract(pad::Tuple{Vararg{Integer}}, d) = pad[d]
47-
48-
lbound(itp::BSplineInterpolation{T,N,TCoefs,IT,OnGrid}, d::Integer) where {T,N,TCoefs,IT} =
49-
first(axes(itp, d))
50-
ubound(itp::BSplineInterpolation{T,N,TCoefs,IT,OnGrid}, d::Integer) where {T,N,TCoefs,IT} =
51-
last(axes(itp, d))
52-
lbound(itp::BSplineInterpolation{T,N,TCoefs,IT,OnCell}, d::Integer) where {T,N,TCoefs,IT} =
53-
first(axes(itp, d)) - 0.5
54-
ubound(itp::BSplineInterpolation{T,N,TCoefs,IT,OnCell}, d::Integer) where {T,N,TCoefs,IT} =
55-
last(axes(itp, d))+0.5
56-
57-
lbound(itp::BSplineInterpolation{T,N,TCoefs,IT,OnGrid}, d, inds) where {T,N,TCoefs,IT} =
58-
first(inds)
59-
ubound(itp::BSplineInterpolation{T,N,TCoefs,IT,OnGrid}, d, inds) where {T,N,TCoefs,IT} =
60-
last(inds)
61-
lbound(itp::BSplineInterpolation{T,N,TCoefs,IT,OnCell}, d, inds) where {T,N,TCoefs,IT} =
62-
first(inds) - 0.5
63-
ubound(itp::BSplineInterpolation{T,N,TCoefs,IT,OnCell}, d, inds) where {T,N,TCoefs,IT} =
64-
last(inds)+0.5
65-
66-
count_interp_dims(::Type{BSplineInterpolation{T,N,TCoefs,IT,GT,pad}}, n) where {T,N,TCoefs,IT<:DimSpec{InterpolationType},GT<:DimSpec{GridType},pad} = count_interp_dims(IT, n)
67-
68-
function size(itp::BSplineInterpolation{T,N,TCoefs,IT,GT,pad}, d) where {T,N,TCoefs,IT,GT,pad}
69-
d <= N ? size(itp.coefs, d) - 2*padextract(pad, d) : 1
70-
end
44+
coefficients(itp::BSplineInterpolation) = itp.coefs
45+
interpdegree(itp::BSplineInterpolation) = interpdegree(itpflag(itp))
46+
interpdegree(::BSpline{T}) where T = T()
47+
interpdegree(it::Tuple{Vararg{Union{BSpline,NoInterp},N}}) where N = interpdegree.(it)
7148

72-
@inline axes(itp::BSplineInterpolation{T,N,TCoefs,IT,GT,pad}) where {T,N,TCoefs,IT,GT,pad} =
73-
indices_removepad.(axes(itp.coefs), pad)
49+
# The unpadded defaults
50+
lbound(ax, ::BSpline, ::OnCell) = first(ax) - 0.5
51+
ubound(ax, ::BSpline, ::OnCell) = last(ax) + 0.5
52+
lbound(ax, ::BSpline, ::OnGrid) = first(ax)
53+
ubound(ax, ::BSpline, ::OnGrid) = last(ax)
7454

7555
fix_axis(r::Base.OneTo) = r
7656
fix_axis(r::Base.Slice) = r
7757
fix_axis(r::UnitRange) = Base.Slice(r)
7858
fix_axis(r::AbstractUnitRange) = fix_axis(UnitRange(r))
79-
function axes(itp::BSplineInterpolation{T,N,TCoefs,IT,GT,pad}, d) where {T,N,TCoefs,IT,GT,pad}
80-
d <= N ? indices_removepad(axes(itp.coefs, d), padextract(pad, d)) : axes(itp.coefs, d)
81-
end
59+
60+
count_interp_dims(::Type{BSI}, n) where BSI<:BSplineInterpolation = count_interp_dims(itptype(BSI), n)
8261

8362
function interpolate(::Type{TWeights}, ::Type{TC}, A, it::IT, gt::GT) where {TWeights,TC,IT<:DimSpec{BSpline},GT<:DimSpec{GridType}}
8463
Apad = prefilter(TWeights, TC, A, it, gt)
8564
BSplineInterpolation(TWeights, Apad, it, gt, axes(A))
8665
end
66+
67+
"""
68+
itp = interpolate(A, interpmode, gridstyle)
69+
70+
Interpolate an array `A` in the mode determined by `interpmode` and `gridstyle`.
71+
`interpmode` may be one of
72+
73+
- `BSpline(NoInterp())`
74+
- `BSpline(Linear())`
75+
- `BSpline(Quadratic(BC()))` (see [`BoundaryCondition`](@ref))
76+
- `BSpline(Cubic(BC()))`
77+
78+
It may also be a tuple of such values, if you want to use different interpolation schemes along each axis.
79+
80+
`gridstyle` should be one of `OnGrid()` or `OnCell()`.
81+
"""
8782
function interpolate(A::AbstractArray, it::IT, gt::GT) where {IT<:DimSpec{BSpline},GT<:DimSpec{GridType}}
8883
interpolate(tweight(A), tcoef(A), A, it, gt)
8984
end
@@ -109,24 +104,7 @@ function interpolate!(A::AbstractArray, it::IT, gt::GT) where {IT<:DimSpec{BSpli
109104
interpolate!(tweight(A), A, it, gt)
110105
end
111106

112-
offsetsym(off, d) = off == -1 ? Symbol("ixm_", d) :
113-
off == 0 ? Symbol("ix_", d) :
114-
off == 1 ? Symbol("ixp_", d) :
115-
off == 2 ? Symbol("ixpp_", d) : error("offset $off not recognized")
116-
117-
# Ideally we might want to shift the indices symmetrically, but this
118-
# would introduce an inconsistency, so we just append on the right
119-
@inline indices_removepad(inds::Base.OneTo, pad) = Base.OneTo(length(inds) - 2*pad)
120-
@inline indices_removepad(inds, pad) = oftype(inds, first(inds):last(inds) - 2*pad)
121-
@inline indices_addpad(inds::Base.OneTo, pad) = Base.OneTo(length(inds) + 2*pad)
122-
@inline indices_addpad(inds, pad) = oftype(inds, first(inds):last(inds) + 2*pad)
123-
@inline indices_interior(inds, pad) = first(inds)+pad:last(inds)-pad
124-
125-
@static if VERSION < v"0.7.0-DEV.3449"
126-
lut!(dl, d, du) = lufact!(Tridiagonal(dl, d, du), Val{false})
127-
else
128-
lut!(dl, d, du) = lu!(Tridiagonal(dl, d, du), Val(false))
129-
end
107+
lut!(dl, d, du) = lu!(Tridiagonal(dl, d, du), Val(false))
130108

131109
include("constant.jl")
132110
include("linear.jl")
@@ -137,4 +115,5 @@ include("prefiltering.jl")
137115
include("../filter1d.jl")
138116

139117
Base.parent(A::BSplineInterpolation{T,N,TCoefs,UT}) where {T,N,TCoefs,UT<:Union{BSpline{Linear},BSpline{Constant}}} = A.coefs
140-
Base.parent(A::BSplineInterpolation{T,N,TCoefs,UT}) where {T,N,TCoefs,UT} = throw(ArgumentError("The given BSplineInterpolation does not serve as a \"view\" for a parent array. This would only be true for Constant and Linear b-splines."))
118+
Base.parent(A::BSplineInterpolation{T,N,TCoefs,UT}) where {T,N,TCoefs,UT} =
119+
throw(ArgumentError("The given BSplineInterpolation does not serve as a \"view\" for a parent array. This would only be true for Constant and Linear b-splines."))

0 commit comments

Comments
 (0)