python - 为 Ising-/Potts-Model Monte-Carlo 加速 Python/Numpy 代码

标签 python arrays numpy montecarlo

对于一个小型的副项目,我使用以下(简化的)代码在 Python/Numpy 中编写了 2D Ising-/Potts-Model Monte-Carlo 模拟。

基本上代码执行以下操作:

  • 设置一个 NXM 数组,其中在 [1,orientations] 中填充随机整数(orientations)
  • 对于每个时间步长 (MCS),数组的每个像素都以伪随机顺序访问一次(因此最大素数在 () 和 索引 = (a*i + rand) % (N**2) x = 指数 % N y = 索引//N )
  • 检查 8 个相邻的数组条目(周期性边界条件)并将条目更改为相邻值之一
  • 如果新配置的能量变低,则接受更改,否则拒绝除非满足条件

我尽量加快速度,但对于大型数组(N、M > 500),代码并不是很快。由于阵列需要大约 10e5 MCS 才能看到明显的趋势,因此实现了

1 loop, best of 3: 276 ms per loop

100x100 阵列上的 1 个 MCS 是不够的。不幸的是,由于我缺乏经验,我不知道如何提高性能。

我认为 Neighbors() 和 calc_dE() 函数是瓶颈,尤其是嵌套循环,但我找不到加快速度的方法。我的 cython 尝试并没有真正成功,因为我之前没有用 cython 做过任何事情,所以我愿意接受任何建议。

代码:

(pyplot 命令仅用于可视化,通常带有注释。)

import math
import numpy as np
import matplotlib.pyplot as plt

def largest_primes_under(N):
    n = N - 1
    while n >= 2:
        if all(n % d for d in range(2, int(n ** 0.5 + 1))):
            return n
        n -= 1

def Neighbors(Lattice,i,j,n=1):
    ''' Returns an flat array of all neighboring sites in the n-th coordination sphere including the center'''
    N, M = Lattice.shape
    rows = [(i-1) % N, i, (i+1) % N]
    cols = [(j-1) % N, j, (j+1) % M]
    return Lattice[rows][:, cols].flatten()

def calc_dE(Lattice, x, y, z):
    N, M = Lattice.shape
    old_energy = 0
    new_energy = 0
    for i in [0,1,-1]:
        for j in [0,1,-1]:
            if i == 0 and j == 0: 
                continue
            if Lattice[x%N,y%M] == Lattice[(x+i)%N,(y+j)%M]:
                old_energy += 1
            elif z == Lattice[(x+i)%N,(y+j)%M]: 
                new_energy += 1 
    return old_energy-new_energy

N, M = 100,100
orientations = N*M
MCS = int(100)

a = largest_primes_under(N*M)
L = np.random.randint(1,orientations+1,size=(N,M))

mat = plt.matshow(L,cmap = plt.get_cmap('plasma', orientations+1), vmin = -0.5, vmax = orientations+0.5, interpolation='kaiser')
plt.axis('off')

for t in range(1,MCS+1):
    rand = np.random.random_integers(N*M)
    for i in range(0,N**2):
        index = (a*i + rand) % (N**2)
        x = index % N
        y = index // N
        n = Neighbors(L,x,y)
        if len(n)-1 == 0: 
            continue
        else: 
            z = np.random.choice(n)
        dE = calc_dE(L,x,y,z)
        if  (dE < 0): 
            L[x%N,y%N] = z      
        elif np.random.sample() < math.exp(-dE*2.5): 
            L[x%N,y%N] = z

    mat.set_data(L)
    plt.draw()
    plt.pause(0.0001)

最佳答案

不确定您在依赖项方面是否有任何限制,但我肯定会调查 Numba .它提供了一组装饰器(特别是 njit),如果您使用兼容的类型(例如 numpy 数组,而不是 pandas),它们会将您的代码编译为机器代码并使其可能更快,更快数据框)。

此外,不确定您正在查看的比例是多少,但我很确定您可以找到比手动实现的 for 循环优化得多的素数搜索算法的示例。

否则你总是可以退回到 Cython,但它需要重写你的代码。


编辑:好的,我尝试使用 numba。

一些注意事项:

  1. 将整个 for 循环移动到一个函数中,以便您可以使用 njit
  2. 对其进行修饰
  3. Neighbors 中,我必须将 rowscolslist 更改为 np. arrays 因为 numba 不接受通过列表进行索引
  4. 我用 np.random.randint 替换了 np.random.random_integers,因为前者已被弃用
  5. 我用 np.exp 替换了 math.exp ,这应该会带来轻微的性能提升(除了为您节省导入之外)
import numpy as np
from numba import njit

def largest_primes_under(N):
    n = N - 1
    while n >= 2:
        if all(n % d for d in range(2, int(n ** 0.5 + 1))):
            return n
        n -= 1

@njit
def Neighbors(Lattice,i,j,n=1):
    ''' Returns an flat array of all neighboring sites in the n-th coordination sphere including the center'''
    N, M = Lattice.shape
    rows = np.array([(i-1) % N, i, (i+1) % N])
    cols = np.array([(j-1) % N, j, (j+1) % M])
    return Lattice[rows,:][:,cols].flatten()

@njit
def calc_dE(Lattice, x, y, z):
    N, M = Lattice.shape
    old_energy = 0
    new_energy = 0
    for i in [0,1,-1]:
        for j in [0,1,-1]:
            if i == 0 and j == 0: 
                continue
            if Lattice[x%N,y%M] == Lattice[(x+i)%N,(y+j)%M]:
                old_energy += 1
            elif z == Lattice[(x+i)%N,(y+j)%M]: 
                new_energy += 1 
    return old_energy-new_energy

@njit
def fun(L, MCS, a):
    N, M = L.shape

    for t in range(1,MCS+1):
        rand = np.random.randint(N*M)
        for i in range(0,N**2):
            index = (a*i + rand) % (N**2)
            x = index % N
            y = index // N
            n = Neighbors(L,x,y)
            if len(n)-1 == 0: continue
            else: z = np.random.choice(n)
            dE = calc_dE(L,x,y,z)
            if  (dE < 0): L[x%N,y%N] = z      
            elif np.random.sample() < np.exp(-dE*2.5): L[x%N,y%N] = z    
    return L

运行相同的例子

N, M = 100,100
orientations = N*M
MCS = 1

L = np.random.randint(1,orientations+1,size=(N,M))
a = largest_primes_under(N*M)

通过 %timeit fun(L, MCS, a) (在 Jupyter 中)给了我

16.9 ms ± 853 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

这比您目前拥有的快约 15 倍。您可能可以进行更多优化,关于 numba 的好处是我获得了 x15 的速度提升,而无需深入研究或显着改变代码的实现方式。

一些一般性观察:

  1. Neighbors 中,未使用自变量/参数 n,因此您应该为清楚起见将其删除(或更新代码)
  2. 在 Python 中,您通常希望对函数名称和变量使用小写字母。大写通常保留给类(不是对象)和“全局”变量
  3. 你上面关于 largest_primes_under 只被调用一次的评论绝对是正确的,我应该仔细看看代码。

premature optimization is the root of all evil

关于python - 为 Ising-/Potts-Model Monte-Carlo 加速 Python/Numpy 代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54725201/

相关文章:

C 二维数组损坏

c++ - 两个不同大小的硬编码数组到一个二维 vector 中

python - Numpy 二维移动平均线

Python VTK : Coordinates directly to PolyData

python - 如何在 Pandas 中将 int64 转换为日期时间

python - Pycharm 在运行测试时找不到 Django 应用程序 'users'

python - Django 字符串换行

arrays - 如何从多个 BezierPath 创建多个路径

python - Flask 多对多循环通过 id 将数据插入表不工作

numpy - 即使安装了 Chapel,Pip Install Arkouda 也会失败