【发布时间】:2018-01-06 01:54:08
【问题描述】:
我已经为“2d active ising 模型”编写了 Monte Carlo 模拟,并且正在尝试改进运行时间。
我的代码做了什么: 我为粒子数(r)创建一个矩阵,为每个点(rgrid 和 mgrid)创建一个磁化矩阵。粒子的自旋可以是 -1/1,因此磁化强度范围为 [-r, r],步长为 2。
然后选择一个随机点和一个随机粒子(+1 或 -1)。由于概率取决于每个位置的正/负粒子数,我创建了 2 个数组并将它们压缩,因此我可以获得正粒子的拟合数,即 [(-3, 0), (-1, 1), ( 1, 2), (3, 3)]。 使用 3 个粒子,我可以具有 (-3, -1, 1, 3) 的磁化强度,其中具有 (0, 1, 2, 3) +1 个粒子。
之后,我计算该点的概率并选择一个动作:旋转翻转、上/下跳、左/右跳,什么都不做。 现在我必须移动(或不移动)粒子并更改 2 个点的磁体/密度(并检查周期性边界条件)。
这是我的代码:
from __future__ import print_function
from __future__ import division
from datetime import datetime
import numpy as np
import math
import matplotlib.pyplot as plt
import cProfile
pr = cProfile.Profile()
pr.enable()
m = 10 # zeilen, spalten
j = 1000 # finale zeit
r = 3 # platzdichte
b = 1.6 # beta
e = 0.9 # epsilon
M = m * m # platzanzahl
N = M * r # teilchenanzahl
dt = 1 / (4 * np.exp(b)) # delta-t
i = 0
rgrid = r * np.ones((m, m)).astype(int) # dichte-matrix, rho = n(+) + n(-)
magrange = np.arange(-r, r + 1, 2) # mögliche magnetisierungen [a, b), schrittweite
mgrid = np.random.choice(magrange, (m, m)) # magnetisierungs-matrix m = n(+) - (n-)
def flip():
mgrid[math.trunc(x / m), x % m] -= 2 * spin
def up():
y = x - m
if y < 0: # periodische randbedingung hoch
y += m * m
x1 = math.trunc(x / m)
x2 = x % m
y1 = math.trunc(y / m)
y2 = y % m
rgrid[x1, x2] -= 1 # [zeile, spalte] masse -1
rgrid[y1, y2] += 1 # [zeile, spalte] masse +1
mgrid[x1, x2] -= spin # [zeile, spalte] spinänderung alter platz
mgrid[y1, y2] += spin # [zeile, spalte] spinänderung neuer platz
def down():
y = x + m
if y >= m * m: # periodische randbedingung unten
y -= m * m
x1 = math.trunc(x / m)
x2 = x % m
y1 = math.trunc(y / m)
y2 = y % m
rgrid[x1, x2] -= 1
rgrid[y1, y2] += 1
mgrid[x1, x2] -= spin
mgrid[y1, y2] += spin
def left():
y = x - 1
if math.trunc(y / m) < math.trunc(x / m): # periodische randbedingung links
y += m
x1 = math.trunc(x / m)
x2 = x % m
y1 = math.trunc(y / m)
y2 = y % m
rgrid[x1, x2] -= 1
rgrid[y1, y2] += 1
mgrid[x1, x2] -= spin
mgrid[y1, y2] += spin
def right():
y = x + 1
if math.trunc(y / m) > math.trunc(x / m): # periodische randbedingung rechts
y -= m
x1 = math.trunc(x / m)
x2 = x % m
y1 = math.trunc(y / m)
y2 = y % m
rgrid[x1, x2] -= 1
rgrid[y1, y2] += 1
mgrid[x1, x2] -= spin
mgrid[y1, y2] += spin
while i < j:
# 1. platz aussuchen
x = np.random.randint(M) # wähle zufälligen platz aus
if rgrid.item(x) != 0:
i += dt / N
# 2. teilchen aussuchen
li1 = np.arange(-abs(rgrid.item(x)), abs(rgrid.item(x)) + 1, 2)
li2 = np.arange(0, abs(rgrid.item(x)) + 1)
li3 = zip(li1, li2) # list1 und list2 als tupel in list3
results = [item[1] for item in li3 if item[0] == mgrid.item(x)] # gebe 2. element von tupel aus für passende magnetisierung
num = int(''.join(map(str, results))) # wandle listeneintrag in int um
spin = 1.0 if np.random.random() < num / rgrid.item(x) else -1.0
# 3. ereignis aussuchen
p = np.random.random()
p1 = np.exp(- spin * b * mgrid.item(x) / rgrid.item(x)) * dt # flip
p2 = dt # hoch
p3 = dt # runter
p4 = (1 - spin * e) * dt # links
p5 = (1 + spin * e) * dt # rechts
p6 = 1 - (4 + p1) * dt # nichts
if p < p6:
continue
elif p < p6 + p1:
flip()
continue
elif p < p6 + p1 + p2:
up()
continue
elif p < p6 + p1 + p2 + p3:
down()
continue
elif p < p6 + p1 + p2 + p3 + p4:
left()
continue
else:
right()
continue
pr.disable()
pr.print_stats(sort='cumtime')
我已经做了什么来加快速度:
- 使用 cProfile 发现我使用的 random.choice 非常慢,并将其更改为自旋和概率 p 的 if 条件
- 安装了 cython 并使用
import pyximport; pyximport.install()创建了一个已编译的 cython 文件。这并没有改善,在检查了cython -a script.py之后,我发现我需要更多的静态变量来获得一些改进。
运行 cProfile 现在向我显示:
188939207 function calls in 151.834 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
5943639 10.582 0.000 40.678 0.000 {method 'join' of 'str' objects}
5943639 32.543 0.000 37.503 0.000 script.py:107(<listcomp>)
5943639 4.722 0.000 30.096 0.000 numeric.py:1905(array_str)
8276949 25.852 0.000 25.852 0.000 {method 'randint' of 'mtrand.RandomState' objects}
5943639 11.855 0.000 25.374 0.000 arrayprint.py:381(wrapper)
11887279 14.403 0.000 14.403 0.000 {built-in method numpy.core.multiarray.arange}
80651998 13.559 0.000 13.559 0.000 {method 'item' of 'numpy.ndarray' objects}
5943639 8.427 0.000 9.364 0.000 arrayprint.py:399(array2string)
11887278 8.817 0.000 8.817 0.000 {method 'random_sample' of 'mtrand.RandomState' objects}
579016 7.351 0.000 7.866 0.000 script.py:79(right)
300021 3.669 0.000 3.840 0.000 script.py:40(up)
152838 1.950 0.000 2.086 0.000 script.py:66(left)
17830917 1.910 0.000 1.910 0.000 {built-in method builtins.abs}
176346 1.147 0.000 1.217 0.000 script.py:37(flip)
5943639 1.131 0.000 1.131 0.000 {method 'discard' of 'set' objects}
5943639 1.054 0.000 1.054 0.000 {built-in method _thread.get_ident}
5943639 1.010 0.000 1.010 0.000 {method 'add' of 'set' objects}
5943639 0.961 0.000 0.961 0.000 {built-in method builtins.id}
3703804 0.892 0.000 0.892 0.000 {built-in method math.trunc}
我使用join 来获取该点上+1 粒子数的整数值,因为我的概率需要它。
如果我想运行像m=400、r=3、j=300000(j:最后一次)这样严肃的事情,我正在考虑以我目前的速度运行大约 4 年。
非常感谢任何帮助。
【问题讨论】:
-
我在物理本科时做过这个。它太酷了。我建议的一件事是在任何循环之外生成所有随机数。如果您需要 1000 个随机数,请在开始时间步之前生成 1000 个数字元组。
-
This thread 讨论快速随机数生成。
-
工作代码的改进也可以作为codereview.SE 的主题,但如果您在那里询问,请确保it's welcome there。
-
至于您的问题,将 listcomps 和 zips 以及字符串操作与 numpy 混合在一起是一个好兆头,表明您的代码可能会得到很大改进。
-
@wrkyle 在已接受答案的 cmets 中,我看到了我已经担心的情况,内存不足。对于我在底部提到的大运行,它将是大约 135 亿次迭代,每次迭代我需要 2 个随机数。
标签: python performance