Cubic Spline Program

前端 未结 2 1726
后悔当初
后悔当初 2020-12-29 15:12

I\'m trying to write a cubic spline interpolation program. I have written the program but, the graph is not coming out correctly. The spline uses natural boundary conditions

2条回答
  •  生来不讨喜
    2020-12-29 15:48

    I wrote a cubic spline package in Mathematica a long time ago. Here is my translation of that package into Matlab. Note I haven't looked at cubic splines in about 7 years, so I'm basing this off my own documentation. You should check everything I say.

    The basic problem is we are given n data points (x(1), y(1)) , ... , (x(n), y(n)) and we wish to calculate a piecewise cubic interpolant. The interpolant is defined as

       S(x) = {  Sk(x)   when x(k) <= x <= x(k+1)
              {  0       otherwise 
    

    Here Sk(x) is a cubic polynomial of the form

      Sk(x) = sk0 + sk1*(x-x(k)) + sk2*(x-x(k))^2 + sk3*(x-x(k))^3
    

    The properties of the spline are:

    1. The spline pass through the data point Sk(x(k)) = y(k)
    2. The spline is continuous at the end-points and thus continuous everywhere in the interpolation interval Sk(x(k+1)) = Sk+1(x(k+1))
    3. The spline has continuous first derivative Sk'(x(k+1)) = Sk+1'(x(k+1))
    4. The spline has continuous second derivative Sk''(x(k+1)) = Sk+1''(x(k+1))

    To construct a cubic spline from a set of data point we need to solve for the coefficients sk0, sk1, sk2 and sk3 for each of the n-1 cubic polynomials. That is a total of 4*(n-1) = 4*n - 4 unknowns. Property 1 supplies n constraints, and properties 2,3,4 each supply an additional n-2 constraints. Thus we have n + 3*(n-2) = 4*n - 6 constraints and 4*n - 4 unknowns. This leaves two degrees of freedom. We fix these degrees of freedom by setting the second derivative equal to zero at the start and end nodes.

    Let m(k) = Sk''(x(k)) , h(k) = x(k+1) - x(k) and d(k) = (y(k+1) - y(k))/h(k). The following three-term recurrence relation holds

      h(k-1)*m(k-1) + 2*(h(k-1) + h(k))*m(k) + h(k)*m(k+1) = 6*(d(k) - d(k-1))
    

    The m(k) are unknowns we wish to solve for. The h(k) and d(k) are defined by the input data. This three-term recurrence relation defines a tridiagonal linear system. Once the m(k) are determined the coefficients for Sk are given by

       sk0 = y(k)
       sk1 = d(k) - h(k)*(2*m(k) + m(k-1))/6
       sk2 = m(k)/2
       sk3 = m(k+1) - m(k)/(6*h(k))
    

    Okay that is all the math you need to know to completely define the algorithm to compute a cubic spline. Here it is in Matlab:

    function [s0,s1,s2,s3]=cubic_spline(x,y)
    
    if any(size(x) ~= size(y)) || size(x,2) ~= 1
       error('inputs x and y must be column vectors of equal length');
    end
    
    n = length(x)
    
    h = x(2:n) - x(1:n-1);
    d = (y(2:n) - y(1:n-1))./h;
    
    lower = h(1:end-1);
    main  = 2*(h(1:end-1) + h(2:end));
    upper = h(2:end);
    
    T = spdiags([lower main upper], [-1 0 1], n-2, n-2);
    rhs = 6*(d(2:end)-d(1:end-1));
    
    m = T\rhs;
    
    % Use natural boundary conditions where second derivative
    % is zero at the endpoints
    
    m = [ 0; m; 0];
    
    s0 = y;
    s1 = d - h.*(2*m(1:end-1) + m(2:end))/6;
    s2 = m/2;
    s3 =(m(2:end)-m(1:end-1))./(6*h);
    

    Here is some code to plot a cubic spline:

    function plot_cubic_spline(x,s0,s1,s2,s3)
    
    n = length(x);
    
    inner_points = 20;
    
    for i=1:n-1
       xx = linspace(x(i),x(i+1),inner_points);
       xi = repmat(x(i),1,inner_points);
       yy = s0(i) + s1(i)*(xx-xi) + ... 
         s2(i)*(xx-xi).^2 + s3(i)*(xx - xi).^3;
       plot(xx,yy,'b')
       plot(x(i),0,'r');
    end
    

    Here is a function that constructs a cubic spline and plots in on the famous Runge function:

    function cubic_driver(num_points)
    
    runge = @(x) 1./(1+ 25*x.^2);
    
    x = linspace(-1,1,num_points);
    y = runge(x);
    
    [s0,s1,s2,s3] = cubic_spline(x',y');
    
    plot_points = 1000;
    xx = linspace(-1,1,plot_points);
    yy = runge(xx);
    
    plot(xx,yy,'g');
    hold on;
    plot_cubic_spline(x,s0,s1,s2,s3);
    

    You can see it in action by running the following at the Matlab prompt

     >> cubic_driver(5)
     >> clf 
     >> cubic_driver(10)
     >> clf
     >> cubic_driver(20)
    

    By the time you have twenty nodes your interpolant is visually indistinguishable from the Runge function.

    Some comments on the Matlab code: I don't use any for or while loops. I am able to vectorize all operations. I quickly form the sparse tridiagonal matrix with spdiags. I solve it using the backslash operator. I counting on Tim Davis's UMFPACK to handle the decomposition and forward and backward solves.

    Hope that helps. The code is available as a gist on github https://gist.github.com/1269709

提交回复
热议问题