# Source code for Arms.Poisson

# -*- coding: utf-8 -*-
""" Poisson distributed arm, possibly truncated.

Example of creating an arm:

>>> import random; import numpy as np
>>> random.seed(0); np.random.seed(0)
>>> Poisson5 = Poisson(5, trunc=10)
>>> Poisson5
P(5, 10)
>>> Poisson5.mean  # doctest: +ELLIPSIS
4.9778...

Examples of sampling from an arm:

>>> Poisson5.draw()  # doctest: +ELLIPSIS
9
>>> Poisson5.draw_nparray(20)  # doctest: +ELLIPSIS
array([ 5,  6,  5,  5,  8,  4,  5,  4,  3,  3,  7,  3,  3,  4,  5,  2,  1,
7,  7, 10])
"""
from __future__ import division, print_function  # Python 2 compatibility

__author__ = "Olivier Cappé, Aurélien Garivier, Lilian Besson"
__version__ = "0.6"

from math import isinf, exp
import numpy as np
from scipy.stats import poisson

# Local imports
try:
from .Arm import Arm
from .kullback import klPoisson
except ImportError:
from Arm import Arm
from kullback import klPoisson

[docs]class Poisson(Arm):
""" Poisson distributed arm, possibly truncated.

- Default is to not truncate.
- Warning: the draw() method is QUITE inefficient! (15 seconds for 200000 draws, 62 µs for 1).
"""

[docs]    def __init__(self, p, trunc=1):
"""New arm."""
assert p >= 0, "Error, the parameter 'p' for Poisson arm has to be >= 0."  # DEBUG
self.p = p  #: Parameter p for Poisson arm
self.trunc = trunc  #: Max value of rewards
if isinf(trunc):
self.mean = p  #: Mean for this Poisson arm
else:  # Warning: this is very slow if self.trunc is large!
q = exp(-p)
sq = q
self.mean = 0
for k in range(1, self.trunc):
q *= p / k
self.mean += k * q
sq += q
self.mean += self.trunc * (1 - sq)

# --- Random samples

[docs]    def draw(self, t=None):
""" Draw one random sample. The parameter t is ignored in this Arm."""
return min(poisson.rvs(self.p), self.trunc)

[docs]    def draw_nparray(self, shape=(1,)):
""" Draw a numpy array of random samples, of a certain shape."""
return np.minimum(poisson.rvs(self.p, size=shape), self.trunc)

[docs]    def set_mean_param(self, p):
self.p = p
if isinf(self.trunc):
self.mean = p  #: Mean for this Poisson arm
else:  # Warning: this is very slow if self.trunc is large!
q = exp(-p)
sq = q
self.mean = 0
for k in range(1, self.trunc):
q *= p / k
self.mean += k * q
sq += q
self.mean += self.trunc * (1 - sq)

# --- Printing

[docs]    def __str__(self):
return "Poisson"

[docs]    def __repr__(self):
if isinf(self.trunc):
return "P({:.3g})".format(self.p)
else:
return "P({:.3g}, {:.3g})".format(self.p, self.trunc)

# --- Lower bound

[docs]    @staticmethod
def kl(x, y):
""" The kl(x, y) to use for this arm."""
return klPoisson(x, y)

[docs]    @staticmethod
def oneLR(mumax, mu):
""" One term of the Lai & Robbins lower bound for Poisson arms: (mumax - mu) / KL(mu, mumax). """
return (mumax - mu) / klPoisson(mu, mumax)

[docs]class UnboundedPoisson(Poisson):
""" Poisson distributed arm, not truncated, ie. trunc =  oo."""

[docs]    def __init__(self, p):
super(UnboundedPoisson, self).__init__(p, trunc=float('+inf'))

# Only export and expose the class defined here
__all__ = ["Poisson", "UnboundedPoisson"]

# --- Debugging

if __name__ == "__main__":
# Code for debugging purposes.
from doctest import testmod
print("\nTesting automatically all the docstring written in each functions of this module :")
testmod(verbose=True)