Skip to content

AlssmProd

AlssmProd(alssms, lambdas=None, **kwargs)

Bases: ModelBase


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

                              lmlib.statespace.model.ModelBase --> lmlib.statespace.model.AlssmProd
                


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

Joins multiple ALSSMs generating the output product.

Creating a joined ALSSM generating the product of all output signals of \(M\) ALSSMs, e.g., on the example of \(M=2\),

\[ \begin{aligned} \tilde{s}_k (\tilde{x}) = s_k^{(1)}(x_1) \cdot s_k^{(2)}(x_2) &= (C_1 A_1^k x_1)(C_2 A_2^k x_2)\\ &= (C_1 A_1^k x_1) \otimes (C_2 A_2^k x_2)\\ &= (C_1 \otimes C_2) (A_1^k \otimes A_2^k) (x_1 \otimes x_2) \ , \end{aligned} \]

where \(s_k^{(1)}(x_1) = C_1 A_1^k x_1\) is the first and \(s_k^{(2)}(x_2) = C_2 A_2^k x_2\) the second ALSSM.

For more details, see also [Zalmai2017] [Proposition 2], [Wildhaber2019] [Eq. 4.21].

Parameters:

  • alssms (iterable of ModelBase, length M) –

    Set of M autonomous linear state space models. For the Kronecker product interpretation to give a scalar output, all sub-ALSSMs should produce scalar outputs (i.e. 1-D C).

  • lambdas (list of float or None, default: None ) –

    List of M scalar factors applied to each output matrix. Default: None (all factors set to 1).

  • **kwargs

    Forwarded to ModelBase.

Example
Multiply two ALSSMs

>>> import lmlib as lm
>>>
>>> alssm_p = lm.AlssmPoly(poly_degree=2, label='poly')
>>> alssm_s = lm.AlssmSin(omega=0.5, rho=0.2, label='sin')
>>> alssm = lm.AlssmProd((alssm_s, alssm_p), label="multi")
>>> print(alssm)
A =
[[ 0.17551651  0.17551651  0.17551651 -0.09588511 -0.09588511 -0.09588511]
 [ 0.          0.17551651  0.35103302 -0.         -0.09588511 -0.19177022]
 [ 0.          0.          0.17551651 -0.         -0.         -0.09588511]
 [ 0.09588511  0.09588511  0.09588511  0.17551651  0.17551651  0.17551651]
 [ 0.          0.09588511  0.19177022  0.          0.17551651  0.35103302]
 [ 0.          0.          0.09588511  0.          0.          0.17551651]]
C =
[[1. 0. 0. 0. 0. 0.]]

Methods:

  • update

    Recompute the Kronecker-product A and C from the sub-ALSSMs and their lambda scaling factors.

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

  • alssms

    list of ModelBase : The \(M\) sub-ALSSMs whose outputs are multiplied.

  • lambdas

    ndarray : Per-ALSSM output scaling factors \(\lambda_m\) (one per sub-ALSSM).

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

  • 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, alssms, lambdas=None, **kwargs):
    super().__init__(**kwargs)
    self.alssms = alssms
    r"""list of ModelBase : The $M$ sub-ALSSMs whose outputs are multiplied."""
    self.lambdas = lambdas
    r"""ndarray : Per-ALSSM output scaling factors $\lambda_m$ (one per sub-ALSSM)."""
    self.update()

Methods

update

update()

Recompute the Kronecker-product A and C from the sub-ALSSMs and their lambda scaling factors.

Source code in lmlib/statespace/model.py
def update(self):
    r"""Recompute the Kronecker-product A and C from the sub-ALSSMs and their lambda scaling factors."""
    for alssm in self.alssms:
        alssm.update()
    A = [1]
    C = [1]
    for alssm, lambda_ in zip(self.alssms, self.lambdas):
        A = np.kron(A, alssm.A)
        C = np.kron(C, lambda_ * alssm.C)
    self.A = A
    self.C = 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