Source code for direct_data_driven_mpc.utilities.hankel_matrix

"""
Functions for constructing Hankel matrices and evaluating persistent
excitation.

This module provides functions for constructing Hankel matrices from
multidimensional data sequences and for evaluating whether a given data
sequence is persistently exciting of a given order based on the rank of its
Hankel matrix.
"""

import numpy as np
from numpy.lib.stride_tricks import as_strided


[docs] def hankel_matrix(X: np.ndarray, L: int) -> np.ndarray: """ Construct a Hankel matrix from the input data matrix `X` with a window length `L`. The matrix `X` consists of a sequence of `N` elements, each of length `n`. Args: X (np.ndarray): Input data matrix of shape (N, n), where N is the number of elements, and n is the length of each element. L (int): Window length for the Hankel matrix. Returns: np.ndarray: A Hankel matrix of shape (L * n, N - L + 1), where each column represents a flattened window of length L sliding over the N data elements. Raises: ValueError: If the number of elements N is less than the window length L, indicating that the window length exceeds the available data length. Examples: >>> import numpy as np >>> N = 4 # Data length >>> L = 2 # Hankel matrix window length >>> n = 2 # Data vector length >>> rng = np.random.default_rng(0) # RNG for reproducibility >>> u_d = rng.uniform(-1, 1, (N, n)) # Generate data matrix >>> print(hankel_matrix(u_d, L)) [[ 0.27392337 -0.91805295 0.62654048] [-0.46042657 -0.96694473 0.82551115] [-0.91805295 0.62654048 0.21327155] [-0.96694473 0.82551115 0.45899312]] """ # Get data matrix shape N, n = X.shape # Validate input dimensions if N < L: raise ValueError("N must be greater than or equal to L.") X = X.ravel() # Transform X into a 1-D array # Construct Hankel matrix striding on the data array out_shp = L * n, N - L + 1 n_row = X.strides[0] # Move 1-by-1 element in rows n_col = X.strides[0] * n # Move n-by-n elements in columns return as_strided(X, shape=out_shp, strides=(n_row, n_col)).copy()
[docs] def evaluate_persistent_excitation( X: np.ndarray, order: int ) -> tuple[int, bool]: """ Evaluate whether a data sequence `X` is persistently exciting of a given order based on the rank of its Hankel matrix. The matrix `X` consists of a sequence of `N` elements, each of length `n`. This is determined by checking if the rank of the Hankel matrix constructed from `X` is greater than or equal to the expected rank `n * order`. Args: X (np.ndarray): Input data matrix of shape (N, n), where N is the number of elements, and n is the length of each element. order (int): The order of persistent excitation to evaluate. Returns: tuple[int, bool]: A tuple containing the rank of the Hankel matrix and a boolean indicating whether `X` is persistently exciting of the given order. """ # Get data sequence element length n = X.shape[1] # Construct Hankel matrix from X H_order = hankel_matrix(X, order) # Calculate the Hankel matrix order rank_H_order = np.linalg.matrix_rank(H_order) # Check if X is persistently exciting of the given order pers_exciting = rank_H_order >= n * order return rank_H_order, pers_exciting