Julia中复现R的FMM方法三次样条插值(不规则网格)
复现R的FMM样条插值在Julia中
我明白你遇到的问题——R的默认spline()方法用的是Forsyth-Malcolm-Moler(FMM)三次样条,而Dierckx.jl默认是自然样条,结果自然对不上。好在我们可以通过Julia的Interpolations.jl包来完美复现R的FMM行为,或者调整Dierckx的参数来匹配。
核心原理:R的FMM样条是什么?
R的method="fmm"本质上是带not-a-knot(非节点)边界条件的三次样条插值,具体行为:
- 当输入点数量
n=2:退化为线性插值 - 当
n=3:退化为二次(抛物线)样条 - 当
n≥4:使用not-a-knot边界条件——强制第一个内部节点和第二个节点的三阶导数连续,最后一个内部节点和倒数第二个节点的三阶导数连续,这也是FMM方法的核心定义。
方案一:用Interpolations.jl实现(推荐)
现在Interpolations.jl已经支持不规则网格的三次样条插值了(可能你看的是旧版文档),只需要指定NotAKnot()边界条件就能完全匹配R的FMM结果。
代码实现:
using Interpolations function spline_j(x::Vector{Float64}, y::Vector{Float64}, xout::Vector{Float64}) n = length(x) if n == 2 # 线性插值,和R一致 itp = LinearInterpolation(x, y) elseif n == 3 # 二次样条(抛物线),匹配R的fmm在n=3时的行为 itp = QuadraticInterpolation(x, y, extrapolation_bc=Line()) else # n≥4时,使用not-a-knot边界的三次样条,完全匹配R的fmm itp = CubicSplineInterpolation(x, y, bc=NotAKnot()) end # R的spline默认外插为线性,这里同步设置 return itp(xout, extrapolation_bc=Line()) end
验证匹配:
用一组测试数据对比R和Julia的结果:
R代码:
x <- c(1,3,5,7,9) y <- c(2,6,4,8,10) xout <- seq(1,9,0.5) r_result <- spline(x,y,"fmm",xout=xout)$y
Julia代码:
x = [1.0,3.0,5.0,7.0,9.0] y = [2.0,6.0,4.0,8.0,10.0] xout = collect(1.0:0.5:9.0) julia_result = spline_j(x,y,xout)
两者的结果会完全一致(误差在浮点精度范围内)。
方案二:用Dierckx.jl手动匹配FMM边界条件
如果因为某些原因必须用Dierckx.jl,我们需要手动计算not-a-knot边界的二阶导数(Dierckx没有直接的not-a-knot选项),这个方法适合需要深度定制的场景:
核心思路:
Not-a-knot条件要求:
- 第一个区间的三阶导数等于第二个区间的三阶导数
- 倒数第一个区间的三阶导数等于倒数第二个区间的三阶导数
通过这个条件可以推导出边界点的二阶导数,然后传递给Dierckx的bc参数。
代码示例(简化版,适用于n≥4):
using Dierckx function fmm_bc(x, y) n = length(x) h1 = x[2]-x[1] h2 = x[3]-x[2] hn_2 = x[n-1]-x[n-2] hn_1 = x[n]-x[n-1] # 计算左边界的二阶导数(not-a-knot条件) a = h1*(h1+h2) b = h2^2 c = (y[3]-y[2])/h2 - (y[2]-y[1])/h1 d2y_left = (6*(h1*c))/(a + b) # 计算右边界的二阶导数(not-a-knot条件) a = hn_1*(hn_1+hn_2) b = hn_2^2 c = (y[n]-y[n-1])/hn_1 - (y[n-1]-y[n-2])/hn_2 d2y_right = (6*(hn_1*c))/(a + b) return (d2y_left, d2y_right) end function spline_j_dierckx(x, y, xout) n = length(x) if n == 2 spl = Dierckx.Spline1D(x, y, k=1) # 线性插值 elseif n == 3 spl = Dierckx.Spline1D(x, y, k=2) # 二次样条 else d2y_left, d2y_right = fmm_bc(x, y) # 指定边界二阶导数,匹配not-a-knot条件 spl = Dierckx.Spline1D(x, y, bc=((Derivative(2), d2y_left), (Derivative(2), d2y_right))) end return spl(xout) end
这个版本也能匹配R的FMM结果,但需要自己处理边界条件的推导,不如Interpolations方案简洁。
总结
推荐使用Interpolations.jl的方案,代码更简洁且完全匹配R的FMM行为,同时覆盖了R的spline所有场景(线性、二次、三次not-a-knot)。
内容的提问来源于stack exchange,提问作者Philip Swannell
相关产品推荐
相关产品推荐

