Use stochastic fictitious play
==============================

One of the learning algorithms implemented in :code:`Nashpy` is called
:ref:`stochastic-fictitious-play`, this is implemented as a method on the :code:`Game`
class::

    >>> import nashpy as nash
    >>> import numpy as np
    >>> A = np.array([[3, 1], [0, 2]])
    >>> B = np.array([[2, 0], [1, 3]])
    >>> game = nash.Game(A, B)

The :code:`stochastic_fictitious_play` method returns a generator of a given collection of
learning steps, comprising of the play counts and the mixed strategy of each player::

    >>> np.random.seed(0)
    >>> iterations = 500
    >>> play_counts_and_distributions = game.stochastic_fictitious_play(iterations=iterations)
    >>> for play_counts, distributions in play_counts_and_distributions:
    ...     row_play_counts, column_play_counts = play_counts
    ...     row_distributions, column_distributions = distributions
    ...     print(row_play_counts, column_play_counts, row_distributions, column_distributions)
    [0 0] [0 0] None None
    [1. 0.] [0. 1.] [9.99953841e-01 4.61594628e-05] [0.501447 0.498553]
    ...
    [498.   1.] [497.   2.] [1.00000000e+00 1.07557011e-13] [9.99999998e-01 2.32299935e-09]
    [499.   1.] [498.   2.] [1.00000000e+00 1.17304491e-13] [9.99999998e-01 2.18403537e-09]

Note that this process is stochastic::

    >>> np.random.seed(1)
    >>> play_counts_and_distributions = game.stochastic_fictitious_play(iterations=iterations)
    >>> for play_counts, distributions in play_counts_and_distributions:
    ...     row_play_counts, column_play_counts = play_counts  
    ...     row_distributions, column_distributions = distributions  
    ...     print(row_play_counts, column_play_counts)
    [0 0] [0 0]
    [1. 0.] [1. 0.]
    ...
    [499.   0.] [499.   0.]
    [500.   0.] [500.   0.]

It is also possible to pass a :code:`play_counts` variable to give a starting
point for the algorithm::

    >>> np.random.seed(0)
    >>> play_counts = (np.array([0., 500.]), np.array([0., 500.]))
    >>> play_counts_and_distributions = game.stochastic_fictitious_play(iterations=iterations, play_counts=play_counts)
    >>> for play_counts, distributions in play_counts_and_distributions:
    ...     row_play_counts, column_play_counts = play_counts  
    ...     row_distributions, column_distributions = distributions  
    ...     print(row_play_counts, column_play_counts)
    ...
    [  0. 500.] [  0. 500.]
    [  0. 501.] [  0. 501.]
    ...
    [  0. 999.] [  0. 999.]
    [   0. 1000.] [   0. 1000.]

A value of :code:`etha` and :code:`epsilon_bar` can be passed. 
See the :ref:`stochastic-fictitious-play` reference section for more information. The default values for etha and epsilon bar are 
:math:`10^-1` and :math:`10^-2` respectively::

    >>> np.random.seed(0)
    >>> etha = 10**-2
    >>> epsilon_bar = 10**-3
    >>> play_counts_and_distributions = game.stochastic_fictitious_play(iterations=iterations, etha=etha, epsilon_bar=epsilon_bar)
    >>> for play_counts, distributions in play_counts_and_distributions:
    ...     row_play_counts, column_play_counts = play_counts  
    ...     row_distributions, column_distributions = distributions  
    ...     print(row_play_counts, column_play_counts)
    ...
    [0 0] [0 0]
    [1. 0.] [0. 1.]
    ...
    [498.   1.] [497.   2.]
    [499.   1.] [498.   2.]

