1 // ---------------------------------------------------------------------
2 //
3 // Copyright (C) 2010 - 2020 by the deal.II authors
4 //
5 // This file is part of the deal.II library.
6 //
7 // The deal.II library is free software; you can use it, redistribute
8 // it, and/or modify it under the terms of the GNU Lesser General
9 // Public License as published by the Free Software Foundation; either
10 // version 2.1 of the License, or (at your option) any later version.
11 // The full text of the license can be found in the file LICENSE.md at
12 // the top level directory of deal.II.
13 //
14 // ---------------------------------------------------------------------
15 
16 #ifndef dealii_parpack_solver_h
17 #define dealii_parpack_solver_h
18 
19 #include <deal.II/base/config.h>
20 
21 #include <deal.II/base/index_set.h>
22 #include <deal.II/base/memory_consumption.h>
23 #include <deal.II/base/smartpointer.h>
24 
25 #include <deal.II/lac/solver_control.h>
26 #include <deal.II/lac/vector_operation.h>
27 
28 #include <cstring>
29 
30 
31 #ifdef DEAL_II_ARPACK_WITH_PARPACK
32 
33 DEAL_II_NAMESPACE_OPEN
34 
35 extern "C"
36 {
37   // http://www.mathkeisan.com/usersguide/man/pdnaupd.html
38   void
39   pdnaupd_(MPI_Fint *comm,
40            int *     ido,
41            char *    bmat,
42            int *     n,
43            char *    which,
44            int *     nev,
45            double *  tol,
46            double *  resid,
47            int *     ncv,
48            double *  v,
49            int *     nloc,
50            int *     iparam,
51            int *     ipntr,
52            double *  workd,
53            double *  workl,
54            int *     lworkl,
55            int *     info);
56 
57   // http://www.mathkeisan.com/usersguide/man/pdsaupd.html
58   void
59   pdsaupd_(MPI_Fint *comm,
60            int *     ido,
61            char *    bmat,
62            int *     n,
63            char *    which,
64            int *     nev,
65            double *  tol,
66            double *  resid,
67            int *     ncv,
68            double *  v,
69            int *     nloc,
70            int *     iparam,
71            int *     ipntr,
72            double *  workd,
73            double *  workl,
74            int *     lworkl,
75            int *     info);
76 
77   // http://www.mathkeisan.com/usersguide/man/pdneupd.html
78   void
79   pdneupd_(MPI_Fint *comm,
80            int *     rvec,
81            char *    howmany,
82            int *     select,
83            double *  d,
84            double *  di,
85            double *  z,
86            int *     ldz,
87            double *  sigmar,
88            double *  sigmai,
89            double *  workev,
90            char *    bmat,
91            int *     n,
92            char *    which,
93            int *     nev,
94            double *  tol,
95            double *  resid,
96            int *     ncv,
97            double *  v,
98            int *     nloc,
99            int *     iparam,
100            int *     ipntr,
101            double *  workd,
102            double *  workl,
103            int *     lworkl,
104            int *     info);
105 
106   // http://www.mathkeisan.com/usersguide/man/pdseupd.html
107   void
108   pdseupd_(MPI_Fint *comm,
109            int *     rvec,
110            char *    howmany,
111            int *     select,
112            double *  d,
113            double *  z,
114            int *     ldz,
115            double *  sigmar,
116            char *    bmat,
117            int *     n,
118            char *    which,
119            int *     nev,
120            double *  tol,
121            double *  resid,
122            int *     ncv,
123            double *  v,
124            int *     nloc,
125            int *     iparam,
126            int *     ipntr,
127            double *  workd,
128            double *  workl,
129            int *     lworkl,
130            int *     info);
131 
132   // other resources:
133   //    http://acts.nersc.gov/superlu/example5/pnslac.c.html
134   //    https://github.com/phpisciuneri/tijo/blob/master/dvr_parpack.cpp
135 }
136 
137 /**
138  * Interface for using PARPACK. PARPACK is a collection of Fortran77
139  * subroutines designed to solve large scale eigenvalue problems. Here we
140  * interface to the routines <code>pdneupd</code>, <code>pdseupd</code>,
141  * <code>pdnaupd</code>, <code>pdsaupd</code> of PARPACK.  The package is
142  * designed to compute a few eigenvalues and corresponding eigenvectors of a
143  * general n by n matrix A. It is most appropriate for large sparse matrices
144  * A.
145  *
146  * In this class we make use of the method applied to the generalized
147  * eigenspectrum problem $(A-\lambda B)x=0$, for $x\neq0$; where $A$ is a
148  * system matrix, $B$ is a mass matrix, and $\lambda, x$ are a set of
149  * eigenvalues and eigenvectors respectively.
150  *
151  * The ArpackSolver can be used in application codes in the following way:
152  * @code
153  *   SolverControl solver_control (1000, 1e-9);
154  *   const unsigned int num_arnoldi_vectors = 2*size_of_spectrum + 2;
155  *   PArpackSolver<V>::AdditionalData
156  *     additional_data(num_arnoldi_vectors,
157  *                     dealii::PArpackSolver<V>::largest_magnitude,
158  *                     true);
159  *
160  *    PArpackSolver<V> eigensolver (solver_control,
161  *                                  mpi_communicator,
162  *                                  additional_data);
163  *    eigensolver.set_shift(sigma);
164  *    eigensolver.reinit(locally_owned_dofs);
165  *    eigensolver.solve (A,
166  *                       B,
167  *                       OP,
168  *                       lambda,
169  *                       x,
170  *                       size_of_spectrum);
171  * @endcode
172  * for the generalized eigenvalue problem $Ax=B\lambda x$, where the variable
173  * <code>size_of_spectrum</code> tells PARPACK the number of
174  * eigenvector/eigenvalue pairs to solve for. Here, <code>lambda</code> is a
175  * vector that will contain the eigenvalues computed, <code>x</code> a vector
176  * of objects of type <code>V</code> that will contain the eigenvectors
177  * computed.
178  *
179  * Currently, only three modes of (P)Arpack are implemented. In mode 3
180  * (default), <code>OP</code> is an inverse operation for the matrix <code>A -
181  * sigma * B</code>, where <code> sigma </code> is a shift value, set to zero
182  * by default. Whereas in mode 2, <code>OP</code> is an inverse of
183  * <code>M</code>. Finally, mode 1 corresponds to standard eigenvalue problem
184  * without spectral transformation $Ax=\lambda x$. The mode can be specified via
185  * AdditionalData object. Note that for shift-and-invert (mode=3), the sought
186  * eigenpairs are those after the spectral transformation is applied.
187  *
188  * The <code>OP</code> can be specified by using a LinearOperator:
189  * @code
190  *   const double shift = 5.0;
191  *   const auto op_A = linear_operator<vector_t>(A);
192  *   const auto op_B = linear_operator<vector_t>(B);
193  *   const auto op_shift = op_A - shift * op_B;
194  *   SolverControl solver_control_lin (1000, 1e-10,false,false);
195  *
196  *   SolverCG<vector_t> cg(solver_control_lin);
197  *   const auto op_shift_invert =
198  *     inverse_operator(op_shift, cg, PreconditionIdentity ());
199  * @endcode
200  *
201  * The class is intended to be used with MPI and can work on arbitrary vector
202  * and matrix distributed classes.  Both symmetric and non-symmetric
203  * <code>A</code> are supported.
204  *
205  * For further information on how the PARPACK routines <code>pdneupd</code>,
206  * <code>pdseupd</code>, <code>pdnaupd</code>, <code>pdsaupd</code> work and
207  * also how to set the parameters appropriately please take a look into the
208  * PARPACK manual.
209  */
210 template <typename VectorType>
211 class PArpackSolver : public Subscriptor
212 {
213 public:
214   /**
215    * Declare the type for container size.
216    */
217   using size_type = types::global_dof_index;
218 
219   /**
220    * An enum that lists the possible choices for which eigenvalues to compute
221    * in the solve() function. Note, that this corresponds to the problem after
222    * shift-and-invert (the only currently supported spectral transformation)
223    * is applied.
224    *
225    * A particular choice is limited based on symmetric or non-symmetric matrix
226    * <code>A</code> considered.
227    */
228   enum WhichEigenvalues
229   {
230     /**
231      * The algebraically largest eigenvalues.
232      */
233     algebraically_largest,
234     /**
235      * The algebraically smallest eigenvalues.
236      */
237     algebraically_smallest,
238     /**
239      * The eigenvalue with the largest magnitudes.
240      */
241     largest_magnitude,
242     /**
243      * The eigenvalue with the smallest magnitudes.
244      */
245     smallest_magnitude,
246     /**
247      * The eigenvalues with the largest real parts.
248      */
249     largest_real_part,
250     /**
251      * The eigenvalues with the smallest real parts.
252      */
253     smallest_real_part,
254     /**
255      * The eigenvalues with the largest imaginary parts.
256      */
257     largest_imaginary_part,
258     /**
259      * The eigenvalues with the smallest imaginary parts.
260      */
261     smallest_imaginary_part,
262     /**
263      * Compute half of the eigenvalues from the high end of the spectrum and
264      * the other half from the low end. If the number of requested
265      * eigenvectors is odd, then the extra eigenvector comes from the high end
266      * of the spectrum.
267      */
268     both_ends
269   };
270 
271   /**
272    * Standardized data struct to pipe additional data to the solver, should it
273    * be needed.
274    */
275   struct AdditionalData
276   {
277     const unsigned int     number_of_arnoldi_vectors;
278     const WhichEigenvalues eigenvalue_of_interest;
279     const bool             symmetric;
280     const int              mode;
281     AdditionalData(
282       const unsigned int     number_of_arnoldi_vectors = 15,
283       const WhichEigenvalues eigenvalue_of_interest    = largest_magnitude,
284       const bool             symmetric                 = false,
285       const int              mode                      = 3);
286   };
287 
288   /**
289    * Access to the object that controls convergence.
290    */
291   SolverControl &
292   control() const;
293 
294   /**
295    * Constructor.
296    */
297   PArpackSolver(SolverControl &       control,
298                 const MPI_Comm &      mpi_communicator,
299                 const AdditionalData &data = AdditionalData());
300 
301   /**
302    * Initialize internal variables.
303    */
304   void
305   reinit(const IndexSet &locally_owned_dofs);
306 
307   /**
308    * Initialize internal variables when working with BlockVectors.
309    * @p locally_owned_dofs is used to set the dimension of the problem,
310    * whereas @p partitioning is used for calling the reinit of the deal.II
311    * blockvectors used.
312    */
313   void
314   reinit(const IndexSet &             locally_owned_dofs,
315          const std::vector<IndexSet> &partitioning);
316 
317   /**
318    * Initialize internal variables from the input @p distributed_vector.
319    */
320   void
321   reinit(const VectorType &distributed_vector);
322 
323   /**
324    * Set initial vector for building Krylov space.
325    */
326   void
327   set_initial_vector(const VectorType &vec);
328 
329   /**
330    * Set shift @p sigma for shift-and-invert spectral transformation.
331    *
332    * If this function is not called, the shift is assumed to be zero.
333    *
334    * @note only relevant for <code>mode=3</code> (see the general documentation of this
335    * class for a definition of what the different modes are).
336    */
337   void
338   set_shift(const std::complex<double> sigma);
339 
340   /**
341    * Solve the generalized eigensprectrum problem $A x=\lambda B x$ by calling
342    * the <code>pd(n/s)eupd</code> and <code>pd(n/s)aupd</code> functions of
343    * PARPACK.
344    *
345    * In <code>mode=3</code>, @p inverse should correspond to $[A-\sigma B]^{-1}$,
346    * whereas in <code>mode=2</code> it should represent $B^{-1}$. For
347    * <code>mode=1</code> both @p B and @p inverse are ignored.
348    */
349   template <typename MatrixType1, typename MatrixType2, typename INVERSE>
350   void
351   solve(const MatrixType1 &                A,
352         const MatrixType2 &                B,
353         const INVERSE &                    inverse,
354         std::vector<std::complex<double>> &eigenvalues,
355         std::vector<VectorType> &          eigenvectors,
356         const unsigned int                 n_eigenvalues);
357 
358   /**
359    * Same as above but takes eigenvectors as pointers.
360    */
361   template <typename MatrixType1, typename MatrixType2, typename INVERSE>
362   void
363   solve(const MatrixType1 &                A,
364         const MatrixType2 &                B,
365         const INVERSE &                    inverse,
366         std::vector<std::complex<double>> &eigenvalues,
367         std::vector<VectorType *> &        eigenvectors,
368         const unsigned int                 n_eigenvalues);
369 
370   /**
371    * Return the memory consumption of this class in bytes.
372    */
373   std::size_t
374   memory_consumption() const;
375 
376 protected:
377   /**
378    * Reference to the object that controls convergence of the iterative
379    * solver.
380    */
381   SolverControl &solver_control;
382 
383   /**
384    * Store a copy of the flags for this particular solver.
385    */
386   const AdditionalData additional_data;
387 
388   // keep MPI communicator non-const as Arpack functions are not const either:
389 
390   /**
391    * C++ MPI communicator.
392    */
393   MPI_Comm mpi_communicator;
394 
395   /**
396    * Fortran MPI communicator.
397    */
398   MPI_Fint mpi_communicator_fortran;
399 
400   // C++98 guarantees that the elements of a vector are stored contiguously
401 
402   /**
403    * Length of the work array workl.
404    */
405   int lworkl;
406 
407   /**
408    * Double precision  work array of length lworkl
409    */
410   std::vector<double> workl;
411 
412   /**
413    * Double precision  work array of length 3*N
414    */
415   std::vector<double> workd;
416 
417   /**
418    * Number of local degrees of freedom.
419    */
420   int nloc;
421 
422   /**
423    * Number of Arnoldi basis vectors specified in additional_data
424    */
425   int ncv;
426 
427 
428   /**
429    * The leading dimension of the array v
430    */
431   int ldv;
432 
433   /**
434    * Double precision vector of size ldv by NCV.  Will contains the final set
435    * of Arnoldi basis vectors.
436    */
437   std::vector<double> v;
438 
439   /**
440    * An auxiliary flag which is set to true when initial vector is provided.
441    */
442   bool initial_vector_provided;
443 
444   /**
445    * The initial residual vector, possibly from a previous run.  On output, it
446    * contains the final residual vector.
447    */
448   std::vector<double> resid;
449 
450   /**
451    * The leading dimension of the array Z equal to nloc.
452    */
453   int ldz;
454 
455   /**
456    * A vector of minimum size of nloc by NEV+1.  Z contains the B-orthonormal
457    * Ritz vectors of the eigensystem A*z = lambda*B*z corresponding to the
458    * Ritz value approximations.
459    */
460   std::vector<double> z;
461 
462   /**
463    * The size of the workev array.
464    */
465   int lworkev;
466 
467   /**
468    * Double precision  work array of dimension 3* NCV.
469    */
470   std::vector<double> workev;
471 
472   /**
473    * A vector of dimension NCV.
474    */
475   std::vector<int> select;
476 
477   /**
478    * Temporary vectors used between Arpack and deal.II
479    */
480   VectorType src, dst, tmp;
481 
482   /**
483    * Indices of local degrees of freedom.
484    */
485   std::vector<types::global_dof_index> local_indices;
486 
487   /**
488    * Real part of the shift
489    */
490   double sigmar;
491 
492   /**
493    * Imaginary part of the shift
494    */
495   double sigmai;
496 
497 private:
498   /**
499    * Initialize internal variables which depend on
500    * @p locally_owned_dofs.
501    *
502    * This function is called inside the reinit() functions
503    */
504   void
505   internal_reinit(const IndexSet &locally_owned_dofs);
506 
507   /**
508    * PArpackExcInfoPdnaupds.
509    */
510   DeclException2(PArpackExcConvergedEigenvectors,
511                  int,
512                  int,
513                  << arg1 << " eigenpairs were requested, but only " << arg2
514                  << " converged");
515 
516   DeclException2(PArpackExcInvalidNumberofEigenvalues,
517                  int,
518                  int,
519                  << "Number of wanted eigenvalues " << arg1
520                  << " is larger that the size of the matrix " << arg2);
521 
522   DeclException2(PArpackExcInvalidEigenvectorSize,
523                  int,
524                  int,
525                  << "Number of wanted eigenvalues " << arg1
526                  << " is larger that the size of eigenvectors " << arg2);
527 
528   DeclException2(
529     PArpackExcInvalidEigenvectorSizeNonsymmetric,
530     int,
531     int,
532     << "To store the real and complex parts of " << arg1
533     << " eigenvectors in real-valued vectors, their size (currently set to "
534     << arg2 << ") should be greater than or equal to " << arg1 + 1);
535 
536   DeclException2(PArpackExcInvalidEigenvalueSize,
537                  int,
538                  int,
539                  << "Number of wanted eigenvalues " << arg1
540                  << " is larger that the size of eigenvalues " << arg2);
541 
542   DeclException2(PArpackExcInvalidNumberofArnoldiVectors,
543                  int,
544                  int,
545                  << "Number of Arnoldi vectors " << arg1
546                  << " is larger that the size of the matrix " << arg2);
547 
548   DeclException2(PArpackExcSmallNumberofArnoldiVectors,
549                  int,
550                  int,
551                  << "Number of Arnoldi vectors " << arg1
552                  << " is too small to obtain " << arg2 << " eigenvalues");
553 
554   DeclException1(PArpackExcIdo,
555                  int,
556                  << "This ido " << arg1
557                  << " is not supported. Check documentation of ARPACK");
558 
559   DeclException1(PArpackExcMode,
560                  int,
561                  << "This mode " << arg1
562                  << " is not supported. Check documentation of ARPACK");
563 
564   DeclException1(PArpackExcInfoPdnaupd,
565                  int,
566                  << "Error with Pdnaupd, info " << arg1
567                  << ". Check documentation of ARPACK");
568 
569   DeclException1(PArpackExcInfoPdneupd,
570                  int,
571                  << "Error with Pdneupd, info " << arg1
572                  << ". Check documentation of ARPACK");
573 
574   DeclException1(PArpackExcInfoMaxIt,
575                  int,
576                  << "Maximum number " << arg1 << " of iterations reached.");
577 
578   DeclException1(PArpackExcNoShifts,
579                  int,
580                  << "No shifts could be applied during implicit"
581                  << " Arnoldi update, try increasing the number of"
582                  << " Arnoldi vectors.");
583 };
584 
585 
586 
587 template <typename VectorType>
588 std::size_t
memory_consumption()589 PArpackSolver<VectorType>::memory_consumption() const
590 {
591   return MemoryConsumption::memory_consumption(double()) *
592            (workl.size() + workd.size() + v.size() + resid.size() + z.size() +
593             workev.size()) +
594          src.memory_consumption() + dst.memory_consumption() +
595          tmp.memory_consumption() +
596          MemoryConsumption::memory_consumption(types::global_dof_index()) *
597            local_indices.size();
598 }
599 
600 
601 
602 template <typename VectorType>
AdditionalData(const unsigned int number_of_arnoldi_vectors,const WhichEigenvalues eigenvalue_of_interest,const bool symmetric,const int mode)603 PArpackSolver<VectorType>::AdditionalData::AdditionalData(
604   const unsigned int     number_of_arnoldi_vectors,
605   const WhichEigenvalues eigenvalue_of_interest,
606   const bool             symmetric,
607   const int              mode)
608   : number_of_arnoldi_vectors(number_of_arnoldi_vectors)
609   , eigenvalue_of_interest(eigenvalue_of_interest)
610   , symmetric(symmetric)
611   , mode(mode)
612 {
613   // Check for possible options for symmetric problems
614   if (symmetric)
615     {
616       Assert(
617         eigenvalue_of_interest != largest_real_part,
618         ExcMessage(
619           "'largest real part' can only be used for non-symmetric problems!"));
620       Assert(
621         eigenvalue_of_interest != smallest_real_part,
622         ExcMessage(
623           "'smallest real part' can only be used for non-symmetric problems!"));
624       Assert(
625         eigenvalue_of_interest != largest_imaginary_part,
626         ExcMessage(
627           "'largest imaginary part' can only be used for non-symmetric problems!"));
628       Assert(
629         eigenvalue_of_interest != smallest_imaginary_part,
630         ExcMessage(
631           "'smallest imaginary part' can only be used for non-symmetric problems!"));
632     }
633   Assert(mode >= 1 && mode <= 3,
634          ExcMessage("Currently, only modes 1, 2 and 3 are supported."));
635 }
636 
637 
638 
639 template <typename VectorType>
PArpackSolver(SolverControl & control,const MPI_Comm & mpi_communicator,const AdditionalData & data)640 PArpackSolver<VectorType>::PArpackSolver(SolverControl &       control,
641                                          const MPI_Comm &      mpi_communicator,
642                                          const AdditionalData &data)
643   : solver_control(control)
644   , additional_data(data)
645   , mpi_communicator(mpi_communicator)
646   , mpi_communicator_fortran(MPI_Comm_c2f(mpi_communicator))
647   , lworkl(0)
648   , nloc(0)
649   , ncv(0)
650   , ldv(0)
651   , initial_vector_provided(false)
652   , ldz(0)
653   , lworkev(0)
654   , sigmar(0.0)
655   , sigmai(0.0)
656 {}
657 
658 
659 
660 template <typename VectorType>
661 void
set_shift(const std::complex<double> sigma)662 PArpackSolver<VectorType>::set_shift(const std::complex<double> sigma)
663 {
664   sigmar = sigma.real();
665   sigmai = sigma.imag();
666 }
667 
668 
669 
670 template <typename VectorType>
671 void
set_initial_vector(const VectorType & vec)672 PArpackSolver<VectorType>::set_initial_vector(const VectorType &vec)
673 {
674   initial_vector_provided = true;
675   Assert(resid.size() == local_indices.size(),
676          ExcDimensionMismatch(resid.size(), local_indices.size()));
677   vec.extract_subvector_to(local_indices.begin(),
678                            local_indices.end(),
679                            resid.data());
680 }
681 
682 
683 
684 template <typename VectorType>
685 void
internal_reinit(const IndexSet & locally_owned_dofs)686 PArpackSolver<VectorType>::internal_reinit(const IndexSet &locally_owned_dofs)
687 {
688   // store local indices to write to vectors
689   locally_owned_dofs.fill_index_vector(local_indices);
690 
691   // scalars
692   nloc = locally_owned_dofs.n_elements();
693   ncv  = additional_data.number_of_arnoldi_vectors;
694 
695   AssertDimension(local_indices.size(), nloc);
696 
697   // vectors
698   ldv = nloc;
699   v.resize(ldv * ncv, 0.0);
700 
701   resid.resize(nloc, 1.0);
702 
703   // work arrays for ARPACK
704   workd.resize(3 * nloc, 0.0);
705 
706   lworkl =
707     additional_data.symmetric ? ncv * ncv + 8 * ncv : 3 * ncv * ncv + 6 * ncv;
708   workl.resize(lworkl, 0.);
709 
710   ldz = nloc;
711   z.resize(ldz * ncv, 0.); // TODO we actually need only ldz*nev
712 
713   // WORKEV  Double precision  work array of dimension 3*NCV.
714   lworkev = additional_data.symmetric ? 0 /*not used in symmetric case*/
715                                         :
716                                         3 * ncv;
717   workev.resize(lworkev, 0.);
718 
719   select.resize(ncv, 0);
720 }
721 
722 
723 
724 template <typename VectorType>
725 void
reinit(const IndexSet & locally_owned_dofs)726 PArpackSolver<VectorType>::reinit(const IndexSet &locally_owned_dofs)
727 {
728   internal_reinit(locally_owned_dofs);
729 
730   // deal.II vectors:
731   src.reinit(locally_owned_dofs, mpi_communicator);
732   dst.reinit(locally_owned_dofs, mpi_communicator);
733   tmp.reinit(locally_owned_dofs, mpi_communicator);
734 }
735 
736 
737 
738 template <typename VectorType>
739 void
reinit(const VectorType & distributed_vector)740 PArpackSolver<VectorType>::reinit(const VectorType &distributed_vector)
741 {
742   internal_reinit(distributed_vector.locally_owned_elements());
743 
744   // deal.II vectors:
745   src.reinit(distributed_vector);
746   dst.reinit(distributed_vector);
747   tmp.reinit(distributed_vector);
748 }
749 
750 
751 
752 template <typename VectorType>
753 void
reinit(const IndexSet & locally_owned_dofs,const std::vector<IndexSet> & partitioning)754 PArpackSolver<VectorType>::reinit(const IndexSet &locally_owned_dofs,
755                                   const std::vector<IndexSet> &partitioning)
756 {
757   internal_reinit(locally_owned_dofs);
758 
759   // deal.II vectors:
760   src.reinit(partitioning, mpi_communicator);
761   dst.reinit(partitioning, mpi_communicator);
762   tmp.reinit(partitioning, mpi_communicator);
763 }
764 
765 
766 
767 template <typename VectorType>
768 template <typename MatrixType1, typename MatrixType2, typename INVERSE>
769 void
solve(const MatrixType1 & A,const MatrixType2 & B,const INVERSE & inverse,std::vector<std::complex<double>> & eigenvalues,std::vector<VectorType> & eigenvectors,const unsigned int n_eigenvalues)770 PArpackSolver<VectorType>::solve(const MatrixType1 &                A,
771                                  const MatrixType2 &                B,
772                                  const INVERSE &                    inverse,
773                                  std::vector<std::complex<double>> &eigenvalues,
774                                  std::vector<VectorType> &eigenvectors,
775                                  const unsigned int       n_eigenvalues)
776 {
777   std::vector<VectorType *> eigenvectors_ptr(eigenvectors.size());
778   for (unsigned int i = 0; i < eigenvectors.size(); ++i)
779     eigenvectors_ptr[i] = &eigenvectors[i];
780   solve(A, B, inverse, eigenvalues, eigenvectors_ptr, n_eigenvalues);
781 }
782 
783 
784 
785 template <typename VectorType>
786 template <typename MatrixType1, typename MatrixType2, typename INVERSE>
787 void
solve(const MatrixType1 & system_matrix,const MatrixType2 & mass_matrix,const INVERSE & inverse,std::vector<std::complex<double>> & eigenvalues,std::vector<VectorType * > & eigenvectors,const unsigned int n_eigenvalues)788 PArpackSolver<VectorType>::solve(const MatrixType1 &system_matrix,
789                                  const MatrixType2 &mass_matrix,
790                                  const INVERSE &    inverse,
791                                  std::vector<std::complex<double>> &eigenvalues,
792                                  std::vector<VectorType *> &eigenvectors,
793                                  const unsigned int         n_eigenvalues)
794 {
795   if (additional_data.symmetric)
796     {
797       Assert(n_eigenvalues <= eigenvectors.size(),
798              PArpackExcInvalidEigenvectorSize(n_eigenvalues,
799                                               eigenvectors.size()));
800     }
801   else
802     Assert(n_eigenvalues + 1 <= eigenvectors.size(),
803            PArpackExcInvalidEigenvectorSizeNonsymmetric(n_eigenvalues,
804                                                         eigenvectors.size()));
805 
806   Assert(n_eigenvalues <= eigenvalues.size(),
807          PArpackExcInvalidEigenvalueSize(n_eigenvalues, eigenvalues.size()));
808 
809 
810   // use eigenvectors to get the problem size so that it is possible to
811   // employ LinearOperator for mass_matrix.
812   Assert(n_eigenvalues < eigenvectors[0]->size(),
813          PArpackExcInvalidNumberofEigenvalues(n_eigenvalues,
814                                               eigenvectors[0]->size()));
815 
816   Assert(additional_data.number_of_arnoldi_vectors < eigenvectors[0]->size(),
817          PArpackExcInvalidNumberofArnoldiVectors(
818            additional_data.number_of_arnoldi_vectors, eigenvectors[0]->size()));
819 
820   Assert(additional_data.number_of_arnoldi_vectors > 2 * n_eigenvalues + 1,
821          PArpackExcSmallNumberofArnoldiVectors(
822            additional_data.number_of_arnoldi_vectors, n_eigenvalues));
823 
824   int mode = additional_data.mode;
825 
826   // reverse communication parameter
827   // must be zero on the first call to pdnaupd
828   int ido = 0;
829 
830   // 'G' generalized eigenvalue problem
831   // 'I' standard eigenvalue problem
832   char bmat[2];
833   bmat[0] = (mode == 1) ? 'I' : 'G';
834   bmat[1] = '\0';
835 
836   // Specify the eigenvalues of interest, possible parameters:
837   // "LA" algebraically largest
838   // "SA" algebraically smallest
839   // "LM" largest magnitude
840   // "SM" smallest magnitude
841   // "LR" largest real part
842   // "SR" smallest real part
843   // "LI" largest imaginary part
844   // "SI" smallest imaginary part
845   // "BE" both ends of spectrum simultaneous
846   char which[3];
847   switch (additional_data.eigenvalue_of_interest)
848     {
849       case algebraically_largest:
850         std::strcpy(which, "LA");
851         break;
852       case algebraically_smallest:
853         std::strcpy(which, "SA");
854         break;
855       case largest_magnitude:
856         std::strcpy(which, "LM");
857         break;
858       case smallest_magnitude:
859         std::strcpy(which, "SM");
860         break;
861       case largest_real_part:
862         std::strcpy(which, "LR");
863         break;
864       case smallest_real_part:
865         std::strcpy(which, "SR");
866         break;
867       case largest_imaginary_part:
868         std::strcpy(which, "LI");
869         break;
870       case smallest_imaginary_part:
871         std::strcpy(which, "SI");
872         break;
873       case both_ends:
874         std::strcpy(which, "BE");
875         break;
876     }
877 
878   // tolerance for ARPACK
879   double tol = control().tolerance();
880 
881   // information to the routines
882   std::vector<int> iparam(11, 0);
883 
884   iparam[0] = 1;
885   // shift strategy: exact shifts with respect to the current Hessenberg matrix
886   // H.
887 
888   // maximum number of iterations
889   iparam[2] = control().max_steps();
890 
891   // Parpack currently works only for NB = 1
892   iparam[3] = 1;
893 
894   // Sets the mode of dsaupd:
895   // 1 is A*x=lambda*x, OP = A, B = I
896   // 2 is A*x = lambda*M*x, OP = inv[M]*A, B = M
897   // 3 is shift-invert mode, OP = inv[A-sigma*M]*M, B = M
898   // 4 is buckling mode,
899   // 5 is Cayley mode.
900 
901   iparam[6] = mode;
902   std::vector<int> ipntr(14, 0);
903 
904   // information out of the iteration
905   //  If INFO .EQ. 0, a random initial residual vector is used.
906   //  If INFO .NE. 0, RESID contains the initial residual vector,
907   //  possibly from a previous run.
908   // Typical choices in this situation might be to use the final value
909   // of the starting vector from the previous eigenvalue calculation
910   int info = initial_vector_provided ? 1 : 0;
911 
912   // Number of eigenvalues of OP to be computed. 0 < NEV < N.
913   int nev             = n_eigenvalues;
914   int n_inside_arpack = nloc;
915 
916   // IDO = 99: done
917   while (ido != 99)
918     {
919       // call of ARPACK pdnaupd routine
920       if (additional_data.symmetric)
921         pdsaupd_(&mpi_communicator_fortran,
922                  &ido,
923                  bmat,
924                  &n_inside_arpack,
925                  which,
926                  &nev,
927                  &tol,
928                  resid.data(),
929                  &ncv,
930                  v.data(),
931                  &ldv,
932                  iparam.data(),
933                  ipntr.data(),
934                  workd.data(),
935                  workl.data(),
936                  &lworkl,
937                  &info);
938       else
939         pdnaupd_(&mpi_communicator_fortran,
940                  &ido,
941                  bmat,
942                  &n_inside_arpack,
943                  which,
944                  &nev,
945                  &tol,
946                  resid.data(),
947                  &ncv,
948                  v.data(),
949                  &ldv,
950                  iparam.data(),
951                  ipntr.data(),
952                  workd.data(),
953                  workl.data(),
954                  &lworkl,
955                  &info);
956 
957       AssertThrow(info == 0, PArpackExcInfoPdnaupd(info));
958 
959       // if we converge, we shall not modify anything in work arrays!
960       if (ido == 99)
961         break;
962 
963       // IPNTR(1) is the pointer into WORKD for X,
964       // IPNTR(2) is the pointer into WORKD for Y.
965       const int shift_x = ipntr[0] - 1;
966       const int shift_y = ipntr[1] - 1;
967       Assert(shift_x >= 0, dealii::ExcInternalError());
968       Assert(shift_x + nloc <= static_cast<int>(workd.size()),
969              dealii::ExcInternalError());
970       Assert(shift_y >= 0, dealii::ExcInternalError());
971       Assert(shift_y + nloc <= static_cast<int>(workd.size()),
972              dealii::ExcInternalError());
973 
974       src = 0.;
975 
976       // switch based on both ido and mode
977       if ((ido == -1) || (ido == 1 && mode < 3))
978         // compute  Y = OP * X
979         {
980           src.add(nloc, local_indices.data(), workd.data() + shift_x);
981           src.compress(VectorOperation::add);
982 
983           if (mode == 3)
984             // OP = inv[K - sigma*M]*M
985             {
986               mass_matrix.vmult(tmp, src);
987               inverse.vmult(dst, tmp);
988             }
989           else if (mode == 2)
990             // OP = inv[M]*K
991             {
992               system_matrix.vmult(tmp, src);
993               // store M*X in X
994               tmp.extract_subvector_to(local_indices.begin(),
995                                        local_indices.end(),
996                                        workd.data() + shift_x);
997               inverse.vmult(dst, tmp);
998             }
999           else if (mode == 1)
1000             {
1001               system_matrix.vmult(dst, src);
1002             }
1003           else
1004             AssertThrow(false, PArpackExcMode(mode));
1005         }
1006       else if (ido == 1 && mode >= 3)
1007         // compute  Y = OP * X for mode 3, 4 and 5, where
1008         // the vector B * X is already available in WORKD(ipntr(3)).
1009         {
1010           const int shift_b_x = ipntr[2] - 1;
1011           Assert(shift_b_x >= 0, dealii::ExcInternalError());
1012           Assert(shift_b_x + nloc <= static_cast<int>(workd.size()),
1013                  dealii::ExcInternalError());
1014 
1015           // B*X
1016           src.add(nloc, local_indices.data(), workd.data() + shift_b_x);
1017           src.compress(VectorOperation::add);
1018 
1019           // solving linear system
1020           Assert(mode == 3, ExcNotImplemented());
1021           inverse.vmult(dst, src);
1022         }
1023       else if (ido == 2)
1024         // compute  Y = B * X
1025         {
1026           src.add(nloc, local_indices.data(), workd.data() + shift_x);
1027           src.compress(VectorOperation::add);
1028 
1029           // Multiplication with mass matrix M
1030           if (mode == 1)
1031             {
1032               dst = src;
1033             }
1034           else
1035             // mode 2,3 and 5 have B=M
1036             {
1037               mass_matrix.vmult(dst, src);
1038             }
1039         }
1040       else
1041         AssertThrow(false, PArpackExcIdo(ido));
1042       // Note: IDO = 3 does not appear to be required for currently
1043       // implemented modes
1044 
1045       // store the result
1046       dst.extract_subvector_to(local_indices.begin(),
1047                                local_indices.end(),
1048                                workd.data() + shift_y);
1049     } // end of pd*aupd_ loop
1050 
1051   // 1 - compute eigenvectors,
1052   // 0 - only eigenvalues
1053   int rvec = 1;
1054 
1055   // which eigenvectors
1056   char howmany[4] = "All";
1057 
1058   std::vector<double> eigenvalues_real(n_eigenvalues + 1, 0.);
1059   std::vector<double> eigenvalues_im(n_eigenvalues + 1, 0.);
1060 
1061   // call of ARPACK pdneupd routine
1062   if (additional_data.symmetric)
1063     pdseupd_(&mpi_communicator_fortran,
1064              &rvec,
1065              howmany,
1066              select.data(),
1067              eigenvalues_real.data(),
1068              z.data(),
1069              &ldz,
1070              &sigmar,
1071              bmat,
1072              &n_inside_arpack,
1073              which,
1074              &nev,
1075              &tol,
1076              resid.data(),
1077              &ncv,
1078              v.data(),
1079              &ldv,
1080              iparam.data(),
1081              ipntr.data(),
1082              workd.data(),
1083              workl.data(),
1084              &lworkl,
1085              &info);
1086   else
1087     pdneupd_(&mpi_communicator_fortran,
1088              &rvec,
1089              howmany,
1090              select.data(),
1091              eigenvalues_real.data(),
1092              eigenvalues_im.data(),
1093              v.data(),
1094              &ldz,
1095              &sigmar,
1096              &sigmai,
1097              workev.data(),
1098              bmat,
1099              &n_inside_arpack,
1100              which,
1101              &nev,
1102              &tol,
1103              resid.data(),
1104              &ncv,
1105              v.data(),
1106              &ldv,
1107              iparam.data(),
1108              ipntr.data(),
1109              workd.data(),
1110              workl.data(),
1111              &lworkl,
1112              &info);
1113 
1114   if (info == 1)
1115     {
1116       AssertThrow(false, PArpackExcInfoMaxIt(control().max_steps()));
1117     }
1118   else if (info == 3)
1119     {
1120       AssertThrow(false, PArpackExcNoShifts(1));
1121     }
1122   else if (info != 0)
1123     {
1124       AssertThrow(false, PArpackExcInfoPdneupd(info));
1125     }
1126 
1127   for (int i = 0; i < nev; ++i)
1128     {
1129       (*eigenvectors[i]) = 0.0;
1130       AssertIndexRange(i * nloc + nloc, v.size() + 1);
1131 
1132       eigenvectors[i]->add(nloc, local_indices.data(), &v[i * nloc]);
1133       eigenvectors[i]->compress(VectorOperation::add);
1134     }
1135 
1136   for (size_type i = 0; i < n_eigenvalues; ++i)
1137     eigenvalues[i] =
1138       std::complex<double>(eigenvalues_real[i], eigenvalues_im[i]);
1139 
1140   // Throw an error if the solver did not converge.
1141   AssertThrow(iparam[4] >= static_cast<int>(n_eigenvalues),
1142               PArpackExcConvergedEigenvectors(n_eigenvalues, iparam[4]));
1143 
1144   // both PDNAUPD and PDSAUPD compute eigenpairs of inv[A - sigma*M]*M
1145   // with respect to a semi-inner product defined by M.
1146 
1147   // resid likely contains residual with respect to M-norm.
1148   {
1149     tmp = 0.0;
1150     tmp.add(nloc, local_indices.data(), resid.data());
1151     solver_control.check(iparam[2], tmp.l2_norm());
1152   }
1153 }
1154 
1155 
1156 
1157 template <typename VectorType>
1158 SolverControl &
control()1159 PArpackSolver<VectorType>::control() const
1160 {
1161   return solver_control;
1162 }
1163 
1164 DEAL_II_NAMESPACE_CLOSE
1165 
1166 
1167 #endif
1168 #endif
1169