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