Program Listing for File qmatrix.hpp#
↰ Return to documentation for file (utils/qmatrix.hpp
)
#pragma once
// #include "utils/timer.hpp"
#include <cmath>
#include <complex>
#include <initializer_list>
#include <iostream>
#include <numeric>
// #include <lapacke.h> // comment this if mkl is used
// #include <cblas.h>
// uncomment if you want to use the mkl class
#define MKL_Complex16 std::complex<double>
#include <mkl.h>
#include <mkl_lapacke.h>
// typedef double _Complex DCOMPLEX
// #include <omp.h>
#include <ostream>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <vector>
template <typename NType1>
std::ostream &operator<<(std::ostream &out, const std::vector<NType1> &val) {
for (auto const &aa : val) {
out << aa << " ";
}
out << "\n";
return out;
}
template <typename NType1>
std::ostream &operator<<(std::ostream &out,
const std::vector<std::vector<NType1>> &val) {
out << "\n";
for (auto x : val) {
for (auto y : x) {
out << y << " ";
}
out << "\n";
}
return out;
}
template <typename T> void LOGGER(const std::string &name, T x) {
std::cout << "## " << name << " : " << x << std::endl;
}
// #define logger(name) LOGGER(#name, (name))
// This is a matrix class quamtum(q) operators
// This is also consistent with Lapack_row_MAJOR
using cm_vec = std::vector<std::complex<double>>;
template <class T = double> // default is double
class qmatrix {
std::vector<T> mat{0};
size_t row{0}, column{0}, dim{0};
public:
qmatrix(const std::initializer_list<T> inputVec, size_t _row = 0,
size_t _column = 0)
: mat(inputVec) {
// check if this is a quare matrix
// std::cout << "constructed with a " << inputVec.size() << "-element
// list\n";
if (_row == 0 && _column == 0) {
this->row = std::sqrt(inputVec.size());
this->column = std::sqrt(inputVec.size());
this->dim = inputVec.size();
if (this->dim != this->row * this->column) {
throw std::runtime_error(
"initializer_list: vector is not a square matrix! ");
}
} else {
this->row = _row;
this->column = _column;
this->dim = _column * _row;
}
this->mat.shrink_to_fit();
}
qmatrix(const std::vector<T> &inputVec, size_t _row = 0, // NOLINT
size_t _column = 0) {
// check if this is a quare matrix
if (_row == 0 && _column == 0) {
this->row = std::sqrt(inputVec.size());
this->column = std::sqrt(inputVec.size());
this->dim = inputVec.size();
if (this->dim != this->row * this->column) {
throw std::runtime_error("vector is not a square matrix! ");
}
} else {
this->row = _row;
this->column = _column;
this->dim = _column * _row;
}
this->mat = inputVec;
this->mat.shrink_to_fit();
}
qmatrix(size_t _row, size_t _column, T populate) {
this->row = _row;
this->column = _column;
this->dim = _column * _row;
this->mat = std::vector<T>(_row * _column, populate);
this->mat.shrink_to_fit();
// TODO(sp): restrict memory usage
}
void resize(size_t _row = 0, size_t _column = 0, T populate = 0) {
this->row = _row;
this->column = _column;
this->dim = _column * _row;
this->mat = std::vector<T>(_row * _column, populate);
this->mat.shrink_to_fit();
// TODO(sp): restrict memory usage
}
void clear() {
this->row = 0;
this->column = 0;
this->dim = 0;
this->mat.clear();
this->mat.shrink_to_fit();
} // This is for square matrix
qmatrix(size_t N, T populate) { qmatrix(N, N, populate); }
qmatrix() { qmatrix(0, 0, 0); }
[[nodiscard]] T &operator()(size_t i) { return this->mat[i]; }
[[nodiscard]] T operator()(size_t i) const { return this->mat[i]; }
[[nodiscard]] T &at(size_t i) { return this->mat[i]; }
[[nodiscard]] T at(size_t i) const { return this->mat[i]; }
[[nodiscard]] size_t size() const { return dim; }
[[nodiscard]] size_t getrow() const { return row; }
[[nodiscard]] size_t getcolumn() const { return column; }
[[nodiscard]] T &operator()(size_t i, size_t j) {
return this->mat[i * column + j];
}
[[nodiscard]] T operator()(size_t i, size_t j) const {
return this->mat[i * column + j];
}
// add a at operator
[[nodiscard]] T &at(size_t i, size_t j) { return this->mat[i * column + j]; }
[[nodiscard]] T at(size_t i, size_t j) const {
return this->mat[i * column + j];
}
// TODO(sp): arithmetic operator
// TODO(sp): use stl
[[nodiscard]] T sum() const {
// Sum of all elements
return std::accumulate(this->mat.begin(), this->mat.end(), 0);
}
[[nodiscard]] T absSum() const {
// Sum of all absolute values of the elements
double sum2 = 0;
for (auto aa : this->mat) {
sum2 = sum2 + std::fabs(aa);
}
return sum;
}
[[nodiscard]] T trace() const {
if (this->column == this->row) {
T result{0};
for (size_t i = 0; i < this->row; i++) {
result += this->mat[i * column + i];
}
return result;
}
throw std::runtime_error("Matrix is not square matrix");
}
[[nodiscard]] auto getdiagonal() {
std::vector<T> result(this->row, 0);
for (size_t i = 0; i < this->row; i++) {
result[i] = this->at(i, i);
}
return result;
}
[[nodiscard]] qmatrix<T> id(size_t _row = 0) const {
if (this->row != this->column) {
throw std::runtime_error("Matrix is not square matrix");
}
if (_row == 0) {
_row = this->row;
}
qmatrix<T> result(_row, _row, 0); // reverse the row and column
for (size_t i = 0; i < _row; i++) {
result(i, i) = 1.0;
}
return result;
}
[[nodiscard]] qmatrix<double> real() const {
qmatrix<double> result(this->column, this->row,
0); // reverse the row and column
for (size_t i = 0; i < this->dim; i++) {
result(i) = this->at(i).real();
}
return result;
}
[[nodiscard]] qmatrix<double> imag() const {
qmatrix<double> result(this->column, this->row,
0); // reverse the row and column
for (size_t i = 0; i < this->dim; i++) {
result(i) = this->at(i).imag();
}
return result;
}
[[nodiscard]] qmatrix<T> cTranspose() const { // Conjugate transpose
qmatrix<T> result(this->column, this->row, 0); // reverse the row and column
if constexpr (std::is_same_v<T, std::complex<double>>) {
for (size_t i = 0; i < this->row; i++) {
for (size_t j = 0; j < this->column; j++) {
result(j, i) = std::conj(this->mat[i * column + j]);
}
}
} else {
for (size_t i = 0; i < this->row; i++) {
for (size_t j = 0; j < this->column; j++) {
result(j, i) = this->mat[i * column + j];
}
}
}
return result;
}
[[nodiscard]] qmatrix operator*(const T &x) const {
qmatrix result(this->row, this->column, 0);
#pragma omp parallel for // NOLINT
for (size_t i = 0; i < this->dim; i++) {
result(i) = this->mat.at(i) * x;
}
return result;
}
[[nodiscard]] qmatrix operator/(const T &x) const {
qmatrix result(this->row, this->column, 0);
#pragma omp parallel for // NOLINT
for (size_t i = 0; i < this->dim; i++) {
result(i) = this->mat.at(i) / x;
}
return result;
}
// void reset(const T &x = 0) {
// for (auto &aa : this->mat) {
// aa = x;
// }
// }
T *data() { return this->mat.data(); }
[[nodiscard]] const T *data() const { return this->mat.data(); }
[[nodiscard]] auto begin() const { return this->mat.begin(); }
[[nodiscard]] auto end() const { return this->mat.end(); }
//
//
void display() {}
[[nodiscard]] qmatrix operator+(const qmatrix<T> &rhs) const {
// std::cout << "Started operator+";
if (this->row == rhs.row && this->column == rhs.column) {
qmatrix result(this->row, this->column, 0);
#pragma omp parallel for // NOLINT
for (size_t i = 0; i < this->dim; i++) {
result(i) = this->at(i) + rhs(i);
}
// std::cout << "End of operator+" << std::endl;
return result;
} //
// else throw
throw std::runtime_error("qmatrix have different size for operator+");
}
// Subtract
[[nodiscard]] qmatrix operator-(const qmatrix<T> &rhs) const {
if (this->row == rhs.row && this->column == rhs.column) {
qmatrix result(this->row, this->column, 0);
#pragma omp parallel for // NOLINT
for (size_t i = 0; i < this->dim; i++) {
result(i) = this->mat[i] - rhs(i);
}
return result;
}
throw std::runtime_error("qmatrix have different size for operator -\n");
}
[[nodiscard]] qmatrix<T> dot(const qmatrix<T> &rhs, double talpha = 1.0) {
if (this->column == rhs.row) {
qmatrix result(this->row, rhs.column, 0);
size_t m = this->row;
size_t k = this->column;
size_t n = rhs.column;
if (m == 0 || k == 0 || n == 0) {
// std::cout << "One of the dimension is zero " << std::endl;
return result;
}
const double alpha = talpha;
const double beta = 0;
if constexpr (std::is_same_v<T, double>) {
cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, m, n, k, alpha,
this->data(), k, rhs.data(), n, beta, result.data(), n);
}
if constexpr (std::is_same_v<T, std::complex<double>>) {
cblas_zgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, m, n, k, &alpha,
this->data(), k, rhs.data(), n, &beta, result.data(), n);
}
return result;
}
throw std::runtime_error("dot:qmatrix have different size for dot\n");
}
[[nodiscard]] std::vector<double> diag() {
// This function diagonalizes a symmetric/harmitian matrix.
// So eigen value are always real(double).
// If the matrix is not symmetric the call nonsys_diag
if (this->row != this->column) {
throw std::runtime_error("Error: Matrix is not a square matrix! \n");
}
std::vector<double> w(this->row, 0);
size_t n = w.size();
if (n == 0) {
// std::cout << "This is a empty matrix" << std::endl;
return w;
}
int info = -1;
if constexpr (std::is_same_v<T, double>) {
info = LAPACKE_dsyevd(LAPACK_ROW_MAJOR, 'V', 'U', n, this->mat.data(), n,
w.data());
}
if constexpr (std::is_same_v<T, std::complex<double>>) {
info = LAPACKE_zheevd(
LAPACK_ROW_MAJOR, 'V', 'U', n,
// reinterpret_cast<__complex__ double *>(this->mat.data()), n,
this->mat.data(), n, w.data());
}
// int info= LAPACKE_dsyev( LAPACK_ROW_MAJOR, 'V', 'U', n, a, n, w );
if (info > 0) {
std::cout << "Error:Not able to solve Eigen value problem." << std::endl;
}
return w;
}
[[nodiscard]] std::tuple<qmatrix<T>, qmatrix<T>, cm_vec>
nonsys_diag_complex() {
if (this->row != this->column) {
throw std::runtime_error("Error: Matrix is not a square matrix! \n");
}
std::vector<T> w(this->row, 0);
size_t n = w.size();
qmatrix<T> lv(n, n, 0);
qmatrix<T> rv(n, n, 0);
if constexpr (std::is_same_v<T, std::complex<double>>) {
auto info = LAPACKE_zgeev(
LAPACK_ROW_MAJOR, 'V', 'V', n,
// recast the complex pointer
// reinterpret_cast<__complex__ double *>(this->data()), n,
this->data(), n,
// recast the complex pointer
w.data(),
// recast the complex pointer
lv.data(), n,
// recast the complex pointer
rv.data(), n);
/* Check for convergence */
// Normalize the vectors only for the diagonal elements
#pragma omp parallel for // NOLINT
for (size_t i = 0; i < n; i++) {
std::complex<double> aa{0};
for (size_t k = 0; k < n; k++) {
aa += std::conj(lv(k, i)) * rv(k, i);
}
// if (std::fabs(aa) < 1e-5) {
// // std::cout << "Warning:Normed:" << aa;
//} else {
for (size_t k = 0; k < n; k++) {
lv(k, i) = lv(k, i) / std::conj(std::sqrt(aa));
rv(k, i) = rv(k, i) / (std::sqrt(aa));
}
//}
}
if (info > 0) {
throw std::runtime_error(
"The algorithm LAPACKE_zgeev failed to compute eigenvalues.\n");
}
} else {
throw std::invalid_argument(
"nonsys_diag_complex: This function is for complex matrices");
}
return std::tuple(lv, rv, w);
// return {lv, rv, w};
}
std::tuple<qmatrix<std::complex<T>>, qmatrix<std::complex<T>>,
std::vector<std::complex<T>>>
nonsys_diag_real() {
size_t nsize = this->getrow();
if (this->getrow() != this->getcolumn()) {
throw std::invalid_argument(
"nonsys_diag_real: This is not a square matrix");
}
if constexpr (!std::is_same_v<T, double>) {
throw std::invalid_argument(
"nonsys_diag_real: This is not a qmatrix<double>! ");
}
std::vector<T> wr(nsize, 0);
std::vector<T> wi(nsize, 0);
std::vector<T> vl(nsize * nsize, 0);
std::vector<T> vr(nsize * nsize, 0);
//
// timer t1("Solving exact");
mkl_set_num_threads(200);
auto info =
LAPACKE_dgeev(LAPACK_ROW_MAJOR, 'V', 'V', nsize, this->data(), nsize,
wr.data(), wi.data(), vl.data(), nsize, vr.data(), nsize);
// std::cout << "ExactSolver Done " << t1.getDuration() << std::endl;
if (info > 0) {
std::cout << "The algorithm failed to compute eigenvalues." << std::endl;
exit(1);
}
std::vector<std::complex<T>> eigenvalues(nsize, 0);
qmatrix<std::complex<T>> leftVectors(nsize, nsize, 0);
qmatrix<std::complex<T>> rightVectors(nsize, nsize, 0);
// set the values
#pragma omp parallel for // NOLINT
for (size_t j = 0; j < nsize; j++) {
eigenvalues[j] = std::complex<T>(wr[j], wi[j]);
}
// set eigenvector
// I dont know why the fuck this is organized this way
#pragma omp parallel for // NOLINT
for (size_t i = 0; i < nsize; i++) {
size_t j = 0;
while (j < nsize) {
if (wi[j] == static_cast<T>(0.0)) {
leftVectors(i, j) = vl[i * nsize + j];
rightVectors(i, j) = vr[i * nsize + j];
j++;
} else {
leftVectors(i, j) =
std::complex<T>(vl[i * nsize + j], vl[i * nsize + j + 1]);
leftVectors(i, j + 1) =
std::complex<T>(vl[i * nsize + j], -vl[i * nsize + j + 1]);
rightVectors(i, j) =
std::complex<T>(vr[i * nsize + j], vr[i * nsize + j + 1]);
rightVectors(i, j + 1) =
std::complex<T>(vr[i * nsize + j], -vr[i * nsize + j + 1]);
j += 2;
}
}
}
// NOrmalize the vector
#pragma omp parallel for // NOLINT
for (size_t i = 0; i < nsize; i++) {
std::complex<T> aa{0};
for (size_t k = 0; k < nsize; k++) {
aa += std::conj(leftVectors(k, i)) * rightVectors(k, i);
}
if (std::fabs(aa) < 1e-5) {
// std::cout << "Warning:Normed:" << std::fabs(aa);
} else {
for (size_t k = 0; k < nsize; k++) {
leftVectors(k, i) = leftVectors(k, i) / std::conj(std::sqrt(aa));
rightVectors(k, i) = rightVectors(k, i) / (std::sqrt(aa));
// aa2 += std::conj(lv(k, i)) * rv(k, j);
}
}
// std::cout << std::endl;
}
return {leftVectors, rightVectors, eigenvalues};
}
friend std::ostream &operator<<(std::ostream &out, const qmatrix<T> &val) {
out << "\n";
for (size_t i = 0; i < val.row; ++i) {
for (size_t j = 0; j < val.column; ++j) {
out << val(i, j) << " ";
}
out << "\n";
}
return out;
}
qmatrix<T> krDot(const qmatrix<T> &rhs, double alpha = 1) {
// https://en.wikipedia.org/wiki/Kronecker_product
size_t m = this->row;
size_t n = this->column;
size_t p = rhs.row;
size_t q = rhs.column;
qmatrix<T> result(this->row * rhs.row, this->column * rhs.column, 0);
#pragma omp parallel for // NOLINT
for (size_t r = 0; r < m; r++) {
for (size_t s = 0; s < n; s++) {
for (size_t v = 0; v < p; v++) {
for (size_t w = 0; w < q; w++) {
result((r * p + v), (s * q + w)) =
this->at(r, s) * rhs(v, w) * alpha;
}
}
}
}
return result;
}
void unitary_transform(const qmatrix<T> &eigen_vector) {
// U^T. x . U
// TODO(sp):
auto result = eigen_vector.cTranspose().dot(this->dot(eigen_vector));
*this = result;
}
};