High-precision calculations for one- and two-valence atomic systems
Matrix.ipp
1#pragma once
2
3namespace LinAlg {
4
5//==============================================================================
6// Returns the determinant. Uses GSL; via LU decomposition. Only works for
7// double/complex<double>
8template <typename T>
10 static_assert(std::is_same_v<T, double> ||
11 std::is_same_v<T, std::complex<double>>,
12 "Determinant only works for double");
13
14 assert(rows() == cols() && "Determinant only defined for square matrix");
15 // Make a copy, since this is destructive. (Performs LU decomp)
16 auto LU = *this; // will become LU decomposed version
17 int sLU = 0;
18 auto gsl_view = LU.as_gsl_view();
19 gsl_permutation *permutn = gsl_permutation_alloc(rows());
20 if constexpr (std::is_same_v<T, double>) {
21 gsl_linalg_LU_decomp(&gsl_view.matrix, permutn, &sLU);
22 gsl_permutation_free(permutn);
23 return gsl_linalg_LU_det(&gsl_view.matrix, sLU);
24 } else if constexpr (std::is_same_v<T, std::complex<double>>) {
25 gsl_linalg_complex_LU_decomp(&gsl_view.matrix, permutn, &sLU);
26 gsl_permutation_free(permutn);
27 const auto gsl_cmplx = gsl_linalg_complex_LU_det(&gsl_view.matrix, sLU);
28 // Can probably avoid this copy? doesn't really matter.
29 return {GSL_REAL(gsl_cmplx), GSL_IMAG(gsl_cmplx)};
30 }
31}
32
33//==============================================================================
34// Inverts the matrix, in place. Uses GSL; via LU decomposition. Only works
35// for double/complex<double>.
36template <typename T>
38 static_assert(
39 std::is_same_v<T, double> || std::is_same_v<T, std::complex<double>>,
40 "invert only works for Matrix<double> or Matrix<complex<double>>");
41
42 assert(rows() == cols() && "Inverse only defined for square matrix");
43 int sLU = 0;
44 // gsl_linalg_LU_decomp(m, permutn, &sLU);
45 // gsl_linalg_LU_invx(m, permutn);
46 // In-place inversion gsl_linalg_LU_invx added sometime after GSL v:2.1
47 // Getafix only has 2.1 installed, so can't use this for now
48 auto LU = *this; // copy! to be LU decomposed
49 auto LU_gsl = LU.as_gsl_view();
50 auto iverse_gsl = this->as_gsl_view();
51 gsl_permutation *permutn = gsl_permutation_alloc(m_rows);
52 if constexpr (std::is_same_v<T, double>) {
53 gsl_linalg_LU_decomp(&LU_gsl.matrix, permutn, &sLU);
54 gsl_linalg_LU_invert(&LU_gsl.matrix, permutn, &iverse_gsl.matrix);
55 } else if constexpr (std::is_same_v<T, std::complex<double>>) {
56 gsl_linalg_complex_LU_decomp(&LU_gsl.matrix, permutn, &sLU);
57 gsl_linalg_complex_LU_invert(&LU_gsl.matrix, permutn, &iverse_gsl.matrix);
58 }
59 gsl_permutation_free(permutn);
60 return *this;
61}
62
63//==============================================================================
64template <typename T>
66 Matrix<T> Tr(m_cols, m_rows);
67 if constexpr (std::is_same_v<T, double>) {
68 auto Tr_gsl = Tr.as_gsl_view();
69 const auto this_gsl = as_gsl_view();
70 gsl_matrix_transpose_memcpy(&Tr_gsl.matrix, &this_gsl.matrix);
71 } else if constexpr (std::is_same_v<T, float>) {
72 auto Tr_gsl = Tr.as_gsl_view();
73 const auto this_gsl = as_gsl_view();
74 gsl_matrix_float_transpose_memcpy(&Tr_gsl.matrix, &this_gsl.matrix);
75 } else if constexpr (std::is_same_v<T, std::complex<double>>) {
76 auto Tr_gsl = Tr.as_gsl_view();
77 const auto this_gsl = as_gsl_view();
78 gsl_matrix_complex_transpose_memcpy(&Tr_gsl.matrix, &this_gsl.matrix);
79 } else if constexpr (std::is_same_v<T, std::complex<float>>) {
80 auto Tr_gsl = Tr.as_gsl_view();
81 const auto this_gsl = as_gsl_view();
82 gsl_matrix_complex_float_transpose_memcpy(&Tr_gsl.matrix, &this_gsl.matrix);
83 } else {
84 // backup, works for any type
85 for (auto i = 0ul; i < Tr.rows(); ++i) {
86 for (auto j = 0ul; j < Tr.cols(); ++j) {
87 Tr[i][j] = (*this)[j][i];
88 }
89 }
90 }
91 return Tr;
92}
93
94//==============================================================================
95// Constructs a diagonal unit matrix (identity)
96template <typename T>
98 assert(m_rows == m_cols && "Can only call make_identity() for square matrix");
99 for (auto i = 0ul; i < m_rows; ++i) {
100 for (auto j = 0ul; j < m_cols; ++j) {
101 at(i, j) = i == j ? T(1) : T(0);
102 }
103 }
104 return *this;
105}
106// Sets all elements to zero
107template <typename T>
109 for (std::size_t i = 0; i < size(); ++i) {
110 m_data[i] = T(0);
111 }
112 return *this;
113}
114
115//==============================================================================
116template <typename T>
118 static_assert(is_complex_v<T>, "conj() only available for complex Matrix");
119 std::vector<T> conj_data;
120 conj_data.reserve(m_data.size());
121 for (std::size_t i = 0; i < m_data.size(); ++i) {
122 conj_data.push_back(std::conj(m_data[i]));
123 }
124 return Matrix<T>{m_rows, m_cols, std::move(conj_data)};
125}
126
127template <typename T>
129 static_assert(is_complex_v<T>, "conj() only available for complex Matrix");
130 for (std::size_t i = 0; i < m_data.size(); ++i) {
131 m_data[i] = std::conj(m_data[i]);
132 }
133 return *this;
134}
135//------------------------------------------------------------------------------
136template <typename T>
137auto Matrix<T>::real() const {
138 static_assert(is_complex_v<T>, "real() only available for complex Matrix");
139 std::vector<typename T::value_type> real_data;
140 real_data.reserve(m_data.size());
141 for (std::size_t i = 0; i < m_data.size(); ++i) {
142 real_data.push_back(std::real(m_data[i]));
143 }
144 return Matrix<typename T::value_type>{m_rows, m_cols, std::move(real_data)};
145}
146//------------------------------------------------------------------------------
147template <typename T>
148auto Matrix<T>::imag() const {
149 static_assert(is_complex_v<T>, "imag() only available for complex Matrix");
150 std::vector<typename T::value_type> imag_data;
151 imag_data.reserve(m_data.size());
152 for (std::size_t i = 0; i < m_data.size(); ++i) {
153 imag_data.push_back(std::imag(m_data[i]));
154 }
155 return Matrix<typename T::value_type>{m_rows, m_cols, std::move(imag_data)};
156}
157//------------------------------------------------------------------------------
158template <typename T>
159auto Matrix<T>::complex() const {
160 static_assert(!is_complex_v<T>, "complex() only available for real Matrix");
161 // use move constructor to avoid default Matrix construction
162 std::vector<std::complex<T>> new_data;
163 new_data.reserve(m_data.size());
164 for (std::size_t i = 0; i < m_data.size(); ++i) {
165 new_data.push_back(m_data[i]);
166 }
167 return Matrix<std::complex<T>>{m_rows, m_cols, std::move(new_data)};
168}
169
170//==============================================================================
171template <typename T>
173 assert(rows() == rhs.rows() && cols() == rhs.cols() &&
174 "Matrices must have same dimensions for addition");
175// this->m_data += rhs.m_data;
176#pragma omp parallel for
177 for (auto i = 0ul; i < m_data.size(); ++i) {
178 m_data[i] += rhs.m_data[i];
179 }
180 return *this;
181}
182template <typename T>
184 assert(rows() == rhs.rows() && cols() == rhs.cols() &&
185 "Matrices must have same dimensions for subtraction");
186// using namespace qip::overloads;
187// this->m_data -= rhs.m_data;
188#pragma omp parallel for
189 for (auto i = 0ul; i < m_data.size(); ++i) {
190 m_data[i] -= rhs.m_data[i];
191 }
192 return *this;
193}
194template <typename T>
196// using namespace qip::overloads;
197// this->m_data *= x;
198#pragma omp parallel for
199 for (auto i = 0ul; i < m_data.size(); ++i) {
200 m_data[i] *= x;
201 }
202 return *this;
203}
204template <typename T>
206// using namespace qip::overloads;
207// this->m_data /= x;
208#pragma omp parallel for
209 for (auto i = 0ul; i < m_data.size(); ++i) {
210 m_data[i] /= x;
211 }
212 return *this;
213}
214
215//==============================================================================
216// Matrix<T> += T : T assumed to be *Identity!
217template <typename T>
219 // Adds 'a' to diagonal elements (Assume a*Ident)
220 assert(m_rows == m_cols && "Can only call M+a for square matrix");
221 for (auto i = 0ul; i < m_rows; ++i) {
222 at(i, i) += aI;
223 }
224 return *this;
225}
226// Matrix<T> -= T : T assumed to be *Identity!
227template <typename T>
229 // Adds 'a' to diagonal elements (Assume a*Ident)
230 assert(m_rows == m_cols && "Can only call M-a for square matrix");
231 for (auto i = 0ul; i < m_rows; ++i) {
232 at(i, i) -= aI;
233 }
234 return *this;
235}
236
237//==============================================================================
238template <typename T>
240 assert(rows() == a.rows() && cols() == a.cols() &&
241 "Matrices must have same dimensions for mult_elements_by");
242 for (auto i = 0ul; i < m_data.size(); ++i) {
243 m_data[i] *= a.m_data[i];
244 }
245 return *this;
246}
247
248//==============================================================================
249template <typename T>
250[[nodiscard]] Matrix<T> operator*(const Matrix<T> &a, const Matrix<T> &b) {
251 // https://www.gnu.org/software/gsl/doc/html/blas.html
252 assert(a.cols() == b.rows() &&
253 "Matrices a and b must have correct dimension for multiplication");
254 Matrix<T> product(a.rows(), b.cols());
255
256 GEMM(a, b, &product);
257
258 return product;
259}
260
261//==============================================================================
262template <typename T>
263void GEMM(const Matrix<T> &a, const Matrix<T> &b, Matrix<T> *c, bool trans_A,
264 bool trans_B) {
265 assert(c);
266
267 const auto ta = to_cblas_trans(trans_A);
268 const auto tb = to_cblas_trans(trans_B);
269
270 // Effective dimensions:
271 // op(A): (trans_A ? a.cols x a.rows : a.rows x a.cols)
272 // op(B): (trans_B ? b.cols x b.rows : b.rows x b.cols)
273 const int A_rows = static_cast<int>(trans_A ? a.cols() : a.rows());
274 const int A_cols = static_cast<int>(trans_A ? a.rows() : a.cols());
275 const int B_rows = static_cast<int>(trans_B ? b.cols() : b.rows());
276 const int B_cols = static_cast<int>(trans_B ? b.rows() : b.cols());
277
278 // GEMM sizes: C = op(A) * op(B), where
279 // M = rows(op(A)), N = cols(op(B)), K = cols(op(A)) = rows(op(B))
280 const int M = A_rows;
281 const int N = B_cols;
282 const int K = A_cols;
283
284 assert(A_cols == B_rows && "op(A) cols must equal op(B) rows");
285 assert(static_cast<int>(c->rows()) == M && static_cast<int>(c->cols()) == N &&
286 "Output matrix c must be sized MxN");
287
288 // Row-major leading dimensions:
289 // lda = number of columns in A's *storage* (i.e., a.cols()) regardless of trans
290 // same for b, c
291 const int lda = static_cast<int>(a.cols());
292 const int ldb = static_cast<int>(b.cols());
293 const int ldc = static_cast<int>(c->cols());
294
295 if constexpr (std::is_same_v<T, double>) {
296 cblas_dgemm(CblasRowMajor, ta, tb, M, N, K, 1.0, a.data(), lda, b.data(),
297 ldb, 0.0, c->data(), ldc);
298
299 } else if constexpr (std::is_same_v<T, float>) {
300 cblas_sgemm(CblasRowMajor, ta, tb, M, N, K, 1.0f, a.data(), lda, b.data(),
301 ldb, 0.0f, c->data(), ldc);
302
303 } else if constexpr (std::is_same_v<T, std::complex<double>>) {
304 const std::complex<double> alpha{1.0, 0.0};
305 const std::complex<double> beta{0.0, 0.0};
306 cblas_zgemm(CblasRowMajor, ta, tb, M, N, K, &alpha, a.data(), lda, b.data(),
307 ldb, &beta, c->data(), ldc);
308
309 } else if constexpr (std::is_same_v<T, std::complex<float>>) {
310 const std::complex<float> alpha{1.0f, 0.0f};
311 const std::complex<float> beta{0.0f, 0.0f};
312 cblas_cgemm(CblasRowMajor, ta, tb, M, N, K, &alpha, a.data(), lda, b.data(),
313 ldb, &beta, c->data(), ldc);
314
315 } else {
316 static_assert(!sizeof(T), "GEMM: unsupported scalar type");
317 }
318}
319
320//==============================================================================
321// M_ab = A_ai B_aj C_ij D_ib E_jb, using BLAS
322template <typename T>
323void PENTA_GEMM(const Matrix<T> &A, const Matrix<T> &B, const Matrix<T> &C,
324 const Matrix<T> &D, const Matrix<T> &E, Matrix<T> *pM) {
325 //
326 const auto N = A.rows(); // assume all square
327 assert(A.cols() == A.rows() && "Must be square");
328
329 Matrix<T> X(N, N);
330 Matrix<T> Y(N, N);
331 auto &M = *pM;
332
333 // M_ab = A_ai B_aj C_ij D_ib E_jb
334 // = A_ai B_aj X(i)_jb D_ib
335 // = A_ai Y(i)_ab D_ib
336 // X(i)_jb = C_ij * E_j2;
337 // Y(i)_aj = B_ij * X(i)_jb
338
339 for (std::size_t i = 0; i < N; ++i) {
340 for (std::size_t j = 0; j < N; ++j) {
341 const auto cij = C[i][j];
342 for (std::size_t b = 0; b < N; ++b) {
343 X[j][b] = cij * E[j][b];
344 }
345 }
346 GEMM(B, X, &Y);
347 for (std::size_t a = 0; a < N; ++a) {
348 for (std::size_t b = 0; b < N; ++b) {
349 M[a][b] += A[a][i] * Y[a][b] * D[i][b];
350 }
351 }
352 }
353}
354
355// M_ab = A_ai B_aj C_ij D_ib E_jb
356template <typename T, bool PARALLEL>
357void PENTA(const Matrix<T> &A, const Matrix<T> &B, const Matrix<T> &C,
358 const Matrix<T> &D, const Matrix<T> &E, Matrix<T> *pM) {
359 //
360 const auto N = A.rows(); // assume all square
361 assert(A.cols() == A.rows() && "Must be square");
362
363 auto &M = *pM;
364
365 // M_ab = A_ai B_aj C_ij D_ib E_jb
366 if constexpr (PARALLEL) {
367
368#pragma omp parallel for collapse(2)
369 for (std::size_t a = 0; a < N; ++a) {
370 for (std::size_t b = 0; b < N; ++b) {
371 const T *Ba = &B[a][0];
372 T Mab = T(0);
373 for (std::size_t i = 0; i < N; ++i) {
374 const auto AaiDib = A[a][i] * D[i][b];
375 const T *Ci = &C[i][0];
376 for (std::size_t j = 0; j < N; ++j) {
377 Mab += AaiDib * Ba[j] * Ci[j] * E[j][b];
378 }
379 }
380 M[a][b] = Mab;
382 }
383
384 } else {
385
386 for (std::size_t a = 0; a < N; ++a) {
387 const T *Ba = &B[a][0];
388 for (std::size_t b = 0; b < N; ++b) {
389 T Mab = T(0);
390 for (std::size_t i = 0; i < N; ++i) {
391 const auto AaiDib = A[a][i] * D[i][b];
392 const T *Ci = &C[i][0];
393 for (std::size_t j = 0; j < N; ++j) {
394 Mab += AaiDib * Ba[j] * Ci[j] * E[j][b];
395 }
396 }
397 M[a][b] = Mab;
398 }
399 }
400 }
402
403//==============================================================================
404template <typename T>
406 if constexpr (std::is_same_v<T, double>) {
407 return gsl_matrix_view_array(m_data.data(), m_rows, m_cols);
408 } else if constexpr (std::is_same_v<T, float>) {
409 return gsl_matrix_float_view_array(m_data.data(), m_rows, m_cols);
410 } else if constexpr (std::is_same_v<T, std::complex<double>>) {
411 // reinterpret_cast OK: cppreference.com/w/cpp/numeric/complex
412 return gsl_matrix_complex_view_array(
413 reinterpret_cast<double *>(m_data.data()), m_rows, m_cols);
414 } else if constexpr (std::is_same_v<T, std::complex<float>>) {
415 return gsl_matrix_complex_float_view_array(
416 reinterpret_cast<float *>(m_data.data()), m_rows, m_cols);
417 } else {
418 assert(false && "as_gsl_view() only available for double/float (or complex "
419 "double/float)");
420 }
421}
422
423template <typename T>
425 if constexpr (std::is_same_v<T, double>) {
426 return gsl_matrix_const_view_array(m_data.data(), m_rows, m_cols);
427 } else if constexpr (std::is_same_v<T, float>) {
428 return gsl_matrix_float_const_view_array(m_data.data(), m_rows, m_cols);
429 } else if constexpr (std::is_same_v<T, std::complex<double>>) {
430 return gsl_matrix_complex_const_view_array(
431 reinterpret_cast<const double *>(m_data.data()), m_rows, m_cols);
432 } else if constexpr (std::is_same_v<T, std::complex<float>>) {
433 return gsl_matrix_complex_float_const_view_array(
434 reinterpret_cast<const float *>(m_data.data()), m_rows, m_cols);
435 } else {
436 assert(false && "as_gsl_view() only for available double/float (or complex "
437 "double/float)");
438 }
439}
440
441//==============================================================================
442template <typename T>
443std::ostream &operator<<(std::ostream &os, const Matrix<T> &a) {
444 for (auto i = 0ul; i < a.rows(); ++i) {
445 for (auto j = 0ul; j < a.cols(); ++j) {
446 os << a(i, j) << " ";
447 }
448 os << "\n";
449 }
450 os << "\n";
451 return os;
453
454//==============================================================================
455//==============================================================================
456//==============================================================================
457
458//==============================================================================
459// Helper for equal()
460template <typename T>
461constexpr auto myEps() {
462 if constexpr (std::is_same_v<T, float> ||
463 std::is_same_v<T, std::complex<float>>) {
464 return 1.0e-6f;
465 } else if constexpr (std::is_same_v<T, double> ||
466 std::is_same_v<T, std::complex<double>>) {
467 return 1.0e-12;
468 } else {
469 return 0;
470 }
471}
473// Compares two matrices; returns true iff all elements compare relatively to
474// better than eps
475template <typename T>
476bool equal(const Matrix<T> &lhs, const Matrix<T> &rhs, T eps) {
477 if (lhs.rows() != rhs.rows())
478 return false;
479 if (lhs.cols() != rhs.cols())
480 return false;
481 for (auto i = 0ul; i < lhs.rows(); ++i) {
482 for (auto j = 0ul; j < lhs.cols(); ++j) {
483 // need abs on eps in case of complex
484 if (std::abs(lhs(i, j) - rhs(i, j)) >
485 std::abs(eps * (lhs(i, j) + rhs(i, j))))
486 return false;
488 }
489 return true;
490}
492} // namespace LinAlg
Row-major dense matrix with arithmetic and linear algebra support.
Definition Matrix.hpp:209
Matrix< T > & make_identity()
Constructs a diagonal unit matrix (identity), in place; only for square.
Definition Matrix.ipp:97
Matrix< T > & invert_in_place()
Inverts the matrix in place via LU decomposition (GSL).
Definition Matrix.ipp:37
T * data()
Pointer to first element; for std::complex<T> this is complex<T>*, not T*.
Definition Matrix.hpp:292
std::size_t rows() const
Return rows [major index size].
Definition Matrix.hpp:282
Matrix< T > conj() const
Returns conjugate of matrix.
Definition Matrix.ipp:117
Matrix< T > & operator+=(const Matrix< T > &rhs)
In-place elementwise addition; dimensions must match.
Definition Matrix.ipp:172
auto complex() const
Converts a real to complex matrix (changes type; returns a complex matrix)
Definition Matrix.ipp:159
auto imag() const
Returns imag part of complex matrix (changes type; returns a real matrix)
Definition Matrix.ipp:148
auto as_gsl_view()
Returns a GSL matrix view for use with GSL functions (no copy).
Definition Matrix.ipp:405
Matrix< T > & conj_in_place()
Conjugates matrix, in place.
Definition Matrix.ipp:128
Matrix< T > & operator/=(const T x)
In-place scalar divide: M_ij /= x.
Definition Matrix.ipp:205
Matrix< T > transpose() const
Returns the transpose of the matrix.
Definition Matrix.ipp:65
Matrix< T > & zero()
Sets all elements to zero, in place.
Definition Matrix.ipp:108
Matrix< T > & operator*=(const T x)
In-place scalar multiply: M_ij *= x.
Definition Matrix.ipp:195
std::size_t cols() const
Return columns [minor index size].
Definition Matrix.hpp:284
T determinant() const
Returns the determinant via LU decomposition (GSL).
Definition Matrix.ipp:9
Matrix< T > & mult_elements_by(const Matrix< T > &a)
Elementwise multiply in place: M_ij *= a_ij.
Definition Matrix.ipp:239
auto real() const
Returns real part of complex matrix (changes type; returns a real matrix)
Definition Matrix.ipp:137
Matrix< T > & operator-=(const Matrix< T > &rhs)
In-place elementwise subtraction; dimensions must match.
Definition Matrix.ipp:183
Linear algebra: matrices, vectors, views, and solvers.
Definition Matrix.hpp:54
void PENTA(const Matrix< T > &A, const Matrix< T > &B, const Matrix< T > &C, const Matrix< T > &D, const Matrix< T > &E, Matrix< T > *M)
5-matrix contraction for N*N matrices: M_ab = A_ai B_aj C_ij D_ib E_jb, without BLAS,...
Definition Matrix.ipp:357
void PENTA_GEMM(const Matrix< T > &A, const Matrix< T > &B, const Matrix< T > &C, const Matrix< T > &D, const Matrix< T > &E, Matrix< T > *M)
5-matrix contraction for N*N matrices: M_ab = A_ai B_aj C_ij D_ib E_jb, with BLAS
Definition Matrix.ipp:323
void GEMM(const Matrix< T > &a, const Matrix< T > &b, Matrix< T > *c, bool trans_A=false, bool trans_B=false)
Matrix multiplication C = op(A) * op(B) via CBLAS GEMM (row-major).
Definition Matrix.ipp:263
CBLAS_TRANSPOSE to_cblas_trans(bool trans)
Converts bool to CBLAS_TRANSPOSE enum (CblasTrans if true, CblasNoTrans if false)
Definition Matrix.hpp:547
constexpr auto myEps()
Default relative tolerance for equal(): 1e-6 for float, 1e-12 for double.
Definition Matrix.ipp:461
bool equal(const Matrix< T > &lhs, const Matrix< T > &rhs, T eps=myEps< T >())
Compares two matrices element-wise to within a relative tolerance.
Definition Matrix.ipp:476