Source code for diffilqrax.utils

"""Includes utility functions for the project. Generic functions to generate data, seeds, etc."""

from typing import Callable, Tuple
import jax
from jax import Array
import jax.random as jr
import jax.numpy as jnp


[docs] def keygen(key, nkeys): """ Generate randomness that JAX can use by splitting the JAX keys. Parameters ---------- key : jax.random.PRNGKey The random key for JAX. nkeys : int Number of keys to generate. Returns ------- tuple A tuple containing the new key for further generators and the key generator. """ keys = jr.split(key, nkeys + 1) return keys[0], (k for k in keys[1:])
[docs] def initialise_stable_dynamics(key: Tuple[int, int], n_dim: int, T: int, radii: float = 0.6) -> Array: """ Generate a state matrix with stable dynamics (all eigenvalues inside the unit circle) discrete dynamics. Parameters ---------- key : tuple of int Random key. n_dim : int State dimensions. T : int Time steps. radii : float, optional Spectral radius, by default 0.6. Returns ------- Array Matrix A with stable dynamics. """ mat = jr.normal(key, (n_dim, n_dim)) * radii mat /= jnp.sqrt(n_dim) return jnp.tile(mat, (T, 1, 1))
[docs] def initialise_stable_time_varying_dynamics(key: Tuple[int, int], n_dim: int, T: int, radii: float = 0.6) -> Array: """ Generate a state matrix with stable dynamics (all eigenvalues inside the unit circle). Parameters ---------- key : tuple of int Random key. n_dim : int State dimensions. T : int Time steps. radii : float, optional Spectral radius, by default 0.6. Returns ------- Array Matrix A with stable dynamics. """ mat = jr.normal(key, (T, n_dim, n_dim)) * radii mat /= jnp.sqrt(n_dim) # mat -= jnp.eye(n_dim) return mat
[docs] def linearise(fun: Callable) -> Callable: """ Find Jacobian with respect to x and u inputs. Parameters ---------- fun : Callable Function with arguments (t, x, u, params). Returns ------- Callable Jacobian tuple evaluated at args 1 and 2. """ return jax.jacrev(fun, argnums=(1, 2))
[docs] def quadratise(fun: Callable) -> Callable: """ Find Hessian with respect to x and u inputs. Parameters ---------- fun : Callable Function with arguments (t, x, u, params). Returns ------- tuple Hessian tuple cross evaluated with args 1 and 2. """ return jax.jacfwd(jax.jacrev(fun, argnums=(1, 2)), argnums=(1, 2))
[docs] def time_map(fun: Callable) -> Callable: """ Vectorise function in time. Assumes 0th-axis is time for x and u args of fun, the last arg (theta) of Callable function assumed to be time-invariant. Parameters ---------- fun : Callable Function that takes args (t, x[Txn], u[Txm], theta). Returns ------- Callable Vectorised function along args 1 and 2 0th-axis. """ return jax.vmap(fun, in_axes=(0, 0, 0, None))
[docs] def smooth_l1(x: Array, eps: float = 1e-6) -> Array: """ Smooth approximation of the L1 norm, sum(sqrt(x^2 + eps) - sqrt(eps)). Twice differentiable, so it can be used as an iLQR sparsity cost. Approaches ``sum(|x|)`` as ``eps -> 0``; anneal ``eps`` by re-solving with smaller values, warm-starting each solve. Parameters ---------- x : Array Input array. eps : float, optional Smoothing parameter, by default 1e-6. Returns ------- Array Smoothed L1 norm of x. """ return jnp.sum(jnp.sqrt(x**2 + eps) - jnp.sqrt(eps))