Skip to content

Commit 6af9558

Browse files
authored
Add developer docs and a note about abandonment (#365)
1 parent 44ad9a6 commit 6af9558

File tree

12 files changed

+288
-30
lines changed

12 files changed

+288
-30
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ Documentation:
1010

1111
**NEWS** v0.9 was a breaking release. See the [news](NEWS.md) for details on how to update.
1212

13+
**NEWS** This package is officially looking for a maintainer.
14+
The original authors are only fixing bugs that affect them personally.
15+
However, to facilitate transition to a long-term maintainer,
16+
for a period of time they will make a concerted attempt to review pull requests.
17+
Step forward soon to avoid the risk of not being able to take advantage of such offers of support.
18+
1319
This package implements a variety of interpolation schemes for the
1420
Julia language. It has the goals of ease-of-use, broad algorithmic
1521
support, and exceptional performance.

docs/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
33

44
[compat]
5-
Documenter = "~0.21"
5+
Documenter = "0.24"

docs/make.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ pages=["Home" => "index.md",
88
"Interpolation algorithms" => "control.md",
99
"Extrapolation" => "extrapolation.md",
1010
"Convenience Constructors" => "convenience-construction.md",
11+
"Developer documentation" => "devdocs.md",
1112
"Library" => "api.md"]
1213
)
1314

14-
deploydocs(repo="github.com/JuliaMath/Interpolations.jl")
15+
deploydocs(repo="github.com/JuliaMath/Interpolations.jl",
16+
push_preview=true)

docs/src/control.md

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,41 +63,44 @@ sitp(5.6, 7.1) # approximately log(5.6 + 7.1)
6363
These use a very similar syntax to BSplines, with the major exception
6464
being that one does not get to choose the grid representation (they
6565
are all `OnGrid`). As such one must specify a set of coordinate arrays
66-
defining the knots of the array.
66+
defining the nodes of the array.
6767

6868
In 1D
6969
```julia
7070
A = rand(20)
71-
A_x = collect(1.0:2.0:40.0)
72-
knots = (A_x,)
73-
itp = interpolate(knots, A, Gridded(Linear()))
71+
A_x = 1.0:2.0:40.0
72+
nodes = (A_x,)
73+
itp = interpolate(nodes, A, Gridded(Linear()))
7474
itp(2.0)
7575
```
7676

77-
The spacing between adjacent samples need not be constant, you can use the syntax
77+
The spacing between adjacent samples need not be constant; indeed, if they
78+
are constant, you'll get better performance with `scaled`.
79+
80+
The general syntax is
7881
```julia
79-
itp = interpolate(knots, A, options...)
82+
itp = interpolate(nodes, A, options...)
8083
```
81-
where `knots = (xknots, yknots, ...)` to specify the positions along
84+
where `nodes = (xnodes, ynodes, ...)` specifies the positions along
8285
each axis at which the array `A` is sampled for arbitrary ("rectangular") samplings.
8386

8487
For example:
8588
```julia
8689
A = rand(8,20)
87-
knots = ([x^2 for x = 1:8], [0.2y for y = 1:20])
88-
itp = interpolate(knots, A, Gridded(Linear()))
90+
nodes = ([x^2 for x = 1:8], [0.2y for y = 1:20])
91+
itp = interpolate(nodes, A, Gridded(Linear()))
8992
itp(4,1.2) # approximately A[2,6]
9093
```
9194
One may also mix modes, by specifying a mode vector in the form of an explicit tuple:
9295
```julia
93-
itp = interpolate(knots, A, (Gridded(Linear()),Gridded(Constant())))
96+
itp = interpolate(nodes, A, (Gridded(Linear()),Gridded(Constant())))
9497
```
9598

9699
Presently there are only three modes for gridded:
97100
```julia
98101
Gridded(Linear())
99102
```
100-
whereby a linear interpolation is applied between knots,
103+
whereby a linear interpolation is applied between nodes,
101104
```julia
102105
Gridded(Constant())
103106
```

docs/src/devdocs.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Developer documentation
2+
3+
Conceptually, `Interpolations.jl` supports two operations: construction and usage of interpolants.
4+
5+
## Interpolant construction
6+
7+
Construction creates the interpolant object. In some situations this is relatively trivial:
8+
for example, when using only `NoInterp`, `Constant`, or `Linear` interpolation schemes,
9+
construction essentially corresponds to recording the array of values and the "settings" (the interpolation scheme) specified at the time of
10+
construction. This case is simple because interpolated values may be efficiently computed directly from the on-grid values supplied at construction time: ``(1-\Delta x) a_i + \Delta x a_{i+1}`` reconstructs ``a_i`` when ``\Delta x = 0``.
11+
12+
For `Quadratic` and higher orders, efficient computation requires that the array of values be *prefiltered*.
13+
This essentially corresponds to "inverting" the computation that will be performed during interpolation, so as to approximately reconstruct the original values at on-grid points. Generally speaking this corresponds to solving a nearly-tridiagonal system of equations, inverting an underlying interpolation
14+
scheme such as ``p(\Delta x) \tilde a_{i-1} + q(\Delta x) \tilde a_i + p(1-\Delta x) \tilde a_{i+1}`` for some functions ``p`` and ``q`` (see [`Quadratic`](@ref) for further details).
15+
Here ``\tilde a`` is the pre-filtered version of `a`, designed so that
16+
substituting ``\Delta x = 0`` (for which one may *not* get 0 and 1 for the ``p`` and ``q`` calls, respectively) approximately recapitulates ``a_i``.
17+
18+
The exact system of equations to be solved depends on the interpolation order and boundary conditions.
19+
Boundary conditions often introduce deviations from perfect tridiagonality; these "extras" are handled efficiently by the `WoodburyMatrices` package.
20+
These computations are implemented independently along each axis using the `AxisAlgorithms` package.
21+
22+
In the `doc` directory there are some old files that give some of the mathematical details.
23+
A useful reference is:
24+
25+
Thévenaz, Philippe, Thierry Blu, and Michael Unser. "Interpolation revisited." IEEE Transactions on Medical Imaging 19.7 (2000): 739-758.
26+
27+
!!! note
28+
As an application of these concepts, note that supporting quadratic or cubic interpolation for `Gridded` would
29+
only require that someone implement prefiltering schemes for non-uniform grids;
30+
it's just a question of working out a little bit of math.
31+
32+
## Interpolant usage
33+
34+
Usage occurs when evaluating `itp(x, y...)`, or `Interpolations.gradient(itp, x, y...)`, etc.
35+
Usage itself involves two sub-steps: computation of the *weights* and then performing the *interpolation*.
36+
37+
### Weight computation
38+
39+
Weights depend on the interpolation scheme and the location `x, y...` but *not* the coefficients of the array we are interpolating.
40+
Consequently there are many circumstances where one might want to reuse previously-computed weights, and `Interpolations.jl` has been carefully designed
41+
with that kind of reuse in mind.
42+
43+
The key concept here is the [`Interpolations.WeightedIndex`](@ref), and there is
44+
no point repeating its detailed docstring here.
45+
It suffices to add that `WeightedIndex` is actually an abstract type, with two
46+
concrete subtypes:
47+
48+
- `WeightedAdjIndex` is for indexes that will address *adjacent* points of the coefficient array (ones where the index increments by 1 along the corresponding dimension). These are used when prefiltering produces padding that can be used even at the edges, or for schemes like `Linear` interpolation which require no padding.
49+
- `WeightedArbIndex` stores both the weight and index associated with each accessed grid point, and can therefore encode grid access patterns. These are used in specific circumstances--a prime example being periodic boundary conditions--where the coefficients array may be accessed at something other than adjacent locations.
50+
51+
`WeightedIndex` computation reflects the interpolation scheme (e.g., `Linear` or `Quadratic`) and also whether one is computing values, `gradient`s, or `hessian`s. The handling of derivatives will be described further below.
52+
53+
### Interpolation
54+
55+
General `AbstractArray`s may be indexed with `WeightedIndex` indices,
56+
and the result produces the interpolated value. In other words, the end result
57+
is just `itp.coefs[wis...]`, where `wis` is a tuple of `WeightedIndex` indices.
58+
59+
Derivatives along a particular axis can be computed just by substituting a component of `wis` for one that has been designed to compute derivatives rather than values.
60+
61+
As a demonstration, let's see how the following computation occurs:
62+
63+
```jldoctest derivs; setup = :(using Interpolations)
64+
julia> A = reshape(1:27, 3, 3, 3)
65+
3×3×3 reshape(::UnitRange{Int64}, 3, 3, 3) with eltype Int64:
66+
[:, :, 1] =
67+
1 4 7
68+
2 5 8
69+
3 6 9
70+
71+
[:, :, 2] =
72+
10 13 16
73+
11 14 17
74+
12 15 18
75+
76+
[:, :, 3] =
77+
19 22 25
78+
20 23 26
79+
21 24 27
80+
81+
julia> itp = interpolate(A, BSpline(Linear()));
82+
83+
julia> x = (1.2, 1.4, 1.7)
84+
(1.2, 1.4, 1.7)
85+
86+
julia> itp(x...)
87+
8.7
88+
```
89+
90+
!!! note
91+
By using the debugging facilities of an IDE like Juno or VSCode,
92+
or using Debugger.jl from the REPL, you can easily step in to
93+
the call above and follow along with the description below.
94+
95+
Aside from details such as bounds-checking, the key call is to [`Interpolations.weightedindexes`](@ref):
96+
97+
```jldoctest derivs
98+
julia> wis = Interpolations.weightedindexes((Interpolations.value_weights,), Interpolations.itpinfo(itp)..., x)
99+
(Interpolations.WeightedAdjIndex{2,Float64}(1, (0.8, 0.19999999999999996)), Interpolations.WeightedAdjIndex{2,Float64}(1, (0.6000000000000001, 0.3999999999999999)), Interpolations.WeightedAdjIndex{2,Float64}(1, (0.30000000000000004, 0.7)))
100+
101+
julia> wis[1]
102+
Interpolations.WeightedAdjIndex{2,Float64}(1, (0.8, 0.19999999999999996))
103+
104+
julia> wis[2]
105+
Interpolations.WeightedAdjIndex{2,Float64}(1, (0.6000000000000001, 0.3999999999999999))
106+
107+
julia> wis[3]
108+
Interpolations.WeightedAdjIndex{2,Float64}(1, (0.30000000000000004, 0.7))
109+
110+
julia> A[wis...]
111+
8.7
112+
```
113+
114+
You can see that each of `wis` corresponds to a specific position: 1.2, 1.4, and 1.7 respectively. We can index `A` at `wis`, and it returns the value of `itp(x...)`, which here is just
115+
116+
0.8 * A[1, wis[2], wis[3]] + 0.2 * A[2, wis[2], wis[3]]
117+
= 0.6 * (0.8 * A[1, 1, wis[3]] + 0.2 * A[2, 1, wis[3]]) +
118+
0.4 * (0.8 * A[1, 2, wis[3]] + 0.2 * A[2, 2, wis[3]])
119+
= 0.3 * (0.6 * (0.8 * A[1, 1, 1] + 0.2 * A[2, 1, 1]) +
120+
0.4 * (0.8 * A[1, 2, 1] + 0.2 * A[2, 2, 1]) ) +
121+
0.7 * (0.6 * (0.8 * A[1, 1, 2] + 0.2 * A[2, 1, 2]) +
122+
0.4 * (0.8 * A[1, 2, 2] + 0.2 * A[2, 2, 2]) )
123+
124+
This computed the value of `itp` at `x...` because we called `weightedindexes` with just a single function, [`Interpolations.value_weights`](@ref) (meaning, "the weights needed to compute the value").
125+
126+
!!! note
127+
Remember that prefiltering is not used for `Linear` interpolation.
128+
In a case where prefiltering is used, we would substitute `itp.coefs[wis...]` for `A[wis...]` above.
129+
130+
To compute derivatives, we *also* pass additional functions like [`Interpolations.gradient_weights`](@ref):
131+
132+
```
133+
julia> wis = Interpolations.weightedindexes((Interpolations.value_weights, Interpolations.gradient_weights), Interpolations.itpinfo(itp)..., x)
134+
((Interpolations.WeightedAdjIndex{2,Float64}(1, (-1.0, 1.0)), Interpolations.WeightedAdjIndex{2,Float64}(1, (0.6000000000000001, 0.3999999999999999)), Interpolations.WeightedAdjIndex{2,Float64}(1, (0.30000000000000004, 0.7))), (Interpolations.WeightedAdjIndex{2,Float64}(1, (0.8, 0.19999999999999996)), Interpolations.WeightedAdjIndex{2,Float64}(1, (-1.0, 1.0)), Interpolations.WeightedAdjIndex{2,Float64}(1, (0.30000000000000004, 0.7))), (Interpolations.WeightedAdjIndex{2,Float64}(1, (0.8, 0.19999999999999996)), Interpolations.WeightedAdjIndex{2,Float64}(1, (0.6000000000000001, 0.3999999999999999)), Interpolations.WeightedAdjIndex{2,Float64}(1, (-1.0, 1.0))))
135+
136+
julia> wis[1]
137+
(Interpolations.WeightedAdjIndex{2,Float64}(1, (-1.0, 1.0)), Interpolations.WeightedAdjIndex{2,Float64}(1, (0.6000000000000001, 0.3999999999999999)), Interpolations.WeightedAdjIndex{2,Float64}(1, (0.30000000000000004, 0.7)))
138+
139+
julia> wis[2]
140+
(Interpolations.WeightedAdjIndex{2,Float64}(1, (0.8, 0.19999999999999996)), Interpolations.WeightedAdjIndex{2,Float64}(1, (-1.0, 1.0)), Interpolations.WeightedAdjIndex{2,Float64}(1, (0.30000000000000004, 0.7)))
141+
142+
julia> wis[3]
143+
(Interpolations.WeightedAdjIndex{2,Float64}(1, (0.8, 0.19999999999999996)), Interpolations.WeightedAdjIndex{2,Float64}(1, (0.6000000000000001, 0.3999999999999999)), Interpolations.WeightedAdjIndex{2,Float64}(1, (-1.0, 1.0)))
144+
145+
julia> A[wis[1]...]
146+
1.0
147+
148+
julia> A[wis[2]...]
149+
3.000000000000001
150+
151+
julia> A[wis[3]...]
152+
9.0
153+
```
154+
In this case you can see that `wis` is a 3-tuple-of-3-tuples. `A[wis[i]...]` can be used to compute the `i`th component of the gradient.
155+
156+
If you look carefully at each of the entries in `wis`, you'll see that
157+
each "inner" 3-tuple copies two of the three elements in the `wis` we
158+
obtained when we called `weightedindexes` with just `value_weights` above.
159+
`wis[1]` replaces the first entry with
160+
a weighted index having weights `(-1.0, 1.0)`, which corresponds to computing the *slope* along this dimension.
161+
Likewise `wis[2]` and `wis[3]` replace the second and third value-index, respectively, with the same slope computation.
162+
163+
Hessian computation is quite similar, with the difference that one sometimes needs to replace two different indices or the same index with a set of weights corresponding to a second derivative.
164+
165+
Consequently derivatives along particular directions are computed simply by "weight replacement" along the corresponding dimensions.
166+
167+
The code to do this replacement is a bit complicated due to the need to support arbitrary dimensionality in a manner that allows Julia's type-inference to succeed.
168+
It makes good use of *tuple* manipulations, sometimes called "lispy tuple programming."
169+
You can search Julia's discourse forum for more tips about how to program this way.
170+
It could alternatively be done using generated functions, but this would increase compile time considerably and can lead to world-age problems.

src/Interpolations.jl

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,12 @@ import Base: convert, size, axes, promote_rule, ndims, eltype, checkbounds, axes
4040

4141
abstract type Flag end
4242
abstract type InterpolationType <: Flag end
43+
"`NoInterp()` indicates that the corresponding axis must use integer indexing (no interpolation is to be performed)"
4344
struct NoInterp <: InterpolationType end
4445
abstract type GridType <: Flag end
46+
"`OnGrid()` indicates that the boundary condition applies at the first & last nodes"
4547
struct OnGrid <: GridType end
48+
"`OnCell()` indicates that the boundary condition applies a half-gridspacing beyond the first & last nodes"
4649
struct OnCell <: GridType end
4750

4851
const DimSpec{T} = Union{T,Tuple{Vararg{Union{T,NoInterp}}},NoInterp}
@@ -73,23 +76,27 @@ An abstract type with one of the following values (see the help for each for det
7376
- `InPlace(gt)`
7477
- `InPlaceQ(gt)`
7578
76-
where `gt` is the grid type, e.g., `OnGrid()` or `OnCell()`. `OnGrid` means that the boundary
77-
condition "activates" at the first and/or last integer location within the interpolation region,
78-
`OnCell` means the interpolation extends a half-integer beyond the edge before
79-
activating the boundary condition.
79+
where `gt` is the grid type, e.g., [`OnGrid()`](@ref) or [`OnCell()`](@ref).
8080
"""
8181
abstract type BoundaryCondition <: Flag end
8282
# Put the gridtype into the boundary condition, since that's all it affects (see issue #228)
8383
# Nothing is used for extrapolation
84+
"`Throw(gt)` causes beyond-the-edge extrapolation to throw an error"
8485
struct Throw{GT<:Union{GridType,Nothing}} <: BoundaryCondition gt::GT end
86+
"`Flat(gt)` sets the extrapolation slope to zero"
8587
struct Flat{GT<:Union{GridType,Nothing}} <: BoundaryCondition gt::GT end
88+
"`Line(gt)` uses a constant slope for extrapolation"
8689
struct Line{GT<:Union{GridType,Nothing}} <: BoundaryCondition gt::GT end
8790
struct Free{GT<:Union{GridType,Nothing}} <: BoundaryCondition gt::GT end
91+
"`Periodic(gt)` applies periodic boundary conditions"
8892
struct Periodic{GT<:Union{GridType,Nothing}} <: BoundaryCondition gt::GT end
93+
"`Reflect(gt)` applies reflective boundary conditions"
8994
struct Reflect{GT<:Union{GridType,Nothing}} <: BoundaryCondition gt::GT end
95+
"`InPlace(gt)` is a boundary condition that allows prefiltering to occur in-place (it typically requires padding)"
9096
struct InPlace{GT<:Union{GridType,Nothing}} <: BoundaryCondition gt::GT end
91-
# InPlaceQ is exact for an underlying quadratic. This is nice for ground-truth testing
92-
# of in-place (unpadded) interpolation.
97+
"""`InPlaceQ(gt)` is similar to `InPlace(gt)`, but is exact when the values being interpolated
98+
arise from an underlying quadratic. It is primarily useful for testing purposes,
99+
allowing near-exact (to machine precision) comparisons against ground truth."""
93100
struct InPlaceQ{GT<:Union{GridType,Nothing}} <: BoundaryCondition gt::GT end
94101
const Natural = Line
95102

@@ -114,7 +121,7 @@ twotuple(x, y) = (x, y)
114121
115122
Return the `bounds` of the domain of `itp` as a tuple of `(min, max)` pairs for each coordinate. This is best explained by example:
116123
117-
```jldoctest
124+
```jldoctest; setup = :(using Interpolations)
118125
julia> itp = interpolate([1 2 3; 4 5 6], BSpline(Linear()));
119126
120127
julia> bounds(itp)
@@ -337,7 +344,7 @@ Compute the weights for interpolation of the value at an offset `δx` from the "
337344
338345
# Example
339346
340-
```jldoctest
347+
```jldoctest; setup = :(using Interpolations)
341348
julia> Interpolations.value_weights(Linear(), 0.2)
342349
(0.8, 0.2)
343350
```
@@ -354,7 +361,7 @@ Compute the weights for interpolation of the gradient at an offset `δx` from th
354361
355362
# Example
356363
357-
```jldoctest
364+
```jldoctest; setup = :(using Interpolations)
358365
julia> Interpolations.gradient_weights(Linear(), 0.2)
359366
(-1.0, 1.0)
360367
```
@@ -371,7 +378,7 @@ Compute the weights for interpolation of the hessian at an offset `δx` from the
371378
372379
# Example
373380
374-
```jldoctest
381+
```jldoctest; setup = :(using Interpolations)
375382
julia> Interpolations.hessian_weights(Linear(), 0.2)
376383
(0.0, 0.0)
377384
```

0 commit comments

Comments
 (0)