当前位置: 首页 > news >正文

Python数值计算(24)——PCHIP

1. 算法描述

PCHIP是“分段三次埃尔米特插值多项式(piecewise cubic  Hermite interpolating polynomial)”的缩写,但是单纯根据这个名字,其实并不能确定是哪一种分段三次埃尔米特插值多项式,因为样条插值也是分段三次埃尔米特插值多项式的一种,只是对斜率的约束条件不同而已,这里讨论的是一种保形(shape preserving)的特定插值函数,和Akima插值算法类似,其关键的思想还是如何确定各个采样点的斜率,使函数的值不会再局部上过多的偏离给定的数据。如果该点和前面一个点的差分值m_k,m_{k-1}符号相反,或者有一个为零,则在x_k处函数为离散的极大值或者极小值,因此可以令d_k=0,如果m_k,m_{k-1}符号相同,则可以令:

\frac{w_1+w_2}{d_k}=\frac{w_1}{m_{k-1}}+\frac{w_2}{m_k} \\

w_1=2h_k+h_{k-1}\\ w_2=h_k+2h_{k-1}

但是对于首位两个端点处的斜率,需要用到一点不同的算法,详细的实现见算法描述部分。

2. 算法实现

首先,为了构造Hermite插值多项式,我们要用到前面实现的Hermite插值函数:

import numpy as np
from numpy.polynomial import Polynomial
from scipy.interpolate import PchipInterpolator,CubicHermiteSpline
import matplotlib.pyplot as pltdef hermiteIntp(x,y,dy):'''通过Lagrange插值法返回Hermite插值多项式'''n=len(x)H=Polynomial([0])for k in range(n):roots=list(x)s=roots[k]del roots[k]L=Polynomial.fromroots(roots)L/=L(s)dL=L.deriv()p1=Polynomial([-s,1])L2=L**2H+=y[k]*(1-2*p1*dL(s))*L2H+=dy[k]*p1*L2return H

然后,我们可以定义一个pchipIntp的类,通过它保持分段插值多项式的信息,并为后面计算其他点的函数值提供便利。注意看其中包含了一个私有的成员方法, __enddf,专门用来计算首位端点处的斜率。

class pchipIntp:__bpx:np.ndarray    # 插值点__bpy:np.ndarray    # 插值点处函数值__dy:np.ndarray     # 插值点处导数值__polys=[]  # 分段多项式def __enddf(self,h1,h2,d1,d2):'''计算两个端点处的斜率值'''d=((2*h1+h2)*d1-h1*d2)/(h1+h2)if np.sign(d) != np.sign(d1):d=0elif (np.sign(d1)!=np.sign(d2)) and (np.abs(d)>np.abs(2*d1)):d=3*d1return ddef __init__(self,x,y):self.__bpx=x.copy()self.__bpy=y.copy()n=len(x)h=np.diff(x)d=np.diff(y)/hself.__dy=np.zeros(n)# 首位处的导数值self.__dy[0]=self.__enddf(h[0],h[1],d[0],d[1])self.__dy[-1]=self.__enddf(h[-1],h[-2],d[-1],d[-2])# 中间各点导数值for k in range(1,n-1):if d[k-1]*d[k]>0:w1=2*h[k]+h[k-1]w2=h[k]+2*h[k-1]self.__dy[k]=(w1+w2)/(w1/d[k-1]+w2/d[k])# 构造分段Hermite多项式,使用了前面的算法for k in range(n-1):# 每个子区间使用一个首位端点的函数值和导数值,# 因此该区间段的最高次数为3次p=hermiteIntp(self.__bpx[k:k+2],self.__bpy[k:k+2],self.__dy[k:k+2])self.__polys.append(p)def __call__(self,w:np.ndarray):n=len(w)ret=np.zeros(n)for i in range(n):if w[i]<=self.__bpx[0]:ret[i]=self.__polys[0](w[i])continueif w[i]>=self.__bpx[-1]:ret[i]=self.__polys[-1](w[i])continuej=0while w[i] > self.__bpx[j]:j+=1ret[i]=self.__polys[j-1](w[i])return ret@propertydef x(self):return self.__bpx@propertydef y(self):return self.__bpy@propertydef dy(self):return self.__dy@propertydef c(self):coef=np.zeros((len(self.__polys),4))for i in range(len(self.__polys)):coef[i,:]=self.__polys[i].coefreturn coef

3. 算法测试

可以测试前面生成的pchip插值曲线:

x=np.array([0,1,2,3,4,5,6])
y=np.array([5,4,0,4,6,1,2])
xp=pchipIntp(x,y)
print(xp.c)
t=np.linspace(x[0]-1,x[-1]+1,50)
plt.plot(t,xp(t),'b-.')

输出的系数如下:

[[ 5.00000000e+00  0.00000000e+00 -1.40000000e+00  4.00000000e-01][-9.60000000e+00  3.52000000e+01 -2.80000000e+01  6.40000000e+00][ 8.00000000e+01 -1.01333333e+02  4.13333333e+01 -5.33333333e+00][ 3.80000000e+01 -3.73333333e+01  1.26666667e+01 -1.33333333e+00][-8.74000000e+02  6.00000000e+02 -1.35000000e+02  1.00000000e+01][-1.24000000e+02  7.50000000e+01 -1.50000000e+01  1.00000000e+00]]

每一行表示一个三次插值多项式,注意这里没有考虑起点的问题,因此,任何一段之间的函数都是:

f_i(x)=r_{i,0}+r_{i,1}*x+r_{i,2}*x^2+r_{i,3}*x^3

绘制效果如下:

4. 现有工具包

Scipy中其实有两个类都支持这种pchip插值,分别是PchipInterpolator和CubicHermiteSpline,但是具体使用方式上存在差别,前者只需要提供(x_i,y_i)即可完成插值工作,而后者需要第三个参数,亦即各点处的斜率,结合前面的自定义类,测试如下:

x=np.array([0,1,2,3,4,5,6])
y=np.array([5,4,0,4,6,1,2])
xp=pchipIntp(x,y)t=np.linspace(x[0]-1,x[-1]+1,50)
plt.plot(t,xp(t),'b-.')p=PchipInterpolator(x,y)
plt.plot(t,p(t),'r--')chs=CubicHermiteSpline(x,y,xp.dy)
print(p.c-chs.c)
plt.plot(t,chs(t),'c.')plt.grid()
plt.show()

绘制效果如下:

可以看到三者是一致的。并且输出的系数差:

[[0. 0. 0. 0. 0. 0.][0. 0. 0. 0. 0. 0.][0. 0. 0. 0. 0. 0.][0. 0. 0. 0. 0. 0.]]

可见两者的系数是完全相同的。 但是它们的系数都是采用分段方式,而且是按幂升幂排列的,即有:

f_i(x)=r_{0,i}*(x-x_i)^3+r_{1,i}*(x-x_i)^2+r_{2,i}*(x-x_i)+r_{3,i}

另外,还可以查看一下插值后的1,2,3阶导函数情况:

fig=plt.figure()
plt.plot(t,p.derivative(1)(t),'r')
plt.plot(t,p.derivative(2)(t),'g')
plt.plot(t,p.derivative(3)(t),'b.')
plt.grid()
plt.show()

效果如下:

可以看到,一阶导(红色)是光滑的,二阶导是连续的(红色),三阶导(蓝色)是分段保持恒定的。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Chapter 9 Operational Amplifiers
  • 快速上手Spring Boot
  • 6G:融合5G与AI,重塑网络交互与智能决策的未来
  • NB模组AT 命令用法记录
  • 如何使用yolov5-master进行训练
  • 书生.浦江大模型实战训练营——(四)书生·浦语大模型全链路开源开放体系
  • JavaScript高阶笔记总结第三天:(JavaScript高阶完结)
  • JavaScript中的字符串与数字转换
  • 人工智能GPU算力评估分析
  • Unity动画模块 之 Animation
  • Gradle相关的语法
  • 官宣|Apache Flink 1.20 发布公告
  • linux系统编程:缓冲区,文件io(19)
  • 【Linux】快速入门系列(四) —— Linux实用操作
  • 【LeetCode】452.用最少数量的箭引发气球
  • [iOS]Core Data浅析一 -- 启用Core Data
  • 【Leetcode】104. 二叉树的最大深度
  • Git初体验
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • Laravel 菜鸟晋级之路
  • LeetCode541. Reverse String II -- 按步长反转字符串
  • orm2 中文文档 3.1 模型属性
  • SpiderData 2019年2月23日 DApp数据排行榜
  • webpack入门学习手记(二)
  • 半理解系列--Promise的进化史
  • 如何在 Tornado 中实现 Middleware
  • 我与Jetbrains的这些年
  • 小而合理的前端理论:rscss和rsjs
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • gunicorn工作原理
  • Salesforce和SAP Netweaver里数据库表的元数据设计
  • 回归生活:清理微信公众号
  • #LLM入门|Prompt#3.3_存储_Memory
  • #ubuntu# #git# repository git config --global --add safe.directory
  • #控制台大学课堂点名问题_课堂随机点名
  • $.each()与$(selector).each()
  • (9)目标检测_SSD的原理
  • (MATLAB)第五章-矩阵运算
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (附源码)基于ssm的模具配件账单管理系统 毕业设计 081848
  • (附源码)计算机毕业设计高校学生选课系统
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (接口封装)
  • (三)centos7案例实战—vmware虚拟机硬盘挂载与卸载
  • (四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • (原)Matlab的svmtrain和svmclassify
  • (原創) 人會胖會瘦,都是自我要求的結果 (日記)
  • (转)如何上传第三方jar包至Maven私服让maven项目可以使用第三方jar包
  • *ST京蓝入股力合节能 着力绿色智慧城市服务
  • *算法训练(leetcode)第四十七天 | 并查集理论基础、107. 寻找存在的路径
  • .[hudsonL@cock.li].mkp勒索病毒数据怎么处理|数据解密恢复
  • .bat批处理(六):替换字符串中匹配的子串
  • .net framework 4.0中如何 输出 form 的name属性。
  • .net 程序发生了一个不可捕获的异常