提示:pchip_pro函数,既能在离散点中进行Hermite分段三次插值,又能指定修改插值曲线中某已知点的导数值
当你有若干个离散点,且需要指定插值曲线在离散点的导数,埃尔米特(Hermite)插值算法是个不错的选择。
Matlab提供的pchip函数是Hermite的分段三次插值函数,解决高阶Hermite插值曲线的收敛性和稳定性差的问题,但pchip函数具有不支持指定设置某离散点处导数的bug。
如果你也有这样的烦恼,那我基于pchip函数改进的pchip_pro函数将完美解决这个问题。
matlab自带函数中:
pchip函数=Hermite分段三次插值函数
spline函数=分段三次样条插值函数
这段时间研究离散点的插值算法,学习过三次样条插值、最小二乘法、拉格朗日插值、Hermite插值、牛顿插值等。
提示:在B站学习三次样条插值和Hermite分段三次插值后再看本文会有更好的效果
简单且插值效果较好较平滑的还得是三次样条和Hermite分段三次插值。
三次样条的特点是达到二阶光滑度(分段函数一阶二阶导数均连续),而Hermite分段三次插值只达到一阶光滑度(分段函数一阶导数连续,二阶不一定)。
在光滑程度方面,spline比pchip更光滑。但对我来说,pchip只达到一阶连续反而更有优势,至少表现在连续两三个数值点差距小的区域内插值数值更稳定。形象表述如官方给的下图红框所示:
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]。
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
新建脚本,复制代码,保存文件至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')
本次pchip_pro函数在pchip函数的基础上改进,解决了pchip中不可指定已知点导数的问题,使Hermite分段三次插值算法更加自定义化。
分享代码若有错漏之处,请大家留言批评指正!!
如果本篇文章对您有用的话,欢迎点赞收藏噢,谢谢谢谢,哈哈哈哈哈哈!!
主页还有更加丰富的内容噢 O(∩_∩)O :
Matlab 地理(经纬度)坐标 转 笛卡尔(直角)坐标
Matlab 土法求航海DCPA和TCPA,并根据DCPA正负判断目标船过本船船首or船尾