如何在python中优化时间步进算法?

如何在python中优化时间步进算法?,python,optimization,numpy,scipy,Python,Optimization,Numpy,Scipy,我有一段代码,它增加了所谓的时间步长(由Ed Lorenz在1995年发明)。它通常作为一个40变量的模型来实现,并且显示出混沌行为。我已将算法的时间步进编码如下: class Lorenz: '''Lorenz-95 equation''' global F, dt, SIZE F = 8 dt = 0.01 SIZE = 40 def __init__(self): self.x = [random.random() for

我有一段代码,它增加了所谓的时间步长(由Ed Lorenz在1995年发明)。它通常作为一个40变量的模型来实现,并且显示出混沌行为。我已将算法的时间步进编码如下:

class Lorenz:
    '''Lorenz-95 equation'''

    global F, dt, SIZE
    F = 8
    dt = 0.01
    SIZE = 40

    def __init__(self):
        self.x = [random.random() for i in range(SIZE)]

    def euler(self):
        '''Euler time stepping'''
        newvals = [0]*SIZE
        for i in range(SIZE-1):
            newvals[i] = self.x[i] + dt * (self.x[i-1] * (self.x[i+1] - self.x[i-2]) - self.x[i] + F)
        newvals[SIZE-1] = self.x[SIZE-1] + dt * (self.x[SIZE-2] * (self.x[0] - self.x[SIZE-3]) - self.x[SIZE-1] + F)
        self.x = newvals
这个函数并不慢,但不幸的是,我的代码需要对它进行大量调用。有没有一种方法可以对时间步进进行编码,使其运行得更快


非常感谢。

至少有两种可能的优化:以更智能的方式工作(算法改进)和更快的速度工作

  • 在算法方面,您使用的是,这是一阶方法(因此全局误差与步长成比例),具有较小的稳定区域。也就是说,它的效率不是很高

  • 另一方面,如果您使用的是标准的CPython实现,这种代码将非常慢。为了避免这种情况,你可以试着在下面运行它。它的即时编译器可以使数字代码的运行速度提高100倍。您还可以编写自定义的C或Cython扩展

但是有更好的办法。求解常微分方程组是非常常见的,因此Python的核心科学库之一scipy包装了快速、经过战斗测试的Fortran库来求解它们。通过使用scipy,您可以获得算法改进(因为积分器将具有更高的阶数)和快速实现

为一组扰动初始条件求解Lorenz 95模型如下所示:

import numpy as np


def lorenz95(x, t):
    return np.roll(x, 1) * (np.roll(x, -1) - np.roll(x, 2)) - x + F

if __name__ == '__main__':
    import matplotlib.pyplot as plt
    from scipy.integrate import odeint
    SIZE = 40
    F = 8
    t = np.linspace(0, 10, 1001)
    x0 = np.random.random(SIZE)
    for perturbation in 0.1 * np.random.randn(5):
        x0i = x0.copy()
        x0i[0] += perturbation
        x = odeint(lorenz95, x0i, t)
        plt.plot(t, x[:, 0])
    plt.show()
而且输出(设置为np.random.seed(7),您的可以不同)非常混乱。初始条件中的小扰动(仅在一个he坐标中!)产生非常不同的解:

但是,它真的比欧拉时间步快吗?对于
dt=0.01
来说,速度似乎快了近三倍,但解决方案除了在一开始就不匹配。

如果减少
dt
,则Euler方法提供的解与
odeint
解越来越相似,但所需时间更长。请注意,较小的dt,后面的Euler解是如何松开
odeint
解的轨迹的。最精确的Euler解计算t=6时的解比计算t=10时的odeint长600倍。请参阅完整的脚本。


最后,这个系统是如此的不稳定,以至于我想odeint解在所有绘制的时间内都是不精确的。

全局的
到底在做什么?你可以展开
for
循环并详细说明它所做的一系列计算——但无法回避这样一个事实,即它们都必须完成。可能需要考虑编写一个C扩展来进行实际的数字压缩。众所周知,使用<代码> Global 是懒惰编程的一个标志。我想确保类
Lorenz
的所有实例的数据数组大小相同。因此,强制
SIZE
成为这个类中的一个全局变量是确保这一点的最简单的方法。谢谢@jorgeca的建议。您的代码做了几个重要的更改:1。使用
np.roll
意味着整个数组可以在一次扫描中更新,因此我现在有了一个新的Euler时间步方案:
def euler2(self):self.x=self.x+dt*(np.roll(self.x,1)*(np.roll(self.x,-1)-np.roll(self.x,2))-self.x+F)
。这比最初的实现快3倍左右。2.使用
odeint
,而不是粗略的Euler时间步长。这可能更稳定,但速度相当慢(比Euler例程慢2倍)。我想我必须使用编译后的代码。@Neilbowler从Euler例程中得到的更快的结果是虚假的,除非是在最开始的时候。也许明天我会扩展我的答案,向您展示这一点,但总结是“为了在解决方案中实现相同的错误,Euler时间步进将比odeint花费更多数量级的时间”。@Neilbowler我扩展了答案。非常感谢您的精彩回复。我已经研究了解算器的准确性。大多数使用此模型的论文(包括[论文])使用时间步长为0.05的Runge-Kutta四阶方案。我用编译的代码对此进行了测试,发现时间步长为0.01的RK4非常精确。
odeint
可以更精确,但速度较慢,减少解算器容差并不能使其更快,尤其是在使用多个调用(我必须)而不是一个大型调用时(就像在你的代码中一样)@Neilbowler不客气!这是一个相当酷的系统,我不知道。也许你对它感兴趣,它让你选择一个RK45,而不是返回整个解决方案。相反,你可以调用
.integrate(time)
,它可能更适合你所做的任何事情。(注:你的链接有一个小的打字错误,我不确定你是否能编辑它)。