如何加速python中的蒙特卡罗模拟

时间:2015-10-23 22:37:45

标签: performance python-3.x cython

我写了一个扑克模拟器,通过运行模拟游戏(即蒙特卡罗模拟)来计算在德州扑克中获胜的概率。目前它在10秒内运行大约10000次模拟,这通常是足够好的。但是,iPhone上的应用程序运行速度提高了约100倍。我想知道我能做些什么来显着加快程序的速度。我已经用列表替换了字符串,并找到了许多方法来加速程序大约2-3次。但是我可以做些什么来加速它50x-100x?我检查了一个分析器,但无法找到任何重大瓶颈。我也遵守了cython(没有做任何改动),但这对速度也没有影响。任何建议表示赞赏。完整列表如下:

__author__ = 'Nicolas Dickreuter'
import time
import numpy as np
from collections import Counter

class MonteCarlo(object):

    def EvalBestHand(self, hands):
        scores = [(i, self.score(hand)) for i, hand in enumerate(hands)]
        winner = sorted(scores, key=lambda x: x[1], reverse=True)[0][0]
        return hands[winner],scores[winner][1][-1]

    def score(self, hand):
        crdRanksOriginal = '23456789TJQKA'
        originalSuits='CDHS'
        rcounts = {crdRanksOriginal.find(r): ''.join(hand).count(r) for r, _ in hand}.items()
        score, crdRanks = zip(*sorted((cnt, rank) for rank, cnt in rcounts)[::-1])

        potentialThreeOfAKind = score[0] == 3
        potentialTwoPair = score == (2, 2, 1, 1, 1)
        potentialPair = score == (2, 1, 1, 1, 1, 1)

        if score[0:2]==(3,2) or score[0:2]==(3,3): # fullhouse (three of a kind and pair, or two three of a kind)
            crdRanks = (crdRanks[0],crdRanks[1])
            score = (3,2)
        elif score[0:4]==(2,2,2,1):
            score=(2,2,1) # three pair are not worth more than two pair
            sortedCrdRanks = sorted(crdRanks,reverse=True) # avoid for example 11,8,6,7
            crdRanks=(sortedCrdRanks[0],sortedCrdRanks[1],sortedCrdRanks[2],sortedCrdRanks[3])

        elif len(score) >= 5:  # high card, flush, straight and straight flush
            # straight
            if 12 in crdRanks:  # adjust if 5 high straight
                crdRanks += (-1,)
            sortedCrdRanks = sorted(crdRanks,reverse=True)  # sort again as if pairs the first rank matches the pair
            for i in range(len(sortedCrdRanks) - 4):
                straight = sortedCrdRanks[i] - sortedCrdRanks[i + 4] == 4
                if straight:
                    crdRanks=(sortedCrdRanks[i],sortedCrdRanks[i+1],sortedCrdRanks[i+2],sortedCrdRanks[i+3],sortedCrdRanks[i+4])
                    break

            # flush
            suits = [s for _, s in hand]
            flush = max(suits.count(s) for s in suits) >= 5
            if flush:
                for flushSuit in originalSuits: # get the suit of the flush
                    if suits.count(flushSuit)>=5:
                        break

                flushHand = [k for k in hand if flushSuit in k]
                rcountsFlush = {crdRanksOriginal.find(r): ''.join(flushHand).count(r) for r, _ in flushHand}.items()
                score, crdRanks = zip(*sorted((cnt, rank) for rank, cnt in rcountsFlush)[::-1])
                crdRanks = tuple(sorted(crdRanks,reverse=True)) # ignore original sorting where pairs had influence

                # check for straight in flush
                if 12 in crdRanks:  # adjust if 5 high straight
                    crdRanks += (-1,)
                for i in range(len(crdRanks) - 4):
                    straight = crdRanks[i] - crdRanks[i + 4] == 4

            # no pair, straight, flush, or straight flush
            score = ([(5,), (2, 1, 2)], [(3, 1, 3), (5,)])[flush][straight]

        if score == (1,) and potentialThreeOfAKind: score = (3, 1)
        elif score == (1,) and potentialTwoPair: score = (2, 2, 1)
        elif score == (1,) and potentialPair: score = (2, 1, 1)

        if score[0]==5:
            handType="StraightFlush"
            #crdRanks=crdRanks[:5] # five card rule makes no difference {:5] would be incorrect
        elif score[0]==4:
            handType="FoufOfAKind"
            crdRanks=crdRanks[:2]
        elif score[0:2]==(3,2):
            handType="FullHouse"
            # crdRanks=crdRanks[:2] # already implmeneted above
        elif score[0:3]==(3,1,3):
            handType="Flush"
            crdRanks=crdRanks[:5] # !! to be verified !!
        elif score[0:3]==(3,1,2):
            handType="Straight"
            crdRanks=crdRanks[:5] # !! to be verified !!
        elif score[0:2]==(3,1):
            handType="ThreeOfAKind"
            crdRanks=crdRanks[:3]
        elif score[0:2]==(2,2):
            handType="TwoPair"
            crdRanks=crdRanks[:3]
        elif score[0]==2:
            handType="Pair"
            crdRanks=crdRanks[:4]
        elif score[0]==1:
            handType="HighCard"
            crdRanks=crdRanks[:5]
        else: raise Exception('Card Type error!')

        return score, crdRanks, handType

    def createCardDeck(self):
        values = "23456789TJQKA"
        suites = "CDHS"
        Deck=[]
        [Deck.append(x+y) for x in values for y in suites]
        return Deck

    def distributeToPlayers(self, Deck, PlayerAmount, PlayerCardList, TableCardsList):
        Players =[]
        CardsOnTable = []
        knownPlayers = 0

        for PlayerCards in PlayerCardList:
            FirstPlayer=[]
            FirstPlayer.append(Deck.pop(Deck.index(PlayerCards[0])))
            FirstPlayer.append(Deck.pop(Deck.index(PlayerCards[1])))
            Players.append(FirstPlayer)

            knownPlayers += 1

        for c in TableCardsList:
            CardsOnTable.append(Deck.pop(Deck.index(c)))  # remove cards that are on the table from the deck

        for n in range(0, PlayerAmount - knownPlayers):
            plr=[]
            plr.append(Deck.pop(np.random.random_integers(0,len(Deck)-1)))
            plr.append(Deck.pop(np.random.random_integers(0,len(Deck)-1)))
            Players.append(plr)

        return Players, Deck

    def distributeToTable(self, Deck, TableCardsList):
        remaningRandoms = 5 - len(TableCardsList)
        for n in range(0, remaningRandoms):
            TableCardsList.append(Deck.pop(np.random.random_integers(0,len(Deck)-1)))

        return TableCardsList

    def RunMonteCarlo(self, originalPlayerCardList, originalTableCardsList, PlayerAmount, gui, maxRuns=6000,maxSecs=5):
        winnerlist = []
        EquityList = []
        winnerCardTypeList=[]
        wins = 0
        runs=0
        timeout_start=time.time()
        for m in range(maxRuns):
            runs+=1
            try:
                if gui.active==True:
                    gui.progress["value"] = int(round(m*100/maxRuns))
            except:
                pass
            Deck = self.createCardDeck()
            PlayerCardList = originalPlayerCardList[:]
            TableCardsList = originalTableCardsList[:]
            Players, Deck = self.distributeToPlayers(Deck, PlayerAmount, PlayerCardList, TableCardsList)
            Deck5Cards = self.distributeToTable(Deck, TableCardsList)
            PlayerFinalCardsWithTableCards = []
            for o in range(0, PlayerAmount):
                PlayerFinalCardsWithTableCards.append(Players[o]+Deck5Cards)

            bestHand,winnerCardType=self.EvalBestHand(PlayerFinalCardsWithTableCards)
            winner = (PlayerFinalCardsWithTableCards.index(bestHand))


            #print (winnerCardType)

            CollusionPlayers = 0
            if winner < CollusionPlayers + 1:
                wins += 1
                winnerCardTypeList.append(winnerCardType)
                # winnerlist.append(winner)
                # self.equity=wins/m
                # if self.equity>0.99: self.equity=0.99
                # EquityList.append(self.equity)
            if time.time()>timeout_start+maxSecs:
                break

        self.equity = wins / runs
        self.winnerCardTypeList = Counter(winnerCardTypeList)
        for key, value in self.winnerCardTypeList.items():
            self.winnerCardTypeList[key] = value / runs

        self.winTypesDict=self.winnerCardTypeList.items()

        # show how the montecarlo converges
        # xaxis=range(500,monteCarloRuns)
        # plt.plot(xaxis,EquityList[499:monteCarloRuns])
        # plt.show()
        return self.equity,self.winTypesDict

if __name__ == '__main__':
    Simulation = MonteCarlo()
    mycards=[['AS', 'KS']]
    cardsOnTable = []
    players = 3
    start_time = time.time()
    Simulation.RunMonteCarlo(mycards, cardsOnTable, players, 1, maxRuns=200000, maxSecs=120)
    print("--- %s seconds ---" % (time.time() - start_time))
    equity = Simulation.equity # considering draws as wins
    print (equity)

2 个答案:

答案 0 :(得分:0)

低效的数组操作可能导致大部分缓慢。特别是,我看到很多这样的行:

Deck.pop(some_index)

每次执行此操作时,列表必须移动该索引后的所有元素。您可以使用以下方法消除此(以及重复的随机函数)

from random import shuffle

# Setup
originalDeck = createCardDeck()

# Individual run
Deck = originalDeck[:]
shuffle(Deck) # shuffles in place

# Draw a card from the end so nothing needs to shift
card = Deck.pop()

# Concise and efficient!

您还可以执行其他一些操作,例如将if-else链更改为使用dict或自定义类的常量时间查找。现在,它会检查每手直接冲洗到最后一张高牌,这看起来效率很低。我怀疑它会产生很大的不同。

答案 1 :(得分:0)

除了通用算法之外,这个算法对于任何明智的建议都太长而且复杂。所以在这里你去:矢量化你可以做的一切,并使用数据向量而不是循环,列表,插入,删除。蒙特卡洛模拟可以很好地实现这一点,因为所有样本都是独立的。

例如,您需要100万次重试,在一行中生成随机100万个值的numpy数组。您希望测试结果,将100万个结果放入numpy数组中,并针对给定条件生成numpy一组布尔值,同时测试所有结果。

如果你设法对其进行矢量化,你将获得巨大的性能提升。