I finally ran an analysis on the game yfimc posted percentages from: https://boardgamearena.com/table?table=445859988
The histogram of 55 rolls from that game has a p-value which is strictly between 1 / 552 and 1 / 551 .
has my code for those bounds. The code is long and certainly not simple, so I also checked it by simulation:
The histogram of 55 rolls from that game has a p-value which is strictly between 1 / 552 and 1 / 551 .
Code: Select all
Python 3.8.0 (default,
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from math import floor,factorial,ceil
>>> from itertools import combinations_with_replacement
>>> from fractions import Fraction as frac
>>> # This is after conditioning on the sum of the counts.
... def probability_of_specific_histogram_(binprobabilities,counts):
... productsofar = 1
... for n in counts:
... productsofar = productsofar*factorial(n)
... multinomialcoefficient = (factorial(sum(counts)))//productsofar
... productsofar = frac(1,1)
... for (p,n) in zip(binprobabilities,counts):
... productsofar = productsofar*(p**n)
... return multinomialcoefficient*productsofar
...
>>> CDFfornumofhighrolls = []
>>> # This loop results in CDFfornumofhighrolls using between 1GB and 2GB, and takes my laptop approximately 6 minutes.
... for sumofcounts in range(56):
... distribforthissumofcounts = dict()
... for cumulativecount in combinations_with_replacement(range(1+sumofcounts),4):
... counts = (cumulativecount[0],cumulativecount[1]-cumulativecount[0],cumulativecount[2]-cumulativecount[1],cumulativecount[3]-cumulativecount[2],sumofcounts-cumulativecount[3])
... p = probability_of_specific_histogram_((frac(5,15),frac(4,15),frac(3,15),frac(2,15),frac(1,15)),counts)
... distribforthissumofcounts[p] = distribforthissumofcounts.get(p,frac(0,1))+p
... pairs = sorted(distribforthissumofcounts.items())
... cumulativeprob = pairs[0][1]
... for i in range(1,len(pairs)):
... x,probabilitymass = pairs[i]
... cumulativeprob = cumulativeprob+probabilitymass
... pairs[i] = (x,cumulativeprob)
... CDFfornumofhighrolls.append(pairs)
...
>>> min(len(entry) for entry in CDFfornumofhighrolls)
1
>>> def search_sorted_dictionary_(needle,sortedhaystack):
... if needle <= sortedhaystack[0][0]:
... if needle == sortedhaystack[0][0]:
... return (0,0)
... else:
... return (-1,0)
... low,high = 0,len(sortedhaystack)-1
... if sortedhaystack[high][0] <= needle:
... if sortedhaystack[high][0] == needle:
... return (high,high)
... else:
... return (high,high+1)
... while low+1 != high:
... mid = (low+high)//2
... if sortedhaystack[mid][0] == needle:
... return (mid,mid)
... if sortedhaystack[mid][0] < needle:
... low = mid
... else:
... high = mid
... return (low,high)
...
>>> all(entry[len(entry)-1][1] == frac(1,1) for entry in CDFfornumofhighrolls)
True
>>> def p_value_of_(histogram,digits=20):
... if not ((type(histogram) == tuple) and (type(digits) == int)):
... raise TypeError
... if not ((len(histogram) == 11) and (0 <= digits < 999)):
... raise ValueError
... if not all(type(entry) == int for entry in histogram):
... raise TypeError
... if not (all(0 <= entry for entry in histogram) and (sum(histogram) < 56)):
... raise ValueError
... threshold = probability_of_specific_histogram_((frac(1,36),frac(2,36),frac(3,36),frac(4,36),frac(5,36),frac(6,36),frac(5,36),frac(4,36),frac(3,36),frac(2,36),frac(1,36)),histogram)
... lowcumulator,highcumulator = frac(0,1),frac(0,1)
... sumofhistogram = sum(histogram)
... for cumulativecount in combinations_with_replacement(range(1+sumofhistogram),6):
... coarsecounts = (cumulativecount[0],cumulativecount[1]-cumulativecount[0],cumulativecount[2]-cumulativecount[1],cumulativecount[3]-cumulativecount[2],cumulativecount[4]-cumulativecount[3],cumulativecount[5]-cumulativecount[4],sumofhistogram-cumulativecount[5])
... probabilityofcoarsecount = probability_of_specific_histogram_((frac(1,36),frac(2,36),frac(3,36),frac(4,36),frac(5,36),frac(6,36),frac(15,36)),coarsecounts)
... relevantCDF = CDFfornumofhighrolls[coarsecounts[6]]
... low,high = search_sorted_dictionary_(threshold/probabilityofcoarsecount,relevantCDF)
... if low == -1:
... continue
... elif high == len(relevantCDF):
... lowcumulator,highcumulator = lowcumulator+probabilityofcoarsecount,highcumulator+probabilityofcoarsecount
... elif low == high:
... highcumulator = highcumulator+(probabilityofcoarsecount*relevantCDF[high][1])
... if low != 0:
... lowcumulator = lowcumulator+(probabilityofcoarsecount*relevantCDF[low-1][1])
... else:
... contribution = probabilityofcoarsecount*relevantCDF[low][1]
... lowcumulator,highcumulator = lowcumulator+contribution,highcumulator+contribution
... if not (frac(0,1) <= lowcumulator < highcumulator <= frac(1,1)):
... raise RuntimeError
... lowstring = '0.'+(str(floor((10**digits)*lowcumulator)).zfill(digits))
... if highcumulator == frac(1,1):
... highstring = '1.'+('0'*digits)
... else:
... highstring = '0.'+(str(ceil((10**digits)*highcumulator)).zfill(digits))
... return '[ '+lowstring+' , '+highstring+' ]'
...
>>> percentages = (0,15,4,20,15,5,7,11,5,15,4)
>>> len(percentages)
11
>>> sum(percentages)
101
>>> max(abs(frac(p*55,100)-round(frac(p*55,100))) for p in percentages)
Fraction(1, 4)
>>> histogram = tuple(round(frac(p*55,100)) for p in percentages)
>>> sum(histogram)
55
>>> p_value_of_(histogram,10) # This line takes my laptop approximately 35 minutes.
'[ 0.0018128154 , 0.0018130603 ]'
>>> frac(1,552) < frac(18128,10**7) < frac(18131,10**7) < frac(1,551)
True
>>> Code: Select all
Python 3.8.0 (default,
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from math import factorial
>>> from random import randrange
>>> from fractions import Fraction as frac
>>> def probability_of_specific_histogram_(binprobabilities,counts):
... productsofar = 1
... for n in counts:
... productsofar = productsofar*factorial(n)
... multinomialcoefficient = (factorial(sum(counts)))//productsofar
... productsofar = frac(1,1)
... for (p,n) in zip(binprobabilities,counts):
... productsofar = productsofar*(p**n)
... return multinomialcoefficient*productsofar
...
>>> def from_fiftyfive_rolls_():
... counts = [0 for _ in range(11)]
... for roll in range(55):
... counts[randrange(6)+randrange(6)] += 1
... return tuple(counts)
...
>>> fortwodice = tuple(frac(6-abs(roll-7),36) for roll in range(2,13))
>>> fortwodice
(Fraction(1, 36), Fraction(1, 18), Fraction(1, 12), Fraction(1, 9), Fraction(5, 36), Fraction(1, 6), Fraction(5, 36), Fraction(1, 9), Fraction(1, 12), Fraction(1, 18), Fraction(1, 36))
>>> def simulation_based_p_value_(histogram,numberofsimulations):
... threshold = probability_of_specific_histogram_(fortwodice,histogram)
... lowcumulator,highcumulator = 0,0
... for _ in range(numberofsimulations):
... p = probability_of_specific_histogram_(fortwodice,from_fiftyfive_rolls_())
... if p <= threshold:
... highcumulator += 1
... if p != threshold:
... lowcumulator += 1
... return (lowcumulator/numberofsimulations,highcumulator/numberofsimulations)
...
>>> percentages = (0,15,4,20,15,5,7,11,5,15,4)
>>> len(percentages)
11
>>> sum(percentages)
101
>>> max(abs(frac(p*55,100)-round(frac(p*55,100))) for p in percentages)
Fraction(1, 4)
>>> histogram = tuple(round(frac(p*55,100)) for p in percentages)
>>> sum(histogram)
55
>>> simulation_based_p_value_(histogram,10**7) # This line takes my laptop approximately 11 minutes.
(0.0018246, 0.0018252)
>>>