2011-10-04 26 views
6

Estoy tratando de escribir un programa de interpolación spline cúbico. He escrito el programa, pero el gráfico no está saliendo correctamente. La spline usa condiciones de contorno naturales (el segundo derivado en el nodo de inicio/fin es 0). El código está en Matlab y se muestra a continuación,Programa spline cúbico

clear all 
%Function to Interpolate 
k = 10;     %Number of Support Nodes-1 
xs(1) = -1; 
for j = 1:k 
    xs(j+1) = -1 +2*j/k; %Support Nodes(Equidistant) 
end; 
fs = 1./(25.*xs.^2+1);  %Support Ordinates 
x = [-0.99:2/(2*k):0.99]; %Places to Evaluate Function 
fx = 1./(25.*x.^2+1);  %Function Evaluated at x 

%Cubic Spline Code(Coefficients to Calculate 2nd Derivatives) 

f(1) = 2*(xs(3)-xs(1)); 
g(1) = xs(3)-xs(2); 
r(1) = (6/(xs(3)-xs(2)))*(fs(3)-fs(2)) + (6/(xs(2)-xs(1)))*(fs(1)-fs(2)); 
e(1) = 0; 

for i = 2:k-2 
    e(i) = xs(i+1)-xs(i); 
    f(i) = 2*(xs(i+2)-xs(i)); 
    g(i) = xs(i+2)-xs(i+1); 
    r(i) = (6/(xs(i+2)-xs(i+1)))*(fs(i+2)-fs(i+1)) + ... 
      (6/(xs(i+1)-xs(i)))*(fs(i)-fs(i+1)); 
end 
e(k-1) = xs(k)-xs(k-1); 
f(k-1) = 2*(xs(k+1)-xs(k-1)); 
r(k-1) = (6/(xs(k+1)-xs(k)))*(fs(k+1)-fs(k)) + ... 
     (6/(xs(k)-xs(k-1)))*(fs(k-1)-fs(k)); 

%Tridiagonal System 

i = 1; 
A = zeros(k-1,k-1); 
while i < size(A)+1; 
    A(i,i) = f(i); 
    if i < size(A); 
     A(i,i+1) = g(i); 
     A(i+1,i) = e(i); 
    end 
    i = i+1; 
end 

for i = 2:k-1        %Decomposition 
    e(i) = e(i)/f(i-1); 
    f(i) = f(i)-e(i)*g(i-1); 
end 

for i = 2:k-1        %Forward Substitution 
    r(i) = r(i)-e(i)*r(i-1); 
end 

xn(k-1)= r(k-1)/f(k-1); 
for i = k-2:-1:1       %Back Substitution 
    xn(i) = (r(i)-g(i)*xn(i+1))/f(i); 
end 

%Interpolation 

if (max(xs) <= max(x)) 
    error('Outside Range'); 
end 

if (min(xs) >= min(x)) 
    error('Outside Range'); 
end 


P = zeros(size(length(x),length(x))); 
i = 1; 
for Counter = 1:length(x) 
    for j = 1:k-1 
     a(j) = x(Counter)- xs(j); 
    end 
    i = find(a == min(a(a>=0))); 
    if i == 1 
     c1 = 0; 
     c2 = xn(1)/6/(xs(2)-xs(1)); 
     c3 = fs(1)/(xs(2)-xs(1)); 
     c4 = fs(2)/(xs(2)-xs(1))-xn(1)*(xs(2)-xs(1))/6; 
     t1 = c1*(xs(2)-x(Counter))^3; 
     t2 = c2*(x(Counter)-xs(1))^3; 
     t3 = c3*(xs(2)-x(Counter)); 
     t4 = c4*(x(Counter)-xs(1)); 
     P(Counter) = t1 +t2 +t3 +t4; 
    else 
     if i < k-1 
     c1 = xn(i-1+1)/6/(xs(i+1)-xs(i-1+1)); 
     c2 = xn(i+1)/6/(xs(i+1)-xs(i-1+1)); 
     c3 = fs(i-1+1)/(xs(i+1)-xs(i-1+1))-xn(i-1+1)*(xs(i+1)-xs(i-1+1))/6; 
     c4 = fs(i+1)/(xs(i+1)-xs(i-1+1))-xn(i+1)*(xs(i+1)-xs(i-1+1))/6; 
     t1 = c1*(xs(i+1)-x(Counter))^3; 
     t2 = c2*(x(Counter)-xs(i-1+1))^3; 
     t3 = c3*(xs(i+1)-x(Counter)); 
     t4 = c4*(x(Counter)-xs(i-1+1)); 
     P(Counter) = t1 +t2 +t3 +t4; 
     else 
     c1 = xn(i-1+1)/6/(xs(i+1)-xs(i-1+1)); 
     c2 = 0; 
     c3 = fs(i-1+1)/(xs(i+1)-xs(i-1+1))-xn(i-1+1)*(xs(i+1)-xs(i-1+1))/6; 
     c4 = fs(i+1)/(xs(i+1)-xs(i-1+1)); 
     t1 = c1*(xs(i+1)-x(Counter))^3; 
     t2 = c2*(x(Counter)-xs(i-1+1))^3; 
     t3 = c3*(xs(i+1)-x(Counter)); 
     t4 = c4*(x(Counter)-xs(i-1+1)); 
     P(Counter) = t1 +t2 +t3 +t4; 
     end  
    end 
end 

P = P'; 
P(length(x)) = NaN; 

plot(x,P,x,fx) 

Cuando ejecuto el código, la función de interpolación no es simétrica y, no converge correctamente. ¿Alguien puede ofrecer alguna sugerencia sobre problemas en mi código? Gracias.

Respuesta

9

Escribí un paquete spline cúbico en Mathematica hace mucho tiempo. Aquí está mi traducción de ese paquete a Matlab. Tenga en cuenta que no he visto splines cúbicos en aproximadamente 7 años, así que estoy basando esto en mi propia documentación. Deberías verificar todo lo que digo.

El problema básico es que se nos dan n puntos de datos (x(1), y(1)) , ... , (x(n), y(n)) y deseamos calcular un interpolador cúbico por partes. El interpolador se define como

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

Aquí Sk (x) es un polinomio cúbico de la forma

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

Las propiedades de la spline son:

  1. El pase spline a través de los datos punto Sk(x(k)) = y(k)
  2. La spline es continua en los puntos finales y por lo tanto continua en todas partes en el intervalo de interpolación Sk(x(k+1)) = Sk+1(x(k+1))
  3. El spline tiene primera derivada continua Sk'(x(k+1)) = Sk+1'(x(k+1))
  4. El spline tiene continua segunda derivada Sk''(x(k+1)) = Sk+1''(x(k+1))

Para construir un spline cúbico de un conjunto de punto de datos que necesitamos para resolver para los coeficientes sk0, sk1, sk2 y sk3 para cada uno de los polinomios cúbicos n-1. Eso es un total de 4*(n-1) = 4*n - 4 incógnitas. La propiedad 1 proporciona n restricciones y las propiedades 2,3,4 proporcionan restricciones adicionales n-2. Por lo tanto, tenemos n + 3*(n-2) = 4*n - 6 restricciones y 4*n - 4 incógnitas. Esto deja dos grados de libertad. Arreglamos estos grados de libertad al establecer la segunda derivada igual a cero en los nodos de inicio y final.

Deje m(k) = Sk''(x(k)), h(k) = x(k+1) - x(k) y d(k) = (y(k+1) - y(k))/h(k). La siguiente relación de recurrencia de tres términos sostiene

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)) 

el m (k) son incógnitas que queremos resolver. h(k) y d(k) están definidos por los datos de entrada. Esta relación de recurrencia a tres términos define un sistema lineal tridiagonal. Una vez que el m(k) se determinan los coeficientes de Sk están dados por

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)) 

bien esto es todas las matemáticas que necesita saber para definir completamente el algoritmo para calcular un spline cúbico.Aquí está en 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); 

Aquí hay un código para trazar una spline cúbica:

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 

aquí es una función que construye un spline cúbico y parcelas en la famosa función Runge:

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); 

Usted puede verlo en acción mediante la ejecución del siguiente en el Matlab pedirá

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

En el momento en que tenga veinte nodos, su interpolador es indistinguible visualmente de la función Runge.

Algunos comentarios sobre el código de Matlab: No utilizo ningún bucle for o while. Puedo vectorizar todas las operaciones. Rápidamente formé la matriz tridiagonal dispersa con spdiags. Lo resuelvo usando el operador de barra invertida. Cuento con UMFPACK de Tim Davis para manejar la descomposición y resolver hacia delante y hacia atrás.

Espero que ayude. El código está disponible como una esencia en github https://gist.github.com/1269709

Cuestiones relacionadas