SciPy-数值计算库

SciPy-数值计算库

  SciPy函数库在NumPy库的基础上增加了众多的数学、科学以及工程计算中常用的库函数。让我们看看如何用SciPy进行插值处理、信号滤波以及用C语言加速计算。

一、最小二乘拟合

假设有一组实验数据(x[i], y[i]),我们知道它们之间的函数关系:y = f(x),通过这些已知信息,需要确定函数中的一些参数项。例如,如果f是一个线型函数f(x) = k*x+b,那么参数k和b就是我们需要确定的值。如果将这些参数用p 表示的话,那么我们就是要找到一组p 值使得如下公式中的S函数最小:

S ( P ) = i = 1 m [ y i f ( x i , P ) ] 2

这种算法被称之为最小二乘拟合(Least-square fitting)。

二、函数最小值

optimize库提供了几个求函数最小值的算法:fmin, fmin_powell, fmin_cg, fmin_bfgs。

三、非线性方程组求解

optimize库中的fsolve函数可以用来对非线性方程组进行求解。它的基本调用形式如下:

fsolve(func, x0)

func(x)是计算方程组误差的函数,它的参数x是一个矢量,表示方程组的各个未知数的一组可能解,
func返回将x代入方程组之后得到的误差;x0为未知数矢量的初始值。如果要对如下方程组进行求解的话:
* f1(u1,u2,u3) = 0
* f2(u1,u2,u3) = 0
* f3(u1,u2,u3) = 0
那么func可以如下定义:

def func(x):
u1,u2,u3 = x
return [f1(u1,u2,u3), f2(u1,u2,u3), f3(u1,u2,u3)]

四、B-Spline样条曲线

interpolate库提供了许多对数据进行插值运算的函数。下面是使用直线和B-Spline对正弦波上的点进行插值的例子。

# -*- coding: utf-8 -*-
import numpy as np
import pylab as pl
from scipy import interpolate
from pylab import mpl  #解决图中汉字乱码问题
mpl.rcParams['font.sans-serif'] = ['SimHei'] 
mpl.rcParams['axes.unicode_minus'] = False #使图表中负号正常输出

x = np.linspace(0, 2*np.pi+np.pi/4, 10)
y = np.sin(x)
x_new = np.linspace(0, 2*np.pi+np.pi/4, 100)
f_linear = interpolate.interp1d(x, y)
tck = interpolate.splrep(x, y)
y_bspline = interpolate.splev(x_new, tck)
pl.plot(x, y, "o", label=u"原始数据")
pl.plot(x_new, f_linear(x_new), label=u"线性插值")
pl.plot(x_new, y_bspline, label=u"B-spline插值")
pl.legend()
pl.show()

这里写图片描述

五、数值积分

数值积分是对定积分的数值求解,例如可以利用数值积分计算某个形状的面积。单位半圆曲线可以用下
面的函数表示:

def half_circle(x):
return (1-x**2)**0.5

多重定积分的求值可以通过多次调用quad函数实现,为了调用方便,integrate库提供了dblquad函数进行二重定积分,tplquad函数进行三重定积分。
dblquad函数的调用方式为:

dblquad(func2d, a, b, gfun, hfun)

对于func2d(x,y)函数进行二重积分,其中a,b为变量x的积分区间,而gfun(x)到hfun(x)为变量y的积分区间。

六、解常微分方程组

scipy.integrate库提供了数值积分和常微分方程组求解算法odeint。
下面让我们来看看如何用odeint计算洛仑兹吸引子的轨迹。洛仑兹吸引子由下面的三个微分方程定义:

d x / d t = σ ( y x )

d y / d t = x ( ρ z ) y

d z / d t = x y β z

这三个方程定义了三维空间中各个坐标点上的速度矢量。从某个坐标开始沿着速度矢量进行积分,就可以计算出无质量点在此空间中的运动轨迹。其中σ, ρ, β 为三个常数,不同的参数可以计算出不同的运动轨迹: x(t), y(t), z(t)。当参数为某些值时,轨迹出现馄饨现象:即微小的初值差别也会显著地影响运动轨迹。下面是洛仑兹吸引子的轨迹计算和绘制程序:

# -*- coding: utf-8 -*-
from scipy.integrate import odeint
import numpy as np

def lorenz(w, t, p, r, b):
 # 给出位置矢量w,和三个参数p, r, b计算出
 # dx/dt, dy/dt, dz/dt的值
   x, y, z = w
# 直接与lorenz的计算公式对应
   return np.array([p*(y-x), x*(r-z)-y, x*y-b*z])

t = np.arange(0, 30, 0.01) # 创建时间点
# 调用ode对lorenz进行求解, 用两个不同的初始值
track1 = odeint(lorenz, (0.0, 1.00, 0.0), t, args=(10.0, 28.0, 3.0))
track2 = odeint(lorenz, (0.0, 1.01, 0.0), t, args=(10.0, 28.0, 3.0))

# 绘图
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt

fig = plt.figure()
ax = Axes3D(fig)
ax.plot(track1[:,0], track1[:,1], track1[:,2])
ax.plot(track2[:,0], track2[:,1], track2[:,2])
plt.show()

这里写图片描述
我们看到即使初始值只相差0.01,两条运动轨迹也是完全不同的。
在程序中先定义一个lorenz函数,它的任务是计算出某个位置的各个方向的微分值,这个计算直接根据洛仑兹吸引子的公式得出。然后调用odeint,对微分方程求解,odeint有许多参数,这里用到的四个参数分别为:
1. lorenz, 它是计算某个位移上的各个方向的速度(位移的微分)
2. (0.0, 1.0, 0.0),位移初始值。计算常微分方程所需的各个变量的初始值
3. t, 表示时间的数组,odeint对于此数组中的每个时间点进行求解,得出所有时间点的位置
4. args, 这些参数直接传递给lorenz函数,因此它们都是常量

七、滤波器设计

scipy.signal库提供了许多信号处理方面的函数。
导入signal库:

import scipy.signal as signal

下面的程序设计一个带通IIR滤波器:

b, a = signal.iirdesign([0.2, 0.5], [0.1, 0.6], 2, 40)

iirdesgin返回的两个数组b和a, 它们分别是IIR滤波器的分子和分母部分的系数。其中a[0]恒等于1。
下面通过调用freqz计算所得到的滤波器的频率响应:

w, h = signal.freqz(b, a)

下面计算h的增益特性,并转换为dB度量。由于h中存在幅值几乎为0的值,因此先用clip函数对其裁剪之后,再调用对数函数,避免计算出错。

power = 20*np.log10(np.clip(np.abs(h), 1e-8, 1e100))

通过下面的语句可以绘制出滤波器的增益特性图,这里假设取样频率为8kHz:

pl.plot(w/np.pi*4000, power)

在实际运用中为了测量未知系统的频率特性,经常将频率扫描波输入到系统中,观察系统的输出,从而计算其频率特性。下面让我们来模拟这一过程。
为了调用chirp函数以产生频率扫描波形的数据,首先需要产生一个等差数组代表取样时间,下面的语句产生2秒钟取样频率为8kHz的取样时间数组:

t = np.arange(0, 2, 1/8000.0)

然后调用chirp得到2秒钟的频率扫描波形的数据:

sweep = signal.chirp(t, f0=0, t1 = 2, f1=4000.0)

下面通过调用lfilter函数计算sweep波形经过带通滤波器之后的结果:

out = signal.lfilter(b, a, sweep)

lfilter内部通过如下算式计算IIR滤波器的输出:
通过如下算式可以计算输入为x时的滤波器的输出,其中数组x代表输入信号,y代表输出信号:
y[n] = b[0]x[n] + b[1]x[n-1] +    + b[P]x[n-P]-a[1]y[n-1]-a[2]y[n-2] -    -a[Q]y[n-Q]
为了和系统的增益特性图进行比较,需要获取输出波形的包络,因此下面先将输出波形数据转换为能量值:

out = 20*np.log10(np.abs(out))

为了计算包络,找到所有能量大于前后两个取样点(局部最大点)的下标:

index = np.where(np.logical_and(out[1:-1] > out[:-2], out[1:-1] > out[2:]))[0] + 1

最后将时间转换为对应的频率,绘制所有局部最大点的能量值:

pl.plot(t[index]/2.0*4000, out[index] )

八、用Weave嵌入C语言

Python作为动态语言其功能虽然强大,但是在数值计算方面有一个最大的缺点:速度不够快。在Python级别的循环和计算的速度只有C语言程序的百分之一。因此才有了NumPy, SciPy这样的函数库,将高度优化的C、Fortran的函数库进行包装,以供Python程序调用。如果这些高度优化的函数库无法实现我们的算法,必须从头开始写循环、计算的话,那么用Python来做显然是不合适的。因此SciPy提供了快速调用C++语言程序的方法– Weave。

用Weave编译的C语言程序比numpy自带的sum函数还要快。而Python的内部函数sum使用数组的迭代器接口进行运算,因此速度是Python语言级别的,只有Weave版本的1/300。

猜你喜欢

转载自blog.csdn.net/y_bing/article/details/82657025