Source code for aleatory.processes.jump.random_walk

"""
Random Walk
"""

from aleatory.processes.base_analytical import SPAnalytical
from abc import ABC
from aleatory.utils.utils import (
    check_positive_integer,
    get_times,
)
from aleatory.utils.plotters import plot_paths_random_walk
import numpy as np


class SimpleRandomWalk(SPAnalytical, ABC):

    def __init__(self, p=0.5, rng=None):
        super().__init__(rng=rng)
        self.step_sizes = (1.0, -1.0)
        self.p = p
        self.q = 1.0 - p
        self.probs = (self.p, self.q)
        self.paths = None
        self.n = None
        self.N = None
        if p == 0.5:
            self.name = "Simple Random Walk"
        else:
            self.name = f"Simple Random Walk with p={self.p}"
        self.times = None

    def __str__(self):
        return f"General Random Walk with step sizes {self.step_sizes} and probabilities {self.probs}"

    def __repr__(self):
        return f"GeneralRandomWalk(step_sizes={self.step_sizes}, probabilities={self.probs})"

    def _sample_random_walk_steps(self, n):
        """Generate a sample of a general random walk increments"""
        check_positive_integer(n)
        steps = self.rng.choice(self.step_sizes, p=self.probs, size=n)
        return steps

    def _sample_random_walk(self, n):
        """Generate a sample from a general random walk"""
        self.T = n
        self.n = n
        sample = np.array([0] + list(np.cumsum(self._sample_random_walk_steps(n))))
        return sample

    def sample(self, n):
        sample = self._sample_random_walk(n)
        self.times = get_times(self.T, self.n + 1)
        return sample

    def _process_expectation(self, times=None):
        if times is None:
            times = self.times
        return times * (self.p - self.q)

    def _process_variance(self, times=None):
        if times is None:
            times = self.times
        return times * 4.0 * self.p * self.q

    def _process_covariance(self, times=None):
        if times is None:
            times = self.times
        t1 = times[:, None]
        t2 = times[None, :]
        return 4.0 * self.p * self.q * np.minimum(t1, t2)

    def marginal_expectation(self, times=None):
        expectations = self._process_expectation(times=times)
        return expectations

    def marginal_variance(self, times):
        variances = self._process_variance(times=times)
        return variances

    def plot(self, *args, n, N, title=None, suptitle=None, **fig_kw):
        """
        Simulates and plots paths/trajectories from the instanced stochastic process.
        Simple plot of times versus process values as lines and/or markers.

        :parameter int n: number of steps in each path
        :parameter int N: number of paths to simulate
        :parameter str title: string to customise plot title
        :return:

        """
        self.simulate(n, N, T=n)
        chart_suptitle = suptitle if suptitle is not None else self.name
        figure = plot_paths_random_walk(
            *args,
            times=self.times,
            paths=self.paths,
            title=title,
            suptitle=chart_suptitle,
            **fig_kw,
        )
        return figure

    def draw(
        self,
        n,
        N,
        marginal=True,
        envelope=False,
        mode="steps",
        title=None,
        suptitle=None,
        **fig_kw,
    ):
        """
        Simulates and plots paths/trajectories from the instanced stochastic process.

        Produces different kind of visualisation illustrating the following elements:

        - times versus process values as lines
        - the expectation of the process across time
        - histogram showing the empirical marginal distribution :math:`X_T` (optional when ``marginal = True``)
        - probability density function of the marginal distribution :math:`X_T` (optional when ``marginal = True``)
        - envelope of confidence intervals across time (optional when ``envelope = True``)

        :param int n: number of steps in each path
        :param int N: number of paths to simulate
        :param bool marginal:  defaults to True
        :param bool envelope:   defaults to False
        :param str mode: defaults to 'steps'
        :param str title:  to be used to customise plot title. If not passed, the title defaults to the name of the process.
        :return:
        """

        return self._draw_3sigmastyle(
            n=n,
            N=N,
            marginal=marginal,
            envelope=envelope,
            title=title,
            suptitle=suptitle,
            mode=mode,
            **fig_kw,
        )


[docs]class RandomWalk(SimpleRandomWalk): r""" Simple Random Walk ================== .. image:: ../_static/simple_random_walk_draw.png Notes ----- Let :math:`\{Z_i, i \geq 1\}` be a sequence of real-valued independent an identically distributed (i.i.d.) random variables defined on a probability space :math:`(\Omega, \mathcal{F}, \mathbb{P})`, such that .. math:: \mathbb{P}(Z_1 = 1) = p, and .. math:: \mathbb{P}(Z_1 = -1) = 1-p, Then, the stochastic process :math:`\{X_n , n\geq 0\}`, defined as :math:`X_0 =0`, and .. math:: X_n = \sum_{i=1}^n Z_i, \qquad \forall n\geq 1, is called a Simple Random Walk. Constructor, Methods, and Attributes ------------------------------------ """
[docs] def __init__(self, rng=None): """ :parameter numpy.random.Generator rng: a custom random number generator """ super().__init__(p=0.5, rng=rng)
if __name__ == "__main__": import matplotlib.pyplot as plt qs = "https://raw.githubusercontent.com/quantgirluk/matplotlib-stylesheets/main/quant-pastel-light.mplstyle" plt.style.use(qs) for prob in [0.25, 0.5, 0.75]: p = SimpleRandomWalk(p=prob) p.plot( n=10, N=10, figsize=(12, 7), mode="steps+points", style=qs, ) p.draw( n=100, N=200, figsize=(12, 7), style=qs, )