```python
import matplotlib.pyplot as plt
from scipy import optimize
import numpy as np
import sympy as sm
import func
import time
%reload_ext line_profiler
```

```python
def utility(c,theta):
    ''' Utility function for consumption including the limit case for rho->1 '''
    '''not actually used because direct calculation is faster'''
    if c<0:
        raise error
    if rho !=1 :
        return (c**(1-theta)-1)/(1-theta)
    else:
        return np.log(c)

def total_utility(c, weight, theta):
    '''
    Sums utility for c for multiple years
    c is an array
    '''
    uts = (c**(1-theta)-1)/(1-theta)
    # sum of utitity
    t_u = np.dot(uts,weight)

    return t_u

def prod(k,l,alpha,b):
    return (b*k**alpha)*(l**(1-alpha))
```

assert np.allclose(total_utility1(c, beta, theta),total_utility2(c, beta, theta))

times = 500000
def loop1(times):
    for i in range(times):
        total_utility1(c, beta, theta)

def loop2(times):
    for i in range(times):
        total_utility2(c, beta, theta)

%lprun -f total_utility1 -f loop1 loop1(times)

%lprun -f total_utility2 -f loop2 loop2(times)

```python
def tot_ut_sk(sk,t, k0, l, b, weight, alpha, theta, delta, quick=True):
    '''
    Returns utitlity for  given period
    depending on initial amount of kapital, k0,
    and a fixed savingsrate, sk, for all periods
    '''
    k_short = np.empty(t)
    c_short = np.empty(t)
    y_short = np.empty(t)
    k_short[0] = k0
    for i in range(t):
        y_short[i] = prod(k_short[i],l[i],alpha,b)
        c_short[i]=(1-sk)*y_short[i]
        if i+1<t:
            k_short[i+1]=sk*y_short[i]+(1-delta)*k_short[i]
    if quick:
        return total_utility(c_short,weight,theta)
    else:
        return k_short,c_short,y_short

def opt_sk(t, b, weight, delta, alpha, theta, k0,l):
    '''Finds optimal fixed savings rate, sk,
    for maximal total utility '''
    obj = lambda sk: -tot_ut_sk(sk,t,k0, l, b, weight, alpha, theta, delta)
    res = optimize.minimize_scalar(obj, alpha, method='bounded',bounds=[0,1])
    return res.x
```

# Fast opsparingsrate

analytisk

```python
sm.init_printing(use_unicode=True)
```

```python
#valgmuligheder:
# andelen af mulig arbejdstid som bruges på forskning:
sr = sm.symbols('s_Rt')
# I starten antages kapitalopspsparingsraten at være konstant
sk = sm.symbols('s_Kt')
# Ud fra de valg nogle udfald
# Først defineres variable
l = sm.symbols('L_{t}')
ly = sm.symbols('L_{Yt}')
la = sm.symbols('L_{At}')
alpha = sm.symbols('alpha')
k =sm.symbols('K_{t}')
k1 = sm.symbols('K_{t+1}')
y = sm.symbols('Y_{t}')
c = sm.symbols('C')
ct = sm.symbols('C_{t}')
a = sm.symbols('A_{t}')
a1 = sm.symbols('A_{t+1}')
r = sm.symbols('r')

#Greek:
rho = sm.symbols('rho')
beta = sm.symbols('beta')
alpha = sm.symbols('alpha')
phi = sm.symbols('phi')
delta = sm.symbols('delta')
theta = sm.symbols('theta')

t = sm.symbols('t')
ts = sm.symbols('T')
# Functions:
uc = sm.Function('u')(ct)
u = sm.Function('U')(c)
```

```python
sm.Eq(uc,(ct**(1-theta)-1)/(1-theta))
```

```python
sm.Eq(u,sm.Sum(beta**t*(ct**(1-theta)-1)/(1-theta),(t,0,ts)))
```

```python
sm.init_printing(use_unicode=False)
```

Nummerisk

```python
theta = 0.5
alpha = 1/3
delta = 0.05
rho = 0.5
n=0.008
```

```python
t = 100
b = 10
k0 = 10
l = [(1+n)**i for i in range(t)]
beta = 0.9
weight = np.array([beta**i for i in range(t)])
c = np.random.normal(100,1,t)
sks  = alpha
```

```python
tot_ut_sk(sks,t, k0, l, b, weight, alpha, theta, delta, quick=True)
```

```python
n = 100
ks = np.linspace(0.0001,300,n)
sks = np.zeros(n)
for i,k in enumerate(ks):
    sks[i] = opt_sk(t, b, weight,delta,alpha,theta,k,l)
```

```python
func.plot(ks,sks)
```

# Need speed?

```python
def opt_sk1(t, b, weight, delta, alpha, theta, k0, l):
    '''Finds optimal fixed savings rate, sk,
    for maximal total utility '''
    obj = lambda sk: -tot_ut_sk(sk,t,k0, l, b, weight, alpha, theta, delta)
    res = optimize.minimize_scalar(obj, alpha, method='bounded',bounds=[0,1])
    return res.x
```

```python
def loop1(n):
    for i in range(n):
        opt_sk1(t, b, beta, delta, alpha, theta, k0,l)
```

```python
#%lprun -f opt_sk1 -f loop1 loop1(n)
```

# Varierende opsparingsrate

```python
def tot_ut_multiple_sks(sks, k0, l, b, weight, theta, alpha, delta, simple=True):
    '''
    Finds total utitilty for a set of years with a savingsrate for each year
    '''
    t = len(sks)
    k_short = np.empty(t)
    k_short[0] = k0

    for i in range(1,t):
        k_short[i]=sks[i-1]*prod(k_short[i-1],l[i-1],alpha,b)+(1-delta)*k_short[i-1]

    y_short = prod(k_short,l,alpha,b)
    if simple:
        return total_utility(y_short*(1-sks)/l, weight, theta)
    else:
        return total_utility(y_short*(1-sks)/l, weight, theta), y_short, y_short*(1-sks), k_short
```

```python
def tot_ut_multiple_sks_quick(sks, k0, l, b, weight, theta, alpha, delta):
    '''
    Finds total utitilty for a set of years with a savingsrate for each year
    '''
    t = len(sks)
    k_short = np.empty(t)
    k_short[0] = k0

    for i in range(1,t):
        k_short[i]=sks[i-1]*prod(k_short[i-1],l[i-1],alpha,b)+(1-delta)*k_short[i-1]

    y_short = prod(k_short,l,alpha,b)

    return total_utility(y_short*(1-sks)/l, weight, theta)
```

```python
theta = 0.9
alpha = 1/3
delta = 0.05
beta = 1
n = 0.01
b = 10
k0 = 1
l0 = 1

```

```python
t = 50
l = np.array([l0*(1+n)**i for i in range(t)])
weight = np.array([beta**i for i in range(t)])

obj = lambda sks: -tot_ut_multiple_sks_quick(sks, k0, l, b, weight, theta, alpha, delta)
sks0 = np.full(t,alpha)
sks0[-1] = 0
bounds = np.full((t,2),[0,1])

res = optimize.minimize(obj, sks0, method='SLSQP', bounds=bounds)

print(f'Optimal sk')
np.set_printoptions(precision=4,suppress=True)
print(res.x)
print('Total utility:')
print(f'{-res.fun:.4f}')
```

    Optimal sk
    [0.6309 0.6264 0.6028 0.5775 0.5537 0.5323 0.5127 0.4949 0.4787 0.4637
     0.4498 0.4369 0.4247 0.4132 0.4022 0.3918 0.3816 0.3717 0.362  0.3524
     0.3428 0.3331 0.3232 0.3132 0.3028 0.2919 0.2806 0.2685 0.2555 0.2414
     0.226  0.2092 0.1905 0.1695 0.1457 0.1184 0.0866 0.049  0.004  0.
     0.     0.     0.     0.     0.     0.     0.     0.     0.     0.    ]
    Total utility:
    218.9665

```python
func.plot(range(t),res.x)
```

```python
def optimal_sks(t, b, l, weight, delta, alpha, theta, k0, first=True):
    obj = lambda sks: -tot_ut_multiple_sks_quick(sks, k0, l, b, weight, theta, alpha, delta)
    sks0 = np.linspace(alpha,0,t)

    bounds = np.full((t,2),[0,1])
    res = optimize.minimize(obj, sks0, method='SLSQP', bounds=bounds)
    if res.success == False:
        print('Optimization was sadly not succesfull')
    elif first:
        return res.x[0]
    else:
        return res.x
```

```python
theta = 0.9
alpha = 1/3
delta = 0.05
beta = 1
b = 2
n = 0.01
beta = 0.99
k0 = 20
l0 = 1
t = 200
l = np.array([l0*(1+n)**i for i in range(t)])
weight = np.array([beta**i for i in range(t)])
```

```python
sks = optimal_sks(t, b, l, weight, delta, alpha, theta, k0, first=False)
total, y, c, k = tot_ut_multiple_sks(sks, k0, l, b, weight, theta, alpha, delta, simple=False)
```

```python
sks_alt = np.array([alpha for i in list(range(t))])
total_alt, y_alt, c_alt, k_alt = tot_ut_multiple_sks(sks_alt, k0, l, b, weight, theta, alpha, delta, simple=False)
```

```python
func.plot2(range(t),sks,sks_alt)
func.plot2(range(t),k/l,k_alt/l)
func.plot2(range(t),c/l,c_alt/l)
```

```python
n = 50
ks = np.linspace(0.1,100,50)
sks1 = np.empty(50)

for i, k in enumerate(ks):
    sks1[i] = optimal_sks(t, b, l, weight, delta, alpha, theta, k)
```

```python
func.plot(ks,sks1)
```

```python
n= 20
ks = np.linspace(1,600,20)
sks1 = np.empty(20)

for i, k in enumerate(ks):
    sks1[i] = optimal_sks(t, b, l, weight, delta, alpha, theta, k)
```

```python
func.plot(ks,sks1)
```

```python
ns = [-0.1,0,0.01,0.02,0.05,0.1,0.2]
sks1 = np.empty(len(ns))
for i,n in enumerate(ns):
    l = np.array([l0*(1+n)**i for i in range(t)])
    sks1[i] = optimal_sks(t, b, l, weight, delta, alpha, theta, k0)

```

```python
func.plot(ns,sks1)
```

# Need for speed

```python
def tot_ut_multiple_sks1(sks, k0, l, b, weight, theta, alpha, delta, simple=True):
    '''
    Finds total utitilty for a set of years with a savingsrate for each year
    '''
    t = len(sks)
    k_short = np.empty(t)
    k_short[0] = k0

    for i in range(1,t):
        k_short[i]=sks[i-1]*prod(k_short[i-1],l[i-1],alpha,b)+(1-delta)*k_short[i-1]

    y_short = prod(k_short,l,alpha,b)
    if simple:
        return total_utility(y_short*(1-sks)/l, weight, theta)
    else:
        return total_utility(y_short*(1-sks)/l, weight, theta), y_short, y_short*(1-sks), k_short

def tot_ut_multiple_sks2(sks, k0, l, b, weight, theta, alpha, delta):
    '''
    Finds total utitilty for a set of years with a savingsrate for each year
    '''
    t = len(sks)
    k_short = np.empty(t)
    k_short[0] = k0

    for i in range(1,t):
        k_short[i]=sks[i-1]*prod(k_short[i-1],l[i-1],alpha,b)+(1-delta)*k_short[i-1]

    y_short = prod(k_short,l,alpha,b)

    return total_utility(y_short*(1-sks)/l, weight, theta)

```

```python
n = 0.005
```

```python
t = 60
l = np.array([(1+n)**i for i in range(t)])
weight = np.array([beta**i for i in range(t)])
sks = np.full(t,alpha)
k = np.full(t,10)
```

```python
times = 100
```

```python
assert np.allclose(tot_ut_multiple_sks1(sks, k0, l, b, weight, theta, alpha, delta),tot_ut_multiple_sks2(sks, k0, l, b, weight, theta, alpha, delta))

def loop1(times):
    for i in range(times):
        tot_ut_multiple_sks1(sks, k0, l, b, weight, theta, alpha, delta)

def loop2(times):
    for i in range(times):
        tot_ut_multiple_sks2(sks, k0, l, b, weight, theta, alpha, delta)
```

```python
tot_ut_multiple_sks1(sks, k0, l, b, weight, theta, alpha, delta)
```

```python
%timeit loop1(times)
%timeit loop2(times)
```

    14.1 ms ± 476 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    14.3 ms ± 463 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

```python
%lprun -f tot_ut_multiple_sks1 -f loop1 loop1(times)
```

    Timer unit: 1e-06 s

    Total time: 0.03645 s
    File: <ipython-input-33-0dd545985c8d>
    Function: tot_ut_multiple_sks1 at line 1

    Line #      Hits         Time  Per Hit   % Time  Line Contents
    ==============================================================
         1                                           def tot_ut_multiple_sks1(sks, k0, l, b, weight, theta, alpha, delta, simple=True):
         2                                               '''
         3                                               Finds total utitilty for a set of years with a savingsrate for each year
         4                                               '''
         5       100        135.0      1.4      0.4      t = len(sks)
         6       100        604.0      6.0      1.7      k_short = np.empty(t)
         7       100        153.0      1.5      0.4      k_short[0] = k0
         8
         9      6000       4246.0      0.7     11.6      for i in range(1,t):
        10      5900      25569.0      4.3     70.1          k_short[i]=sks[i-1]*prod(k_short[i-1],l[i-1],alpha,b)+(1-delta)*k_short[i-1]
        11
        12       100       2220.0     22.2      6.1      y_short = prod(k_short,l,alpha,b)
        13       100         95.0      0.9      0.3      if simple:
        14       100       3428.0     34.3      9.4          return total_utility(y_short*(1-sks)/l, weight, theta)
        15                                               else:
        16                                                   return total_utility(y_short*(1-sks)/l, weight, theta), y_short, y_short*(1-sks), k_short

    Total time: 0.045146 s
    File: <ipython-input-37-0944c6798fd4>
    Function: loop1 at line 3

    Line #      Hits         Time  Per Hit   % Time  Line Contents
    ==============================================================
         3                                           def loop1(times):
         4       101        406.0      4.0      0.9      for i in range(times):
         5       100      44740.0    447.4     99.1          tot_ut_multiple_sks1(sks, k0, l, b, weight, theta, alpha, delta)

```python
%lprun -f tot_ut_multiple_sks2 -f loop2 loop2(times)
```

    Timer unit: 1e-06 s

    Total time: 0.027266 s
    File: <ipython-input-33-0dd545985c8d>
    Function: tot_ut_multiple_sks2 at line 19

    Line #      Hits         Time  Per Hit   % Time  Line Contents
    ==============================================================
        19                                           def tot_ut_multiple_sks2(sks, k0, l, b, weight, theta, alpha, delta):
        20                                               '''
        21                                               Finds total utitilty for a set of years with a savingsrate for each year
        22                                               '''
        23       100         95.0      0.9      0.3      t = len(sks)
        24       100        354.0      3.5      1.3      k_short = np.empty(t)
        25       100        115.0      1.1      0.4      k_short[0] = k0
        26
        27      6000       3220.0      0.5     11.8      for i in range(1,t):
        28      5900      19478.0      3.3     71.4          k_short[i]=sks[i-1]*prod(k_short[i-1],l[i-1],alpha,b)+(1-delta)*k_short[i-1]
        29
        30       100       1425.0     14.2      5.2      y_short = prod(k_short,l,alpha,b)
        31
        32       100       2579.0     25.8      9.5      return total_utility(y_short*(1-sks)/l, weight, theta)

    Total time: 0.033976 s
    File: <ipython-input-37-0944c6798fd4>
    Function: loop2 at line 7

    Line #      Hits         Time  Per Hit   % Time  Line Contents
    ==============================================================
         7                                           def loop2(times):
         8       101         97.0      1.0      0.3      for i in range(times):
         9       100      33879.0    338.8     99.7          tot_ut_multiple_sks2(sks, k0, l, b, weight, theta, alpha, delta)

```python
def optimal_sks2(t, b, l, weight, delta, alpha, theta, k0, first=True):
    obj = lambda sks: -tot_ut_multiple_sks2(sks, k0, l, b, weight, theta, alpha, delta)
    sks0 = np.linspace(alpha,0,t)

    bounds = np.full((t,2),[0,1])
    res = optimize.minimize(obj, sks0, method='L-BFGS-B', bounds=bounds)
    if res.success == False:
        print('Optimization was sadly not succesfull')
    elif first:
        return res.x[0]
    else:
        return res.x
```

```python
optimal_sks2(t, b, l, weight, delta, alpha, theta, k0)
```

```python
optimal_sks(t, b, l, weight, delta, alpha, theta, k0)
```

```python
times = 5
```

```python
assert np.allclose(tot_ut_multiple_sks_quick(optimal_sks(t, b, l, weight, delta, alpha, theta, k0, first =False), k0, l, b, weight, theta, alpha, delta),tot_ut_multiple_sks_quick(optimal_sks2(t, b, l, weight, delta, alpha, theta, k0, first =False), k0, l, b, weight, theta, alpha, delta))

def loop1(times):
    for i in range(times):
        optimal_sks(t, b, l, weight, delta, alpha, theta, k0)

def loop2(times):
    for i in range(times):
        optimal_sks2(t, b, l, weight, delta, alpha, theta, k0)
```

```python
%timeit loop1(times)
%timeit loop2(times)
```

    476 ms ± 2.45 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    543 ms ± 13.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

```python
%lprun -f optimal_sks -f loop1 loop1(times)
```

    Timer unit: 1e-06 s

    Total time: 0.701143 s
    File: <ipython-input-22-be530121cece>
    Function: optimal_sks at line 1

    Line #      Hits         Time  Per Hit   % Time  Line Contents
    ==============================================================
         1                                           def optimal_sks(t, b, l, weight, delta, alpha, theta, k0, first=True):
         2         5          7.0      1.4      0.0      obj = lambda sks: -tot_ut_multiple_sks_quick(sks, k0, l, b, weight, theta, alpha, delta)
         3         5        441.0     88.2      0.1      sks0 = np.linspace(alpha,0,t)
         4
         5         5        130.0     26.0      0.0      bounds = np.full((t,2),[0,1])
         6         5     700474.0 140094.8     99.9      res = optimize.minimize(obj, sks0, method='SLSQP', bounds=bounds)
         7         5         71.0     14.2      0.0      if res.success == False:
         8                                                   print('Optimization was sadly not succesfull')
         9         5          4.0      0.8      0.0      elif first:
        10         5         16.0      3.2      0.0          return res.x[0]
        11                                               else:
        12                                                   return res.x

    Total time: 0.701236 s
    File: <ipython-input-46-0339548f23e0>
    Function: loop1 at line 3

    Line #      Hits         Time  Per Hit   % Time  Line Contents
    ==============================================================
         3                                           def loop1(times):
         4         6          8.0      1.3      0.0      for i in range(times):
         5         5     701228.0 140245.6    100.0          optimal_sks(t, b, l, weight, delta, alpha, theta, k0)


```python
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import binom, multinomial
```

# Diablo 2 Drop Calculations

© youbetterdont 2019

This article looks at a few of the more challenging probability calculations in Diablo 2's drop system.

## n-picks, m-drops

### Problem Statement

In Diablo 2, a monster can drop up to $m$ items, but monsters have up to $n$ picks, where $n$ is possibly greater than $m$. In practice, $m=6$ and the maximum value of $n$ is 7. We would like to know the probability that a monster drops at least $c$ of a desired item, where the probabilty is $p$ on a single pick. The chance of nodrop is $z$ on each pick. The conditional probability of dropping the item is $\frac{p}{1-z}$ on a single pick if nodrop is not selected. The monster will continue to drop items from its $n$ picks until at most $m$ items are selected.

The analysis will make use of binomial random variables. A binomial random variable is denoted as $B(n,p)$, where $n$ is the number of trials and $p$ is the probability of success.

### Anaylsis

The number of drops before the max limitaiton is $X \sim B(n, 1-z)$. The number of drops is limited to $m$, so we get the actual number of drops $Y = \mathrm{min}(X, m)$.

The number of desired items given $Y$ drops is $U|Y \sim B(Y, \frac{p}{(1-z)})$. We would like to find $\mathrm{Pr}(U \geq c)$ and $\mathrm{E}[U]$. We generate some example data below.

```python
z = 0.3 # probability of nodrop
p = 0.01 # probability of wanted drop
m = 6 # maximum number of drops (drops that aren't nodrop)
n = 7 # maximum number of picks

num_samples = 50000 # number of samples to generate

X = binom(n,1-z)
Xsamples = X.rvs(size=num_samples)
Ysamples = np.minimum(Xsamples, m)
Uarr = [binom(y,p/(1-z)) for y in Ysamples]
Usamples = np.array([u.rvs(size=1)[0] for u in Uarr], dtype=int)
#Usamples = np.array([np.average(u) for u in Usamples_arr], dtype=int)
```

Plotted below, the histogram of the generated data reveals its distribution.

```python
plt.figure()
plt.hist((Xsamples,Ysamples,Usamples), bins=range(np.maximum(n,m)+2), log=2)
plt.legend(['Xsamples','Ysamples','Usamples'])
plt.ylabel('Occurences')
plt.title('Histogram of generated data\n$n={},z={},p={}$'.format(n,z,p))
plt.xlabel('Number of drops or desired items')
```

    <IPython.core.display.Javascript object>

    Text(0.5, 0, 'Number of drops or desired items')

Using `scipy.stats.binom.expect`, it is easy to get an accurate estimate on the expected value of $Y$.

```python
print('Expected value of Y using binom.expect: {:.6f}'.format(X.expect(lambda x: np.minimum(x, m))))
#print('Intermediate formula: {}'.format(X.expect(ub=m, conditional=True)*X.cdf(m)+m*(1-X.cdf(m))))
print('Average of Ysamples: {:.6f}'.format(np.average(Ysamples)))
```

    Expected value of Y using binom.expect: 4.817646
    Average of Ysamples: 4.821160

#### Probability

By the law of total probability,

\begin{align}
\mathrm{Pr}(U \geq c) &= \sum_{k=0}^{m} \mathrm{Pr}(U \geq c| Y=k) \cdot \mathrm{Pr}(Y=k),
\end{align}

where $U | Y=k \sim B(k, \frac{p}{1-z})$, that is, the random variable $U$ given $Y=k$ is itself a binomial random variable. The probability of $Y=k$ is

\begin{align}
\mathrm{Pr}(Y=k)&= \begin{cases}
      \mathrm{Pr}(X=k) & k < m \\
      \mathrm{Pr}(X \geq m) & k = m \\
      0 & k > m
   \end{cases}.
\end{align}

The probability of $U \geq c$ is then

\begin{align}
\mathrm{Pr}(U \geq c) &= \mathrm{Pr}(U \geq c | Y=m) \cdot \mathrm{Pr}(X \geq m)  + \sum_{k=0}^{m-1} \mathrm{Pr}(U \geq c| Y=k) \cdot \mathrm{Pr}(X=k) \\
                      &= \mathrm{Pr}(U \geq c | Y=m) \cdot \mathrm{Pr}(X > m)  + \sum_{k=0}^{m} \mathrm{Pr}(U \geq c| Y=k) \cdot \mathrm{Pr}(X=k).
\end{align}

The above form of the solution can be evaluated easily using the CDFs of standard binomial random variables $U|Y=k$ and $X$. We can check this against the empirical results from the generated data.

```python
for c in range(1, m+1):
    EPrUsamples_geq_c = np.sum(Usamples>=c)/len(Usamples)
    print('Empirical probability of U>={}: {:.9f}'.format(c, EPrUsamples_geq_c))
    PrUsamples_geq_c = binom.sf(c-1, m, p/(1-z))*X.sf(m)+np.sum([binom.sf(c-1, k, p/(1-z))*X.pmf(k) for k in range(m+1)])
    print('Pr(U>={})={:.9f}'.format(c,PrUsamples_geq_c))
```

    Empirical probability of U>=1: 0.065060000
    Pr(U>=1)=0.066855470
    Empirical probability of U>=2: 0.001480000
    Pr(U>=2)=0.001937200
    Empirical probability of U>=3: 0.000000000
    Pr(U>=3)=0.000030562
    Empirical probability of U>=4: 0.000000000
    Pr(U>=4)=0.000000276
    Empirical probability of U>=5: 0.000000000
    Pr(U>=5)=0.000000001
    Empirical probability of U>=6: 0.000000000
    Pr(U>=6)=0.000000000

The results from the formulas closely match the empirical results, within the margin of error we would expect given the sample size.

As a sanity check, we can consider the case where $n \leq m$.

If $n \leq m$, then $X = Y$ and $U|X \sim B(X, \frac{p}{1-z})$. It follows that $U \sim B(n,p)$, which is simply the number of desired items in $n$ picks. This is the distribution we would expect without the complication of the $m$ pick limit. We can expand the first form to show that that this result is equivalent to the general result above when $n \leq m$.

\begin{align}
\mathrm{if}~n \leq m\mathrm{:}\quad\quad&\\
\mathrm{Pr}(U \geq c) &= \sum_{k=0}^{n} \mathrm{Pr}(U \geq c| X=k) \cdot \mathrm{Pr}(X=k) \\
                      &= \sum_{k=0}^{m} \mathrm{Pr}(U \geq c| Y=k) \cdot \mathrm{Pr}(Y=k)
\end{align}

#### Expectation

By the law of total expectation, the expected number of wanted drops is

\begin{align}
\mathrm{E}[U] &= \sum_{k=0}^{m} \mathrm{E}[U | Y=k] \cdot \mathrm{Pr}(Y=k) \\
              &= \mathrm{E}[U | Y=m] \cdot \mathrm{Pr}(X \geq m)  + \sum_{k=0}^{m-1} \mathrm{E}[U | Y=k] \cdot \mathrm{Pr}(X=k) \\
              &= \mathrm{E}[U | Y=m] \cdot \mathrm{Pr}(X > m)  + \sum_{k=0}^{m} \mathrm{E}[U | Y=k] \cdot \mathrm{Pr}(X=k) \\
              &= \frac{p}{1-z} \left (m \cdot \mathrm{Pr}(X > m)  + \sum_{k=0}^{m} k \cdot \mathrm{Pr}(X=k) \right )
\end{align}

If $n = m+1$, then

\begin{align}
\mathrm{E}[U] &= \frac{p}{1-z} \left (m \cdot \mathrm{Pr}(X = m + 1)  + \sum_{k=0}^{m} k \cdot \mathrm{Pr}(X=k) \right ) \\
              &= \frac{p}{1-z} \left (- \mathrm{Pr}(X = m + 1)  + \sum_{k=0}^{m+1} k \cdot \mathrm{Pr}(X=k) \right ) \\
              &= \frac{p}{1-z} \left (\mathrm{E}[X] - \mathrm{Pr}(X=m+1) \right ) \\
              &= p \left (n - \frac{\mathrm{Pr}(X=m+1)}{1-z} \right ) \\
              &= p \left (m+1 - (1-z)^m \right )
\end{align}

We can easily check the $\mathrm{E}[U]$ result by comparing to the average of the generated `Usamples`. The resulting relative error is very small and within the margin of error we would expected given the sample size.

```python
Usamples_mean = np.average(Usamples)
print('Average of Usamples: {:.6f}'.format(Usamples_mean))
EU_formula = p*(m+1-(1-z)**m)
print('Formula for expected value of U with n=m+1: {:.6f} (exact solution)'.format(EU_formula))
print('Relative error: {:.6f} (due to variance of E[U])'.format((EU_formula-Usamples_mean)/Usamples_mean))
#print(p/(1-z)*(m*X.sf(m-1)+np.sum([k*X.pmf(k) for k in range(m)])))
#print(binom.expect(args=(m,p/(1-z)))*X.sf(m-1)+np.sum([binom.expect(args=(k,p/(1-z)))*X.pmf(k) for k in range(m)]))
```

    Average of Usamples: 0.066540
    Formula for expected value of U with n=m+1: 0.068824 (exact solution)
    Relative error: 0.034318 (due to variance of E[U])

## Negative Picks

### Problem Statement
If the picks column is negative, then the selected TCs are not random. The game will drop the specified TCs in order and quantity listed. For example, the TC `Countess` has `Picks=-2` and TCs ordered as `Countess Item` with `Prob=1` and `Countess Rune` with `Prob=1`. This means `Countess` will first drop from `Countess Item` once, then from `Countess Rune` once.

This means that the TCs with `Picks<0` should not be included in the probability graph. Rather, the monsters that drop from such TCs should reference the derivative TCs with `Picks>0` directly. The process is equivalent to killing two or more monsters, where each monster drops from a TC with `Picks>0`, with the exception that if more than $m$ drops are generated, we can only take the first $m$. The number of monsters is $q$.

The first monster has probability $p_1$ to drop the item and $z_1$ of `NoDrop`, the second $p_2$ and $z_2$, and so on. The first monster has $n_1$ picks, the second $n_2$ picks, and so on.

The number of drops from the first monster before the $m$ drop limitation is $X_1 \sim B(n_1, 1-z_1)$. Applying the limitation, the number of drops is then $Y_1 = \mathrm{min}(X_1, m)$. For the second monster, $X_2 \sim B(n_2, 1-z_2)$ and $Y_2 = \mathrm{min}(X_2, m-Y_1)$. For the $q$th monster, $X_q \sim B(n_q, 1-z_q)$ and $Y_q = \mathrm{min}(X_q, m-\sum_{k=1}^{q-1} Y_{k})$. The number of wanted items from each monster given $Y_i$ is $U_i|Y_i \sim B(Y_i, \frac{p_i}{1-z_i})$ where $i \in \{1,\dotsc,q\}$. The total number of wanted items is then $U = \sum_{k=1}^{q} U_k$.

We would like to find both $\mathrm{Pr}(U \geq c)$ and $\mathrm{E}[U]$.

### Analysis for $q=2$

The probabilty and expectation of random variables $X_1$, $Y_1$, and $U_1$ has already been completed in the *n-drops, m-picks* analysis.

#### Probability

We will focus on $X_2$, $Y_2$, and $U_2$. The number of desired items given $Y_2$ is $U_2|Y_2 \sim B(Y_2, \frac{p_2}{1-z_2})$. By the law of total probability,

\begin{align}
\mathrm{Pr}(U_2 \geq c) = \sum_{k=0}^{m} \mathrm{Pr}(U_2 \geq c | Y_2 = k) \cdot \mathrm{Pr}(Y_2=k),
\end{align}

where $U_2|Y_2=k \sim B(k,\frac{p_2}{1-z_2})$. The number of drops is $Y_2 = \mathrm{min}(X_2, m-Y_1)$. The probability that $Y_2=k$ is

\begin{align}
\mathrm{Pr}(Y_2=k)&= \begin{cases}
      \mathrm{Pr}(X_2=k)\cdot\mathrm{Pr}(Y_1 < m-k) + \mathrm{Pr}(Y_1=m-k)\cdot\mathrm{Pr}(X_2 > k) + \mathrm{Pr}(X_2=k) \cdot \mathrm{Pr}(Y_1=m-k) & k < m \\
      \mathrm{Pr}(X_2 \geq m)\cdot\mathrm{Pr}(Y_1=0) & k = m \\
      0 & k > m
   \end{cases}.
\end{align}

At this point, it probably makes sense to assume $n_i \leq m$. There is no situation in the game where a TC with negative picks leads to a TC with $n_i > m$. If $n_i \leq m$, then $Y_1 = X_1$ and

\begin{align}
\mathrm{Pr}(Y_2=k)&=
      \mathrm{Pr}(X_2=k)\cdot\mathrm{Pr}(X_1 < m-k) + \mathrm{Pr}(X_1=m-k)\cdot\mathrm{Pr}(X_2 > k) + \mathrm{Pr}(X_2=k) \cdot \mathrm{Pr}(X_1=m-k) .
\end{align}

Substituting above, we now have an expression for $\mathrm{Pr}(U_2 \geq c)$ that can be evaluated using the PMFs and CDFs of standard binomial random variables. Because of the $n_1 \leq m$ assumption, we have that $U_1 \sim B(n_1, p_1)$. The probability $U_1 \geq c$ is then also easily found using the CDF. The probability of the $U \geq c$ is found as

\begin{align}
\mathrm{Pr}(U \geq c) &= \mathrm{Pr}(U_1 + U_2 \geq c).
\end{align}

The PMF of the above may be very difficult to compute analytically. At this point, let's restrict $c=1$, then

\begin{align}
\mathrm{Pr}(U \geq 1) &= \mathrm{Pr}(U_1 + U_2 \geq 1) \\
&= 1 - \mathrm{Pr}(U_1 = 0 \cap U_2 = 0) \\
&= 1 - \mathrm{Pr}(U_1 = 0 | U_2 = 0) \cdot \mathrm{Pr}( U_2 = 0),
\end{align}

where

\begin{align}
\mathrm{Pr}(U_2 = 0) = \sum_{k=0}^{m} \mathrm{Pr}(U_2 = 0 | Y_2 = k) \cdot \mathrm{Pr}(Y_2=k).
\end{align}

Now the problem is that $U_1$ and $U_2$ are not independent. However, the error introduced by assuming independence is small given that the product of the probabilities $p_1p_2$ is small. Either $U_1=0$ most of the time, or $U_2=0$ most of the time, and either condition results in little effect on the distribution of the other. In other words, we are still sampling a large percentage of the total population of $U_1$ when $U_2=0$. We can then write

\begin{align}
\mathrm{Pr}(U \geq 1) \approx 1 - \mathrm{Pr}(U_1 = 0) \cdot \mathrm{Pr}( U_2 = 0).
\end{align}

Next, we generate some data to validate the assumption.

```python
z1 = 0.3 # probability of nodrop
p1 = 0.01 # probability of wanted drop
n1 = 5 # maximum number of picks

X1 = binom(n1,1-z1)
X1samples = X1.rvs(size=num_samples)
Y1samples = np.minimum(X1samples, m)
U1arr = [binom(y,p1/(1-z1)) for y in Y1samples]
U1samples = np.array([u.rvs(size=1)[0] for u in U1arr], dtype=int)
#U1samples = np.array([np.average(u) for u in U1samples_arr], dtype=int)

n2 = 4
p2 = 0.05
z2 = 0.35
X2 = binom(n2, 1-z2)
X2samples = X2.rvs(size=num_samples)
Y2samples = np.minimum(X2samples, m-Y1samples)
U2arr = [binom(y,p2/(1-z2)) for y in Y2samples]
U2samples = np.array([u.rvs(size=1)[0] for u in U2arr], dtype=int)
#U2samples = np.array([np.average(u) for u in U2samples_arr], dtype=int)
```

We plot the histograms of the distributions and compare them to the histograms with the $U_i=0$ condition. We also plot $U_1$ and $U_2$ on a scatterplot to see if there is any obvious correlation. Additionally, we compute the Pearson correlation coefficient and relative error introduced by the unconditional probability estimate.

```python
plt.figure()
plt.hist((U1samples, U1samples[U2samples==0]), density=True, bins=range(m+2), log=True)
plt.legend(['U1', 'U1|U2=0'])
plt.title('Distribution of $U1$ and $U1|U2=0$\n$n_1={},z_1={},p_1={}$'.format(n1,z1,p1))
plt.ylabel('Normalized occurences')
plt.xlabel('Wanted item drops from 1st monster')

plt.figure()
plt.hist((U2samples, U2samples[U1samples==0]), density=True, bins=range(m+2), log=True)
plt.legend(['U2', 'U2|U1=0'])
plt.title('Distribution of $U2$ and $U2|U1=0$\n$n_2={},z_2={},p_2={}$'.format(n2,z2,p2))
plt.ylabel('Normalized occurences')
plt.xlabel('Wanted item drops from 2nd monster')
#plt.hist(Usamples[U2samples==0], density=True, bins=range(m+2))

plt.figure()
plt.scatter(U1samples, U2samples, alpha=0.05)
plt.xlabel('U1')
plt.ylabel('U2')
plt.title('Scatterplot of U2 vs U1')

print("Pearson correlation, corr(U1, U2): {:.6f}".format(np.corrcoef(U1samples, U2samples, rowvar=False)[0,1]))
EPU1eq0 = np.sum(U1samples==0)/len(U1samples)
print('Empirical probability of U1samples=0: {:.6f}'.format(EPU1eq0))
EPU1eq0_U2eq0 = np.sum(U1samples[U2samples==0]==0)/len(U1samples[U2samples==0])
print('Empirical probability of U1samples=0 when U2samples=0: {:.6f}'.format(EPU1eq0_U2eq0))
print('Relative error of emprical probabilities: {:.6f}'.format((EPU1eq0_U2eq0-EPU1eq0)/EPU1eq0))

EPU2eq0 = np.sum(U2samples==0)/len(U2samples)
print('Empirical probability of U2samples=0: {:.6f}'.format(EPU2eq0))
EPU2eq0_U1eq0 = np.sum(U2samples[U1samples==0]==0)/len(U2samples[U1samples==0])
print('Empirical probability of U2samples=0 when U1samples=0: {:.6f}'.format(EPU2eq0_U1eq0))
print('Relative error of emprical probabilities: {:.6f}'.format((EPU2eq0_U1eq0-EPU2eq0)/EPU2eq0))
sum_U1_U2 = U1samples + U2samples
EPUsum_geq_1 = np.sum(sum_U1_U2>=1)/len(sum_U1_U2)
print('Empirical probabilty of U>=1: {:.6f}'.format(EPUsum_geq_1))

#plt.hist(Usamples)
```

    <IPython.core.display.Javascript object>

    <IPython.core.display.Javascript object>

    <IPython.core.display.Javascript object>

    Pearson correlation, corr(U1, U2): -0.002997
    Empirical probability of U1samples=0: 0.951760
    Empirical probability of U1samples=0 when U2samples=0: 0.951594
    Relative error of emprical probabilities: -0.000174
    Empirical probability of U2samples=0: 0.854440
    Empirical probability of U2samples=0 when U1samples=0: 0.854291
    Relative error of emprical probabilities: -0.000174
    Empirical probabilty of U>=1: 0.186920

From the above, we can see that the impact of the independence assumption is very small.

We can now check the overall probability estimate.

```python
def PrY2_eq_k(k):
    """
    Return the probability that Y2=k, based on the formula above.
    """
    return X2.pmf(k)*X1.cdf(m-k-1)+X1.pmf(m-k)*X2.sf(k)+X2.pmf(k)*X1.pmf(m-k)

PrU1_eq_0 = binom.pmf(0,n1,p1)
PrU2_eq_0 = np.sum([binom.pmf(0, k, p2/(1-z2))*PrY2_eq_k(k) for k in range(m+1)])
PrUsum_geq_1 = 1-PrU1_eq_0*PrU2_eq_0

print('Pr(U_1=0)={:.6f} (exact solution)'.format(PrU1_eq_0))
print('Pr(U_2=0)={:.6f}, relative error: {:.6f}'.format(PrU2_eq_0, (PrU2_eq_0-EPU2eq0)/EPU2eq0))
print('Pr(U>=1)={:.6f}, relative error: {:.6f}'.format(PrUsum_geq_1, (PrUsum_geq_1-EPUsum_geq_1)/EPUsum_geq_1))
```

    Pr(U_1=0)=0.950990 (exact solution)
    Pr(U_2=0)=0.853461, relative error: -0.001146
    Pr(U>=1)=0.188367, relative error: 0.007742

The probability estimates from the derived formulas agree very well with the empirical probabilities from the generated data.

#### Expectation

Again we will assume $n_i \leq m$. By the law of total expectation,

\begin{align}
\mathrm{E}[U_2] &= \sum_{k=0}^{m} \mathrm{E}[U_2 | Y_2 = k] \cdot \mathrm{Pr}(Y_2=k) \\
                &= \frac{p_2}{1-z_2}\sum_{k=0}^{m} k \mathrm{Pr}(Y_2=k),
\end{align}

with $\mathrm{Pr}(Y_2=k)$ given above in the *Probability* section. The expected value of $U_1$ is

\begin{align}
\mathrm{E}[U_1] = n_1p_1.
\end{align}

Then the expected value of the sum is

\begin{align}
\mathrm{E}[U] = \mathrm{E}[U_1 + U_2] = \mathrm{E}[U_1] + \mathrm{E}[U_2] = n_1p_1 + \frac{p_2}{1-z_2}\sum_{k=0}^{m} k \mathrm{Pr}(Y_2=k).
\end{align}

Next, we compare the results of this formula with the empirical results from the generated data.

```python
EEU1samples = np.average(U1samples)
EEU2samples = np.average(U2samples)
EEUsum = np.average(U1samples+U2samples)
print('Empirical expected value of U1: {:.6f}'.format(EEU1samples))
print('Empirical expected value of U2: {:.6f}'.format(EEU2samples))
print('Empirical expected value of U: {:.6f}'.format(EEUsum))
EU1 = n1*p1
EU2 = p2/(1-z2)*np.sum([k*PrY2_eq_k(k) for k in range(m+1)])
EUsum = EU1 + EU2
print('E[U1]: {:.6f} (exact solution)'.format(EU1))
print('E[U2]: {:.6f} (exact solution)'.format(EU2))
print('E[U]: {:.6f} (exact solution)'.format(EUsum))
```

    Empirical expected value of U1: 0.049220
    Empirical expected value of U2: 0.153880
    Empirical expected value of U: 0.203100
    E[U1]: 0.050000 (exact solution)
    E[U2]: 0.154340 (exact solution)
    E[U]: 0.204340 (exact solution)

The results from the formulas match the empirical results very well.

## Duriel drops

### Problem statement

The `Duriel` TC is the only TC in the game that randomly drops into a TC that itself has more than 1 pick. Other TCs, such as `Countess`, drop into secondary TCs with multiple picks, but they will always drop a set number of picks from each. This is also true with `Super` TCs. They drop potions from the `Cpot` TCs, which have 2 picks each, but they drop a fixed amount of times from `Cpot`, and their total drops never exceed 6. The point is that if a TC drops a fixed number of times $\alpha$ into a secondary TC with $\beta$ picks, then it's no different than the primary TC dropping $\alpha \beta$ of those picks directly.

With `Duriel`, the number of times we drop from `Duriel - Base` is random, and this TC has 7 picks itself. We have no choice but to first roll the dice to see if `Duriel - Base` is selected, then evaluate the 7 picks if it was. This procedure must repeat another 4 times, or until the maximum of 6 drops.

We assume that the desired item can only come from `Duriel - Base`, as the only other item `Duriel` drops is a scroll of town portal (`tsc`). The probability of getting a `Duriel - Base` roll is $p_b$. The probability of no-drop from a single `Duriel - Base` drop is $z$. The probability of getting the desired item in a single `Duriel - Base` drop is $p$.

### Analysis

We can use a Markov chain model to calculate the probability of terminating in any of a number of finite states. The system state must be two dimensional because it's not enough to track only the total number of drops (which would include the `tsc` drops). It's also not enough to use only the number of `Duriel - Base` drops because we do not know how many drops are remaining. Therefore, the system state is chosen as the ordered pair $(T_n, Y_n)$, where $T_n$ is the number of `tsc` drops at step $n$ and $Y_n$ is the number of `Duriel - Base` drops at step $n$.

There are 28 possible states in the general $m=6$ case. They are enumerated below. We choose to include the $(5,1)$ and $(6,0)$ states for completeness, but in reality, only up to five `Duriel` Drops will occur, and these states are inaccessible.

| T              | Y                      | count                   |
|---------------:|-----------------------:|------------------------:|
| 0              | {0, 1, 2, 3, 4, 5, 6}  | 7                       |
| 1              | {0, 1, 2, 3, 4, 5}     | 6                       |
| 2              | {0, 1, 2, 3, 4}        | 5                       |
| 3              | {0, 1, 2, 3}           | 4                       |
| 4              | {0, 1, 2}              | 3                       |
| 5              | {0, 1}                 | 2                       |
| 6              | 0                      | 1                       |

What remains is to calculate the probability of advancing from one state to the next in order to populate the transition matrix. The transition matrix is 28 $\times$ 28. It is upper triangular, leaving 406 entries to populate. The upper triangular property is because the transitions are directed. It's possible to advance the state to a greater or equal number of items, but not to go in the reverse direction.

We must order the state vector in a manner to enforce the upper triangular condition. The sum of $T$ and $Y$ must not decrease as we advance though the vector. This means that $T$ and $Y$ are never permitted to both decrease from one element to the next, otherwise the sum would decrease. Equivalently, this means that traversing the elements in reverse order necessitates either $T$ or $Y$ to decrease, which is not a permitted transition, thus has zero probability. Therefore, the matrix is upper triangular.

We order the states the following way.

```python
states = sorted([(x,y) for x in range(7) for y in range(7-x)], key=lambda x: x[0]+x[1])
print("Number of states: {}".format(len(states)))
print("Ordered states: {}".format(states))
```

    Number of states: 28
    Ordered states: [(0, 0), (0, 1), (1, 0), (0, 2), (1, 1), (2, 0), (0, 3), (1, 2), (2, 1), (3, 0), (0, 4), (1, 3), (2, 2), (3, 1), (4, 0), (0, 5), (1, 4), (2, 3), (3, 2), (4, 1), (5, 0), (0, 6), (1, 5), (2, 4), (3, 3), (4, 2), (5, 1), (6, 0)]

There are two types of possible transitions. They are

\begin{align}
(t, y) &\rightarrow (t, y+k) \\
(t, y) &\rightarrow (t+1, y),
\end{align}

where $k \geq 0$. The first possibility is that `Duriel - Base` drops zero or more items. The second is the possibility that `Duriel` drops a `tsc` directly. Any transitions that do not fit the above description have zero probability. For the first case, we find

\begin{align}
&\mathrm{Pr}(T_{n+1} = t \cap Y_{n+1} = y+k | T_{n} = t \cap Y_{n} =  y) \\
 & \qquad = \mathrm{Pr}(T_{n+1}-T_n = 0 \cap Y_{n+1}-Y_{n} = k | T_{n} + Y_{n} = t + y) \\
 & \qquad = \mathrm{Pr}(T_{n+1}-T_n = 0 | T_{n} + Y_{n} = t + y) \cdot \mathrm{Pr}(Y_{n+1}-Y_{n} = k | T_{n+1}-T_n = 0 \cap T_{n} + Y_{n} = t + y).
\end{align}

The first term is the probability that `Duriel` will *not* drop another `tsc` given how many total drops have occured. We find

\begin{align}
\mathrm{Pr}(T_{n+1}-T_n = 0 | T_{n} + Y_{n} = t + y) &= \begin{cases}
    p_b & t+y < 6 \\
    1 & t+y \geq 6
\end{cases},
\end{align}

The above states that `Duriel` cannot drop any more `tsc`s once the drop limitation is reached.

Much earlier, we found that the number of drops $Y$ from a monster with $n$ picks and $m$ maximum drops has the PMF

\begin{align}
\mathrm{Pr}(Y=k)&= \begin{cases}
      \mathrm{Pr}(X=k) & k < m \\
      \mathrm{Pr}(X \geq m) & k = m \\
      0 & k > m
   \end{cases},
\end{align}

where $X \sim B(n, 1-z)$ is the number of drops before the maximum limitation. We can modify this slightly here to say

\begin{align}
\mathrm{Pr}(Y_{n+1}-Y_{n} = k | T_{n+1}-T_n = 0 \cap T_{n} + Y_{n} = t + y) &= \begin{cases}
      \mathrm{Pr}(X=k) & k < 6-t-y \\
      \mathrm{Pr}(X \geq 6-t-y) & k = 6-t-y \\
      0 & k > 6-t-y
   \end{cases}.
\end{align}

The probability of the second type of transition is

\begin{align}
&\mathrm{Pr}(T_{n+1} = t+1 \cap Y_{n+1} = y | T_{n} + Y_{n} = t + y) \\
 & \qquad = \mathrm{Pr}(T_{n+1}-T_n = 1 \cap Y_{n+1}-Y_{n} = 0 | T_{n} + Y_{n} = t + y) \\
 & \qquad = \mathrm{Pr}(T_{n+1}-T_n = 1 | T_{n}+Y_{n}=t+y) \cdot \mathrm{Pr}(Y_{n+1}-Y_{n} = 0 | T_{n+1}-T_n = 1 \cap T_{n} + Y_{n} = t + y) \\
 & \qquad = \mathrm{Pr}(T_{n+1}-T_n = 1 | T_{n} + Y_{n} = t + y) \\
 & \qquad = \begin{cases}
     (1-p_b) & t+y < 6 \\
     0 & t+y \geq 6
 \end{cases} .
\end{align}

We now have all the necessary PMFs to fill out the matrix. We do so programmatically below.

```python
pb = 2./3

Td = np.zeros((len(states),len(states)))
for i, row in enumerate(Td):
    for j, _ in enumerate(row):
        tn, yn = states[i]
        tn1, yn1 = states[j]
        k = yn1 - yn
        if tn==tn1 and k >= 0:
            Td[i, j] = (X.sf(6-tn-yn-1) if k==6-tn-yn else 0 if k > 6-tn-yn else X.pmf(k))*(pb if tn+yn < 6 else 1)
        elif tn1-tn==1 and k == 0:
            Td[i, j] = (1-pb) if tn+yn < 6 else 0

print('The sum of each row of the transition matrix (should be all ones):\n {}'.format(np.sum(Td, axis=1)))
```

    The sum of each row of the transition matrix (should be all ones):
     [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
     1. 1. 1. 1.]

We can now find the probability of ending in any state at step $n$ by multiplying the initial state row vector by the transition matrix, that is,

\begin{align}
\mathbf{x}_n = \mathbf{x}_o \mathbf{P}^n.
\end{align}

The initial state $\mathbf{x}_o$ has a 1 in the first column and is zero everywhere else, meaning we start in state $(0,0)$. The probability of ending in each state is found as above. The PMF of the number of `Duriel - Base` drops can be computed by summing the probabilities of the states with identical `Duriel - Base` drops. Below, we perform this calculation and also generate a large sample of data for comparison. The results are presented on a combined histogram.

```python
x_o = np.zeros((1,len(states)))
x_o[0,0] = 1

x5 = np.dot(x_o, np.linalg.matrix_power(Td, 5))
y5 = [np.sum(x5[0,np.array(states)[:,1]==i]) for i in range(7)]

num_d = 50000
Td_samples = np.zeros(num_d).astype('int')
Yd_samples = np.zeros(num_d).astype('int')
for i in range(len(Yd_samples)):
    for j in range(5):
        t = binom.rvs(1, 1-pb)
        Td_samples[i] += t
        if t == 0:
            Yd_samples[i] += np.minimum(X.rvs(), m-Td_samples[i]-Yd_samples[i])
        if Td_samples[i] + Yd_samples[i] >= m:
            if Td_samples[i] + Yd_samples[i] > m: print('This should not happen.')
            break
plt.figure()
plt.bar(np.arange(7), y5, color='C0', width=0.5)
plt.hist(Yd_samples, bins=np.arange(8), density=True, rwidth=0.5, color='C1')
plt.legend(['calculated', 'simulated (N={})'.format(num_d)])
plt.title('PMF of "Duriel - Base" drops\nwith NoDrop={:.3f}'.format(z))
plt.xlabel('Number of "Duriel - Base" drops')
plt.ylabel('Probability or normalized occurences')
```

    <IPython.core.display.Javascript object>

    Text(0, 0.5, 'Probability or normalized occurences')

From the above, we can see that the results calculated through the Markov chain model are almost exactly matching the simulated results.

The complete transition matrix is given below.

```python
import sys
print('Transition matrix for z={:.3f}'.format(z))
np.savetxt(sys.stdout, Td, fmt='%.3e', delimiter=',')
```

    Transition matrix for z=0.300
    1.458e-04,2.381e-03,3.333e-01,1.667e-02,0.000e+00,0.000e+00,6.483e-02,0.000e+00,0.000e+00,0.000e+00,1.513e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,2.118e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,2.196e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,1.458e-04,0.000e+00,2.381e-03,3.333e-01,0.000e+00,1.667e-02,0.000e+00,0.000e+00,0.000e+00,6.483e-02,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.513e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,4.314e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,1.458e-04,0.000e+00,2.381e-03,3.333e-01,0.000e+00,1.667e-02,0.000e+00,0.000e+00,0.000e+00,6.483e-02,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.513e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,4.314e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,2.381e-03,3.333e-01,0.000e+00,0.000e+00,1.667e-02,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.483e-02,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,5.826e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,2.381e-03,3.333e-01,0.000e+00,0.000e+00,1.667e-02,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.483e-02,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,5.826e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,2.381e-03,3.333e-01,0.000e+00,0.000e+00,1.667e-02,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.483e-02,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,5.826e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,2.381e-03,3.333e-01,0.000e+00,0.000e+00,0.000e+00,1.667e-02,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.475e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,2.381e-03,3.333e-01,0.000e+00,0.000e+00,0.000e+00,1.667e-02,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.475e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,2.381e-03,3.333e-01,0.000e+00,0.000e+00,0.000e+00,1.667e-02,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.475e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,2.381e-03,3.333e-01,0.000e+00,0.000e+00,0.000e+00,1.667e-02,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.475e-01,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,0.000e+00,2.381e-03,3.333e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.641e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,0.000e+00,2.381e-03,3.333e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.641e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,0.000e+00,2.381e-03,3.333e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.641e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,0.000e+00,2.381e-03,3.333e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.641e-01,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,0.000e+00,2.381e-03,3.333e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.641e-01,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.665e-01,3.333e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.665e-01,3.333e-01,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.665e-01,3.333e-01,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.665e-01,3.333e-01,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.665e-01,3.333e-01,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.458e-04,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,6.665e-01,3.333e-01
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.000e+00,0.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.000e+00,0.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.000e+00,0.000e+00
    0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,0.000e+00,1.000e+00

```python

```