use*_*040 7 python numpy vectorization
我正在数值求解一阶微分方程组的x(t).该系统是:
dx/dt = y
dy/dt = -x - a*y(x ^ 2 + y ^ 2 -1)
我已经实现了Forward Euler方法来解决这个问题,如下所示:
def forward_euler():
h = 0.01
num_steps = 10000
x = np.zeros([num_steps + 1, 2]) # steps, number of solutions
y = np.zeros([num_steps + 1, 2])
a = 1.
x[0, 0] = 10. # initial condition 1st solution
y[0, 0] = 5.
x[0, 1] = 0. # initial condition 2nd solution
y[0, 1] = 0.0000000001
for step in xrange(num_steps):
x[step + 1] = x[step] + h * y[step]
y[step + 1] = y[step] + h * (-x[step] - a * y[step] * (x[step] ** 2 + y[step] ** 2 - 1))
return x, y
Run Code Online (Sandbox Code Playgroud)
现在我想进一步向量化代码并将x和y保持在同一个数组中,我已经提出了以下解决方案:
def forward_euler_vector():
num_steps = 10000
h = 0.01
x = np.zeros([num_steps + 1, 2, 2]) # steps, variables, number of solutions
a = 1.
x[0, 0, 0] = 10. # initial conditions 1st solution
x[0, 1, 0] = 5.
x[0, 0, 1] = 0. # initial conditions 2nd solution
x[0, 1, 1] = 0.0000000001
def f(x):
return np.array([x[1],
-x[0] - a * x[1] * (x[0] ** 2 + x[1] ** 2 - 1)])
for step in xrange(num_steps):
x[step + 1] = x[step] + h * f(x[step])
return x
Run Code Online (Sandbox Code Playgroud)
问题:forward_euler_vector()有效,但是这是最好的矢量化方法吗?我问,因为矢量化版本在我的笔记本电脑上运行速度大约20毫秒:
In [27]: %timeit forward_euler()
1 loops, best of 3: 301 ms per loop
In [65]: %timeit forward_euler_vector()
1 loops, best of 3: 320 ms per loop
Run Code Online (Sandbox Code Playgroud)
@Ophion 评论很好地解释了发生的事情。array()对inside的调用f(x)引入了一些开销,从而消除了在表达式 中使用矩阵乘法的好处h * f(x[step])。
正如他所说,您可能有兴趣查看scipy.integrate一组不错的数值积分器。
为了解决代码矢量化的问题,您希望避免每次调用时都重新创建数组f。您希望初始化该数组一次,并在每次调用时返回修改后的数组。这类似于staticC/C++ 中的变量。
您可以使用可变的默认参数来实现此目的,该默认参数在定义 function 时解释一次f(x),并且具有本地作用域。由于它必须是可变的,因此您将其封装在单个元素的列表中:
def f(x,static_tmp=[empty((2,2))]):
static_tmp[0][0]=x[1]
static_tmp[0][1]=-x[0] - a * x[1] * (x[0] ** 2 + x[1] ** 2 - 1)
return static_tmp[0]
Run Code Online (Sandbox Code Playgroud)
通过对代码的修改,数组创建的开销消失了,并且在我的机器上我获得了一个小小的改进:
%timeit forward_euler() #258ms
%timeit forward_euler_vector() #248ms
Run Code Online (Sandbox Code Playgroud)
这意味着用 numpy 优化矩阵乘法的增益相当小,至少在手头的问题上是这样。
f您可能还想立即删除该函数,在 for 循环中执行其操作,从而消除调用开销。然而,默认参数的这个技巧也可以应用于scipy更通用的时间积分器,其中您必须提供一个函数f。
编辑:正如 Jaime 所指出的,另一种方法是将其视为static_tmpfunction 的属性f,并在声明该函数之后但在调用它之前创建它:
def f(x):
f.static_tmp[0]=x[1]
f.static_tmp[1]=-x[0] - a * x[1] * (x[0] ** 2 + x[1] ** 2 - 1)
return f.static_tmp
f.static_tmp=empty((2,2))
Run Code Online (Sandbox Code Playgroud)