Skip to content

Commit c5bd734

Browse files
committed
2 parents f2f2163 + 894cce3 commit c5bd734

File tree

8 files changed

+184
-8
lines changed

8 files changed

+184
-8
lines changed

Diff for: docs/src/extrapolation.md

+25
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,28 @@ itp = interpolate(1:7, BSpline(Linear()))
1212
etpf = extrapolate(itp, Flat()) # gives 1 on the left edge and 7 on the right edge
1313
etp0 = extrapolate(itp, 0) # gives 0 everywhere outside [1,7]
1414
```
15+
16+
### Periodic extrapolation
17+
18+
For uniformly sampled periodic data, one can perform periodic extrapolation for all types of
19+
B-Spline interpolations. By using the `Periodic(OnCell())` boundary condition in `interpolate`,
20+
one does not need to include the periodic image of the starting sample point.
21+
22+
Examples:
23+
24+
```julia
25+
f(x) = sin((x-3)*2pi/7 - 1)
26+
A = Float64[f(x) for x in 1:7] # Does not include the periodic image
27+
28+
# Constant(Periodic())) is an alias for Constant{Nearest}(Periodic(OnCell()))
29+
itp0 = interpolate(A, BSpline(Constant(Periodic())))
30+
# Linear(Periodic())) is an alias for Linear(Periodic(OnCell()))
31+
itp1 = interpolate(A, BSpline(Linear(Periodic())))
32+
itp2 = interpolate(A, BSpline(Quadratic(Periodic(OnCell()))))
33+
itp3 = interpolate(A, BSpline(Cubic(Periodic(OnCell()))))
34+
35+
etp0 = extrapolate(itp0, Periodic())
36+
etp1 = extrapolate(itp1, Periodic())
37+
etp2 = extrapolate(itp2, Periodic())
38+
etp3 = extrapolate(itp3, Periodic())
39+
```

Diff for: src/b-splines/b-splines.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,6 @@ include("indexing.jl")
254254
include("prefiltering.jl")
255255
include("../filter1d.jl")
256256

257-
Base.parent(A::BSplineInterpolation{T,N,TCoefs,UT}) where {T,N,TCoefs,UT<:Union{BSpline{Linear},BSpline{<:Constant}}} = A.coefs
257+
Base.parent(A::BSplineInterpolation{T,N,TCoefs,UT}) where {T,N,TCoefs,UT<:Union{BSpline{<:Linear},BSpline{<:Constant}}} = A.coefs
258258
Base.parent(A::BSplineInterpolation{T,N,TCoefs,UT}) where {T,N,TCoefs,UT} =
259259
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."))

Diff for: src/b-splines/constant.jl

+32-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,25 @@ struct Nearest <: ConstantInterpType end
55
struct Previous <: ConstantInterpType end
66
struct Next <: ConstantInterpType end
77

8-
struct Constant{T<:ConstantInterpType} <: Degree{0} end
9-
Constant() = Constant{Nearest}()
8+
struct Constant{T<:ConstantInterpType,BC<:Union{Throw{OnGrid},Periodic{OnCell}}} <: DegreeBC{0}
9+
bc::BC
10+
end
11+
12+
# Default to Nearest and Throw{OnGrid}
13+
Constant(args...) = Constant{Nearest}(args...)
14+
Constant{T}() where {T<:ConstantInterpType} = Constant{T,Throw{OnGrid}}(Throw(OnGrid()))
15+
Constant{T}(bc::BC) where {T<:ConstantInterpType,BC<:BoundaryCondition} = Constant{T,BC}(bc)
16+
Constant{T}(::Periodic{Nothing}) where {T<:ConstantInterpType} = Constant{T,Periodic{OnCell}}(Periodic(OnCell()))
17+
18+
function Base.show(io::IO, deg::Constant)
19+
print(io, nameof(typeof(deg)), '{', typeof(deg).parameters[1], '}', '(')
20+
show(io, deg.bc)
21+
print(io, ')')
22+
end
23+
24+
function Base.show(io::IO, deg::Constant{T,Throw{OnGrid}}) where {T <: ConstantInterpType}
25+
print(io, nameof(typeof(deg)), '{', typeof(deg).parameters[1], '}', '(', ')')
26+
end
1027

1128
"""
1229
Constant b-splines are *nearest-neighbor* interpolations, and effectively
@@ -30,6 +47,19 @@ function positions(c::Constant{Nearest}, ax, x) # discontinuity occurs at half-
3047
fast_trunc(Int, xm), δx
3148
end
3249

50+
function positions(c::Constant{Previous,Periodic{OnCell}}, ax, x)
51+
# We do not use floorbounds because we do not want to add a half at
52+
# the lowerbound to round up.
53+
xm = floor(x)
54+
δx = x - xm
55+
modrange(fast_trunc(Int, xm), ax), δx
56+
end
57+
function positions(c::Constant{Next,Periodic{OnCell}}, ax, x) # discontinuity occurs at integer locations
58+
xm = ceilbounds(x, ax)
59+
δx = x - xm
60+
modrange(fast_trunc(Int, xm), ax), δx
61+
end
62+
3363
value_weights(::Constant, δx) = (1,)
3464
gradient_weights(::Constant, δx) = (0,)
3565
hessian_weights(::Constant, δx) = (0,)

Diff for: src/b-splines/linear.jl

+16-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
struct Linear <: Degree{1} end # boundary conditions not supported
1+
struct Linear{BC<:Union{Throw{OnGrid},Periodic{OnCell}}} <: DegreeBC{1}
2+
bc::BC
3+
end
4+
5+
Linear() = Linear(Throw(OnGrid()))
6+
Linear(::Periodic{Nothing}) = Linear(Periodic(OnCell()))
7+
8+
function Base.show(io::IO, deg::Linear{Throw{OnGrid}})
9+
print(io, nameof(typeof(deg)), '(', ')')
10+
end
211

312
"""
413
Linear()
@@ -27,16 +36,19 @@ a piecewise linear function connecting each pair of neighboring data points.
2736
"""
2837
Linear
2938

30-
function positions(::Linear, ax::AbstractUnitRange{<:Integer}, x)
39+
function positions(deg::Linear, ax::AbstractUnitRange{<:Integer}, x)
3140
f = floor(x)
3241
# When x == last(ax) we want to use the x-1, x pair
3342
f = ifelse(x == last(ax), f - oneunit(f), f)
3443
fi = fast_trunc(Int, f)
35-
return fi, x-f
44+
expand_index(deg, fi, ax), x-f
3645
end
46+
expand_index(::Linear{Throw{OnGrid}}, fi::Number, ax::AbstractUnitRange) = fi
47+
expand_index(::Linear{Periodic{OnCell}}, fi::Number, ax::AbstractUnitRange) =
48+
(modrange(fi, ax), modrange(fi+1, ax))
3749

3850
value_weights(::Linear, δx) = (1-δx, δx)
3951
gradient_weights(::Linear, δx) = (-oneunit(δx), oneunit(δx))
4052
hessian_weights(::Linear, δx) = (zero(δx), zero(δx))
4153

42-
padded_axis(ax::AbstractUnitRange, ::BSpline{Linear}) = ax
54+
padded_axis(ax::AbstractUnitRange, ::BSpline{<:Linear}) = ax

Diff for: test/b-splines/constant.jl

+51-1
Original file line numberDiff line numberDiff line change
@@ -116,5 +116,55 @@
116116
end
117117
end
118118
end
119-
end
120119

120+
@testset "Constant periodic" begin
121+
# Constructors
122+
@test Constant() === Constant(Throw(OnGrid()))
123+
@test Constant() isa Constant{Nearest,Throw{OnGrid}}
124+
@test Constant(Periodic()) === Constant(Periodic(OnCell()))
125+
@test Constant(Periodic()) isa Constant{Nearest,Periodic{OnCell}}
126+
for T in (Nearest, Previous, Next)
127+
it = Constant{T}()
128+
@test it isa Constant{T, Throw{OnGrid}}
129+
@test "$it" == "Constant{$T}()"
130+
it = Constant{T}(Periodic())
131+
@test it isa Constant{T, Periodic{OnCell}}
132+
@test "$it" == "Constant{$T}(Periodic(OnCell()))"
133+
end
134+
135+
for (constructor, copier) in ((interpolate, x -> x), (interpolate!, copy))
136+
isinplace = constructor == interpolate!
137+
itp_periodic = @inferred(constructor(copier(A1), BSpline(Constant(Periodic()))))
138+
itp_previous = @inferred(constructor(copier(A1), BSpline(Constant{Previous}(Periodic()))))
139+
itp_next = @inferred(constructor(copier(A1), BSpline(Constant{Next}(Periodic()))))
140+
141+
for itp in (itp_periodic, itp_previous, itp_next)
142+
@test parent(itp) === itp.coefs
143+
@test all(Interpolations.lbounds(itp) .≈ (0.5,))
144+
@test all(Interpolations.ubounds(itp) .≈ (N1 + 0.5,))
145+
146+
check_axes(itp, A1, isinplace)
147+
check_inbounds_values(itp, A1)
148+
check_oob(itp)
149+
can_eval_near_boundaries(itp)
150+
end
151+
152+
# Evaluation between data points (tests constancy)
153+
for i in 2:N1-1
154+
@test A1[i] == itp_periodic(i + .3) == itp_periodic(i - .3)
155+
@test A1[i] == itp_previous(i + .3) == itp_previous(i + .6)
156+
@test A1[i - 1] == itp_previous(i - .3) == itp_previous(i - .6)
157+
@test A1[i + 1] == itp_next(i + .3) == itp_next(i + .6)
158+
@test A1[i] == itp_next(i - .3) == itp_next(i - .6)
159+
end
160+
161+
# Evaluation between data points in [0.5, 1.5], [N1 - 0.5, N1 + 0.5].
162+
@test A1[1] == itp_periodic(1 - .3) == itp_periodic(1 + .3)
163+
@test A1[N1] == itp_periodic(N1 - .3) == itp_periodic(N1 + .3)
164+
@test A1[1] == itp_previous(1 + .3)
165+
@test A1[N1] == itp_previous(N1 + .3) == itp_previous(1 - .3)
166+
@test A1[1] == itp_next(1 - .3) == itp_next(N1 + .3)
167+
@test A1[N1] == itp_next(N1 - .3)
168+
end
169+
end
170+
end

Diff for: test/b-splines/linear.jl

+35
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,39 @@
6161
etp = extrapolate(itp, Flat())
6262
@test sum(isnan.(etp)) == 0
6363
end
64+
65+
@testset "Linear periodic" begin
66+
# Constructors
67+
@test Linear() === Linear(Throw(OnGrid()))
68+
@test Linear() isa Linear{Throw{OnGrid}}
69+
@test Linear(Periodic()) === Linear(Periodic(OnCell()))
70+
@test Linear(Periodic()) isa Linear{Periodic{OnCell}}
71+
72+
xmax = 10
73+
f2(x) = sin((x-3)*2pi/xmax - 1) # Periodicity is xmax, not xmax-1
74+
A1 = Float64[f2(x) for x in 1:xmax]
75+
76+
for (constructor, copier) in ((interpolate, identity), (interpolate!, copy))
77+
isinplace = constructor == interpolate!
78+
itp = @inferred(constructor(copier(A1), BSpline(Linear())))
79+
itp_periodic = @inferred(constructor(copier(A1), BSpline(Linear(Periodic()))))
80+
81+
@test all(Interpolations.lbounds(itp_periodic) .≈ (0.5,))
82+
@test all(Interpolations.ubounds(itp_periodic) .≈ (10.5,))
83+
84+
for x in 1:.2:xmax
85+
@test itp(x) itp_periodic(x)
86+
end
87+
88+
# Interpolation range for periodic should be [0.5, xmax + 0.5]
89+
for x in 0.5:.1:0.9
90+
@test f2(x) itp_periodic(x) atol=abs(0.1 * f2(x))
91+
@test_throws BoundsError itp(x)
92+
end
93+
for x in xmax+0.1:.1:xmax+0.5
94+
@test f2(x) itp_periodic(x) atol=abs(0.1 * f2(x))
95+
@test_throws BoundsError itp(x)
96+
end
97+
end
98+
end
6499
end

Diff for: test/extrapolation/periodic.jl

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
@testset "Periodic extrapolation" begin
3+
xmax = 7
4+
f(x) = sin((x-3)*2pi/xmax - 1)
5+
A = Float64[f(x) for x in 1:xmax] # Does not include the periodic image
6+
7+
itp0 = interpolate(A, BSpline(Constant(Periodic())))
8+
itp1 = interpolate(A, BSpline(Linear(Periodic())))
9+
itp2 = interpolate(A, BSpline(Quadratic(Periodic(OnCell()))))
10+
itp3 = interpolate(A, BSpline(Cubic(Periodic(OnCell()))))
11+
12+
etp0 = extrapolate(itp0, Periodic())
13+
etp1 = extrapolate(itp1, Periodic())
14+
etp2 = extrapolate(itp2, Periodic())
15+
etp3 = extrapolate(itp3, Periodic())
16+
17+
for x in -xmax:.4:2*xmax
18+
@test etp0(x) f(x) atol=0.5
19+
@test etp1(x) f(x) atol=0.1
20+
@test etp2(x) f(x) atol=0.01
21+
@test etp3(x) f(x) atol=0.003
22+
end
23+
end

Diff for: test/extrapolation/runtests.jl

+1
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,5 @@ using Test
107107

108108
include("type-stability.jl")
109109
include("non1.jl")
110+
include("periodic.jl")
110111
end

0 commit comments

Comments
 (0)