Source code for pyrenew.process.differencedprocess

# numpydoc ignore=GL08

from __future__ import annotations

from typing import Any

import jax.numpy as jnp
from jax.typing import ArrayLike

from pyrenew.math import integrate_discrete
from pyrenew.metaclass import RandomVariable


[docs] class DifferencedProcess(RandomVariable): """ Class for differenced stochastic process X(t), constructed by placing a fundamental stochastic process on the :math:`n^{th}` differences (rates of change). See https://otexts.com/fpp3/stationarity.html for a discussion of differencing in the context of discrete timeseries data. Notes ----- The order of differencing is the discrete analogue of the order of a derivative in single variable calculus. A first difference (derivative) represents a rate of change. A second difference (derivative) represents the rate of change of that rate of change, et cetera. """ def __init__( self, fundamental_process: RandomVariable, differencing_order: int, **kwargs, ) -> None: """ Default constructor Parameters ---------- fundamental_process : RandomVariable Stochastic process for the differences. Must accept an `n` argument specifying the number of samples to draw. differencing_order : int How many fold-differencing the the process represents. Must be an integer greater than or equal to 1. 1 represents a process on the first differences (the rate of change), 2 a process on the 2nd differences (rate of change of the rate of change), et cetera. Returns ------- None """ self.assert_valid_differencing_order(differencing_order) self.fundamental_process = fundamental_process self.differencing_order = differencing_order super().__init__(**kwargs)
[docs] @staticmethod def assert_valid_differencing_order(differencing_order: Any): """ To be valid, a differencing order must be an integer and must be strictly positive. This function raises a value error if its argument is not a valid differencing order. Parameters ---------- differencing_order : Any Potential differencing order to validate. Returns ------- None or raises a :class:`ValueError` """ if not isinstance(differencing_order, int): raise ValueError( "differencing_order must be an integer. " f"got type {type(differencing_order)} " f"and value {differencing_order}" ) if not differencing_order >= 1: raise ValueError( "differencing_order must be an integer " "greater than or equal to 1. Got " f"{differencing_order}" )
[docs] def validate(self): """ Empty validation method. """ pass
[docs] def sample( self, init_vals: ArrayLike, n: int, *args, fundamental_process_init_vals: ArrayLike = None, **kwargs, ) -> jnp.ndarray: """ Sample from the process Parameters ---------- init_vals : ArrayLike initial values for the :math:`0^{th}` through :math:`(n-1)^{st}` differences, passed as the :code:`init_diff_vals` argument to :func:`~pyrenew.math.integrate_discrete()` n : int Number of values to sample. Will sample :code:`n - differencing_order` values from :meth:`self.fundamental_process` to ensure that the de-differenced output is of length :code`n`. *args : Additional positional arguments passed to :meth:`self.fundamental_process.sample` fundamental_process_init_vals : ArrayLike, optional Initial values for the fundamental process. Passed as the :code:`init_vals` keyword argument to :meth:`self.fundamental_process.sample`. Default :obj:`None`. **kwargs : dict, optional Keyword arguments passed to :meth:`self.fundamental_process.sample()`. Returns ------- jnp.ndarray An array representing the undifferenced timeseries """ if not isinstance(n, int): raise ValueError("n must be an integer. " f"Got {type(n)}") if n < 1: raise ValueError("n must be positive. " f"Got {n}") init_vals = jnp.atleast_1d(init_vals) n_inits = init_vals.shape[0] if not n_inits == self.differencing_order: raise ValueError( "Must have exactly as many " "initial difference values as " "the differencing order, given " "in the sequence :math:`X(t=0), X^1(t=1),` " "et cetera. " f"Got {n_inits} values " "for a process of order " f"{self.differencing_order}." ) n_diffs = n - self.differencing_order if n_diffs > 0: diff_samp = self.fundamental_process.sample( *args, n=n_diffs, init_vals=fundamental_process_init_vals, **kwargs, ) diffs = diff_samp else: diffs = jnp.array([]) integrated_ts = integrate_discrete(init_vals, diffs)[:n] return integrated_ts