Skip to content

Commit c28db3b

Browse files
Tomas Lyckentomasaschan
authored andcommitted
Implement scaling of interpolation objects
This is a start at implementing the functionality provided to Grid.jl by the CoordInterpGrid type, and in fact the code here is heavily based on that contributed by @simonbyrne there.
1 parent b0c6164 commit c28db3b

File tree

13 files changed

+271
-23
lines changed

13 files changed

+271
-23
lines changed

src/Interpolations.jl

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export
44
interpolate,
55
interpolate!,
66
extrapolate,
7+
scale,
78

89
gradient!,
910

@@ -22,10 +23,11 @@ export
2223
# see the following files for further exports:
2324
# b-splines/b-splines.jl
2425
# extrapolation/extrapolation.jl
26+
# scaling/scaling.jl
2527

2628
using WoodburyMatrices, Ratios, AxisAlgorithms
2729

28-
import Base: convert, size, getindex, gradient, promote_rule
30+
import Base: convert, size, getindex, gradient, scale, promote_rule
2931

3032
abstract InterpolationType
3133
immutable NoInterp <: InterpolationType end
@@ -36,7 +38,8 @@ immutable OnCell <: GridType end
3638
typealias DimSpec{T} Union{T,Tuple{Vararg{Union{T,NoInterp}}},NoInterp}
3739

3840
abstract AbstractInterpolation{T,N,IT<:DimSpec{InterpolationType},GT<:DimSpec{GridType}} <: AbstractArray{T,N}
39-
abstract AbstractExtrapolation{T,N,ITPT,IT,GT} <: AbstractInterpolation{T,N,IT,GT}
41+
abstract AbstractInterpolationWrapper{T,N,ITPT,IT,GT} <: AbstractInterpolation{T,N,IT,GT}
42+
abstract AbstractExtrapolation{T,N,ITPT,IT,GT} <: AbstractInterpolationWrapper{T,N,ITPT,IT,GT}
4043

4144
abstract BoundaryCondition
4245
immutable Flat <: BoundaryCondition end
@@ -53,7 +56,13 @@ typealias Natural Line
5356
# TODO: size might have to be faster?
5457
size{T,N}(itp::AbstractInterpolation{T,N}) = ntuple(i->size(itp,i), N)::NTuple{N,Int}
5558
size(exp::AbstractExtrapolation, d) = size(exp.itp, d)
56-
itptype{T,N,IT,GT}(itp::AbstractInterpolation{T,N,IT,GT}) = IT
59+
bounds{T,N}(itp::AbstractInterpolation{T,N}) = tuple(zip(lbounds(itp), ubounds(itp))...)
60+
bounds{T,N}(itp::AbstractInterpolation{T,N}, d) = (lbound(itp,d),ubound(itp,d))
61+
lbounds{T,N}(itp::AbstractInterpolation{T,N}) = ntuple(i->lbound(itp,i), N)::NTuple{N,T}
62+
ubounds{T,N}(itp::AbstractInterpolation{T,N}) = ntuple(i->ubound(itp,i), N)::NTuple{N,T}
63+
lbound{T,N}(itp::AbstractInterpolation{T,N}, d) = convert(T, 1)
64+
ubound{T,N}(itp::AbstractInterpolation{T,N}, d) = convert(T, size(itp, d))
65+
itptype{T,N,IT,GT}(itp::AbstractInterpolation{T,N,IT,GT}) = IT
5766
gridtype{T,N,IT,GT}(itp::AbstractInterpolation{T,N,IT,GT}) = GT
5867

5968
@inline gradient{T,N}(itp::AbstractInterpolation{T,N}, xs...) = gradient!(Array(T,N), itp, xs...)
@@ -62,5 +71,6 @@ include("nointerp/nointerp.jl")
6271
include("b-splines/b-splines.jl")
6372
include("gridded/gridded.jl")
6473
include("extrapolation/extrapolation.jl")
74+
include("scaling/scaling.jl")
6575

6676
end # module

src/b-splines/b-splines.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ iextract(t, d) = t.parameters[d]
3636
padextract(pad::Integer, d) = pad
3737
padextract(pad::Tuple{Vararg{Integer}}, d) = pad[d]
3838

39+
lbound{T,N,TCoefs,IT}(itp::BSplineInterpolation{T,N,TCoefs,IT,OnGrid}, d) = one(T)
40+
ubound{T,N,TCoefs,IT}(itp::BSplineInterpolation{T,N,TCoefs,IT,OnGrid}, d) = convert(T, size(itp, d))
41+
lbound{T,N,TCoefs,IT}(itp::BSplineInterpolation{T,N,TCoefs,IT,OnCell}, d) = convert(T, .5)
42+
ubound{T,N,TCoefs,IT}(itp::BSplineInterpolation{T,N,TCoefs,IT,OnCell}, d) = convert(T, size(itp, d) + .5)
43+
3944
@generated function size{T,N,TCoefs,IT,GT,pad}(itp::BSplineInterpolation{T,N,TCoefs,IT,GT,pad}, d)
4045
quote
4146
d <= $N ? size(itp.coefs, d) - 2*padextract($pad, d) : 1

src/extrapolation/constant.jl

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,12 @@ ConstantExtrapolation{T,ITP,IT,GT}(::Type{T}, N, itp::ITP, ::Type{IT}, ::Type{GT
77
extrapolate{T,N,IT,GT}(itp::AbstractInterpolation{T,N,IT,GT}, ::Type{Flat}) =
88
ConstantExtrapolation(T,N,itp,IT,GT)
99

10-
function extrap_prep{T,ITP,IT}(exp::Type{ConstantExtrapolation{T,1,ITP,IT,OnGrid}}, x)
11-
:(x = clamp(x, 1, size(exp,1)))
10+
function extrap_prep{T,ITP,IT,GT}(etp::Type{ConstantExtrapolation{T,1,ITP,IT,GT}}, x)
11+
:(x = clamp(x, lbound(etp,1), ubound(etp,1)))
1212
end
13-
function extrap_prep{T,ITP,IT}(exp::Type{ConstantExtrapolation{T,1,ITP,IT,OnCell}}, x)
14-
:(x = clamp(x, .5, size(exp,1)+.5))
15-
end
16-
function extrap_prep{T,N,ITP,IT}(exp::Type{ConstantExtrapolation{T,N,ITP,IT,OnGrid}}, xs...)
17-
:(@nexprs $N d->(xs[d] = clamp(xs[d], 1, size(exp,d))))
18-
end
19-
function extrap_prep{T,N,ITP,IT}(exp::Type{ConstantExtrapolation{T,N,ITP,IT,OnCell}}, xs...)
20-
:(@nexprs $N d->(xs[d] = clamp(xs[d], .5, size(exp,d)+.5)))
13+
function extrap_prep{T,N,ITP,IT,GT}(etp::Type{ConstantExtrapolation{T,N,ITP,IT,GT}}, xs...)
14+
:(@nexprs $N d->(xs[d] = clamp(xs[d], lbound(etp,d), ubound(etp,d))))
2115
end
16+
17+
lbound(etp::ConstantExtrapolation, d) = lbound(etp.itp, d)
18+
ubound(etp::ConstantExtrapolation, d) = ubound(etp.itp, d)

src/extrapolation/error.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ ErrorExtrapolation{T,ITPT,IT,GT}(::Type{T}, N, itp::ITPT, ::Type{IT}, ::Type{GT}
77
extrapolate{T,N,IT,GT}(itp::AbstractInterpolation{T,N,IT,GT}, ::Type{Throw}) =
88
ErrorExtrapolation(T,N,itp,IT,GT)
99

10-
11-
function extrap_prep{T,N,ITPT,IT}(exp::Type{ErrorExtrapolation{T,N,ITPT,IT,OnGrid}}, xs...)
12-
:(@nexprs $N d->(@show 1 <= xs[d] <= size(exp,d) || throw(BoundsError())))
10+
function extrap_prep{T,N,ITPT,IT,GT}(etp::Type{ErrorExtrapolation{T,N,ITPT,IT,GT}}, xs...)
11+
:(@nexprs $N d->(lbound(etp,d) <= xs[d] <= ubound(etp,d) || throw(BoundsError())))
1312
end
13+
14+
lbound(etp::ErrorExtrapolation, d) = lbound(etp.itp, d)
15+
ubound(etp::ErrorExtrapolation, d) = ubound(etp.itp, d)

src/extrapolation/filled.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@ extrapolate{T,N,IT,GT}(itp::AbstractInterpolation{T,N,IT,GT}, fillvalue) = Fille
2727
$meta
2828
# Check to see if we're in the extrapolation region, i.e.,
2929
# out-of-bounds in an index
30-
@nexprs $N d->((args[d] < 1 || args[d] > size(fitp.itp, d)) && return fitp.fillvalue)
30+
@nexprs $N d->((args[d] < lbound(fitp,d) || args[d] > ubound(fitp, d)) && return fitp.fillvalue)
3131
# In the interpolation region
3232
return getindex(fitp.itp,args...)
3333
end
3434
end
3535

3636
getindex{T}(fitp::FilledExtrapolation{T,1}, x::Number, y::Int) = y == 1 ? fitp[x] : throw(BoundsError())
37+
38+
lbound(etp::FilledExtrapolation, d) = lbound(etp.itp, d)
39+
ubound(etp::FilledExtrapolation, d) = ubound(etp.itp, d)

src/extrapolation/indexing.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
@generated function getindex{T}(exp::AbstractExtrapolation{T,1}, x)
1+
@generated function getindex{T}(etp::AbstractExtrapolation{T,1}, x)
22
quote
3-
$(extrap_prep(exp, x))
4-
exp.itp[x]
3+
$(extrap_prep(etp, x))
4+
etp.itp[x]
55
end
66
end
77

8-
@generated function getindex{T,N,ITP,GT}(exp::AbstractExtrapolation{T,N,ITP,GT}, xs...)
8+
@generated function getindex{T,N,ITP,GT}(etp::AbstractExtrapolation{T,N,ITP,GT}, xs...)
99
quote
10-
$(extrap_prep(exp, xs...))
11-
exp.itp[xs...]
10+
$(extrap_prep(etp, xs...))
11+
etp.itp[xs...]
1212
end
1313
end

src/scaling/scaling.jl

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
type ScaledInterpolation{T,N,ITPT,IT,GT,RT} <: AbstractInterpolationWrapper{T,N,ITPT,IT,GT}
2+
itp::ITPT
3+
ranges::RT
4+
end
5+
ScaledInterpolation{T,ITPT,IT,GT,RT}(::Type{T}, N, itp::ITPT, ::Type{IT}, ::Type{GT}, ranges::RT) =
6+
ScaledInterpolation{T,N,ITPT,IT,GT,RT}(itp, ranges)
7+
function scale{T,N,IT,GT}(itp::AbstractInterpolation{T,N,IT,GT}, ranges::Range...)
8+
length(ranges) == N || throw(ArgumentError("Must scale $N-dimensional interpolation object with exactly $N ranges (you used $(length(ranges)))"))
9+
for d in 1:N
10+
if iextract(IT,d) != NoInterp
11+
length(ranges[d]) == size(itp,d) || throw(ArgumentError("The length of the range in dimension $d ($(length(ranges[d]))) did not equal the size of the interpolation object in that direction ($(size(itp,d)))"))
12+
elseif ranges[d] != 1:size(itp,d)
13+
throw(ArgumentError("NoInterp dimension $d must be scaled with unit range 1:$(size(itp,d))"))
14+
end
15+
end
16+
17+
ScaledInterpolation(T,N,itp,IT,GT,ranges)
18+
end
19+
20+
@generated function getindex{T,N,ITPT,IT<:DimSpec}(sitp::ScaledInterpolation{T,N,ITPT,IT}, xs...)
21+
length(xs) == N || throw(ArgumentError("Must index into $N-dimensional scaled interpolation object with exactly $N indices (you used $(length(xs)))"))
22+
interp_types = length(IT.parameters) == N ? IT.parameters : tuple([IT.parameters[1] for _ in 1:N]...)
23+
interp_dimens = map(it -> interp_types[it] != NoInterp, 1:N)
24+
interp_indices = map(i -> interp_dimens[i] ? :(coordlookup(sitp.ranges[$i], xs[$i])) : :(xs[$i]), 1:N)
25+
return :(getindex(sitp.itp, $(interp_indices...)))
26+
end
27+
28+
getindex{T}(sitp::ScaledInterpolation{T,1}, x::Number, y::Int) = y == 1 ? sitp[x] : throw(BoundsError())
29+
30+
size(sitp::ScaledInterpolation, d) = size(sitp.itp, d)
31+
lbound{T,N,ITPT,IT}(sitp::ScaledInterpolation{T,N,ITPT,IT,OnGrid}, d) = 1 <= d <= N ? sitp.ranges[d][1] : throw(BoundsError())
32+
lbound{T,N,ITPT,IT}(sitp::ScaledInterpolation{T,N,ITPT,IT,OnCell}, d) = 1 <= d <= N ? sitp.ranges[d][1] - boundstep(sitp.ranges[d]) : throw(BoundsError())
33+
ubound{T,N,ITPT,IT}(sitp::ScaledInterpolation{T,N,ITPT,IT,OnGrid}, d) = 1 <= d <= N ? sitp.ranges[d][end] : throw(BoundsError())
34+
ubound{T,N,ITPT,IT}(sitp::ScaledInterpolation{T,N,ITPT,IT,OnCell}, d) = 1 <= d <= N ? sitp.ranges[d][end] + boundstep(sitp.ranges[d]) : throw(BoundsError())
35+
36+
boundstep(r::LinSpace) = ((r.stop - r.start) / r.divisor) / 2
37+
boundstep(r::FloatRange) = r.step / 2
38+
boundstep(r::StepRange) = r.step / 2
39+
boundstep(r::UnitRange) = 1//2
40+
41+
coordlookup(r::LinSpace, x) = (r.divisor * x + r.stop - r.len * r.start) / (r.stop - r.start)
42+
coordlookup(r::FloatRange, x) = (r.divisor * x - r.start) / r.step + one(eltype(r))
43+
coordlookup(r::StepRange, x) = (x - r.start) / r.step + one(eltype(r))
44+
coordlookup(r::UnitRange, x) = x - r.start + one(eltype(r))
45+
coordlookup(i::Bool, r::Range, x) = i ? coordlookup(r, x) : convert(typeof(coordlookup(r,x)), x)
46+
47+
gradient{T,N}(sitp::ScaledInterpolation{T,N}, xs...) = gradient!(Array(T,N), sitp, xs...)
48+
@generated function gradient!{T,N,ITPT,IT}(g, sitp::ScaledInterpolation{T,N,ITPT,IT}, xs...)
49+
ndims(g) == 1 || throw(ArgumentError("g must be a vector (but had $(ndims(g)) dimensions)"))
50+
length(xs) == count_interp_dims(IT, N) || throw(ArgumentError("Must index into $N-dimensional scaled interpolation object with exactly $N indices (you used $(length(xs)))"))
51+
52+
interp_types = length(IT.parameters) == N ? IT.parameters : tuple([IT.parameters[1] for _ in 1:N]...)
53+
interp_dimens = map(it -> interp_types[it] != NoInterp, 1:N)
54+
interp_indices = map(i -> interp_dimens[i] ? :(coordlookup(sitp.ranges[$i], xs[$i])) : :(xs[$i]), 1:N)
55+
56+
quote
57+
length(g) == N || throw(ArgumentError(string("g must be a vector of length ", N, " (was ", length(g), ")")))
58+
gradient!(g, sitp.itp, $(interp_indices...))
59+
for i in eachindex(g)
60+
g[i] = rescale_gradient(sitp.ranges[i], g[i])
61+
end
62+
g
63+
end
64+
end
65+
66+
rescale_gradient(r::FloatRange, g) = g * r.divisor / r.step
67+
rescale_gradient(r::StepRange, g) = g / r.step
68+
rescale_gradient(r::UnitRange, g) = g

test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ include("b-splines/runtests.jl")
99
# extrapolation tests
1010
include("extrapolation/runtests.jl")
1111

12+
# scaling tests
13+
include("scaling/runtests.jl")
14+
1215
# # test gradient evaluation
1316
include("gradient.jl")
1417

test/scaling/dimspecs.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module ScalingDimspecTests
2+
3+
using Interpolations, DualNumbers, Base.Test
4+
5+
xs = -pi:(2pi/10):pi-2pi/10
6+
ys = -2:.1:2
7+
f(x,y) = sin(x) * y^2
8+
9+
itp = interpolate(Float64[f(x,y) for x in xs, y in ys], Tuple{BSpline(Quadratic(Periodic)), BSpline(Linear)}, OnGrid)
10+
sitp = scale(itp, xs, ys)
11+
12+
# Don't test too near the edges until issue #64 is resolved
13+
for (ix,x0) in enumerate(xs[5:end-5]), (iy,y0) in enumerate(ys[2:end-1])
14+
x, y = x0 + 2pi/20, y0 + .05
15+
@test_approx_eq sitp[x0, y0] f(x0,y0)
16+
@test_approx_eq_eps sitp[x0, y0] f(x0,y0) 0.05
17+
18+
g = gradient(sitp, x, y)
19+
fx = epsilon(f(dual(x,1), y))
20+
fy = (f(x, ys[iy+2]) - f(x, ys[iy+1])) / (ys[iy+2] - ys[iy+1])
21+
22+
@test_approx_eq_eps g[1] fx 0.15
23+
@test_approx_eq_eps g[2] fy 0.05 # gradients for linear interpolation is "easy"
24+
end
25+
26+
end

test/scaling/nointerp.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
module ScalingNoInterpTests
2+
3+
using Interpolations, Base.Test
4+
5+
xs = -pi:2pi/10:pi
6+
f1(x) = sin(x)
7+
f2(x) = cos(x)
8+
f3(x) = sin(x) .* cos(x)
9+
f(x,y) = y == 1 ? f1(x) : (y == 2 ? f2(x) : (y == 3 ? f3(x) : error("invalid value for y (must be 1, 2 or 3, you used $y)")))
10+
ys = 1:3
11+
12+
A = hcat(f1(xs), f2(xs), f3(xs))
13+
14+
itp = interpolate(A, Tuple{BSpline(Quadratic(Periodic)), NoInterp}, OnGrid)
15+
sitp = scale(itp, xs, ys)
16+
17+
for (ix,x0) in enumerate(xs[1:end-1]), y0 in ys
18+
x,y = x0, y0
19+
@test_approx_eq_eps sitp[x,y] f(x,y) .05
20+
end
21+
22+
# Test error messages for incorrect initialization
23+
function message_is(message)
24+
r -> r.err.msg == message || error("Incorrect error message: expected '$message' but was '$(r.err.msg)'")
25+
end
26+
Test.with_handler(message_is("Must scale 2-dimensional interpolation object with exactly 2 ranges (you used 1)")) do
27+
@test scale(itp, xs)
28+
end
29+
Test.with_handler(message_is("NoInterp dimension 2 must be scaled with unit range 1:3")) do
30+
@test scale(itp, xs, -1:1)
31+
end
32+
Test.with_handler(message_is("The length of the range in dimension 1 (8) did not equal the size of the interpolation object in that direction (11)")) do
33+
@test scale(itp, -pi:2pi/7:pi, 1:3)
34+
end
35+
Test.with_handler(message_is("Must index into 2-dimensional scaled interpolation object with exactly 2 indices (you used 1)")) do
36+
@test sitp[2.3]
37+
end
38+
end

test/scaling/runtests.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
include("scaling.jl")
2+
include("dimspecs.jl")
3+
include("nointerp.jl")
4+
include("withextrap.jl")

test/scaling/scaling.jl

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
module ScalingTests
2+
3+
using Interpolations
4+
using Base.Test
5+
6+
# Model linear interpolation of y = -3 + .5x by interpolating y=x
7+
# and then scaling to the new x range
8+
9+
itp = interpolate(1:1.0:10, BSpline(Linear), OnGrid)
10+
11+
sitp = scale(itp, -3:.5:1.5)
12+
13+
for (x,y) in zip(-3:.05:1.5, 1:.1:10)
14+
@test_approx_eq sitp[x] y
15+
end
16+
17+
# Verify that it works in >1D, with different types of ranges
18+
19+
gauss(phi, mu, sigma) = exp(-(phi-mu)^2 / (2sigma)^2)
20+
testfunction(x,y) = gauss(x, 0.5, 4) * gauss(y, -.5, 2)
21+
22+
xs = -5:.5:5
23+
ys = -4:.2:4
24+
zs = Float64[testfunction(x,y) for x in xs, y in ys]
25+
26+
itp2 = interpolate(zs, BSpline(Quadratic(Flat)), OnGrid)
27+
sitp2 = scale(itp2, xs, ys)
28+
29+
for x in xs, y in ys
30+
@test_approx_eq testfunction(x,y) sitp2[x,y]
31+
end
32+
33+
# Test gradients of scaled grids
34+
xs = -pi:.1:pi
35+
ys = sin(xs)
36+
itp = interpolate(ys, BSpline(Linear), OnGrid)
37+
sitp = scale(itp, xs)
38+
39+
for x in -pi:.1:pi
40+
g = @inferred(gradient(sitp, x))[1]
41+
@test_approx_eq_eps cos(x) g .05
42+
end
43+
44+
# Verify that return types are reasonable
45+
@inferred(getindex(sitp2, -3.4, 1.2))
46+
@inferred(getindex(sitp2, -3, 1))
47+
@inferred(getindex(sitp2, -3.4, 1))
48+
49+
sitp32 = scale(interpolate(Float32[testfunction(x,y) for x in -5:.5:5, y in -4:.2:4], BSpline(Quadratic(Flat)), OnGrid), -5f0:.5f0:5f0, -4f0:.2f0:4f0)
50+
@test typeof(@inferred(getindex(sitp32, -3.4f0, 1.2f0))) == Float32
51+
52+
end

test/scaling/withextrap.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
2+
module ScalingWithExtrapTests
3+
4+
using Interpolations, Base.Test
5+
6+
xs = linspace(-5, 5, 10)
7+
ys = sin(xs)
8+
9+
function run_tests{T,N,IT}(sut::Interpolations.AbstractInterpolation{T,N,IT,OnGrid}, itp)
10+
for x in xs
11+
@test_approx_eq_eps sut[x] sin(x) sqrt(eps(sin(x)))
12+
end
13+
@test sut[-5] == sut[-5.1] == sut[-15.8] == sut[-Inf] == itp[1]
14+
@test sut[5] == sut[5.1] == sut[15.8] == sut[Inf] == itp[end]
15+
end
16+
17+
function run_tests{T,N,IT}(sut::Interpolations.AbstractInterpolation{T,N,IT,OnCell}, itp)
18+
halfcell = (xs[2] - xs[1]) / 2
19+
20+
for x in (5 + halfcell, 5 + 1.1halfcell, 15.8, Inf)
21+
@test sut[-x] == itp[.5]
22+
@test sut[x] == itp[end+.5]
23+
end
24+
end
25+
26+
for GT in (OnGrid, OnCell)
27+
itp = interpolate(ys, BSpline(Quadratic(Flat)), GT)
28+
29+
# Test extrapolating, then scaling
30+
eitp = extrapolate(itp, Flat)
31+
seitp = scale(eitp, xs)
32+
run_tests(seitp, itp)
33+
34+
# Test scaling, then extrapolating
35+
sitp = scale(itp, xs)
36+
esitp = extrapolate(sitp, Flat)
37+
run_tests(esitp, itp)
38+
end
39+
40+
end

0 commit comments

Comments
 (0)