def subspace_identification(self): """ Perform subspace identification based on the PO-MOESP method. The instrumental variable contains past outputs and past inputs. The implementation uses a QR-decomposition for numerical efficiency and is based on page 329 of [1]. A key result of this function is the eigenvalue decomposition of the :math:`R_{32}` matrix ``self.R32_decomposition``, based on which the order of the system should be determined. [1] Verhaegen, Michel, and Vincent Verdult. *Filtering and system identification: a least squares approach.* Cambridge university press, 2007. """ u_hankel = Utils.block_hankel_matrix(self.u_array, self.num_block_rows) y_hankel = Utils.block_hankel_matrix(self.y_array, self.num_block_rows) u_past, u_future = u_hankel[:, :-self. num_block_rows], u_hankel[:, self. num_block_rows:] y_past, y_future = y_hankel[:, :-self. num_block_rows], y_hankel[:, self. num_block_rows:] u_instrumental_y = np.concatenate([u_future, u_past, y_past, y_future]) q, r = map(lambda matrix: matrix.T, np.linalg.qr(u_instrumental_y.T, mode='reduced')) y_rows, u_rows = self.y_dim * self.num_block_rows, self.u_dim * self.num_block_rows self.R32 = r[-y_rows:, u_rows:-y_rows] self.R22 = r[u_rows:-y_rows, u_rows:-y_rows] self.R32_decomposition = Utils.eigenvalue_decomposition(self.R32)
def _get_observability_matrix_decomposition(self) -> Decomposition: """ Calculate the eigenvalue decomposition of the estimate of the observability matrix as per N4SID. """ u_hankel = Utils.block_hankel_matrix(self.u_array, self.num_block_rows) y_hankel = Utils.block_hankel_matrix(self.y_array, self.num_block_rows) u_and_y = np.concatenate([u_hankel, y_hankel]) observability = self.R32 @ np.linalg.pinv(self.R22) @ u_and_y observability_decomposition = Utils.reduce_decomposition( Utils.eigenvalue_decomposition(observability), self.x_dim) return observability_decomposition
def test_eigenvalue_decomposition(self): matrix = np.fliplr(np.diag(range(1, 3))) decomposition = Utils.eigenvalue_decomposition(matrix) self.assertTrue( np.all( np.isclose([[0, -1], [-1, 0]], decomposition.left_orthogonal))) self.assertTrue( np.all(np.isclose([2, 1], np.diagonal(decomposition.eigenvalues)))) self.assertTrue( np.all( np.isclose([[-1, 0], [0, -1]], decomposition.right_orthogonal))) reduced_decomposition = Utils.reduce_decomposition(decomposition, 1) self.assertTrue( np.all( np.isclose([[0], [-1]], reduced_decomposition.left_orthogonal))) self.assertTrue( np.all(np.isclose([[2]], reduced_decomposition.eigenvalues))) self.assertTrue( np.all( np.isclose([[-1, 0]], reduced_decomposition.right_orthogonal)))