数值集成Python与Matlab

时间:2019-02-26 02:53:21

标签: python matlab numpy numerical-integration

我的python代码运行大约6.2秒。 Matlab代码在0.05秒内运行。为什么会这样,我该怎么做才能加快Python代码的速度? Cython解决方案吗?

Matlab:

function X=Test

nIter=1000000;
Step=.001;
X0=1;

X=zeros(1,nIter+1); X(1)=X0;

tic
for i=1:nIter
    X(i+1)=X(i)+Step*(X(i)^2*cos(i*Step+X(i)));
end
toc

figure(1) plot(0:nIter,X)

Python:

nIter = 1000000
Step = .001
x = np.zeros(1+nIter)
x[0] = 1
start = time.time()
for i in range(1,1+nIter):
      x[i] = x[i-1] + Step*x[i-1]**2*np.cos(Step*(i-1)+x[i-1])
end = time.time()
print(end - start)

2 个答案:

答案 0 :(得分:1)

如何加快您的Python代码

您最大的耗时是np.cos,它对输入格式进行多次检查。 这些是相关的,对于高维输入通常可以忽略不计,但是对于一维输入,这成为了瓶颈。 解决方案是使用math.cos,它仅接受一维数字作为输入,因此速度更快(尽管灵活性较差)。

另一个耗时的指标多次索引x。 您可以通过更新一个状态变量并每次迭代仅写入一次x来加快速度。

通过所有这些,您可以将速度提高大约十倍:

import numpy as np
from math import cos

nIter = 1000000
Step = .001
x = np.zeros(1+nIter)
state = x[0] = 1
for i in range(nIter):
    state += Step*state**2*cos(Step*i+state)
    x[i+1] = state

现在,您的主要问题是,您真正的最内层循环完全发生在Python中,即您有很多包装操作会占用时间。 您可以通过使用uFunc(例如,由SymPy的ufuncify创建)和NumPy的accumulate来避免这种情况:

import numpy as np
from sympy.utilities.autowrap import ufuncify
from sympy.abc import t,y
from sympy import cos

nIter = 1000000
Step = 0.001
state = x[0] = 1
f = ufuncify([y,t],y+Step*y**2*cos(t+y))

times = np.arange(0,nIter*Step,Step)
times[0] = 1
x = f.accumulate(times)

这实际上是在瞬间完成的。

...以及为什么那不是您应该担心的

如果您所关心的只是您的确切代码,那么您就不必担心运行时,因为这两种方法都很短。 另一方面,如果您使用它来评估运行时间较长的问题的效率,则示例将失败,因为它仅考虑一个初始条件并且是非常简单的动态。

此外,您正在使用Euler方法,根据步长的不同,该方法不是非常有效或不够可靠。 在您的情况下,后者(Step)太低了,产生的数据比您可能需要的多得多: 步长为1时,您可以看到正在发生的事情。

如果您想在这种情况下进行可靠的集成,最好总是使用现代的自适应集成器,它可以自行调整步长,例如,这是使用本机Python集成器解决问题的一种方法:

from math import cos
import numpy as np
from scipy.integrate import solve_ivp

T = 1000
dt = 0.001

x = solve_ivp(
        lambda t,state: state**2*cos(t+state),
        t_span = (0,T),
        t_eval = np.arange(0,T,dt),
        y0 = [1],
        rtol = 1e-5
    ).y

这将根据误差容限rtol自动将步长调整为更高的值。 它仍然返回相同数量的输出数据,但这是通过解决方案的插值来实现的。 对我来说它的运行时间为0.3秒。

如何以可扩展的方式加速事物

如果您仍然需要加快这样的速度,则可能是导数(f)比示例中复杂得多,因此是瓶颈。 根据您的问题,您可以将其计算结果矢量化(使用NumPy或类似方法)。

如果您无法向量化,我写了一个module,专门针对此问题,通过对您的派生词进行了硬编码。 这是您的示例,采样步骤为1。

import numpy as np
from jitcode import jitcode,y,t
from symengine import cos

T = 1000
dt = 1

ODE = jitcode([y(0)**2*cos(t+y(0))])
ODE.set_initial_value([1])
ODE.set_integrator("dop853")
x = np.hstack([ODE.integrate(t) for t in np.arange(0,T,dt)])

这会在瞬间重新运行。尽管这可能与提高速度无关,但这可以扩展到大型系统。

答案 1 :(得分:0)

差异是jit编译,Matlab默认使用该编译。让我们使用Numba(Python jit编译器)

尝试一下示例

代码

import numba as nb
import numpy as np
import time

nIter = 1000000
Step = .001

@nb.njit()
def integrate(nIter,Step):
  x = np.zeros(1+nIter)
  x[0] = 1
  for i in range(1,1+nIter):
    x[i] = x[i-1] + Step*x[i-1]**2*np.cos(Step*(i-1)+x[i-1])
  return x

#Avoid measuring the compilation time,
#this would be also recommendable for Matlab to have a fair comparison
res=integrate(nIter,Step)

start = time.time()
for i in range(100):
  res=integrate(nIter,Step)

end=time.time()
print((end - start)/100)

这导致每次调用的运行时间为0.022s。