Skip to content

AlssmSin

AlssmSin(omega: float, rho: float = 1.0, C=None, **kwargs)

Bases: ModelBase


              flowchart TD
              lmlib.statespace.model.AlssmSin[AlssmSin]
              lmlib.statespace.model.ModelBase[ModelBase]

                              lmlib.statespace.model.ModelBase --> lmlib.statespace.model.AlssmSin
                


              click lmlib.statespace.model.AlssmSin href "" "lmlib.statespace.model.AlssmSin"
              click lmlib.statespace.model.ModelBase href "" "lmlib.statespace.model.ModelBase"
            

ALSSM with a discrete-time (damped) sinusoidal output sequence.

Generates a sinusoidal sequence with angular frequency \(\omega\) (radians per sample) and per-sample amplitude decay factor \(\rho\):

\[ \begin{aligned} A = \begin{bmatrix} \rho \cos\omega & -\rho \sin\omega \\ \rho \sin\omega & \rho \cos\omega \end{bmatrix} \end{aligned} \]

The state vector holds \([a\cos, a\sin]\) components at the current sample. With the default output \(C = [1, 0]\), the output is the cosine component.

For more details see [Wildhaber2019] PDF

Parameters:

  • omega (float) –

    Normalised discrete-time angular frequency \(\omega = 2\pi f / f_s\) (radians per sample), where \(f\) is the signal frequency and \(f_s\) is the sampling frequency.

  • rho (float, default: 1.0 ) –

    Per-sample amplitude decay factor. rho = 1.0 gives an undamped oscillation. Default: 1.0.

  • C (array_like of shape ([Q,] N), default: None ) –

    Output matrix. If not given, defaults to [1, 0], selecting the cosine component. With force_MC=True the shape is broadcast to (1, N). Default: None.

  • **kwargs

    Forwarded to ModelBase.

Notes

N : ALSSM system order, corresponding to the number of state variables
Q : output order / number of signal channels

To convert a signal period in samples to a normalised angular frequency, use k_period_to_omega:

import lmlib as lm
from lmlib.utils.generator import k_period_to_omega

k_period = 20
alssm = lm.AlssmSin(k_period_to_omega(k_period), rho=0.9)
Example
>>> alssm = lm.AlssmSin(omega=0.1, rho=0.9)
>>> print(alssm)
AlssmSin(A=[[ 0.89550375 -0.08985007],[ 0.08985007  0.89550375]], C=[1 0], label=n/a)

Methods:

  • update

    Recompute the 2×2 rotation/decay matrix A and default C from \(\omega\) and \(\rho\).

  • eval_output

    Evaluate the ALSSM output for one or more state vectors.

  • dump_tree

    Return the internal ALSSM tree structure as a string.

  • set_state_var_label

    Register a label for one or more state vector indices.

  • get_state_var_labels

    Return all registered state-variable labels together with their index tuples.

  • get_state_var_indices

    Return the state-vector indices for a state variable identified by its label.

  • get_alssm_output_dimension

    Return the ALSSM output dimension \(Q\) (number of output channels).

Attributes:

  • omega

    float : Normalised angular frequency \(\omega = 2\pi f / f_s\) (radians per sample).

  • rho

    float : Decay factor \(\rho\)

  • steady_state_basis
  • label

    str : Label of the model

  • C_init

    ndarray, shape=([Q,] N) : Initialized Output matrix \(C \in \mathbb{R}^{Q \times N}\)

  • force_MC

    bool : If True, a 1-D output vector C is broadcast to a 2-D array of shape (1, N) (multi-channel form).

  • A

    ndarray, shape=(N, N) : State matrix \(A \in \mathbb{R}^{N \times N}\)

  • C

    ndarray, shape=([Q,] N) : Output matrix \(C \in \mathbb{R}^{Q \times N}\)

  • N

    int : Model order \(N\)

  • Q

    int : Number of output channels \(Q\).

  • alssms

    list : Sub-ALSSMs that compose this model (empty for leaf nodes such as Alssm).

  • lambdas

    ndarray : Per-ALSSM scalar output scaling factors \(\lambda_m\) applied to each sub-model's output matrix \(C_m\).

  • is_MC

    bool : True if the output matrix C is 2-D (multi-channel form), False if 1-D (scalar output).

Source code in lmlib/statespace/model.py
def __init__(self, omega:float, rho:float=1.0, C=None, **kwargs):
    super().__init__(C_init=C, **kwargs)
    self.omega = omega
    self.rho = rho
    self.update()
    self.set_state_var_label('cos', (0,))
    self.set_state_var_label('sin', (1,))

Methods

update

update()

Recompute the 2×2 rotation/decay matrix A and default C from \(\omega\) and \(\rho\).

Source code in lmlib/statespace/model.py
def update(self):
    r"""Recompute the 2×2 rotation/decay matrix A and default C from $\omega$ and $\rho$."""
    if self.C_init is None:
        self.C = np.array([1, 0])
    else:
        self.C = self.C_init
    c, s = np.cos(self.omega), np.sin(self.omega)
    self.A = self.rho * np.array([[c, -s], [s, c]])
    self._init_state_var_labels()
    self._broadcast_C_to_multichannel()

eval_output

eval_output(xs, js=None)

Evaluate the ALSSM output for one or more state vectors.

Without evaluation index (js=None):

\[ s(x) = C x \]

With evaluation indices (js provided):

\[ s_j(x) = C A^j x \]

Parameters:

  • xs (array_like of shape (..., N)) –

    State vector(s). The last dimension must equal the model order N.

  • js (array_like of shape (J,) or None, default: None ) –

    Sequence of integer evaluation indices. If None, evaluates at \(j = 0\) only (i.e. returns \(Cx\)).

Returns:

  • s ( ndarray ) –

    If js is None: shape (..., [Q]). If js is provided: shape (J, ..., [Q]). The [Q] dimension is present only when is_MC is True.

Source code in lmlib/statespace/model.py
def eval_output(self, xs, js=None):
    r"""
    Evaluate the ALSSM output for one or more state vectors.

    Without evaluation index (``js=None``):

    $$
    s(x) = C x
    $$

    With evaluation indices (``js`` provided):

    $$
    s_j(x) = C A^j x
    $$

    Parameters
    ----------
    xs : array_like of shape (..., N)
        State vector(s). The last dimension must equal the model order N.
    js : array_like of shape (J,) or None, optional
        Sequence of integer evaluation indices. If None, evaluates at
        $j = 0$ only (i.e. returns $Cx$).

    Returns
    -------
    s : ndarray
        If ``js`` is None: shape ``(..., [Q])``.
        If ``js`` is provided: shape ``(J, ..., [Q])``.
        The ``[Q]`` dimension is present only when [`is_MC`][lmlib.statespace.model.ModelBase.is_MC] is True.
    """
    xs = np.asarray(xs)

    # Ensure last axis is state dimension
    if xs.shape[-1] != self.N:
        raise ValueError(f"Last dimension of xs must be {self.N}")

    # No propagation: s = C x
    if js is None:
        _subscript = 'ln,...n->...l' if self.is_MC else 'n,...n->...'
        return np.einsum(_subscript, self.C, xs)

    # Propagation: s_j = C A^j x
    A_powers = [matrix_power(self.A, int(j)) for j in js]
    _subscript = 'ln,...n->...l' if self.is_MC else 'n,...n->...'
    return np.asarray([np.einsum(_subscript, self.C @ Aj, xs) for Aj in A_powers])

dump_tree

dump_tree() -> str

Return the internal ALSSM tree structure as a string.

Returns:

  • out ( str ) –

    Multi-line string representing the nested ALSSM structure.

Example
>>> import lmlib as lm
>>> import numpy as np
>>> alssm_poly = lm.AlssmPoly(4, label="high order polynomial")
>>> A = [[1, 1], [0, 1]]
>>> C = [[1, 0]]
>>> alssm_line = lm.Alssm(A, C, label="line")
>>> stacked_alssm = lm.AlssmStacked((alssm_poly, alssm_line), label='stacked model')
>>> print(stacked_alssm.dump_tree())
-AlssmStacked, A: (7, 7), C: (2, 7), label: stacked model
  -AlssmPoly, A: (5, 5), C: (5,), label: high order polynomial
  -Alssm, A: (2, 2), C: (1, 2), label: line
Source code in lmlib/statespace/model.py
def dump_tree(self) -> str:
    """
    Return the internal ALSSM tree structure as a string.

    Returns
    -------
    out : str
        Multi-line string representing the nested ALSSM structure.

    Example
    --------
    ```python
    >>> import lmlib as lm
    >>> import numpy as np
    >>> alssm_poly = lm.AlssmPoly(4, label="high order polynomial")
    >>> A = [[1, 1], [0, 1]]
    >>> C = [[1, 0]]
    >>> alssm_line = lm.Alssm(A, C, label="line")
    >>> stacked_alssm = lm.AlssmStacked((alssm_poly, alssm_line), label='stacked model')
    >>> print(stacked_alssm.dump_tree())
    └-AlssmStacked, A: (7, 7), C: (2, 7), label: stacked model
      └-AlssmPoly, A: (5, 5), C: (5,), label: high order polynomial
      └-Alssm, A: (2, 2), C: (1, 2), label: line
    ```
    """
    return self._rec_tree(level=0)

set_state_var_label

set_state_var_label(label: str, indices: tuple[int])

Register a label for one or more state vector indices.

Labels allow state components to be referenced by name rather than by numeric index; see get_state_var_indices.

Parameters:

  • label (str) –

    Label name to register.

  • indices (tuple of int) –

    State vector indices associated with this label.

Example
>>> import lmlib as lm
>>> alssm = lm.AlssmPoly(poly_degree=1, label='slope_with_offset')
>>> alssm.set_state_var_label('slope', (1,))
>>> alssm.get_state_var_indices('slope_with_offset.slope')
(1,)
Source code in lmlib/statespace/model.py
def set_state_var_label(self, label:str, indices:tuple[int]):
    r"""
    Register a label for one or more state vector indices.

    Labels allow state components to be referenced by name rather than by
    numeric index; see [`get_state_var_indices`][lmlib.statespace.model.ModelBase.get_state_var_indices].

    Parameters
    ----------
    label : str
        Label name to register.
    indices : tuple of int
        State vector indices associated with this label.

    Example
    --------
    ```python
    >>> import lmlib as lm
    >>> alssm = lm.AlssmPoly(poly_degree=1, label='slope_with_offset')
    >>> alssm.set_state_var_label('slope', (1,))
    >>> alssm.get_state_var_indices('slope_with_offset.slope')
    (1,)
    ```
    """
    self._state_var_labels[label] = indices

get_state_var_labels

get_state_var_labels()

Return all registered state-variable labels together with their index tuples.

Labels are accumulated recursively from all nested sub-ALSSMs, with each label prefixed by the current model's label. The state indices are adjusted to reflect the position within the combined (block-diagonal) state vector.

Returns:

  • out ( list of (str, tuple of int) ) –

    List of (label_string, indices) pairs. label_string is a dot-separated path (e.g. 'stacked.poly.x0') and indices is the corresponding tuple of integer state-vector positions.

Source code in lmlib/statespace/model.py
def get_state_var_labels(self):
    r"""
    Return all registered state-variable labels together with their index tuples.

    Labels are accumulated recursively from all nested sub-ALSSMs, with
    each label prefixed by the current model's [`label`][lmlib.statespace.model.ModelBase.label].  The state
    indices are adjusted to reflect the position within the combined
    (block-diagonal) state vector.

    Returns
    -------
    out : list of (str, tuple of int)
        List of ``(label_string, indices)`` pairs.  ``label_string`` is a
        dot-separated path (e.g. ``'stacked.poly.x0'``) and ``indices`` is
        the corresponding tuple of integer state-vector positions.
    """
    state_list = []
    for var_label, indices in self._state_var_labels.items():
        state_list.append((self.label + '.' + var_label, indices))

    N = 0
    for alssm in self.alssms:
        for var_label, indices in alssm.get_state_var_labels():
            state_list.extend([(self.label + '.' + var_label, tuple(i + N for i in indices))])
        N += alssm.N
    return state_list

get_state_var_indices

get_state_var_indices(label)

Return the state-vector indices for a state variable identified by its label.

Parameters:

Returns:

  • out ( tuple of int or list of int ) –

    State-vector indices associated with label. Returns an empty list if label is not found.

Source code in lmlib/statespace/model.py
def get_state_var_indices(self, label):
    r"""
    Return the state-vector indices for a state variable identified by its label.

    Parameters
    ----------
    label : str
        Fully qualified state label (dot-separated path), as returned by
        [`get_state_var_labels`][lmlib.statespace.model.ModelBase.get_state_var_labels].

    Returns
    -------
    out : tuple of int or list of int
        State-vector indices associated with ``label``.
        Returns an empty list if ``label`` is not found.
    """

    for l, indices in self.get_state_var_labels():
        if label == l:
            return indices
    return []

get_alssm_output_dimension

get_alssm_output_dimension()

Return the ALSSM output dimension \(Q\) (number of output channels).

Source code in lmlib/statespace/model.py
def get_alssm_output_dimension(self):
    r"""Return the ALSSM output dimension $Q$ (number of output channels)."""
    return self.Q