You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:42:08