Skip to content

ModelBase

ModelBase(label: str = 'n/a', C_init=None, force_MC: bool = False)

Bases: ABC


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

              

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

Abstract base class for autonomous linear state space models (ALSSMs).

Parameters:

  • label (str, default: 'n/a' ) –

    Label of the ALSSM. Default: 'n/a'.

  • C_init (array_like or None, default: None ) –

    Initial output matrix stored for use by update. Default: None.

  • force_MC (bool, default: False ) –

    If True, broadcasts a 1-dimensional C vector to a 2-dimensional array of shape (1, N) (multi-channel form). Default: False.

Methods:

  • update

    Recompute the internal model matrices A and C.

  • 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:

  • steady_state_basis
  • 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}\)

  • C_init

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

  • label

    str : Label of the model

  • 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\).

  • 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).

  • 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, label:str='n/a', C_init=None, force_MC:bool=False):
    self._alssms = list()
    self._lambdas = np.ndarray([])
    self._A = None
    self._C = None
    self.label = label
    self.C_init = C_init
    self._state_var_labels = dict()
    self.force_MC = force_MC

Methods

update abstractmethod

update()

Recompute the internal model matrices A and C.

Updates A and C from the stored initialisation parameters (e.g. poly_degree, omega, gamma). Called automatically during __init__ and should be called again after manually changing any parameter.

Source code in lmlib/statespace/model.py
@abstractmethod
def update(self):
    r"""
    Recompute the internal model matrices A and C.

    Updates [`A`][lmlib.statespace.model.ModelBase.A] and [`C`][lmlib.statespace.model.ModelBase.C] from the stored initialisation parameters
    (e.g. ``poly_degree``, ``omega``, ``gamma``). Called automatically during
    ``__init__`` and should be called again after manually changing any parameter.
    """
    pass

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