如何针对大量迭代优化此随机熵计算?

时间:2018-08-13 17:01:53

标签: arrays random julia

我的代码正在尝试做什么:

我有一个初始数组,其中包含一个分子排列,每个分子在一个单元中,并局限于在2D数组(Up Down Left Right)中移动(数组大小:200x200)。在每个步骤中,我都会提取一个 random 分子,并将其移至 random 相邻单元格中。

从一定数量开始,每隔几次迭代,我计算该网格的熵。网格被切成25x25的小方块。然后,我使用Shannon熵来计算系统的熵。

目标:

使用i5-6500(无需GPU)在适当的时间内进行1e8 +次迭代。

我的代码:

function advance_multi_lattice(grid)   #Find the next state of the system

    rnd=rand(1:count(!iszero,grid))        #Random number to be used for a random molecule.
    slots=find(!iszero,grid)    #Cells containing molecules.
    chosen_slot=find(!iszero,grid)[rnd]    #Random cell. May contain multiple molecules.
    dim=size(grid)[1]    #Need this for rnd=3,4 later.  
    grid[chosen_slot]-=1    #Remove the molecules from the cell
    rnd_arr=[1,2,3,4]     #Array to random from.


    while true


        rnd=rand(rnd_arr)   #Random number to see which side should the molecules go.

        if rnd==1    #Right for example.
            try     #In case moving right is impossible, ie: moving right gets the molecule out. Remove 1 from rnd_arr and repeat.
                grid[chosen_slot+1]+=1
                break
            catch
                filter!(e->e!=1,rnd_arr)
            end
        elseif rnd==2
            try   #Same
                grid[chosen_slot-1]+=1
                break
            catch
                filter!(e->e!=2,rnd_arr)
            end
        #Repeat for the other numbers : 3 and 4...
    return Grid
end

function S(P)   #Entropy, if no molecules then return 0.
    s=[]
    for k in P
        if k==0
            push!(s,0)
        else
            push!(s,-k*log(k))
        end

    end
    return s
end

function find_molecules(grid) #How many molecules in the array
    s=0

    for slot in grid
        s+=slot
    end
    return s
end

function entropy_scale(grid,total_molecules)    #Calculate the entropy of the grid.
    P_array=Array{Float64}([])
    for i=1:8
        for j=1:8
            push!(P_array,find_molecules(grid[(i-1)*25+1:i*25,(j-1)*25+1:j*25]))
        end
    end

    P_array=P_array./total_molecules

    return sum(S(P_array))
end

function entropy_evolution(grid,n)    #The loop function. Changes the grid and returns the entropy as a function of steps.
    t_arr=Array{Int64}([])
    S_arr=Array{Float64}([])
    p=Progress(Int(n))    #Progress bar, using ProgressMeter.
    total_molecules=find_molecules(grid)
    for k=1:1e3
        grid=advance_multi_lattice(grid)

        next!(p)
    end

    for k=1e3+1:n
        grid=advance_multi_lattice(grid)

        if  k%500==0  #Only record entropy every 500 steps
            push!(S_arr,entropy_scale(grid,totel_molecules))
        end
        next!(p)
    end

    return S_arr,grid

end        

我的代码的结果:

对于1e5迭代,我得到43秒。这意味着如果我想要一个有趣的结果(1e9 +),我需要很多时间,直到1hour +。除非它很小,否则更改熵计算阈值几乎不会影响性能。

1 个答案:

答案 0 :(得分:3)

我假设您正在使用Julia 1.0(对于Julia 0.6,则需要进行一些小的更改-我在代码中已指出)。

为了提高性能,您应该保留一个分子向量-而不是一个网格(您不需要它,因为您可以让分子占据相同的位置)。

我们将分子的位置编码为元组(x,y)。现在,您需要一个随机移动一个分子的功能。这是实现它的方法(我对边界进行了硬编码,但是您当然可以将其更改为参数):

function move_molecule((x,y)) # in Julia 0.6 it should be move_molecule(t)
    # and here in Julia 0.6 you should add: x, y = t
    if x == 1
        if y == 1
            ((1,2), (2,1))[rand(1:2)]
        elseif y == 200
            ((1,199), (2,200))[rand(1:2)]
        else
            ((2,y), (1,y-1), (1, y+1))[rand(1:3)]
        end
    elseif x == 200
        if y == 1
            ((200,2), (199,1))[rand(1:2)]
        elseif y == 200
            ((200,199), (199,200))[rand(1:2)]
        else
            ((200,y), (200,y-1), (200, y+1))[rand(1:3)]
        end
    else
        if y == 1
            ((x,2), (x-1,1), (x+1, 1))[rand(1:3)]
        elseif y == 200
            ((x,199), (x-1,200), (x+1, 200))[rand(1:3)]
        else
            ((x+1,y), (x-1,y), (x, y+1), (x,y-1))[rand(1:4)]
        end
    end
end

现在,将给定数量的steps一步移动随机分子的函数是:

function go_sim!(molecules, steps)
    for k in 1:steps
        i = rand(axes(molecules, 1)) # in Julia 0.6 it should be: i = rand(1:length(molecules))
        @inbounds molecules[i] = move_molecule(molecules[i])
        if k % 500 == 0
        # here do entropy calculation
        end
    end
end

您没有提供完全可复制的示例,所以我在这里停止-但是使用此数据结构来重写用于熵计算的其余代码应该足够容易(实际上,它甚至可能更简单)。这是一个基准(性能不取决于网格的大小也不取决于分子的数量,这是比使用网格的代码重要的优势):

julia> molecules = [(rand(1:200), rand(1:200)) for i in 1:1000];

julia> @time go_sim!(molecules, 1e9)
 66.212943 seconds (22.64 k allocations: 1.191 MiB)

大约一分钟,您将获得1e9个步骤(无需进行熵计算)。

取得良好性能需要哪些关键要素:

  • 不要使用try-catch块,因为它们非常慢;
  • 尝试避免分配内存(即创建可变对象);我的代码基本上没有分配-特别是这就是为什么我到处都使用元组的原因(为简单起见,您可以在move_molecule函数中使用矩阵,但性能会差2倍左右)

希望这会有所帮助。