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