Skip to content

Commit 3ac4780

Browse files
Merge pull request #525 from ChrisRackauckas-Claude/makima-via-akima-kwarg
Add modified Akima (makima) as a kwarg on AkimaInterpolation
2 parents 7e2853f + 3eca531 commit 3ac4780

3 files changed

Lines changed: 87 additions & 12 deletions

File tree

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ makedocs(
1717
),
1818
linkcheck_ignore = [
1919
"https://www.mathworks.com/content/dam/mathworks/mathworks-dot-com/moler/interp.pdf",
20+
"https://blogs.mathworks.com/cleve/2019/04/29/makima-piecewise-cubic-interpolation/",
2021
],
2122
pages = [
2223
"index.md",

src/interpolation_caches.jl

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ function LagrangeInterpolation(
220220
end
221221

222222
"""
223-
AkimaInterpolation(u, t; extrapolation::ExtrapolationType.T = ExtrapolationType.None, extrapolation_left::ExtrapolationType.T = ExtrapolationType.None,
223+
AkimaInterpolation(u, t; modified = false, extrapolation::ExtrapolationType.T = ExtrapolationType.None, extrapolation_left::ExtrapolationType.T = ExtrapolationType.None,
224224
extrapolation_right::ExtrapolationType.T = ExtrapolationType.None, cache_parameters = false)
225225
226226
It is a spline interpolation built from cubic polynomials. It forms a continuously differentiable function. For more details, refer: [https://en.wikipedia.org/wiki/Akima_spline](https://en.wikipedia.org/wiki/Akima_spline).
@@ -233,6 +233,11 @@ Extrapolation extends the last cubic polynomial on each side.
233233
234234
## Keyword Arguments
235235
236+
- `modified`: if `true`, use the modified Akima (makima) formula for the slopes at the knots,
237+
which adds an extra term `|m_{i+1} + m_i| / 2` to each weight. Tends to reduce overshoot
238+
and oscillation on data with flat regions or repeated values. See
239+
[https://blogs.mathworks.com/cleve/2019/04/29/makima-piecewise-cubic-interpolation/](https://blogs.mathworks.com/cleve/2019/04/29/makima-piecewise-cubic-interpolation/).
240+
Defaults to `false`.
236241
- `extrapolation`: The extrapolation type applied left and right of the data. Possible options
237242
are `ExtrapolationType.None` (default), `ExtrapolationType.Constant`, `ExtrapolationType.Linear`
238243
`ExtrapolationType.Extension`, `ExtrapolationType.Periodic` and `ExtrapolationType.Reflective`.
@@ -284,7 +289,8 @@ struct AkimaInterpolation{uType, tType, IType, bType, cType, dType, T} <:
284289
end
285290

286291
function AkimaInterpolation(
287-
u, t; extrapolation::ExtrapolationType.T = ExtrapolationType.None,
292+
u, t; modified::Bool = false,
293+
extrapolation::ExtrapolationType.T = ExtrapolationType.None,
288294
extrapolation_left::ExtrapolationType.T = ExtrapolationType.None,
289295
extrapolation_right::ExtrapolationType.T = ExtrapolationType.None, cache_parameters = false, assume_linear_t = 1.0e-2
290296
)
@@ -303,16 +309,30 @@ function AkimaInterpolation(
303309
m[end - 1] = 2m[end - 2] - m[end - 3]
304310
m[end] = 2m[end - 1] - m[end - 2]
305311

306-
b = (m[4:end] .+ m[1:(end - 3)]) ./ 2
307-
dm = abs.(diff(m))
308-
f1 = dm[3:(n + 2)]
309-
f2 = dm[1:n]
310-
f12 = f1 + f2
311-
ind = findall(f12 .> 1.0e-9 * maximum(f12))
312-
b[ind] = (
313-
f1[ind] .* m[ind .+ 1] .+
314-
f2[ind] .* m[ind .+ 2]
315-
) ./ f12[ind]
312+
if modified
313+
# Modified Akima (makima): adds |m_{i+1} + m_i| / 2 to each weight, which
314+
# reduces overshoot on flat regions. The simple-average fallback still
315+
# guards the case where all four neighboring slopes vanish.
316+
w1 = abs.(m[4:end] .- m[3:(end - 1)]) .+
317+
abs.(m[4:end] .+ m[3:(end - 1)]) ./ 2
318+
w2 = abs.(m[2:(end - 2)] .- m[1:(end - 3)]) .+
319+
abs.(m[2:(end - 2)] .+ m[1:(end - 3)]) ./ 2
320+
w12 = w1 .+ w2
321+
b = (m[2:(end - 2)] .+ m[3:(end - 1)]) ./ 2
322+
ind = findall(w12 .> 1.0e-9 * maximum(w12))
323+
b[ind] = (w1[ind] .* m[ind .+ 1] .+ w2[ind] .* m[ind .+ 2]) ./ w12[ind]
324+
else
325+
b = (m[4:end] .+ m[1:(end - 3)]) ./ 2
326+
dm = abs.(diff(m))
327+
f1 = dm[3:(n + 2)]
328+
f2 = dm[1:n]
329+
f12 = f1 + f2
330+
ind = findall(f12 .> 1.0e-9 * maximum(f12))
331+
b[ind] = (
332+
f1[ind] .* m[ind .+ 1] .+
333+
f2[ind] .* m[ind .+ 2]
334+
) ./ f12[ind]
335+
end
316336
c = (3 .* m[3:(end - 2)] .- 2 .* b[1:(end - 1)] .- b[2:end]) ./ dt
317337
d = (b[1:(end - 1)] .+ b[2:end] .- 2 .* m[3:(end - 2)]) ./ dt .^ 2
318338

test/interpolation_tests.jl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,60 @@ end
511511
A = @inferred(AkimaInterpolation(u, t))
512512
@test_throws DataInterpolations.LeftExtrapolationError A(-1.0)
513513
@test_throws DataInterpolations.RightExtrapolationError A(11.0)
514+
515+
# Modified Akima (makima) — same struct, different b computation
516+
@testset "Modified Akima (makima)" begin
517+
A_makima = @inferred(AkimaInterpolation(u, t; modified = true))
518+
# Interpolates the data points exactly
519+
for i in eachindex(t)
520+
@test A_makima(t[i]) u[i]
521+
end
522+
# Differs from standard Akima on this data set
523+
A_std = AkimaInterpolation(u, t)
524+
@test any(
525+
!isapprox(A_makima(ti), A_std(ti); atol = 1.0e-12)
526+
for ti in 0.5:1.0:9.5
527+
)
528+
529+
# Compare against a direct vectorized implementation of the makima formula
530+
n = length(t)
531+
dt_vec = diff(t)
532+
m = Array{eltype(u)}(undef, n + 3)
533+
m[3:(end - 2)] = diff(u) ./ dt_vec
534+
m[2] = 2m[3] - m[4]
535+
m[1] = 2m[2] - m[3]
536+
m[end - 1] = 2m[end - 2] - m[end - 3]
537+
m[end] = 2m[end - 1] - m[end - 2]
538+
w1 = abs.(m[4:end] .- m[3:(end - 1)]) .+
539+
abs.(m[4:end] .+ m[3:(end - 1)]) ./ 2
540+
w2 = abs.(m[2:(end - 2)] .- m[1:(end - 3)]) .+
541+
abs.(m[2:(end - 2)] .+ m[1:(end - 3)]) ./ 2
542+
b_ref = (w1 .* m[2:(end - 2)] .+ w2 .* m[3:(end - 1)]) ./ (w1 .+ w2)
543+
@test A_makima.b b_ref
544+
545+
# Makima avoids the w1 + w2 == 0 division-by-zero edge case
546+
# that the original Akima formula explicitly works around: a
547+
# constant-then-constant signal produces zero forward and backward
548+
# slope differences at the transition.
549+
u_flat = [0.0, 0.0, 0.0, 1.0, 1.0, 1.0]
550+
t_flat = collect(0.0:5.0)
551+
A_flat = @inferred(AkimaInterpolation(u_flat, t_flat; modified = true))
552+
@test all(isfinite, A_flat.b)
553+
@test all(isfinite, A_flat.c)
554+
@test all(isfinite, A_flat.d)
555+
for i in eachindex(t_flat)
556+
@test A_flat(t_flat[i]) u_flat[i]
557+
end
558+
559+
# Extrapolation, derivatives and integrals work via the shared struct path
560+
A_ext = AkimaInterpolation(
561+
u, t; modified = true, extrapolation = ExtrapolationType.Extension
562+
)
563+
@test isfinite(A_ext(-1.0))
564+
@test isfinite(A_ext(11.0))
565+
@test isfinite(DataInterpolations.derivative(A_makima, 5.0))
566+
@test isfinite(DataInterpolations.integral(A_makima, 0.0, 10.0))
567+
end
514568
end
515569

516570
@testset "ConstantInterpolation" begin

0 commit comments

Comments
 (0)