Blob Blame History Raw
# -*- coding: utf-8 -*-
from __future__ import division
import numpy as np


def alias_method(idx, probs, nsyn):
    """
    Alias method for drawing random numbers from a discrete probability
    distribution. See http://www.keithschwarz.com/darts-dice-coins/
    
    Parameters
    ----------
    idx : np.ndarray
        compartment indices as array of ints
    probs : np.ndarray
        compartment areas as array of floats
    nsyn : int
        number of randomized compartment indices

    Returns
    -------
    out : np.ndarray
        integer array of randomly drawn compartment indices

    """
    try:
        assert idx.size == probs.size
    except AssertionError as ae:
        raise ae('length of idx and probs arrays must be equal')

    # Construct the table.
    J, q = alias_setup(probs)
     
    #output array
    spc = np.zeros(nsyn, dtype=int)
    
    #prefetch random numbers, alias_draw needs nsyn x 2 numbers
    rands = np.random.rand(nsyn, 2)
    
    K = J.size 
    # Generate variates using alias draw method
    for nn in range(nsyn):
        kk = np.floor(rands[nn, 0]*K).astype(int)
        if rands[nn, 1] < q[kk]:
            spc[nn] = idx[kk]
        else:
            spc[nn] = idx[J[kk]]
        
    return spc


def alias_setup(probs):
    """Set up function for alias method.
    See http://www.keithschwarz.com/darts-dice-coins/
    
    Parameters
    ----------
    probs : np.ndarray
        float array of compartment areas

    Returns
    -------
    J : np.ndarray
        array of ints
    q : np.ndarray
        array of floats

    """        
    K = probs.size
    q = probs*K
    J = np.zeros(K, dtype=int)

    # Sort the data into the outcomes with probabilities
    # that are larger and smaller than 1/K.
    smaller = np.zeros(K, dtype=int)
    larger = np.zeros(K, dtype=int)
    s_i = 0
    l_i = 0
    for kk in range(K):
        if q[kk] < 1:
            smaller[s_i] = kk
            s_i += 1
        else:
            larger[l_i] = kk
            l_i += 1

    s_i -= 1
    l_i -= 1
    
    # Loop though and create little binary mixtures that
    # appropriately allocate the larger outcomes over the
    # overall uniform mixture.
    while s_i >= 0 and l_i >= 0:
        small = smaller[s_i]
        large = larger[l_i]
        
        J[small] = large
        q[large] = q[large] + q[small] - 1

        s_i -= 1
    
        if q[large] < 1:
            s_i += 1
            l_i -= 1
            smaller[s_i] = large
 
    return J, q