src/lds.cpp

src/lds.cpp #

misc lds namespace functions More…

Namespaces #

Name
lds
Linear Dynamical Systems (LDS) namespace.

Detailed Description #

This file implements miscellaneous lds namespace functions not bound to a class.

Source code #

//===-- lds.cpp - LDS -----------------------------------------------------===//
//
// Copyright 2021 Michael Bolus
// Copyright 2021 Georgia Institute of Technology
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//

#include <ldsCtrlEst_h/lds.h>

// insert any necessary function definitions here.
namespace lds {

void ForceSymPD(Matrix& X) {
  if (X.is_sympd() || !X.is_square()) {
    return;
  }

  // make symmetric
  X = (X + X.t()) / 2;

  // for eigenval decomp
  bool did_succeed(true);
  Vector d;
  Matrix u;

  // see first method (which may not be ideal):
  // https://nhigham.com/2021/02/16/diagonally-perturbing-a-symmetric-matrix-to-make-it-positive-definite/
  size_t k(1);
  bool is_sympd = X.is_sympd();
  Matrix id = Matrix(X.n_rows, X.n_cols, fill::eye);

  while (!is_sympd) {
    if (k > 100) {
      did_succeed = arma::eig_sym(d, u, X, "std");
      data_t min_eig = arma::min(d);
      std::cerr << "After multiple iterations, min eigen val = " << min_eig
                << ".\n";
      throw std::runtime_error(
          "Failed to make matrix symmetric positive definite.");
      return;
    }

    // Limit(d, arma::eps(0), kInf);  // force to be positive...
    // Matrix d_diag = arma::diagmat(d);
    // X = u * d_diag * u.t();

    did_succeed = arma::eig_sym(d, u, X, "std");
    if (!did_succeed) {
      throw std::runtime_error("ForceSymPD failed.");
    }

    data_t min_eig = arma::min(d);
    X += id * abs(min_eig) + arma::datum::eps;

    // make sure symm:
    X = (X + X.t()) / 2;
    // double check eigenvals positive after symmetrizing:
    arma::eig_sym(d, u, X, "std");
    min_eig = arma::min(d);
    is_sympd = min_eig > 0;
    k++;
  }
}

void ForceSymMinEig(Matrix& X, data_t eig_min) {
  if (!X.is_square()) {
    return;
  }

  // make symmetric
  X = (X + X.t()) / 2;

  bool did_succeed(true);
  Vector d;
  Matrix u;
  did_succeed = arma::eig_sym(d, u, X, "std");
  if (!did_succeed) {
    throw std::runtime_error("ForceSymMinEig failed.");
  }
  Limit(d, eig_min + arma::eps(eig_min), kInf);  // enforce lower bound
  Matrix d_diag = arma::diagmat(d);
  X = u * d_diag * u.t();

  // double check symmetric
  X = (X + X.t()) / 2;
}

void lq(Matrix& L, Matrix& Qt, const Matrix& X) {
  bool did_succeed(true);
  did_succeed = arma::qr_econ(Qt, L, X.t());
  if (!did_succeed) {
    throw std::runtime_error("LQ decomposition failed.");
  }
  arma::inplace_trans(L);
  arma::inplace_trans(Qt);
}

Matrix calcCov(const Matrix& A, const Matrix& B) {
  // subtract out mean
  auto m_a = arma::mean(A, 1);
  Matrix a0 = A;
  a0.each_col() -= m_a;

  auto m_b = arma::mean(B, 1);
  Matrix b0 = B;
  b0.each_col() -= m_b;

  Matrix cov = a0 * b0.t() / a0.n_cols;
  return cov;
}

}  // namespace lds

Updated on 19 May 2022 at 17:16:05 Eastern Daylight Time