"""
Brownian Bridge
"""
import numpy as np
from scipy.stats import norm
from aleatory.processes.analytical.brownian_motion import BrownianMotion
from aleatory.utils.utils import check_numeric, draw_paths_with_end_point
from aleatory.utils.utils import check_positive_integer, get_times
[docs]class BrownianBridge(BrownianMotion):
r"""
Brownian Bridge
.. image:: _static/brownian_bridge_drawn.png
A Brownian bridge is a continuous-time stochastic process :math:`\{B_t : t \geq 0\}`
whose probability distribution is the conditional probability distribution of a
standard Wiener process (Brownian Motion) :math:`\{W_t : t \geq 0\}`
subject to the condition that :math:`W(T) = 0`, so that the process is pinned to
the same value at both :math:`t = 0` and :math:`t = T`. More specifically,
.. math::
B_t = (W_t | W_T = 0), \ \ \ \ t\in (0,T].
More generally, a Brownian Bridge is subject to the conditions :math:`W(0) = a` and :math:`W(T) = b`.
Parameters
:param float initial: initial condition
:param float end: end condition
: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, initial=0.0, end=0.0, T=1.0, rng=None):
"""
Parameters
:param float initial: initial condition
:param float end: end condition
: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
"""
super().__init__(T=T, rng=rng)
self.initial = initial
self.end = end
self._brownian_motion = BrownianMotion(T=T, rng=rng)
self.name = "Brownian Bridge"
self.n = None
self.times = None
def __str__(self):
return "Brownian Bridge starting at {a} and ending at {b} on [0, {T}].".format(
T=str(self.T), a=str(self.initial), b=str(self.end))
def __repr__(self):
return "BrownianBridge(initial={a}, end={b}, T={T})".format(
T=str(self.T), a=str(self.initial), b=str(self.end))
@property
def initial(self):
return self._initial
@property
def end(self):
return self._end
@initial.setter
def initial(self, value):
check_numeric(value, "Initial Point")
self._initial = value
@end.setter
def end(self, value):
check_numeric(value, "End Point")
self._end = value
def _sample_brownian_bridge(self, n):
"""Generate a realization of a Brownian Bridge."""
check_positive_integer(n)
self.n = n
self.times = get_times(self.T, n)
brownian_path = self._brownian_motion.sample(n)
a = self.initial
b = self.end
scaled_times = self.times / self.T
bridge_path = a * (1.0 - scaled_times) + b * scaled_times + brownian_path - scaled_times * brownian_path[-1]
return bridge_path
def _sample_brownian_bridge_at(self, times):
self.times = times
brownian_path = self._brownian_motion.sample_at(times)
a = self.initial
b = self.end
scaled_times = self.times / self.T
bridge_path = a * (1.0 - scaled_times) + b * scaled_times + (brownian_path - scaled_times * brownian_path[-1])
return bridge_path
[docs] def sample(self, n):
return self._sample_brownian_bridge(n)
[docs] def sample_at(self, times):
return self._sample_brownian_bridge_at(times)
def _process_expectation(self, times=None):
if times is None:
times = self.times
scaled_times = times / self.T
return self.initial * (1.0 - scaled_times) + self.end * scaled_times
def _process_variance(self, times=None):
if times is None:
times = self.times
scaled_times = times / self.T
return (self.T - times) * scaled_times
def get_marginal(self, t):
scaled_time = t / self.T
mean = self.initial * (1.0 - scaled_time) + self.end * scaled_time
var = (self.T - t) * scaled_time
marginal = norm(loc=mean, scale=np.sqrt(var))
return marginal
def _draw_paths(self, n, N, envelope=False, type=None, title=None, **fig_kw):
self.simulate(n, N)
expectations = self._process_expectation()
if envelope:
marginals = [self.get_marginal(t) for t in self.times[1:-1]]
upper = [self.initial] + [m.ppf(0.005) for m in marginals] + [self.end]
lower = [self.initial] + [m.ppf(0.995) for m in marginals] + [self.end]
else:
upper = None
lower = None
chart_title = title if title else self.name
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,
expectations=expectations, title=chart_title,
envelope=envelope,
lower=lower, upper=upper,
**fig_kw)
return fig
[docs] def draw(self, n, N, envelope=False, title=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
- 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 envelope: bool, default: False
:param title: string to customise plot title
:return:
"""
return self._draw_paths(n, N, envelope=envelope, title=title, **fig_kw)