Source code for stochastic.processes.continuous.multifractional_brownian_motion

"""Multifractional Brownian motion."""
import inspect

import numpy as np
from scipy.special import gamma

from stochastic.processes.base import BaseTimeProcess


[docs]class MultifractionalBrownianMotion(BaseTimeProcess): r"""Multifractional Brownian motion process. .. image:: _static/multifractional_brownian_motion.png :scale: 50% A multifractional Brownian motion generalizes a fractional Brownian motion with a Hurst parameter which is a function of time, :math:`h(t)`. If the Hurst is constant, the process is a fractional Brownian motion. If Hurst is constant equal to 0.5, the process is a Brownian motion. Approximate method originally proposed for fBm in * Rambaldi, Sandro, and Ombretta Pinazza. "An accurate fractional Brownian motion generator." Physica A: Statistical Mechanics and its Applications 208, no. 1 (1994): 21-30. Adapted to approximate mBm in * Muniandy, S. V., and S. C. Lim. "Modeling of locally self-similar processes using multifractional Brownian motion of Riemann-Liouville type." Physical Review E 63, no. 4 (2001): 046104. :param float hurst: a callable with one argument :math:`h(t)` such that :math:`h(t') \in (0, 1) \forall t' \in [0, t]`. Default is :math:`h(t) = 0.5`. :param float t: the right hand endpoint of the time interval :math:`[0,t]` for the process :param numpy.random.Generator rng: a custom random number generator """ def __init__(self, hurst=None, t=1, rng=None): super().__init__(t=t, rng=rng) self.hurst = hurst if hurst is not None else lambda x: 0.5 self._n = None def __str__(self): return ( "Multifractional Brownian motion with Hurst function " + "{h} on [0, {t}].".format(t=str(self.t), h=self.hurst.__name__) ) def __repr__(self): return "FractionalBrownianMotion(hurst={h}, t={t})".format( t=str(self.t), h=self.hurst.__name__ ) @property def hurst(self): """Hurst function.""" return self._hurst @hurst.setter def hurst(self, value): try: num_args = len(inspect.signature(value).parameters) except Exception: raise ValueError("Hurst parameter must be a function of one argument.") if not callable(value) or num_args != 1: raise ValueError("Hurst parameter must be a function of one argument.") self._hurst = value self._changed = True def _check_hurst(self, value): self._hs = [value(t) for t in self.times(self._n)] for h in self._hs: if h <= 0 or h >= 1: raise ValueError("Hurst range must be on interval (0, 1).") def _sample_multifractional_brownian_motion(self, n): """Generate Riemann-Liouville mBm.""" gn = self.rng.normal(0.0, 1.0, n) self._set_times(n) self._dt = 1.0 * self.t / self._n self._check_hurst(self.hurst) mbm = [0] coefs = [(g / np.sqrt(self._dt)) * self._dt for g in gn] for k in range(1, self._n + 1): weights = [self._w(t, self._hs[k]) for t in self._times[1 : k + 1]] seq = [coefs[i - 1] * weights[k - i] for i in range(1, k + 1)] mbm.append(sum(seq)) return np.array(mbm)
[docs] def sample(self, n): """Generate a realization. :param int n: the number of increments to generate """ return self._sample_multifractional_brownian_motion(n)
def _w(self, t, hurst): """Get the Riemann-Liouville method weight for time t.""" w = ( 1.0 / gamma(hurst + 0.5) * np.sqrt( (t ** (2 * hurst) - (t - self._dt) ** (2 * hurst)) / (2 * hurst * self._dt) ) ) return w