827262198f2235545275df0f994773fe767d2237
[alexxy/gromacs.git] / src / gromacs / linearalgebra / eigensolver.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5  * Copyright (c) 2001-2004, The GROMACS development team.
6  * Copyright (c) 2012,2013,2014,2015,2016,2017,2019, by the GROMACS development team, led by
7  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
8  * and including many others, as listed in the AUTHORS file in the
9  * top-level source directory and at http://www.gromacs.org.
10  *
11  * GROMACS is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public License
13  * as published by the Free Software Foundation; either version 2.1
14  * of the License, or (at your option) any later version.
15  *
16  * GROMACS is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with GROMACS; if not, see
23  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
24  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
25  *
26  * If you want to redistribute modifications to GROMACS, please
27  * consider that scientific software is very special. Version
28  * control is crucial - bugs must be traceable. We will be happy to
29  * consider code for inclusion in the official distribution, but
30  * derived work must not be called official GROMACS. Details are found
31  * in the README & COPYING files - if they are missing, get the
32  * official version at http://www.gromacs.org.
33  *
34  * To help us fund GROMACS development, we humbly ask that you cite
35  * the research papers on the package. Check out http://www.gromacs.org.
36  */
37 #include "gmxpre.h"
38
39 #include "eigensolver.h"
40
41 #include "gromacs/linearalgebra/sparsematrix.h"
42 #include "gromacs/utility/fatalerror.h"
43 #include "gromacs/utility/real.h"
44 #include "gromacs/utility/smalloc.h"
45
46 #include "gmx_arpack.h"
47 #include "gmx_lapack.h"
48
49 void eigensolver(real* a, int n, int index_lower, int index_upper, real* eigenvalues, real* eigenvectors)
50 {
51     int*        isuppz;
52     int         lwork, liwork;
53     int         m, iw0, info;
54     real        w0, abstol;
55     int*        iwork;
56     real*       work;
57     real        vl, vu;
58     const char* jobz;
59
60     if (index_lower < 0)
61     {
62         index_lower = 0;
63     }
64
65     if (index_upper >= n)
66     {
67         index_upper = n - 1;
68     }
69
70     /* Make jobz point to the character "V" if eigenvectors
71      * should be calculated, otherwise "N" (only eigenvalues).
72      */
73     jobz = (eigenvectors != nullptr) ? "V" : "N";
74
75     /* allocate lapack stuff */
76     snew(isuppz, 2 * n);
77     vl = vu = 0;
78
79     /* First time we ask the routine how much workspace it needs */
80     lwork  = -1;
81     liwork = -1;
82     abstol = 0;
83
84     /* Convert indices to fortran standard */
85     index_lower++;
86     index_upper++;
87
88     /* Call LAPACK routine using fortran interface. Note that we use upper storage,
89      * but this corresponds to lower storage ("L") in Fortran.
90      */
91 #if GMX_DOUBLE
92     F77_FUNC(dsyevr, DSYEVR)
93     (jobz, "I", "L", &n, a, &n, &vl, &vu, &index_lower, &index_upper, &abstol, &m, eigenvalues,
94      eigenvectors, &n, isuppz, &w0, &lwork, &iw0, &liwork, &info);
95 #else
96     F77_FUNC(ssyevr, SSYEVR)
97     (jobz, "I", "L", &n, a, &n, &vl, &vu, &index_lower, &index_upper, &abstol, &m, eigenvalues,
98      eigenvectors, &n, isuppz, &w0, &lwork, &iw0, &liwork, &info);
99 #endif
100
101     if (info != 0)
102     {
103         sfree(isuppz);
104         gmx_fatal(FARGS, "Internal errror in LAPACK diagonalization.");
105     }
106
107     lwork  = static_cast<int>(w0);
108     liwork = iw0;
109
110     snew(work, lwork);
111     snew(iwork, liwork);
112
113     abstol = 0;
114
115 #if GMX_DOUBLE
116     F77_FUNC(dsyevr, DSYEVR)
117     (jobz, "I", "L", &n, a, &n, &vl, &vu, &index_lower, &index_upper, &abstol, &m, eigenvalues,
118      eigenvectors, &n, isuppz, work, &lwork, iwork, &liwork, &info);
119 #else
120     F77_FUNC(ssyevr, SSYEVR)
121     (jobz, "I", "L", &n, a, &n, &vl, &vu, &index_lower, &index_upper, &abstol, &m, eigenvalues,
122      eigenvectors, &n, isuppz, work, &lwork, iwork, &liwork, &info);
123 #endif
124
125     sfree(isuppz);
126     sfree(work);
127     sfree(iwork);
128
129     if (info != 0)
130     {
131         gmx_fatal(FARGS, "Internal errror in LAPACK diagonalization.");
132     }
133 }
134
135
136 #ifdef GMX_MPI_NOT
137 void sparse_parallel_eigensolver(gmx_sparsematrix_t* A, int neig, real* eigenvalues, real* eigenvectors, int maxiter)
138 {
139     int   iwork[80];
140     int   iparam[11];
141     int   ipntr[11];
142     real* resid;
143     real* workd;
144     real* workl;
145     real* v;
146     int   n;
147     int   ido, info, lworkl, i, ncv, dovec;
148     real  abstol;
149     int*  select;
150     int   iter;
151     int   nnodes, rank;
152
153     MPI_Comm_size(MPI_COMM_WORLD, &nnodes);
154     MPI_Comm_rank(MPI_COMM_WORLD, &rank);
155
156     if (eigenvectors != NULL)
157     {
158         dovec = 1;
159     }
160     else
161     {
162         dovec = 0;
163     }
164
165     n   = A->nrow;
166     ncv = 2 * neig;
167
168     if (ncv > n)
169     {
170         ncv = n;
171     }
172
173     for (i = 0; i < 11; i++)
174     {
175         iparam[i] = ipntr[i] = 0;
176     }
177
178     iparam[0] = 1;       /* Don't use explicit shifts */
179     iparam[2] = maxiter; /* Max number of iterations */
180     iparam[6] = 1;       /* Standard symmetric eigenproblem */
181
182     lworkl = ncv * (8 + ncv);
183     snew(resid, n);
184     snew(workd, (3 * n + 4));
185     snew(workl, lworkl);
186     snew(select, ncv);
187     snew(v, n * ncv);
188
189     /* Use machine tolerance - roughly 1e-16 in double precision */
190     abstol = 0;
191
192     ido = info = 0;
193     fprintf(stderr, "Calculation Ritz values and Lanczos vectors, max %d iterations...\n", maxiter);
194
195     iter = 1;
196     do
197     {
198 #    if GMX_DOUBLE
199         F77_FUNC(pdsaupd, PDSAUPD)
200         (&ido, "I", &n, "SA", &neig, &abstol, resid, &ncv, v, &n, iparam, ipntr, workd, iwork,
201          workl, &lworkl, &info);
202 #    else
203         F77_FUNC(pssaupd, PSSAUPD)
204         (&ido, "I", &n, "SA", &neig, &abstol, resid, &ncv, v, &n, iparam, ipntr, workd, iwork,
205          workl, &lworkl, &info);
206 #    endif
207         if (ido == -1 || ido == 1)
208         {
209             gmx_sparsematrix_vector_multiply(A, workd + ipntr[0] - 1, workd + ipntr[1] - 1);
210         }
211
212         fprintf(stderr, "\rIteration %4d: %3d out of %3d Ritz values converged.", iter++, iparam[4], neig);
213         fflush(stderr);
214     } while (info == 0 && (ido == -1 || ido == 1));
215
216     fprintf(stderr, "\n");
217     if (info == 1)
218     {
219         gmx_fatal(FARGS,
220                   "Maximum number of iterations (%d) reached in Arnoldi\n"
221                   "diagonalization, but only %d of %d eigenvectors converged.\n",
222                   maxiter, iparam[4], neig);
223     }
224     else if (info != 0)
225     {
226         gmx_fatal(FARGS, "Unspecified error from Arnoldi diagonalization:%d\n", info);
227     }
228
229     info = 0;
230     /* Extract eigenvalues and vectors from data */
231     fprintf(stderr, "Calculating eigenvalues and eigenvectors...\n");
232
233 #    if GMX_DOUBLE
234     F77_FUNC(pdseupd, PDSEUPD)
235     (&dovec, "A", select, eigenvalues, eigenvectors, &n, NULL, "I", &n, "SA", &neig, &abstol, resid,
236      &ncv, v, &n, iparam, ipntr, workd, workl, &lworkl, &info);
237 #    else
238     F77_FUNC(psseupd, PSSEUPD)
239     (&dovec, "A", select, eigenvalues, eigenvectors, &n, NULL, "I", &n, "SA", &neig, &abstol, resid,
240      &ncv, v, &n, iparam, ipntr, workd, workl, &lworkl, &info);
241 #    endif
242
243     sfree(v);
244     sfree(resid);
245     sfree(workd);
246     sfree(workl);
247     sfree(select);
248 }
249 #endif
250
251
252 void sparse_eigensolver(gmx_sparsematrix_t* A, int neig, real* eigenvalues, real* eigenvectors, int maxiter)
253 {
254     int   iwork[80];
255     int   iparam[11];
256     int   ipntr[11];
257     real* resid;
258     real* workd;
259     real* workl;
260     real* v;
261     int   n;
262     int   ido, info, lworkl, i, ncv, dovec;
263     real  abstol;
264     int*  select;
265     int   iter;
266
267 #ifdef GMX_MPI_NOT
268     MPI_Comm_size(MPI_COMM_WORLD, &n);
269     if (n > 1)
270     {
271         sparse_parallel_eigensolver(A, neig, eigenvalues, eigenvectors, maxiter);
272         return;
273     }
274 #endif
275
276     if (eigenvectors != nullptr)
277     {
278         dovec = 1;
279     }
280     else
281     {
282         dovec = 0;
283     }
284
285     n   = A->nrow;
286     ncv = 2 * neig;
287
288     if (ncv > n)
289     {
290         ncv = n;
291     }
292
293     for (i = 0; i < 11; i++)
294     {
295         iparam[i] = ipntr[i] = 0;
296     }
297
298     iparam[0] = 1;       /* Don't use explicit shifts */
299     iparam[2] = maxiter; /* Max number of iterations */
300     iparam[6] = 1;       /* Standard symmetric eigenproblem */
301
302     lworkl = ncv * (8 + ncv);
303     snew(resid, n);
304     snew(workd, (3 * n + 4));
305     snew(workl, lworkl);
306     snew(select, ncv);
307     snew(v, n * ncv);
308
309     /* Use machine tolerance - roughly 1e-16 in double precision */
310     abstol = 0;
311
312     ido = info = 0;
313     fprintf(stderr, "Calculation Ritz values and Lanczos vectors, max %d iterations...\n", maxiter);
314
315     iter = 1;
316     do
317     {
318 #if GMX_DOUBLE
319         F77_FUNC(dsaupd, DSAUPD)
320         (&ido, "I", &n, "SA", &neig, &abstol, resid, &ncv, v, &n, iparam, ipntr, workd, iwork,
321          workl, &lworkl, &info);
322 #else
323         F77_FUNC(ssaupd, SSAUPD)
324         (&ido, "I", &n, "SA", &neig, &abstol, resid, &ncv, v, &n, iparam, ipntr, workd, iwork,
325          workl, &lworkl, &info);
326 #endif
327         if (ido == -1 || ido == 1)
328         {
329             gmx_sparsematrix_vector_multiply(A, workd + ipntr[0] - 1, workd + ipntr[1] - 1);
330         }
331
332         fprintf(stderr, "\rIteration %4d: %3d out of %3d Ritz values converged.", iter++, iparam[4], neig);
333         fflush(stderr);
334     } while (info == 0 && (ido == -1 || ido == 1));
335
336     fprintf(stderr, "\n");
337     if (info == 1)
338     {
339         gmx_fatal(FARGS,
340                   "Maximum number of iterations (%d) reached in Arnoldi\n"
341                   "diagonalization, but only %d of %d eigenvectors converged.\n",
342                   maxiter, iparam[4], neig);
343     }
344     else if (info != 0)
345     {
346         gmx_fatal(FARGS, "Unspecified error from Arnoldi diagonalization:%d\n", info);
347     }
348
349     info = 0;
350     /* Extract eigenvalues and vectors from data */
351     fprintf(stderr, "Calculating eigenvalues and eigenvectors...\n");
352
353 #if GMX_DOUBLE
354     F77_FUNC(dseupd, DSEUPD)
355     (&dovec, "A", select, eigenvalues, eigenvectors, &n, nullptr, "I", &n, "SA", &neig, &abstol,
356      resid, &ncv, v, &n, iparam, ipntr, workd, workl, &lworkl, &info);
357 #else
358     F77_FUNC(sseupd, SSEUPD)
359     (&dovec, "A", select, eigenvalues, eigenvectors, &n, nullptr, "I", &n, "SA", &neig, &abstol,
360      resid, &ncv, v, &n, iparam, ipntr, workd, workl, &lworkl, &info);
361 #endif
362
363     sfree(v);
364     sfree(resid);
365     sfree(workd);
366     sfree(workl);
367     sfree(select);
368 }