对于一个小型的副项目,我使用以下(简化的)代码在 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。
一些注意事项:
- 将整个 for 循环移动到一个函数中,以便您可以使用
njit
对其进行修饰
- 在
Neighbors
中,我必须将rows
和cols
从list
更改为np. array
s 因为 numba 不接受通过列表进行索引 - 我用
np.random.randint
替换了np.random.random_integers
,因为前者已被弃用 - 我用
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 的速度提升,而无需深入研究或显着改变代码的实现方式。
一些一般性观察:
- 在
Neighbors
中,未使用自变量/参数n
,因此您应该为清楚起见将其删除(或更新代码) - 在 Python 中,您通常希望对函数名称和变量使用小写字母。大写通常保留给类(不是对象)和“全局”变量
- 你上面关于
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/