“Know how to solve every problem that has been solved.” “What I cannot create, I do not understand.” — Richard Feynman
cpp 302 lines · 8.0 KB
#include "qc/matrix.hpp"

#include <algorithm>
#include <cmath>
#include <limits>

namespace qc {

Matrix::Matrix(std::size_t rows, std::size_t cols, double fill)
    : rows_(rows), cols_(cols), data_(rows * cols, fill) {}

void Matrix::resize(std::size_t rows, std::size_t cols, double fill) {
  rows_ = rows;
  cols_ = cols;
  data_.assign(rows * cols, fill);
}

Matrix Matrix::transposed() const {
  Matrix T(cols_, rows_);
  for (std::size_t r = 0; r < rows_; ++r) {
    for (std::size_t c = 0; c < cols_; ++c) {
      T(c, r) = (*this)(r, c);
    }
  }
  return T;
}

Matrix Matrix::symmetrize_lower() const {
  if (rows_ != cols_) {
    throw std::invalid_argument("symmetrize_lower requires square matrix");
  }
  Matrix S(rows_, cols_);
  for (std::size_t r = 0; r < rows_; ++r) {
    for (std::size_t c = 0; c <= r; ++c) {
      double v = 0.5 * ((*this)(r, c) + (*this)(c, r));
      S(r, c) = v;
      S(c, r) = v;
    }
  }
  return S;
}

Matrix Matrix::identity(std::size_t n) {
  Matrix I(n, n, 0.0);
  for (std::size_t i = 0; i < n; ++i) {
    I(i, i) = 1.0;
  }
  return I;
}

Vector::Vector(std::size_t n, double fill) : data_(n, fill) {}

void Vector::resize(std::size_t n, double fill) { data_.assign(n, fill); }

void gemm(double alpha, const Matrix& A, const Matrix& B, double beta, Matrix& C) {
  if (A.cols() != B.rows() || C.rows() != A.rows() || C.cols() != B.cols()) {
    throw std::invalid_argument("gemm: dimension mismatch");
  }
  const std::size_t m = A.rows();
  const std::size_t n = B.cols();
  const std::size_t k = A.cols();
  if (beta == 0.0) {
    C.resize(m, n, 0.0);
  } else {
    for (std::size_t i = 0; i < m * n; ++i) {
      C.data()[i] *= beta;
    }
  }
  for (std::size_t r = 0; r < m; ++r) {
    for (std::size_t c = 0; c < n; ++c) {
      double sum = 0.0;
      for (std::size_t t = 0; t < k; ++t) {
        sum += A(r, t) * B(t, c);
      }
      C(r, c) += alpha * sum;
    }
  }
}

void axpy(double a, const Vector& x, Vector& y) {
  if (x.size() != y.size()) {
    throw std::invalid_argument("axpy: size mismatch");
  }
  for (std::size_t i = 0; i < x.size(); ++i) {
    y[i] += a * x[i];
  }
}

double dot(const Vector& x, const Vector& y) {
  if (x.size() != y.size()) {
    throw std::invalid_argument("dot: size mismatch");
  }
  double s = 0.0;
  for (std::size_t i = 0; i < x.size(); ++i) {
    s += x[i] * y[i];
  }
  return s;
}

double norm2(const Vector& x) { return std::sqrt(dot(x, x)); }

CholeskyResult cholesky_lower(const Matrix& A) {
  const std::size_t n = A.rows();
  if (n != A.cols()) {
    throw std::invalid_argument("cholesky: matrix must be square");
  }
  CholeskyResult out;
  out.L.resize(n, n, 0.0);
  out.ok = true;
  for (std::size_t j = 0; j < n; ++j) {
    for (std::size_t i = j; i < n; ++i) {
      double sum = A(i, j);
      for (std::size_t k = 0; k < j; ++k) {
        sum -= out.L(i, k) * out.L(j, k);
      }
      if (i == j) {
        if (sum <= 0.0) {
          out.ok = false;
          return out;
        }
        out.L(j, j) = std::sqrt(sum);
      } else {
        out.L(i, j) = sum / out.L(j, j);
      }
    }
  }
  return out;
}

void cholesky_solve_lower(const Matrix& L, Vector& x) {
  const std::size_t n = L.rows();
  for (std::size_t i = 0; i < n; ++i) {
    double sum = x[i];
    for (std::size_t j = 0; j < i; ++j) {
      sum -= L(i, j) * x[j];
    }
    x[i] = sum / L(i, i);
  }
}

static void cholesky_solve_lower_trans(const Matrix& L, Vector& x) {
  const std::size_t n = L.rows();
  for (int ii = static_cast<int>(n) - 1; ii >= 0; --ii) {
    std::size_t i = static_cast<std::size_t>(ii);
    double sum = x[i];
    for (std::size_t j = i + 1; j < n; ++j) {
      sum -= L(j, i) * x[j];
    }
    x[i] = sum / L(i, i);
  }
}

SymmetricEigenDecomp symmetric_eigen_jacobi(Matrix A, double tol,
                                             int max_sweeps) {
  const std::size_t n = A.rows();
  if (n != A.cols()) {
    throw std::invalid_argument("jacobi: matrix must be square");
  }
  SymmetricEigenDecomp out;
  out.eigenvectors = Matrix::identity(n);
  for (int sweep = 0; sweep < max_sweeps; ++sweep) {
    double max_off = 0.0;
    std::size_t p = 0, q = 1;
    for (std::size_t i = 0; i < n; ++i) {
      for (std::size_t j = i + 1; j < n; ++j) {
        double v = std::abs(A(i, j));
        if (v > max_off) {
          max_off = v;
          p = i;
          q = j;
        }
      }
    }
    if (max_off <= tol) {
      break;
    }
    const double app = A(p, p);
    const double aqq = A(q, q);
    const double apq = A(p, q);
    const double tau = (aqq - app) / (2.0 * apq);
    const double t = (tau >= 0.0 ? 1.0 : -1.0) /
                     (std::abs(tau) + std::sqrt(1.0 + tau * tau));
    const double c = 1.0 / std::sqrt(1.0 + t * t);
    const double s = t * c;
    for (std::size_t k = 0; k < n; ++k) {
      if (k != p && k != q) {
        const double akp = A(k, p);
        const double akq = A(k, q);
        A(k, p) = A(p, k) = c * akp - s * akq;
        A(k, q) = A(q, k) = c * akq + s * akp;
      }
    }
    A(p, p) = app - t * apq;
    A(q, q) = aqq + t * apq;
    A(p, q) = A(q, p) = 0.0;
    for (std::size_t k = 0; k < n; ++k) {
      const double vkp = out.eigenvectors(k, p);
      const double vkq = out.eigenvectors(k, q);
      out.eigenvectors(k, p) = c * vkp - s * vkq;
      out.eigenvectors(k, q) = c * vkq + s * vkp;
    }
  }
  out.eigenvalues.resize(n);
  for (std::size_t i = 0; i < n; ++i) {
    out.eigenvalues[i] = A(i, i);
  }
  std::vector<std::size_t> order(n);
  for (std::size_t i = 0; i < n; ++i) {
    order[i] = i;
  }
  std::sort(order.begin(), order.end(), [&](std::size_t a, std::size_t b) {
    return out.eigenvalues[a] < out.eigenvalues[b];
  });
  Vector sorted_evals(n);
  Matrix sorted_evecs(n, n);
  for (std::size_t j = 0; j < n; ++j) {
    std::size_t src = order[j];
    sorted_evals[j] = out.eigenvalues[src];
    for (std::size_t i = 0; i < n; ++i) {
      sorted_evecs(i, j) = out.eigenvectors(i, src);
    }
  }
  out.eigenvalues = std::move(sorted_evals);
  out.eigenvectors = std::move(sorted_evecs);
  return out;
}

void generalized_symmetric_eigen(const Matrix& F, const Matrix& S,
                                 Vector& evals, Matrix& evecs) {
  const std::size_t n = F.rows();
  if (F.cols() != n || S.rows() != n || S.cols() != n) {
    throw std::invalid_argument("generalized_symmetric_eigen: dimension mismatch");
  }
  auto chol = cholesky_lower(S);
  if (!chol.ok) {
    throw std::runtime_error("overlap matrix is not positive definite");
  }
  Matrix LinvF(n, n);
  for (std::size_t c = 0; c < n; ++c) {
    Vector col(n);
    for (std::size_t r = 0; r < n; ++r) {
      col[r] = F(r, c);
    }
    cholesky_solve_lower(chol.L, col);
    for (std::size_t r = 0; r < n; ++r) {
      LinvF(r, c) = col[r];
    }
  }
  Matrix Fp(n, n);
  for (std::size_t r = 0; r < n; ++r) {
    Vector row(n);
    for (std::size_t c = 0; c < n; ++c) {
      row[c] = LinvF(r, c);
    }
    cholesky_solve_lower(chol.L, row);
    for (std::size_t c = 0; c < n; ++c) {
      Fp(r, c) = row[c];
    }
  }
  Matrix Fsym = Fp.symmetrize_lower();
  auto decomp = symmetric_eigen_jacobi(std::move(Fsym));
  evals = std::move(decomp.eigenvalues);
  evecs.resize(n, n);
  for (std::size_t c = 0; c < n; ++c) {
    Vector v(n);
    for (std::size_t r = 0; r < n; ++r) {
      v[r] = decomp.eigenvectors(r, c);
    }
    cholesky_solve_lower_trans(chol.L, v);
    for (std::size_t r = 0; r < n; ++r) {
      evecs(r, c) = v[r];
    }
  }
}

Matrix matrix_add(const Matrix& A, const Matrix& B) {
  if (A.rows() != B.rows() || A.cols() != B.cols()) {
    throw std::invalid_argument("matrix_add: dimension mismatch");
  }
  Matrix C(A.rows(), A.cols());
  for (std::size_t i = 0; i < A.rows() * A.cols(); ++i) {
    C.data()[i] = A.data()[i] + B.data()[i];
  }
  return C;
}

void matrix_scale(double s, Matrix& A) {
  const std::size_t n = A.rows() * A.cols();
  for (std::size_t i = 0; i < n; ++i) {
    A.data()[i] *= s;
  }
}

Matrix matrix_copy(const Matrix& A) {
  Matrix B(A.rows(), A.cols());
  for (std::size_t i = 0; i < A.rows() * A.cols(); ++i) {
    B.data()[i] = A.data()[i];
  }
  return B;
}

}  // namespace qc