3个嵌套循环:优化简单的速度仿真

时间:2018-12-10 14:58:19

标签: python montecarlo markov-chains

背景

我遇到了一个难题。在这里:

  

有一天,一个外星人来到地球。每天,每个外星人都会做以下四件事之一,每件事的发生概率均相等:

     
      
  • 杀死自己
  •   
  • 什么都不做
  •   
  • 将自己分裂成两个外星人(杀死自己)
  •   
  • 将自己分裂成三个外星人(杀死自己)
  •   
     

外来物种最终完全灭绝的可能性是多少?

Link to the source and the solution, problem #10

不幸的是,我一直无法从理论上解决问题。然后我着手进行基本的马尔可夫链和蒙特卡洛模拟。

这不是在采访中问我的。我从朋友那里学到了问题,然后在搜索数学解时找到了上面的链接。

重新解释问题

我们从外星人n = 1的数量开始。 n有机会保持不变,以1递减,以1递增,以2递减,各为%25。如果n递增,即外星人乘以,我们将重复此过程n次。这对应于每个外星人都会再次做自己的事情。但是,我必须设置一个上限,以便我们停止模拟并避免崩溃。 n可能会增加,我们一次又一次地循环n

如果外星人以某种方式灭绝,我们将停止模拟,因为没有其他东西可以模拟了。

n达到零或上限之后,我们还记录了人口(将为零或某个数字>= max_pop)。

我重复了很多遍,并记录了每个结果。最后,零的数量除以结果的总数应该可以得出一个近似值。

代码

from random import randint
import numpy as np

pop_max = 100
iter_max = 100000

results = np.zeros(iter_max, dtype=int)

for i in range(iter_max):
    n = 1
    while n > 0 and n < pop_max:
        for j in range(n):
            x = randint(1, 4)
            if x == 1:
                n = n - 1
            elif x == 2:
                continue
            elif x == 3:
                n = n + 1
            elif x == 4:
                n = n + 2
    results[i] = n

print( np.bincount(results)[0] / iter_max )

iter_maxpop_max的确可以更改,但是我认为如果有100个外星人,他们灭绝的可能性就很小。不过,这只是一个猜测,我没有做任何事情来计算(更)适当的人口上限。

这段代码给出了可喜的结果,非常接近真实答案,大约为%41.4。

一些输出

> python aliens.py
0.41393
> python aliens.py
0.41808
> python aliens.py
0.41574
> python aliens.py
0.4149
> python aliens.py
0.41505
> python aliens.py
0.41277
> python aliens.py
0.41428
> python aliens.py
0.41407
> python aliens.py
0.41676

后果

我对结果没问题,但是在这段代码花费的时间我不能说相同。大约需要16-17秒:)

如何提高速度?如何优化循环(尤其是while循环)?也许有更好的方法或更好的模型?

2 个答案:

答案 0 :(得分:2)

您可以通过使用numpy同时生成n随机整数(更快)来向量化内部循环,并使用算术而不是布尔逻辑来摆脱所有if语句。

while...: 
    #population changes by (-1, 0, +1, +2) for each alien
    n += np.random.randint(-1,3, size=n).sum()

使用您所有其他代码的精确代码(您可能会在其他地方找到其他优化方法),通过这一更改,我从21.2秒缩短到4.3秒。

在不更改算法的情况下(即使用monte carlo以外的方法进行求解),直到您编译到机器代码之前,我看不到其他任何可以使速度更快的全面更改(幸运的是,如果您拥有numba已安装)。

我不会提供有关numba进行的即时编译的完整教程,但是,我只是分享我的代码并记下所做的更改:

from time import time
import numpy as np
from numpy.random import randint
from numba import njit, int32, prange

@njit('i4(i4)')
def simulate(pop_max): #move simulation of one population to a function for parallelization
    n = 1
    while 0 < n < pop_max:
        n += np.sum(randint(-1,3,n))
    return n

@njit('i4[:](i4,i4)', parallel=True)
def solve(pop_max, iter_max):
    #this could be easily simplified to just return the raio of populations that die off vs survive to pop_max
    # which would save you some ram (though the speed is about the same)
    results = np.zeros(iter_max, dtype=int32) #numba needs int32 here rather than python int
    for i in prange(iter_max): #prange specifies that this loop can be parallelized
        results[i] = simulate(pop_max)
    return results

pop_max = 100
iter_max = 100000

t = time()
print( np.bincount(solve(pop_max, iter_max))[0] / iter_max )
print('time elapsed: ', time()-t)

使用并行化进行编译可使我的系统上的评估速度降低至约0.15秒。

答案 1 :(得分:1)

没有numpy解决方案,进行10万次仿真大约需要5s:

from random import choices

def simulate_em():
    def spwn(aliens):
        return choices(range(-1,3), k=aliens)

    aliens = {1:1}
    i = 1
    while aliens[i] > 0 and aliens[i] < 100:    
        i += 1
        num = aliens[i-1]
        aliens[i] = num + sum(spwn(num))

    # commented for speed
    # print(f"Round {i:<5} had {aliens[i]:>20} alien alive.")
    return (i,aliens[i])

对其进行测试(在pyfiddle.io上大约5秒钟):

from datetime import datetime

t = datetime.now()    
d = {}
wins = 0
test = 100000
for k in range(test):
    d[k] = simulate_em()
    wins += d[k][1]>=100

print(1-wins/test)         # 0.41532
print(datetime.now()-t)    # 0:00:04.840127

因此,进行10万次测试大约需要5秒钟...

输出(共2次运行):

Round 1     had                    1 alien alive.
Round 2     had                    3 alien alive.
Round 3     had                    6 alien alive.
Round 4     had                    9 alien alive.
Round 5     had                    7 alien alive.
Round 6     had                   13 alien alive.
Round 7     had                   23 alien alive.
Round 8     had                   20 alien alive.
Round 9     had                   37 alien alive.
Round 10    had                   54 alien alive.
Round 11    had                   77 alien alive.
Round 12    had                  118 alien alive.

Round 1     had                    1 alien alive.
Round 2     had                    0 alien alive.

通过在amount_of_aliens上使用sum + choices(range(-1,3),k=amount_of_aliens),可以使求和变得更容易,并更快地完成字典?如果您的外星人数量降到0以下,它们就灭绝了。