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