montecarlo模拟中python代码的改进

montecarlo模拟中python代码的改进,python,performance,Python,Performance,我已经为“2d活动伊辛模型”编写了一个蒙特卡罗模拟,我正在尝试改进运行时 我的代码的作用是: 我为粒子数(r)创建了一个矩阵,为每个点(rgrid和mgrid)创建了一个磁化矩阵。粒子的自旋可以是-1/1,因此磁化范围为[-r,r],分2步进行 然后选择一个随机点和一个随机粒子(+1或-1)。由于概率取决于每个位置正/负粒子的数量,我创建了两个数组并压缩它们,这样我就可以得到正粒子的合适数量,即[(-3,0),(-1,1),(1,2),(3,3)]。 对于3个粒子,我可以有一个(-3,-1,1,

我已经为“2d活动伊辛模型”编写了一个蒙特卡罗模拟,我正在尝试改进运行时

我的代码的作用是: 我为粒子数(r)创建了一个矩阵,为每个点(rgrid和mgrid)创建了一个磁化矩阵。粒子的自旋可以是-1/1,因此磁化范围为[-r,r],分2步进行

然后选择一个随机点和一个随机粒子(+1或-1)。由于概率取决于每个位置正/负粒子的数量,我创建了两个数组并压缩它们,这样我就可以得到正粒子的合适数量,即[(-3,0),(-1,1),(1,2),(3,3)]。 对于3个粒子,我可以有一个(-3,-1,1,3)的磁化,它有(0,1,2,3)+1个粒子

之后,我计算出该点的概率,然后选择一个动作:旋转翻转、上/下跳跃、左/右跳跃、不做任何动作。 现在我必须移动粒子(或不移动),改变两个点的磁铁/密度(并检查周期性边界条件)

这是我的密码:

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')
我使用
join
获取该点上+1粒子数的整数值,因为我需要它来计算概率

如果我想运行像
m=400
r=3
j=300000
(j:最后一次)这样的严肃程序,我将以目前的速度运行大约4年


非常感谢您的帮助。

蒙特卡罗模拟

起初我去掉了列表,后来我使用了即时编译器(numba)。如果没有编译,则得到196s(您的版本),如果编译,则得到0.44s。因此,通过使用jit编译器和一些简单的优化,改进了因数435

另一个主要优点是,GIL(全局解释器锁)也在这里释放。如果代码受处理器限制且不受内存带宽限制,则可以在另一个线程中计算随机数,同时在另一个线程中运行模拟(可以使用多个内核)。这还可以进一步提高性能,其工作原理如下:

  • 计算第一个随机数块(足够小,整个问题可以很容易地放入处理器缓存(至少三级缓存)
  • 用那个随机数开始一次迭代。在运行迭代时,计算另一个随机数块
  • 重复(2)直到完成
  • 代码

    import numba as nb
    import numpy as np
    
    def calc (m,j,e,r,dt,b,rgrid,mgrid):
        M=m*m
        N = M * r
        i=0
        while i < j:
            # 1. platz aussuchen
            x = np.random.randint(M)  # wähle zufälligen platz aus
    
            if rgrid[x] != 0:
                i += dt / N
    
                ########
                # 2. teilchen aussuchen
                #li1 = np.arange(-abs(rgrid[x]), abs(rgrid[x]) + 1, 2)
                #li2 = np.arange(0, abs(rgrid[x]) + 1)
    
                #li3 = zip(li1, li2)  # list1 und list2 als tupel in list3
                #results = [item[1] for item in li3 if item[0] == mgrid[x]]  # gebe 2. element von tupel aus für passende magnetisierung
                #num = int(''.join(map(str, results)))  # wandle listeneintrag in int um
                #######
    
                # This should be equivalent
                jj=0 #li2 starts with 0 and has a increment of 1
    
                for ii in range(-abs(rgrid[x]),abs(rgrid[x])+1, 2):
                    if (ii==mgrid[x]):
                        num=jj
                        break
    
                    jj+=1
    
                spin = 1.0 if np.random.random() < num / rgrid[x] else -1.0
    
                # 3. ereignis aussuchen
                p = np.random.random()
                p1 = np.exp(- spin * b * mgrid[x] / rgrid[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()
                    mgrid[x] -= 2 * spin 
                    continue
                elif p < p6 + p1 + p2:
                    #up()
                    y = x - m
                    if y < 0:  # periodische randbedingung hoch
                        y += m * m
                    rgrid[x] -= 1  # [zeile, spalte] masse -1
                    rgrid[y] += 1  # [zeile, spalte] masse +1
                    mgrid[x] -= spin  # [zeile, spalte] spinänderung alter platz
                    mgrid[y] += spin  # [zeile, spalte] spinänderung neuer platz
                    continue
                elif p < p6 + p1 + p2:
                    #down()
                    y = x + m
                    if y >= m * m:  # periodische randbedingung unten
                        y -= m * m
                    rgrid[x] -= 1
                    rgrid[y] += 1
                    mgrid[x] -= spin
                    mgrid[y] += spin
                    continue
                elif p < p6 + p2 + p3:
                    #left()
                    y = x - 1
                    if (y // m) < (x // m):  # periodische randbedingung links
                        y += m
                    rgrid[x] -= 1
                    rgrid[y] += 1
                    mgrid[x] -= spin
                    mgrid[y] += spin
                    continue
                else:
                    #right()
                    y = x + 1
                    if (y // m) > (x // m):  # periodische randbedingung rechts
                        y -= m
                    rgrid[x] -= 1
                    rgrid[y] += 1
                    mgrid[x] -= spin
                    mgrid[y] += spin
                    continue
        return (mgrid,rgrid)
    
    编辑
    我重写了代码“2.Teilchen aussuchen”重新编写代码,使其与标量索引一起工作。这使速度提高了4倍。

    我在物理本科时就这样做了。这很酷。我建议您在任何循环之外生成所有随机数。如果您需要1000个随机数,请在开始之前生成1000个数字元组您的时间步长。讨论快速随机数生成。工作代码的改进也可以作为主题,但请确保您在那里提问。至于您的问题,将listcomps、zips和string操作与numpy混合在一起是一个很好的迹象,表明您的代码可能会有很大的改进。@wrkyle在接受答案的注释中,我看到了我的想法已经担心了,内存不足。对于我在底部提到的大运行,它将是大约135亿次迭代,每次迭代我需要2个随机数。num应该总是大小为1,因为它是自旋1粒子的numper。我正在尝试安装numba来测试它。哦,好的。这是有意义的。我建议使用Anaconda..与其他许多python软件包一样,安装过程会更简单。(conda install numba)仅此而已;)如果您不想使用anaconda,并且您使用的是windows,此网站将帮助您现在在pycharm上安装anaconda和virtenv:D。有几个问题:1)在最后的nb_calc行中(calc)。pycharm将此标记为错误,删除后效果良好。2) nb_计算线的作用是什么?3) 为什么b不是计算中的函数?现在我添加了它,因为它被用于旋转计算4)我从numba导入了jit,并将“@jit”放在“def calc”之前。这就是你所说的编译吗?nb_calc行进行编译。您应该将该行放在calc的函数定义之后。有一些方法可以完成计算。numba主页上也有一些很好的例子。我忘了b。Njit或jit与noPython一起,你可以获得一些速度。装饰字体也很有好处,就像在nb_calc系列中一样。我需要研究一下,这看起来很神奇。nb_计算行中的(计算)是错误的?最后一个问题:p6是什么都不做的机会,它的发生率最高。我先检查一下,如果是这样的话,下一个周期可以马上开始。如果我进行万亿次或迭代,这对节省时间有意义吗?
    import numba as nb
    import numpy as np
    
    def calc (m,j,e,r,dt,b,rgrid,mgrid):
        M=m*m
        N = M * r
        i=0
        while i < j:
            # 1. platz aussuchen
            x = np.random.randint(M)  # wähle zufälligen platz aus
    
            if rgrid[x] != 0:
                i += dt / N
    
                ########
                # 2. teilchen aussuchen
                #li1 = np.arange(-abs(rgrid[x]), abs(rgrid[x]) + 1, 2)
                #li2 = np.arange(0, abs(rgrid[x]) + 1)
    
                #li3 = zip(li1, li2)  # list1 und list2 als tupel in list3
                #results = [item[1] for item in li3 if item[0] == mgrid[x]]  # gebe 2. element von tupel aus für passende magnetisierung
                #num = int(''.join(map(str, results)))  # wandle listeneintrag in int um
                #######
    
                # This should be equivalent
                jj=0 #li2 starts with 0 and has a increment of 1
    
                for ii in range(-abs(rgrid[x]),abs(rgrid[x])+1, 2):
                    if (ii==mgrid[x]):
                        num=jj
                        break
    
                    jj+=1
    
                spin = 1.0 if np.random.random() < num / rgrid[x] else -1.0
    
                # 3. ereignis aussuchen
                p = np.random.random()
                p1 = np.exp(- spin * b * mgrid[x] / rgrid[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()
                    mgrid[x] -= 2 * spin 
                    continue
                elif p < p6 + p1 + p2:
                    #up()
                    y = x - m
                    if y < 0:  # periodische randbedingung hoch
                        y += m * m
                    rgrid[x] -= 1  # [zeile, spalte] masse -1
                    rgrid[y] += 1  # [zeile, spalte] masse +1
                    mgrid[x] -= spin  # [zeile, spalte] spinänderung alter platz
                    mgrid[y] += spin  # [zeile, spalte] spinänderung neuer platz
                    continue
                elif p < p6 + p1 + p2:
                    #down()
                    y = x + m
                    if y >= m * m:  # periodische randbedingung unten
                        y -= m * m
                    rgrid[x] -= 1
                    rgrid[y] += 1
                    mgrid[x] -= spin
                    mgrid[y] += spin
                    continue
                elif p < p6 + p2 + p3:
                    #left()
                    y = x - 1
                    if (y // m) < (x // m):  # periodische randbedingung links
                        y += m
                    rgrid[x] -= 1
                    rgrid[y] += 1
                    mgrid[x] -= spin
                    mgrid[y] += spin
                    continue
                else:
                    #right()
                    y = x + 1
                    if (y // m) > (x // m):  # periodische randbedingung rechts
                        y -= m
                    rgrid[x] -= 1
                    rgrid[y] += 1
                    mgrid[x] -= spin
                    mgrid[y] += spin
                    continue
        return (mgrid,rgrid)
    
    def main():
        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),dtype=np.int) #don't convert the array build it up with the right datatype  # 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-)
    
        #Compile the function
        nb_calc = nb.njit(nb.types.Tuple((nb.int32[:], nb.int32[:]))(nb.int32, nb.int32,nb.float32,nb.int32,nb.float32,nb.float32,nb.int32[:], nb.int32[:]),nogil=True)(calc)
    
        Results=nb_calc(m,j,e,r,dt,b,rgrid.flatten(),mgrid.flatten())
    
        #Get the results
        mgrid_new=Results[0].reshape(mgrid.shape)
        rgrid_new=Results[1].reshape(rgrid.shape)