blob: 6a3e9e3c12e4ef66dfcdebab5b7ae5a442605554 [file]
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
// The controllability Gramian X of a damped mass-spring chain x' = A x + B w,
// forced at the last mass, solves the Lyapunov equation A X + X A^T + B B^T = 0.
// With vec stacking columns, vec(A X + X A^T) = (A (+) A) vec(X), so the
// equation is one Kronecker-sum solve. A is not symmetric and has complex
// eigenvalue pairs (an underdamped chain); X is symmetric positive definite
// when the chain is controllable.
#include <contrib/Eigen/StructuredMatrices>
#include <iostream>
using namespace Eigen;
int main() {
const int masses = 20, n = 2 * masses;
const double damping = 0.1;
// State (positions, velocities): A = [0 I; -K -c I] with K = tridiag(-1, 2, -1).
MatrixXd A = MatrixXd::Zero(n, n);
A.topRightCorner(masses, masses).setIdentity();
for (int i = 0; i < masses; ++i) {
A(masses + i, i) = -2;
if (i > 0) A(masses + i, i - 1) = 1;
if (i + 1 < masses) A(masses + i, i + 1) = 1;
A(masses + i, masses + i) = -damping;
}
VectorXd B = VectorXd::Zero(n);
B[n - 1] = 1;
const MatrixXd Q = B * B.transpose();
const MatrixXd X = makeKroneckerSum(A, A).solve(-Q.reshaped()).reshaped(n, n);
std::cout << "residual |AX + XA^T + BB^T| / |BB^T|: " << (A * X + X * A.transpose() + Q).norm() / Q.norm() << "\n";
std::cout << "asymmetry |X - X^T| / |X|: " << (X - X.transpose()).norm() / X.norm() << "\n";
std::cout << "smallest eigenvalue of X: "
<< SelfAdjointEigenSolver<MatrixXd>((X + X.transpose()) / 2).eigenvalues()[0] << "\n";
}