改进埃尔米特(Hermite)分段三次插值——(可在pchip函数中自定义导数值)

文章目录

  • 前言
  • 一、三次样条插值 & Hermite插值
  • 二、pchip_pro函数
    • 1.pchip_pro函数说明
    • 2.pchip_pro函数代码
    • 3.pchip_pro使用
  • 总结


前言

提示:pchip_pro函数,既能在离散点中进行Hermite分段三次插值,又能指定修改插值曲线中某已知点的导数值
当你有若干个离散点,且需要指定插值曲线在离散点的导数,埃尔米特(Hermite)插值算法是个不错的选择。

Matlab提供的pchip函数是Hermite的分段三次插值函数,解决高阶Hermite插值曲线的收敛性和稳定性差的问题,但pchip函数具有不支持指定设置某离散点处导数的bug

如果你也有这样的烦恼,那我基于pchip函数改进的pchip_pro函数将完美解决这个问题。


一、三次样条插值 & Hermite插值

matlab自带函数中:
pchip函数=Hermite分段三次插值函数
spline函数=分段三次样条插值函数

这段时间研究离散点的插值算法,学习过三次样条插值、最小二乘法、拉格朗日插值、Hermite插值、牛顿插值等。
提示:在B站学习三次样条插值和Hermite分段三次插值后再看本文会有更好的效果
简单且插值效果较好较平滑的还得是三次样条和Hermite分段三次插值。

三次样条的特点是达到二阶光滑度(分段函数一阶二阶导数均连续),而Hermite分段三次插值只达到一阶光滑度(分段函数一阶导数连续,二阶不一定)。

在光滑程度方面,spline比pchip更光滑。但对我来说,pchip只达到一阶连续反而更有优势,至少表现在连续两三个数值点差距小的区域内插值数值更稳定。形象表述如官方给的下图红框所示:
改进埃尔米特(Hermite)分段三次插值——(可在pchip函数中自定义导数值)_第1张图片


二、pchip_pro函数

1.pchip_pro函数说明

pchip_pro为主函数,后面附属有 chckxy_pro 和 pchipslopes 两个子函数。
在主函数中我加入的输入参数yy相当于个“小插件”(代码中有明确标注)。

提示:yy中的“999”为不指定数值点的导数(默认为原pchip计算的已知点处导数值)
e.g:已知点(x1,y1)、(x2,y2)、(x3,y3)、(x4,y4),在四个点中进行pchip插值计算,且只需要修改(x2,y2)处导数为6,则调用pchip_pro前设参数yy=[999 6 999 999]。

2.pchip_pro函数代码

function v = pchip_pro(x,y,xx,yy)
%% 改进pchip函数,输入输出的参数说明:
% x:已知点的X轴坐标
% y:已知点的Y轴坐标
% xx:需要插值的X轴坐标
% yy:已知点的处的导数值
% 注:默认使用pchip自动生成的导数值处用“999”输入
% (如原pchip函数生成的导数为[0 1 2 3],我只需要在3个值处指定导数为0,那调用pchip_pro前设yy=[999 999 0 999]% v:对应需要插值的xx处的数值
%% pchip原函数部分
[x,y,sizey] = chckxy_pro(x,y)
h = diff(x)
m = prod(sizey)  % prod:数组元素的乘积

%ensure that the interpolation points are real

if nargin==3 && any(~isreal(reshape(xx,numel(xx),1))) 
  error(message('MATLAB:pchip:ComplexInterpPts')) 
end

% Compute slopes

del = diff(y,1,2)./repmat(h,m,1)
slopes = zeros(size(y))
for r = 1:m
     if isreal(del)
      slopes(r,:) = pchipslopes(x,y(r,:),del(r,:))
     else
      realslopes = pchipslopes(x,y(r,:),real(del(r,:))) 
      imagslopes = pchipslopes(x,y(r,:),imag(del(r,:)))
      slopes(r,:) = complex(realslopes, imagslopes)
     end
end

%% 我添加的“小插件”部分,其他都是matlba自带的函数,哈哈哈哈哈
a=find(yy~=999)
for i=1:numel(a)
slopes(a(i))=yy(a(i))
end

%% pchip原函数部分
% Compute piecewise cubic Hermite interpolant to those values and slopes

v = pwch(x,y,slopes,h,del); 
v.dim = sizey
v = ppval(v,xx)
end


%% pchip原函数中调用的chckxy部分

function [x,y,sizey,endslopes] = chckxy_pro(x,y)
%CHCKXY check and adjust input for SPLINE and PCHIP
%   [X,Y,SIZEY] = CHCKXY(X,Y) checks the data sites X and corresponding data
%   values Y, making certain that there are exactly as many sites as values,
%   that no two data sites are the same, removing any data points that involve 
%   NaNs, reordering the sites if necessary to ensure that X is a strictly
%   increasing row vector and reordering the data values correspondingly,
%   and reshaping Y if necessary to make sure that it is a matrix, with Y(:,j)
%   the data value corresponding to the data site X(j), and with SIZEY the
%   actual dimensions of the given values. 
%   This call to CHCKXY is suitable for PCHIP.
%
%   [X,Y,SIZEY,ENDSLOPES] = CHCKXY(X,Y) also considers the possibility that
%   there are two more data values than there are data sites.
%   If there are, then the first and the last data value are removed from Y
%   and returned separately as ENDSLOPES. Otherwise, an empty ENDSLOPES is
%   returned.  This call to CHCKXY is suitable for SPLINE.
%
%   See also PCHIP, SPLINE.

%   Copyright 1984-2011 The MathWorks, Inc.

% make sure X is a vector:
if length(find(size(x)>1))>1 
  error(message('MATLAB:chckxy:XNotVector')) 
end

% ensure X is real
if any(~isreal(x)) 
  error(message('MATLAB:chckxy:XComplex')) 
end

% deal with NaN's among the sites:
nanx = find(isnan(x));
if ~isempty(nanx)
   x(nanx) = [];
   warning(message('MATLAB:chckxy:nan'))
end

n=length(x);
if n<2 
  error(message('MATLAB:chckxy:NotEnoughPts')) 
end

% re-sort, if needed, to ensure strictly increasing site sequence:
x=x(:).'; 
dx = diff(x);

if any(dx<0), [x,ind] = sort(x); dx = diff(x); else ind=1:n; end

if ~all(dx), error(message('MATLAB:chckxy:RepeatedSites')), end

% if Y is ND, reshape it to a matrix by combining all dimensions but the last:
sizey = size(y);


while length(sizey)>2&&sizey(end)==1, sizey(end) = []; end


yn = sizey(end); 
sizey(end)=[]; 
yd = prod(sizey);

if length(sizey)>1
   y = reshape(y,yd,yn);
else
   % if Y happens to be a column matrix, change it to the expected row matrix.
   if yn==1
       yn = yd;
       y = reshape(y,1,yn); 
       yd = 1; 
       sizey = yd;
   end
end

% determine whether not-a-knot or clamped end conditions are to be used:
nstart = n+length(nanx);
if yn==nstart
   endslopes = [];
elseif nargout==4&&yn==nstart+2
   endslopes = y(:,[1 n+2]); y(:,[1 n+2])=[];
   if any(isnan(endslopes))
      error(message('MATLAB:chckxy:EndslopeNaN'))
   end
   if any(isinf(endslopes))
       error(message('MATLAB:chckxy:EndslopeInf'))
   end
else
   error(message('MATLAB:chckxy:NumSitesMismatchValues',nstart, yn))
end

% deal with NaN's among the values:
if ~isempty(nanx)
    y(:,nanx) = [];
end

y=y(:,ind);
nany = find(sum(isnan(y),1));
if ~isempty(nany)
   y(:,nany) = []; x(nany) = [];
   warning(message('MATLAB:chckxy:IgnoreNaN'))
   n = length(x);
   if n<2 
     error(message('MATLAB:chckxy:NotEnoughPts')) 
   end
end
end



%% pchip原函数中调用的pchipslopes部分

function d = pchipslopes(x,y,del)
%PCHIPSLOPES  Derivative values for shape-preserving Piecewise Cubic Hermite
% Interpolation.
% d = pchipslopes(x,y,del) computes the first derivatives, d(k) = P'(x(k)).

%  Special case n=2, use linear interpolation.

   n = length(x);
   if n==2  
      d = repmat(del(1),size(y));
      return
   end

%  Slopes at interior points.
%  d(k) = weighted average of del(k-1) and del(k) when they have the same sign.
%  d(k) = 0 when del(k-1) and del(k) have opposites signs or either is zero.

   d = zeros(size(y));
  
   k = find(sign(del(1:n-2)).*sign(del(2:n-1)) > 0);
  
   h = diff(x);
   hs = h(k)+h(k+1);
   w1 = (h(k)+hs)./(3*hs);
   w2 = (hs+h(k+1))./(3*hs);
   dmax = max(abs(del(k)), abs(del(k+1)));
   dmin = min(abs(del(k)), abs(del(k+1)));
   d(k+1) = dmin./conj(w1.*(del(k)./dmax) + w2.*(del(k+1)./dmax));

%  Slopes at end points.
%  Set d(1) and d(n) via non-centered, shape-preserving three-point formulae.

   d(1) = ((2*h(1)+h(2))*del(1) - h(1)*del(2))/(h(1)+h(2));
   if sign(d(1)) ~= sign(del(1))
      d(1) = 0;
   elseif (sign(del(1)) ~= sign(del(2))) && (abs(d(1)) > abs(3*del(1)))
      d(1) = 3*del(1);
   end
   d(n) = ((2*h(n-1)+h(n-2))*del(n-1) - h(n-1)*del(n-2))/(h(n-1)+h(n-2));
   if sign(d(n)) ~= sign(del(n-1))
      d(n) = 0;
   elseif (sign(del(n-1)) ~= sign(del(n-2))) && (abs(d(n)) > abs(3*del(n-1)))
      d(n) = 3*del(n-1);
   end
end

3.pchip_pro使用

新建脚本,复制代码,保存文件至matlab搜索路径下。
调用pchip_pro前确定需要调整点的导数,可先创建数值全为“999”的yy数组,再针对某点设插值曲线的导数。

使用示例:
在官方给出spline和pchip区别示例上加入pchip_pro,使(-2,-1)、(2,1)处的导数为1,代码及结果如下图所示:

x = -3:3;
y = [-1 -1 -1 0 1 1 1];
t = -3:.01:3;
p = pchip(x,y,t);
s = spline(x,y,t);
yy=[999 1 999 999 999 1 999]
a = pchip_pro(x,y,t,yy)
plot(x,y,'o',t,p,'-',t,s,'-.',t,a,'--')
legend('data','pchip','spline','pchip_pro','Location','SouthEast')

改进埃尔米特(Hermite)分段三次插值——(可在pchip函数中自定义导数值)_第2张图片


总结

本次pchip_pro函数在pchip函数的基础上改进,解决了pchip中不可指定已知点导数的问题,使Hermite分段三次插值算法更加自定义化。

分享代码若有错漏之处,请大家留言批评指正!!
如果本篇文章对您有用的话,欢迎点赞收藏噢,谢谢谢谢,哈哈哈哈哈哈!!

主页还有更加丰富的内容噢 O(∩_∩)O :
Matlab 地理(经纬度)坐标 转 笛卡尔(直角)坐标
Matlab 土法求航海DCPA和TCPA,并根据DCPA正负判断目标船过本船船首or船尾

你可能感兴趣的:(matlab,算法,线性代数)