Source code for aleatory.processes.analytical.brownian_meander

"""
Brownian Meander
"""

import numpy as np

from aleatory.processes import BrownianBridge
from aleatory.processes.base_analytical import SPAnalytical
from aleatory.utils.utils import (
    check_positive_integer,
)
from aleatory.utils.plotters import draw_paths, draw_paths_with_end_point


[docs]class BrownianMeander(SPAnalytical): r""" Brownian Meander ================ .. image:: ../_static/brownian_meander_drawn.png .. image:: ../_static/tied_brownian_meander_drawn.png Notes ----- Let :math:`\{W_t : t \geq 0\}` be a standard Brownian motion and .. math:: \tau = \sup\{ t \in [0,1] : W_t =0\}, i.e. the last time before t = 1 when :math:`W_t` visits zero. Then the Brownian Meander is defined as follows .. math:: W_t^{+} = \frac{1}{\sqrt{1-\tau}} |W(\tau + t (1-\tau))|, \ \ \ \ t\in (0,1]. Constructor, Methods, and Attributes ------------------------------------ """
[docs] def __init__(self, T=1.0, fixed_end=False, end=None, rng=None): """ :param float T: the right hand endpoint of the time interval :math:`[0,T]` for the process :param bool fixed_end: flag to indicate if the process has a fixed end point. Defaults to `False` :param float end: end point for the Meander, in the case of `fixed_end` equal `True` :param numpy.random.Generator rng: a custom random number generator """ super().__init__(T=T, rng=rng) self.fixed_end = fixed_end self.end = end if self.fixed_end and self.end: self.name = ( f"Tied Brownian Meander ending at {self.end:.2f} on [0, {self.T}]" ) else: self.name = f"Brownian Meander on [0, {self.T}]" self._BrownianBridge = BrownianBridge(T=self.T) self.n = None self.times = None
def __str__(self): return f"Brownian Meander" def __repr__(self): return f"BrownianMeander" @property def end(self): return self._end @end.setter def end(self, value): if value is None: self.exponential = self.rng.exponential(1) self._end = np.sqrt(2.0 * self.T * self.rng.exponential(1)) elif value < 0: raise ValueError("end point cannot be negative") else: self._end = True self._end = value def _sample_brownian_meander(self, n): """ Generate a realization from Brownian Meander """ check_positive_integer(n) self.n = n self.times = np.linspace(0, self.T, n) bridges = self._BrownianBridge.simulate(n=n, N=3) end = ( self.end if self.fixed_end else np.sqrt(2.0 * self.T * self.rng.exponential(1)) ) times_scaled = self.times / self.T meander = np.sqrt( (end * times_scaled + bridges[0]) ** 2 + bridges[1] ** 2 + bridges[2] ** 2 ) return meander def _sample_brownian_meander_at(self, times): self.times = times bridges = self._BrownianBridge.sample_at(times) end = np.sqrt(2.0 * self.T * self.rng.exponential(1)) times_scaled = self.times / self.T meander = np.sqrt( (end * times_scaled + bridges[0]) ** 2 + bridges[1] ** 2 + bridges[2] ** 2 ) return meander def sample(self, n): return self._sample_brownian_meander(n) def sample_at(self, times): return self._sample_brownian_meander_at(times) def _draw_paths(self, n, N, title=None, suptitle=None, **fig_kw): self.simulate(n, N) chart_suptitle = suptitle if suptitle is not None else self.name fig_kw["envelope"] = False if self.fixed_end: if "marginal" in fig_kw: fig_kw.pop("marginal") if "orientation" in fig_kw: fig_kw.pop("orientation") fig = draw_paths_with_end_point( times=self.times, paths=self.paths, title=title, suptitle=chart_suptitle, **fig_kw, ) else: fig = draw_paths( times=self.times, paths=self.paths, N=N, expectations=None, KDE=False, title=title, suptitle=chart_suptitle, marginal=True, **fig_kw, ) return fig def draw(self, n, N, 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 n: number of steps in each path :param N: number of paths to simulate :param title: string to customise plot title :return: """ return self._draw_paths(n, N, title=title, suptitle=suptitle, **fig_kw)
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 p, cm in [ # (BrownianMeander(), "twilight"), # (BrownianMeander(fixed_end=True, end=3.0), "RdPu"), # (BrownianMeander(T=2.0, fixed_end=True), "viridis"), # (BrownianMeander(T=10.0), "Accent"), # ]: # # p.draw(n=200, N=100, figsize=(12, 7), style=qs, colormap=cm, envelope=False) # p = BrownianMeander() p.draw(n=200, N=100, figsize=(12, 7), style=qs, envelope=False) # p.draw(n=200, N=100, T=3.0, figsize=(12, 7), style=qs, envelope=False) # p.plot_mean_variance(times=np.linspace(0, 1, 100)) # p.plot(n=500, N=5, figsize=(12, 7), style=qs)