f2py有更多有趣的例子

现在让我们看看使用f2py的一些更有趣的示例。我们将实现一个简单的迭代方法来求解拉普拉斯方程的平方。实际上,这个实现是从http://www.scipy.org/PerformancePython?突出显示scipy网站上的%28performance%29页面。它有很多关于在python中实现数值算法的信息。

下面的fortran代码实现了求解正方形拉普拉斯方程的松弛方法的一次迭代。

%fortran
      subroutine timestep(u,n,m,dx,dy,error)
      double precision u(n,m)
      double precision dx,dy,dx2,dy2,dnr_inv,tmp,diff
      integer n,m,i,j
cf2py intent(in) :: dx,dy
cf2py intent(in,out) :: u
cf2py intent(out) :: error
cf2py intent(hide) :: n,m
      dx2 = dx*dx
      dy2 = dy*dy
      dnr_inv = 0.5d0 / (dx2+dy2)
      error = 0d0
      do 200,j=2,m-1
         do 100,i=2,n-1
            tmp = u(i,j)
            u(i,j) = ((u(i-1,j) + u(i+1,j))*dy2+
     &           (u(i,j-1) + u(i,j+1))*dx2)*dnr_inv
            diff = u(i,j) - tmp
            error = error + diff*diff
 100     continue
 200  continue
      error = sqrt(error)
      end

如果你这么做的话

timestep?

你发现你需要传递timestep一个numpy数组u,网格间距dx,dy,它将返回更新后的u,以及一个错误估计。要将此应用于实际解决问题,请使用以下驱动程序代码

import numpy
j=numpy.complex(0,1)
num_points=50
u=numpy.zeros((num_points,num_points),dtype=float)
pi_c=float(pi)
x=numpy.r_[0.0:pi_c:num_points*j]
u[0,:]=numpy.sin(x)
u[num_points-1,:]=numpy.sin(x)
def solve_laplace(u,dx,dy):
   iter =0
   err = 2
   while(iter <10000 and err>1e-6):
      (u,err)=timestep(u,dx,dy)
      iter+=1
   return (u,err,iter)

现在调用例程

(sol,err,iter)=solve_laplace(u,pi_c/(num_points-1),pi_c/(num_points-1))

该方法求解的边界条件为:正方形的左、右边缘为正弦函数的半振荡。对于我们正在使用的51x51网格,我发现需要大约0.2秒来解决这个问题,需要2750次迭代。如果您已经安装了gnuplot包(使用可选的packages()来查找它的名称,并使用install package来安装它),那么可以使用

import Gnuplot
g=Gnuplot.Gnuplot(persist=1)
g('set parametric')
g('set data style lines')
g('set hidden')
g('set contour base')
g('set zrange [-.2:1.2]')
data=Gnuplot.GridData(sol,x,x,binary=0)
g.splot(data)

为了了解使用f2py所取得的成果,让我们比较纯python中的相同算法和使用numpy数组的矢量化版本。

def slowTimeStep(u,dx,dy):
    """Takes a time step using straight forward Python loops."""
    nx, ny = u.shape
    dx2, dy2 = dx**2, dy**2
    dnr_inv = 0.5/(dx2 + dy2)


    err = 0.0
    for i in range(1, nx-1):
        for j in range(1, ny-1):
            tmp = u[i,j]
            u[i,j] = ((u[i-1, j] + u[i+1, j])*dy2 +
                      (u[i, j-1] + u[i, j+1])*dx2)*dnr_inv
            diff = u[i,j] - tmp
            err += diff*diff

    return u,numpy.sqrt(err)

def numpyTimeStep(u,dx,dy):
    dx2, dy2 = dx**2, dy**2
    dnr_inv = 0.5/(dx2 + dy2)
    u_old=u.copy()
    # The actual iteration
    u[1:-1, 1:-1] = ((u[0:-2, 1:-1] + u[2:, 1:-1])*dy2 +
                     (u[1:-1,0:-2] + u[1:-1, 2:])*dx2)*dnr_inv
    v = (u - u_old).flat
    return u,numpy.sqrt(numpy.dot(v,v))

您可以通过更改驱动程序例程中使用的timestep函数来尝试这些方法。即使在50x50网格上,python版本也很慢。在3000次迭代中求解系统需要70秒。在大约5000次迭代中,numpy例程需要2秒才能达到误差容限。相比之下,使用3000次迭代,f2py例程大约需要0.2秒才能达到误差容限。我要指出的是,numpy例程不是完全相同的算法,因为它是jacobi迭代,而f2py例程是gauss seidel。这就是为什么numpy版本需要更多的迭代。即便如此,你也可以看到f2py版本的速度似乎是numpy版本的5倍左右。实际上,如果你在一个500x500的网格上尝试这个,我发现numpy例程需要30秒来完成500次迭代,而f2py只需要2秒。所以f2py版本的速度要快15倍。在较小的网格上,每个实际的迭代都相对便宜,因此调用f2py的开销更为明显,在迭代开销较大的示例中,f2py的优势显而易见。即使是在小例子上,它仍然非常快。请注意,使用python编写的500x500网格需要大约半个小时来进行500次迭代。

据我所知,在matlab中实现这个算法最快的方法就是将它矢量化,就像我们的numpy例程一样。matlab和numpy中的向量加法具有可比性。所以除非有我不知道的诀窍,否则使用f2py你可以以交互方式编写比用matlab编写的任何代码快15倍的速度(如果我错了,请纠正我)。实际上,通过使用intent(in、out、overwrite)并使用order='FORTRAN'创建初始numpy数组,可以使f2py版本快一点。这就消除了内存中不必要的复制。