Source code for tigercontrol.problems.time_series.arma

"""
Autoregressive moving-average
"""

import jax
import jax.numpy as np
import jax.random as random

import tigercontrol
from tigercontrol.utils import generate_key
from tigercontrol.problems.time_series import TimeSeriesProblem


[docs]class ARMA(TimeSeriesProblem): """ Description: Simulates an autoregressive moving-average time-series. """ compatibles = set(['ARMA-v0', 'TimeSeries'])
[docs] def __init__(self): self.initialized = False self.has_regressors = False
def initialize(self, p=3, q=3, noise_list = None, c=None, noise_magnitude=0.1, noise_distribution = 'normal'): """ Description: Randomly initialize the hidden dynamics of the system. Args: p (int/numpy.ndarray): Autoregressive dynamics. If type int then randomly initializes a Gaussian length-p vector with L1-norm bounded by 1.0. If p is a 1-dimensional numpy.ndarray then uses it as dynamics vector. q (int/numpy.ndarray): Moving-average dynamics. If type int then randomly initializes a Gaussian length-q vector (no bound on norm). If p is a 1-dimensional numpy.ndarray then uses it as dynamics vector. c (float): Default value follows a normal distribution. The ARMA dynamics follows the equation x_t = c + AR-part + MA-part + noise, and thus tends to be centered around mean c. Returns: The first value in the time-series """ self.initialized = True self.T = 0 if type(p) == int: phi = random.normal(generate_key(), shape=(p,)) self.phi = 0.99 * phi / np.linalg.norm(phi, ord=1) else: self.phi = p if type(q) == int: self.psi = random.normal(generate_key(), shape=(q,)) else: self.psi = q if(type(self.phi) is list): self.p = self.phi[0].shape[0] else: self.p = self.phi.shape[0] if(type(self.psi) is list): self.q = self.psi[0].shape[0] else: self.q = self.psi.shape[0] self.noise_magnitude, self.noise_distribution = noise_magnitude, noise_distribution self.c = random.normal(generate_key()) if c == None else c self.x = random.normal(generate_key(), shape=(self.p,)) self.noise_list = None if(noise_list is not None): self.noise_list = noise_list self.noise = np.array(noise_list[0:self.q]) elif(noise_distribution == 'normal'): self.noise = self.noise_magnitude * random.normal(generate_key(), shape=(self.q,)) elif(noise_distribution == 'unif'): self.noise = self.noise_magnitude * random.uniform(generate_key(), shape=(self.q,), minval=-1., maxval=1.) def _step(x, noise, eps): if(type(self.phi) is list): x_ar = np.dot(x, self.phi[self.T]) else: x_ar = np.dot(x, self.phi) if(type(self.psi) is list): x_ma = np.dot(noise, self.psi[self.T]) else: x_ma = np.dot(noise, self.psi) x_new = self.c + x_ar + x_ma + eps next_x = np.roll(x, 1) next_noise = np.roll(noise, 1) next_x = jax.ops.index_update(next_x, 0, x_new) # equivalent to self.x[0] = x_new next_noise = jax.ops.index_update(next_noise, 0, eps) # equivalent to self.noise[0] = eps return (next_x, next_noise, x_new) self._step = jax.jit(_step) return self.x[0] def step(self): """ Description: Moves the system dynamics one time-step forward. Args: None Returns: The next value in the ARMA time-series. """ assert self.initialized self.T += 1 if(self.noise_list is not None): self.x, self.noise, x_new = self._step(self.x, self.noise, self.noise_list[self.q + self.T - 1]) else: if(self.noise_distribution == 'normal'): self.x, self.noise, x_new = self._step(self.x, self.noise, \ self.noise_magnitude * random.normal(generate_key())) elif(self.noise_distribution == 'unif'): self.x, self.noise, x_new = self._step(self.x, self.noise, \ self.noise_magnitude * random.uniform(generate_key(), minval=-1., maxval=1.)) return x_new def hidden(self): """ Description: Return the hidden state of the system. Args: None Returns: (x, eps): The hidden state consisting of the last p x-values and the last q noise-values. """ assert self.initialized return (self.x, self.noise) def help(self): """ Description: Prints information about this class and its methods. Args: None Returns: None """ print(ARMA_help) def __str__(self): return "<ARMA Problem>"
# string to print when calling help() method ARMA_help = """ -------------------- *** -------------------- Id: ARMA-v0 Description: Simulates an autoregressive moving-average time-series. Methods: initialize(p, q, c=None) Description: Randomly initialize the hidden dynamics of the system. Args: p (int/numpy.ndarray): Autoregressive dynamics. If type int then randomly initializes a Gaussian length-p vector with L1-norm bounded by 1.0. If p is a 1-dimensional numpy.ndarray then uses it as dynamics vector. q (int/numpy.ndarray): Moving-average dynamics. If type int then randomly initializes a Gaussian length-q vector (no bound on norm). If p is a 1-dimensional numpy.ndarray then uses it as dynamics vector. c (float): Default value follows a normal distribution. The ARMA dynamics follows the equation x_t = c + AR-part + MA-part + noise, and thus tends to be centered around mean c. Returns: The first value in the time-series step() Description: Moves the system dynamics one time-step forward. Args: None Returns: The next value in the ARMA time-series. hidden() Description: Return the hidden state of the system. Args: None Returns: (x, eps): The hidden state consisting of the last p x-values and the last q noise-values. help() Description: Prints information about this class and its methods. Args: None Returns: None -------------------- *** -------------------- """