Developer documentation
October 8, 2015 ยท View on GitHub
Interpolations provides flexibility without compromising on performance by exploiting metaprogramming to generate streamlined code. However, for people new to metaprogramming this can can be a barrier. Fortunately, with a few tips a lot of the mystique goes away.
Looking under the hood
First let's create an interpolation object:
julia> using Interpolations
julia> A = rand(5)
5-element Array{Float64,1}:
0.74838
0.995383
0.978916
0.134746
0.430876
julia> yitp = interpolate(A, BSpline(Linear()), OnGrid())
5-element Interpolations.BSplineInterpolation{Float64,1,Float64,Interpolations.BSpline{Interpolations.Linear},Interpolations.OnGrid}:
0.74838
0.995383
0.978916
0.134746
0.430876
We can use this object to learn a lot about how Interpolations works.
For example, the key functionality provided by yitp is getindex, i.e., itp[3.2].
Where is this implemented?
julia> @which yitp[3.2]
getindex{T,N}(itp::Interpolations.BSplineInterpolation{T,N,TCoefs,IT<:Interpolations.BSpline{D<:Interpolations.Degree{N}},GT<:Interpolations.GridType},xs::Real) at /home/tim/.julia/v0.4/Interpolations/src/b-splines/indexing.jl:42
Your specific output (and especially the line number) may differ, but the point is that you've now found out where this is implemented. If you take a look at that function definition, you might see something like this:
@generated function getindex{T,N}(itp::BSplineInterpolation{T,N}, xs::Real)
if N > 1
error("Linear indexing is not supported for interpolation objects")
end
getindex_impl(itp)
end
This is a generated function, and you'll need to familiarize yourself with how these work.
The "interesting" part of the function is the call to getindex_impl; we can see the code that gets generated like this:
julia> Interpolations.getindex_impl(typeof(yitp))
quote # /home/tim/.julia/v0.4/Interpolations/src/b-splines/indexing.jl, line 7:
@nexprs 1 (d->begin # /home/tim/.julia/v0.4/Interpolations/src/b-splines/indexing.jl, line 7:
x_d = xs[d]
end) # line 11:
begin # /home/tim/.julia/v0.4/Interpolations/src/b-splines/linear.jl, line 5:
@nexprs 1 (d->begin # /home/tim/.julia/v0.4/Interpolations/src/b-splines/linear.jl, line 5:
begin # /home/tim/.julia/v0.4/Interpolations/src/b-splines/linear.jl, line 6:
ix_d = clamp(floor(Int,real(x_d)),1,size(itp,d) - 1) # line 7:
ixp_d = ix_d + 1 # line 8:
fx_d = x_d - ix_d
end
end)
end # line 14:
@nexprs 1 (d->begin # /home/tim/.julia/v0.4/Interpolations/src/b-splines/linear.jl, line 14:
begin # /home/tim/.julia/v0.4/Interpolations/src/b-splines/linear.jl, line 20:
c_d = 1 - fx_d # line 21:
cp_d = fx_d
end
end) # line 17:
@inbounds ret = c_1 * itp.coefs[ix_1] + cp_1 * itp.coefs[ixp_1] # line 18:
ret
end
You can see that this code makes use of Base.Cartesian, which you may also need to study.
However, the impact of these macros can be gleaned through macroexpand:
julia> using Base.Cartesian
julia> macroexpand(Interpolations.getindex_impl(typeof(yitp)))
quote # /home/tim/.julia/v0.4/Interpolations/src/b-splines/indexing.jl, line 7:
begin
x_1 = xs[1]
end # line 11:
begin # /home/tim/.julia/v0.4/Interpolations/src/b-splines/linear.jl, line 5:
begin
begin # /home/tim/.julia/v0.4/Interpolations/src/b-splines/linear.jl, line 6:
ix_1 = clamp(floor(Int,real(x_1)),1,size(itp,1) - 1) # line 7:
ixp_1 = ix_1 + 1 # line 8:
fx_1 = x_1 - ix_1
end
end
end # line 14:
begin
begin # /home/tim/.julia/v0.4/Interpolations/src/b-splines/linear.jl, line 20:
c_1 = 1 - fx_1 # line 21:
cp_1 = fx_1
end
end # line 17:
begin
$(Expr(:boundscheck, false))
begin
ret = c_1 * itp.coefs[ix_1] + cp_1 * itp.coefs[ixp_1]
$(Expr(:boundscheck, :(Base.pop)))
end
end # line 18:
ret
end
This is probably starting to look like something you can read. Briefly, what's happening is:
floor(Int,x_1)gets clamped to the range1:size(itp,1)-1and assigned toix_1; this is the lower-bound integer grid point for the first dimension (this is a one-dimensional problem, but in two or higher dimensions you'd haveix_2, etc.)ixp_1is defined asix_1+1; this is the upper-bound integer grid point. In Interpolations,mandpoften mean "minus" and "plus", meaning the lower or upper grid point.- The fractional part is stored in
fx_1 - Position-coefficients
c_1andcp_1associated with the lower and upper grid point are computed fromfx_1 - The interpolation is performed using the position-coefficients, grid points, and data-coefficients and stored in
ret, which is returned.
As useful exercises:
- Try creating a 2-dimensional linear interpolation object and examine the created code
- Create a
Quadraticinterpolation object and do the same
Once you've gotten this far, you probably understand quite a lot about how Interpolations works.
At this point, your best bet is to start looking into the helper functions used by getindex_impl;
once you learn how to define these, you should be able to extend Interpolations to support new algorithms.