High-precision calculations for one- and two-valence atomic systems
Solvers.ipp
1//==============================================================================
2// Implementations:
3
4extern "C" {
5
6using complex_double = long double;
7// https://gcc.gnu.org/onlinedocs/gcc-4.5.4/gfortran/ISO_005fC_005fBINDING.html
8// on apple M1, long double is just double
9// nb: it actually doesn't matter, since all operations happen on fortran end
10
11void dsyevd_(char *, char *, int *, double *, int *, double *, double *, int *,
12 int *, int *, int *);
13void zheev_(char *, char *, int *, complex_double *, int *, double *,
14 complex_double *, int *, double *, int *);
15void dsygv_(int *, char *, char *, int *, double *, int *, double *, int *,
16 double *, double *, int *, int *);
17void zhegv_(int *, char *, char *, int *, complex_double *, int *,
18 complex_double *, int *, double *, complex_double *, int *,
19 double *, int *);
20
21void dsyevr_(char *jobz, char *range, char *uplo, int *n, double *a, int *lda,
22 double *vl, double *vu, int *il, int *iu, double *abstol, int *m,
23 double *w, double *z, int *ldz, int *isuppz, double *work,
24 int *lwork, int *iwork, int *liwork, int *info);
25
26void dgetrf_(int *m, int *n, double *a, int *lda, int *ipiv, int *info);
27void dgetrs_(char *trans, int *n, int *nrhs, double *a, int *lda, int *ipiv,
28 double *b, int *ldb, int *info);
29void zgetrf_(int *m, int *n, complex_double *a, int *lda, int *ipiv, int *info);
30void zgetrs_(char *trans, int *n, int *nrhs, complex_double *a, int *lda,
31 int *ipiv, complex_double *b, int *ldb, int *info);
32}
33
34namespace LinAlg {
35
36//==============================================================================
37// Solves matrix equation Ax=b for x, for known square matrix A and vector b.
38template <typename T>
40 static_assert(std::is_same_v<T, double> ||
41 std::is_same_v<T, std::complex<double>>,
42 "solve_Axeqb only available for Matrix<double> or "
43 "Matrix<complex<double>>");
44 assert(Am.rows() == b.size());
45
46 // LAPACK solves in place, so start from a copy of b
47 Vector<T> x = b;
48
49 int dim = int(Am.rows());
50 int nrhs = 1;
51 int info = 0;
52 std::vector<int> ipiv(static_cast<std::size_t>(dim));
53
54 // Matrix is row-major, so LAPACK sees the transpose of Am. Factorise that,
55 // then solve with trans='T', which gives Am*x=b
56 char trans = 'T';
57
58 if constexpr (std::is_same_v<T, double>) {
59 dgetrf_(&dim, &dim, Am.data(), &dim, ipiv.data(), &info);
60 dgetrs_(&trans, &dim, &nrhs, Am.data(), &dim, ipiv.data(), x.data(), &dim,
61 &info);
62 } else if constexpr (std::is_same_v<T, std::complex<double>>) {
63 complex_double *Am_ptr = reinterpret_cast<complex_double *>(Am.data());
64 complex_double *x_ptr = reinterpret_cast<complex_double *>(x.data());
65 zgetrf_(&dim, &dim, Am_ptr, &dim, ipiv.data(), &info);
66 zgetrs_(&trans, &dim, &nrhs, Am_ptr, &dim, ipiv.data(), x_ptr, &dim, &info);
67 }
68
69 return x;
70}
71
72//============================================================================*
73// Solves Av = ev for eigenvalues e and eigenvectors v of symmetric/Hermetian
74// matrix A. Returns [e,v], where v(i,j) is the jth element of the ith
75// eigenvector corresponding to ith eigenvalue, e(i). e is always real.
76template <typename T>
77std::pair<Vector<double>, Matrix<T>> symmhEigensystem(Matrix<T> A) {
78 assert(A.rows() == A.cols());
79
80 // E. values of Hermetian complex matrix are _real_
81 auto eigen_vv = std::make_pair(Vector<double>(A.rows()), std::move(A));
82 auto &[e_values, e_vectors] = eigen_vv;
83
84 int dim = (int)e_vectors.rows();
85 char jobz{'V'};
86 char uplo{'U'};
87 int info = 0;
88
89 if constexpr (std::is_same_v<T, double>) {
90
91 // Workspace query
92 int lwork = -1, liwork = -1;
93 double work_query = 0.0;
94 int iwork_query = 0;
95 dsyevd_(&jobz, &uplo, &dim, e_vectors.data(), &dim, e_values.data(),
96 &work_query, &lwork, &iwork_query, &liwork, &info);
97 lwork = static_cast<int>(work_query);
98 liwork = iwork_query;
99 std::vector<double> work(std::size_t(std::max(1, lwork)));
100 std::vector<int> iwork(std::size_t(std::max(1, liwork)));
101 dsyevd_(&jobz, &uplo, &dim, e_vectors.data(), &dim, e_values.data(),
102 work.data(), &lwork, iwork.data(), &liwork, &info);
103
104 } else if constexpr (std::is_same_v<T, std::complex<double>>) {
105 std::vector<T> work(3 * e_vectors.rows());
106 int workspace_size = (int)work.size();
107 // static_assert(sizeof(complex_double) == 16); // LAPACK assumption
108 // nb: this isn't actually required
109
110 // For complex double (Hermetian) matrix
111 std::vector<double> Rwork(3 * e_vectors.rows() - 2ul);
112 complex_double *evec_ptr =
113 reinterpret_cast<complex_double *>(e_vectors.data());
114 complex_double *work_ptr = reinterpret_cast<complex_double *>(work.data());
115 zheev_(&jobz, &uplo, &dim, evec_ptr, &dim, e_values.data(), work_ptr,
116 &workspace_size, Rwork.data(), &info);
117 }
118
119 if (info != 0) {
120 std::cerr << "\nError 91: symmhEigensystem " << info << " " << dim << " "
121 << std::endl;
122 if (info < 0) {
123 std::cerr << "The " << -info << "-th argument had an illegal value\n";
124 } else {
125 std::cerr << "The algorithm failed to converge; " << info
126 << " off-diagonal elements of an intermediate "
127 "tridiagonal form did not converge to zero.\n";
128 }
129 std::cerr << info << " " << A.size() << "\n";
130 }
131
132 return eigen_vv;
133}
134
135//============================================================================*
136template <typename T>
137std::pair<Vector<double>, Matrix<T>> symmhEigensystem(Matrix<T> A, int number) {
138 assert(A.rows() == A.cols());
139 assert(number < (int)A.rows());
140 static_assert(std::is_same_v<T, double>,
141 "This overload uses dsyevr_ (real symmetric). "
142 "For complex Hermitian use zheevr/cheevr.");
143
144 // E. values of Hermetian complex matrix are _real_
145 auto eigen_vv = std::make_pair(Vector<double>(A.rows()), Matrix<T>(A.rows()));
146 auto &[e_values, e_vectors] = eigen_vv;
147
148 int dim = (int)A.rows();
149 char jobz{'V'};
150 char uplo{'U'};
151 char range{'I'};
152 double vl{0.0};
153 double vu{0.0};
154 int il{1};
155 int iu{number};
156 double abstol{1.0e-12};
157 int m{0};
158 int info{0};
159
160 std::vector<int> isuppz(std::size_t(2 * std::max(1, number)));
161
162 // Workspace query
163 int lwork = -1, liwork = -1;
164 double work_query = 0.0;
165 int iwork_query = 0;
166 dsyevr_(&jobz, &range, &uplo, &dim, A.data(), &dim, &vl, &vu, &il, &iu,
167 &abstol, &m, e_values.data(), e_vectors.data(), &dim, isuppz.data(),
168 &work_query, &lwork, &iwork_query, &liwork, &info);
169
170 if (info != 0) {
171 std::cerr << "\nError: dsyevr workspace query info=" << info << "\n";
172 }
173
174 lwork = static_cast<int>(work_query);
175 liwork = iwork_query;
176 std::vector<double> work(std::size_t(std::max(1, lwork)));
177 std::vector<int> iwork(std::size_t(std::max(1, liwork)));
178
179 dsyevr_(&jobz, &range, &uplo, &dim, A.data(), &dim, &vl, &vu, &il, &iu,
180 &abstol, &m, e_values.data(), e_vectors.data(), &dim, isuppz.data(),
181 work.data(), &lwork, iwork.data(), &liwork, &info);
182
183 if (info != 0) {
184 std::cerr << "\nError: dsyevr (range=I) symmhEigensystem info=" << info
185 << " dim=" << dim << "\n";
186 }
187
188 return eigen_vv;
189}
190
191//============================================================================*
192template <typename T>
193std::tuple<int, Vector<double>, Matrix<T>> symmhEigensystem(Matrix<T> A,
194 double all_below) {
195 static_assert(std::is_same_v<T, double>,
196 "This overload uses dsyevr (real symmetric). "
197 "For complex Hermitian use zheevr/cheevr.");
198
199 assert(A.rows() == A.cols());
200 int n = static_cast<int>(A.rows());
201
202 // Return containers (same idea as your code: values size n, vectors n x n)
203 auto eigen_vv =
204 std::make_tuple(int{0}, Vector<double>(A.rows()), Matrix<T>(A.rows()));
205 auto &[num_solutions, e_values, e_vectors] = eigen_vv;
206
207 char jobz{'V'}; // eigenvectors
208 char range{'V'}; // value range
209 char uplo{'U'};
210
211 // dsyevr uses (vl,vu]; pick vl very negative so we get everything <= all_below
212 // Avoid +/-inf: many Fortran ABIs dislike it.
213 double vl = -std::numeric_limits<double>::max();
214 double vu = all_below;
215
216 int il{0}, iu{0}; // unused for RANGE='V'
217 double abstol{1.0e-12}; // your choice; could also use DLAMCH("S")
218 num_solutions = 0;
219 int info{0};
220
221 // Required by dsyevr
222 std::vector<int> isuppz(std::size_t(2 * std::max(1, n)));
223
224 // Workspace query
225 int lwork{-1}, liwork{-1};
226 double work_query{0.0};
227 int iwork_query{0};
228
229 dsyevr_(&jobz, &range, &uplo, &n, A.data(), &n, &vl, &vu, &il, &iu, &abstol,
230 &num_solutions, e_values.data(), e_vectors.data(), &n, isuppz.data(),
231 &work_query, &lwork, &iwork_query, &liwork, &info);
232
233 if (info != 0) {
234 std::cerr << "\nError: symmhEigensystem dsyevr(work query) info=" << info
235 << " n=" << n << "\n";
236 return eigen_vv;
237 }
238
239 lwork = static_cast<int>(work_query);
240 liwork = iwork_query;
241 std::vector<double> work(std::size_t(std::max(1, lwork)));
242 std::vector<int> iwork(std::size_t(std::max(1, liwork)));
243
244 // Actual call
245 dsyevr_(&jobz, &range, &uplo, &n, A.data(), &n, &vl, &vu, &il, &iu, &abstol,
246 &num_solutions, e_values.data(), e_vectors.data(), &n, isuppz.data(),
247 work.data(), &lwork, iwork.data(), &liwork, &info);
248
249 if (info != 0) {
250 std::cerr << "\nError: symmhEigensystem dsyevr info=" << info << " n=" << n
251 << " m=" << num_solutions << "\n";
252 }
253
254 return eigen_vv;
255}
256
257//==============================================================================
258// Solves Av = eBv for eigenvalues e and eigenvectors v of symmetric/Hermetian
259// matrix pair A,B. Returns [e,v], where v(i,j) is the jth element of the ith
260// eigenvector corresponding to ith eigenvalue, e(i). e is always real.
261template <typename T>
262std::pair<Vector<double>, Matrix<T>> symmhEigensystem(Matrix<T> A,
263 Matrix<T> B) {
264 assert(A.rows() == A.cols());
265 assert(B.rows() == B.cols());
266 assert(A.rows() == B.rows());
267
268 // E. values of Hermetian complex matrix are _real_
269 auto eigen_vv = std::make_pair(Vector<double>(A.rows()), std::move(A));
270 auto &[e_values, e_vectors] = eigen_vv;
271
272 int itype = 1;
273 int dim = (int)e_vectors.rows();
274 char jobz{'V'};
275 char uplo{'U'};
276 int info = 0;
277
278 std::vector<T> work(3 * e_vectors.rows());
279 int workspace_size = (int)work.size();
280
281 if constexpr (std::is_same_v<T, double>) {
282
283 // For double (symmetric real) matrix
284 dsygv_(&itype, &jobz, &uplo, &dim, e_vectors.data(), &dim, B.data(), &dim,
285 e_values.data(), work.data(), &workspace_size, &info);
286
287 } else if constexpr (std::is_same_v<T, std::complex<double>>) {
288 // static_assert(sizeof(complex_double) == 16); // LAPACK assumption
289 // nb: this isn't actually required
290
291 // For complex double (Hermetian) matrix
292 std::vector<double> Rwork(3 * e_vectors.rows() - 2ul);
293 complex_double *evec_ptr =
294 reinterpret_cast<complex_double *>(e_vectors.data());
295 complex_double *Bmat_ptr = reinterpret_cast<complex_double *>(B.data());
296 complex_double *work_ptr = reinterpret_cast<complex_double *>(work.data());
297 zhegv_(&itype, &jobz, &uplo, &dim, evec_ptr, &dim, Bmat_ptr, &dim,
298 e_values.data(), work_ptr, &workspace_size, Rwork.data(), &info);
299 e_vectors.conj_in_place(); // ?? why?
300 }
301
302 if (info != 0) {
303 std::cerr << "\nError 198: symmhEigensystem " << info << " " << dim << " "
304 << B.size() << std::endl;
305 if (info < 0) {
306 std::cerr << "The " << -info << "-th argument had an illegal value\n";
307 } else if (info <= dim) {
308 std::cerr << "DSYEV failed to converge; " << info
309 << " off-diagonal elements of an intermediate tridiagonal form "
310 "did not converge to zero;";
311 } else {
312 std::cerr << "The leading minor of order " << info
313 << " of B is not positive definite. "
314 "The factorization of B could not be completed and no "
315 "eigenvalues or eigenvectors were computed.";
316 }
317 }
318
319 return eigen_vv;
320}
321
322//==============================================================================
323// Solves Av = ev for eigenvalues e and eigenvectors v of non-symmetric real
324// matrix A. Returns [e,v], where v(i,j) is the jth element of the ith
325// eigenvector corresponding to ith eigenvalue, e(i). A must be real, while e
326// and v are complex.
327template <typename T>
328std::pair<Vector<std::complex<double>>, Matrix<std::complex<double>>>
330 assert(A.rows() == A.cols());
331 static_assert(std::is_same_v<T, double>,
332 "genEigensystem only for Real matrix");
333
334 // Solves Av = ev for eigenvalues e and eigenvectors v
335 const auto dim = A.rows();
336 auto eigen_vv = std::make_pair(Vector<std::complex<double>>(dim),
337 Matrix<std::complex<double>>(dim));
338 auto &[e_values, e_vectors] = eigen_vv;
339
340 // GSL: This function computes eigenvalues and right eigenvectors of the
341 // n-by-n real nonsymmetric matrix A.
342 auto A_gsl = A.as_gsl_view();
343 auto val_gsl = e_values.as_gsl_view();
344 auto vec_gsl = e_vectors.as_gsl_view();
345 gsl_eigen_nonsymmv_workspace *work = gsl_eigen_nonsymmv_alloc(dim);
346 gsl_eigen_nonsymmv(&A_gsl.matrix, &val_gsl.vector, &vec_gsl.matrix, work);
347 gsl_eigen_nonsymmv_free(work);
348 if (sort)
349 gsl_eigen_nonsymmv_sort(&val_gsl.vector, &vec_gsl.matrix,
350 GSL_EIGEN_SORT_ABS_ASC);
351
352 // Eigensystems using GSL. NOTE: e-vectors are stored in COLUMNS (not rows) of
353 // matrix! Therefore, we transpose the matrix (duuumb)
354 auto tmp = e_vectors.transpose();
355 e_vectors = std::move(tmp);
356
357 return eigen_vv;
358}
359
360} // namespace LinAlg
Row-major dense matrix with arithmetic and linear algebra support.
Definition Matrix.hpp:208
std::size_t size() const
Return rows*columns [total array size].
Definition Matrix.hpp:285
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
Owning 1D array; inherits from Matrix<T> with a single column.
Definition Vector.hpp:25
Linear algebra: matrices, vectors, views, and solvers.
Definition Matrix.hpp:53
std::pair< Vector< std::complex< double > >, Matrix< std::complex< double > > > genEigensystem(Matrix< T > A, bool sort)
Solves Av = ev for all eigenvalues and right eigenvectors of a general (non-symmetric) real matrix A.
Definition Solvers.ipp:329
Vector< T > solve_Axeqb(Matrix< T > Am, const Vector< T > &b)
Solves the linear system Ax = b for x, given square matrix A and vector b.
Definition Solvers.ipp:39
std::pair< Vector< double >, Matrix< T > > symmhEigensystem(Matrix< T > A)
Solves Av = ev for all eigenvalues and eigenvectors of a real symmetric or complex Hermitian matrix A...
Definition Solvers.ipp:77