LCOV - code coverage report
Current view: top level - linalg - nleps.cpp (source / functions) Coverage Total Hit
Test: Palace Coverage Report Lines: 0.0 % 411 0
Test Date: 2025-10-23 22:45:05 Functions: 0.0 % 35 0
Legend: Lines: hit not hit

            Line data    Source code
       1              : // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
       2              : // SPDX-License-Identifier: Apache-2.0
       3              : 
       4              : #include "nleps.hpp"
       5              : 
       6              : #include <algorithm>
       7              : #include <string>
       8              : #include <Eigen/Dense>
       9              : #include <mfem.hpp>
      10              : #include "linalg/divfree.hpp"
      11              : #include "linalg/rap.hpp"
      12              : #include "utils/communication.hpp"
      13              : 
      14              : namespace palace
      15              : {
      16              : 
      17              : using namespace std::complex_literals;
      18              : // Base class methods.
      19              : 
      20            0 : NonLinearEigenvalueSolver::NonLinearEigenvalueSolver(MPI_Comm comm, int print)
      21            0 :   : comm(comm), print(print)
      22              : {
      23              :   // Initialization.
      24            0 :   nleps_it = 0;
      25              : 
      26            0 :   gamma = delta = 1.0;
      27              :   sigma = 0.0;
      28              : 
      29            0 :   opInv = nullptr;
      30            0 :   opProj = nullptr;
      31            0 :   opB = nullptr;
      32            0 : }
      33              : 
      34            0 : void NonLinearEigenvalueSolver::SetLinearSolver(ComplexKspSolver &ksp)
      35              : {
      36            0 :   opInv = &ksp;
      37            0 : }
      38              : 
      39            0 : void NonLinearEigenvalueSolver::SetDivFreeProjector(
      40              :     const DivFreeSolver<ComplexVector> &divfree)
      41              : {
      42            0 :   opProj = &divfree;
      43            0 : }
      44              : 
      45            0 : void NonLinearEigenvalueSolver::SetBMat(const Operator &B)
      46              : {
      47            0 :   opB = &B;
      48            0 : }
      49              : 
      50            0 : void NonLinearEigenvalueSolver::SetNumModes(int num_eig, int num_vec)
      51              : {
      52            0 :   if (nev > 0 && num_eig != nev)
      53              :   {
      54              :     res.reset();
      55              :     xscale.reset();
      56              :     perm.reset();
      57              :   }
      58            0 :   nev = num_eig;
      59            0 : }
      60              : 
      61            0 : void NonLinearEigenvalueSolver::SetTol(double tol)
      62              : {
      63            0 :   rtol = tol;
      64            0 : }
      65              : 
      66            0 : void NonLinearEigenvalueSolver::SetMaxIter(int max_it)
      67              : {
      68            0 :   nleps_it = max_it;
      69            0 : }
      70              : 
      71            0 : void NonLinearEigenvalueSolver::SetWhichEigenpairs(EigenvalueSolver::WhichType type)
      72              : {
      73            0 :   which_type = type;
      74            0 : }
      75              : 
      76            0 : void NonLinearEigenvalueSolver::SetShiftInvert(std::complex<double> s, bool precond)
      77              : {
      78            0 :   MFEM_VERIFY(!precond, "Nonlinear eigenvalue solver does not support preconditioned "
      79              :                         "spectral transformation option!");
      80            0 :   sigma = s;
      81            0 :   sinvert = true;
      82            0 : }
      83              : 
      84            0 : void NonLinearEigenvalueSolver::SetInitialSpace(const ComplexVector &v)
      85              : {
      86            0 :   MFEM_ABORT("SetInitialSpace not defined for base class NonLinearEigenvalueSolver!");
      87              : }
      88              : 
      89            0 : std::complex<double> NonLinearEigenvalueSolver::GetEigenvalue(int i) const
      90              : {
      91            0 :   MFEM_VERIFY(i >= 0 && i < nev,
      92              :               "Out of range eigenpair requested (i = " << i << ", nev = " << nev << ")!");
      93            0 :   const int &j = perm.get()[i];
      94            0 :   return eigenvalues[j];
      95              : }
      96              : 
      97            0 : void NonLinearEigenvalueSolver::GetEigenvector(int i, ComplexVector &x) const
      98              : {
      99            0 :   MFEM_VERIFY(i >= 0 && i < nev,
     100              :               "Out of range eigenpair requested (i = " << i << ", nev = " << nev << ")!");
     101            0 :   MFEM_VERIFY(x.Size() == n, "Invalid size mismatch for provided eigenvector!");
     102            0 :   const int &j = perm.get()[i];
     103            0 :   x = eigenvectors[j];
     104            0 :   if (xscale.get()[j] > 0.0)
     105              :   {
     106            0 :     x *= xscale.get()[j];
     107              :   }
     108            0 : }
     109              : 
     110            0 : double NonLinearEigenvalueSolver::GetEigenvectorNorm(const ComplexVector &x,
     111              :                                                      ComplexVector &Bx) const
     112              : {
     113            0 :   if (opB)
     114              :   {
     115            0 :     return linalg::Norml2(comm, x, *opB, Bx);
     116              :   }
     117              :   else
     118              :   {
     119            0 :     return linalg::Norml2(comm, x);
     120              :   }
     121              : }
     122              : 
     123            0 : double NonLinearEigenvalueSolver::GetError(int i, EigenvalueSolver::ErrorType type) const
     124              : {
     125            0 :   MFEM_VERIFY(i >= 0 && i < nev,
     126              :               "Out of range eigenpair requested (i = " << i << ", nev = " << nev << ")!");
     127            0 :   const int &j = perm.get()[i];
     128            0 :   switch (type)
     129              :   {
     130            0 :     case ErrorType::ABSOLUTE:
     131            0 :       return res.get()[j];
     132            0 :     case ErrorType::RELATIVE:
     133            0 :       return res.get()[j] / std::abs(eigenvalues[j]);
     134            0 :     case ErrorType::BACKWARD:
     135            0 :       return res.get()[j] / GetBackwardScaling(eigenvalues[j]);
     136              :   }
     137              :   return 0.0;
     138              : }
     139              : 
     140            0 : void NonLinearEigenvalueSolver::RescaleEigenvectors(int num_eig)
     141              : {
     142            0 :   res = std::make_unique<double[]>(num_eig);
     143            0 :   xscale = std::make_unique<double[]>(num_eig);
     144            0 :   for (int i = 0; i < num_eig; i++)
     145              :   {
     146            0 :     x1 = eigenvectors[i];
     147            0 :     xscale.get()[i] = 1.0 / GetEigenvectorNorm(x1, y1);
     148            0 :     res.get()[i] = GetResidualNorm(eigenvalues[i], x1, y1) / linalg::Norml2(comm, x1);
     149              :   }
     150            0 : }
     151              : 
     152              : // Quasi-Newton specific methods.
     153            0 : QuasiNewtonSolver::QuasiNewtonSolver(MPI_Comm comm,
     154              :                                      std::unique_ptr<EigenvalueSolver> linear_eigensolver,
     155            0 :                                      int num_conv, int print, bool refine)
     156              :   : NonLinearEigenvalueSolver(comm, print),
     157            0 :     linear_eigensolver_(std::move(linear_eigensolver)), nev_linear(num_conv),
     158            0 :     refine_nonlinear(refine)
     159              : {
     160            0 :   opK = opC = opM = nullptr;
     161            0 :   normK = normC = normM = 0.0;
     162            0 : }
     163              : 
     164              : // Set the update frequency of the preconditioner.
     165            0 : void QuasiNewtonSolver::SetPreconditionerLag(int preconditioner_update_freq,
     166              :                                              double preconditioner_update_tol)
     167              : {
     168            0 :   preconditioner_lag = preconditioner_update_freq;
     169            0 :   preconditioner_tol = preconditioner_update_tol;
     170            0 : }
     171              : 
     172              : // Set the maximum number of restarts with the same initial guess.
     173            0 : void QuasiNewtonSolver::SetMaxRestart(int max_num_restart)
     174              : {
     175            0 :   max_restart = max_num_restart;
     176            0 : }
     177              : 
     178            0 : void QuasiNewtonSolver::SetExtraSystemMatrix(
     179              :     std::function<std::unique_ptr<ComplexOperator>(double)> A2)
     180              : {
     181            0 :   funcA2 = A2;
     182            0 : }
     183              : 
     184            0 : void QuasiNewtonSolver::SetPreconditionerUpdate(
     185              :     std::function<std::unique_ptr<ComplexOperator>(
     186              :         std::complex<double>, std::complex<double>, std::complex<double>, double)>
     187              :         P)
     188              : {
     189            0 :   funcP = P;
     190            0 : }
     191              : 
     192            0 : void QuasiNewtonSolver::SetOperators(const ComplexOperator &K, const ComplexOperator &M,
     193              :                                      EigenvalueSolver::ScaleType type)
     194              : {
     195            0 :   MFEM_VERIFY(!opK || K.Height() == n, "Invalid modification of eigenvalue problem size!");
     196            0 :   bool first = (opK == nullptr);
     197            0 :   opK = &K;
     198            0 :   opM = &M;
     199              : 
     200            0 :   if (first && type != ScaleType::NONE)
     201              :   {
     202            0 :     normK = linalg::SpectralNorm(comm, *opK, opK->IsReal());
     203            0 :     normM = linalg::SpectralNorm(comm, *opM, opM->IsReal());
     204            0 :     MFEM_VERIFY(normK >= 0.0 && normM >= 0.0,
     205              :                 "Invalid matrix norms for Quasi-Newton scaling!");
     206            0 :     if (normK > 0 && normM > 0.0)
     207              :     {
     208            0 :       gamma = std::sqrt(normK / normM);
     209            0 :       delta = 2.0 / (normK);
     210              :     }
     211              :   }
     212              : 
     213            0 :   n = opK->Height();
     214              : 
     215              :   // Set up workspace.
     216            0 :   x1.SetSize(opK->Height());
     217            0 :   y1.SetSize(opK->Height());
     218            0 :   x1.UseDevice(true);
     219            0 :   y1.UseDevice(true);
     220            0 : }
     221              : 
     222            0 : void QuasiNewtonSolver::SetOperators(const ComplexOperator &K, const ComplexOperator &C,
     223              :                                      const ComplexOperator &M,
     224              :                                      EigenvalueSolver::ScaleType type)
     225              : {
     226            0 :   MFEM_VERIFY(!opK || K.Height() == n, "Invalid modification of eigenvalue problem size!");
     227            0 :   bool first = (opK == nullptr);
     228            0 :   opK = &K;
     229            0 :   opC = &C;
     230            0 :   opM = &M;
     231              : 
     232            0 :   if (first && type != ScaleType::NONE)
     233              :   {
     234            0 :     normK = linalg::SpectralNorm(comm, *opK, opK->IsReal());
     235            0 :     normC = linalg::SpectralNorm(comm, *opC, opC->IsReal());
     236            0 :     normM = linalg::SpectralNorm(comm, *opM, opM->IsReal());
     237            0 :     MFEM_VERIFY(normK >= 0.0 && normC >= 0.0 && normM >= 0.0,
     238              :                 "Invalid matrix norms for Quasi-Newton scaling!");
     239            0 :     if (normK > 0 && normC >= 0.0 && normM > 0.0)
     240              :     {
     241            0 :       gamma = std::sqrt(normK / normM);
     242            0 :       delta = 2.0 / (normK + gamma * normC);
     243              :     }
     244              :   }
     245              : 
     246            0 :   n = opK->Height();
     247              : 
     248              :   // Set up workspace.
     249            0 :   x1.SetSize(opK->Height());
     250            0 :   y1.SetSize(opK->Height());
     251            0 :   x1.UseDevice(true);
     252            0 :   y1.UseDevice(true);
     253            0 : }
     254              : 
     255            0 : void QuasiNewtonSolver::SetInitialGuess()
     256              : {
     257              : 
     258            0 :   MFEM_VERIFY(n > 0, "Must call SetOperators before using SetInitialguess for nonlinear "
     259              :                      "eigenvalue solver!");
     260            0 :   MFEM_VERIFY(nev > 0, "Must call SetNumModes before using SetInitialguess for nonlinear "
     261              :                        "eigenvalue solver!");
     262              : 
     263              :   // Get eigenmodes initial guesses from linear eigensolver
     264            0 :   eigenvalues.resize(nev_linear);
     265            0 :   eigenvectors.resize(nev_linear);
     266            0 :   for (int i = 0; i < nev_linear; i++)
     267              :   {
     268            0 :     eigenvalues[i] = linear_eigensolver_->GetEigenvalue(i);
     269            0 :     linear_eigensolver_->GetEigenvector(i, x1);
     270              :     eigenvectors[i] = x1;
     271              :   }
     272              : 
     273              :   // Compute errors.
     274            0 :   RescaleEigenvectors(nev_linear);
     275              : 
     276              :   // Initialize eigenpairs ordering.
     277            0 :   perm = std::make_unique<int[]>(nev_linear);
     278            0 :   std::iota(perm.get(), perm.get() + nev_linear, 0);
     279              : 
     280              :   // Early return if nonlinear Newton won't be used.
     281            0 :   if (!refine_nonlinear)
     282            0 :     return;
     283              : 
     284              :   // If the number of initial guesses is greater than the number of requested modes
     285              :   // de-prioritize the initial guesses that have larger errors.
     286            0 :   std::vector<size_t> indices(nev_linear);
     287              :   std::iota(indices.begin(), indices.end(), 0);
     288            0 :   if (nev_linear > nev)
     289              :   {
     290            0 :     double min_error = res.get()[0];
     291            0 :     for (int i = 0; i < nev_linear; i++)
     292              :     {
     293            0 :       min_error = std::min(min_error, res.get()[i]);
     294              :     }
     295            0 :     const double threshold = 100.0 * min_error;
     296            0 :     std::sort(indices.begin(), indices.end(),
     297            0 :               [&](const auto i, const auto j)
     298              :               {
     299            0 :                 if (res.get()[i] < threshold && res.get()[j] > threshold)
     300              :                 {
     301              :                   return true;
     302              :                 }
     303            0 :                 else if (res.get()[i] > threshold && res.get()[j] < threshold)
     304              :                 {
     305              :                   return false;
     306              :                 }
     307              :                 else
     308              :                 {
     309            0 :                   return eigenvalues[i].imag() < eigenvalues[j].imag();
     310              :                 }
     311              :               });
     312              :   }
     313            0 :   for (int i = 0; i < nev_linear; i++)
     314              :   {
     315            0 :     eigenvalues[i] = linear_eigensolver_->GetEigenvalue(indices[i]);
     316            0 :     linear_eigensolver_->GetEigenvector(indices[i], x1);
     317            0 :     linalg::NormalizePhase(comm, x1);
     318              :     eigenvectors[i] = x1;
     319              :   }
     320              : 
     321              :   // Get ordering of the eigenpairs.
     322            0 :   std::sort(perm.get(), perm.get() + nev_linear, [&eig = this->eigenvalues](auto l, auto r)
     323            0 :             { return eig[l].imag() < eig[r].imag(); });
     324              : }
     325              : 
     326              : namespace
     327              : {
     328              : // Multiply an (n x k) matrix (vector of size k of ComplexVectors of size n) by a vector of
     329              : // size k, returning a ComplexVector of size n.
     330            0 : ComplexVector MatVecMult(const std::vector<ComplexVector> &X, const Eigen::VectorXcd &y)
     331              : {
     332              :   MFEM_ASSERT(X.size() == y.size(), "Mismatch in dimension of input vectors!");
     333              :   const size_t k = X.size();
     334              :   const size_t n = X[0].Size();
     335              :   const bool use_dev = X[0].UseDevice();
     336            0 :   ComplexVector z;
     337            0 :   z.SetSize(n);
     338            0 :   z.UseDevice(use_dev);
     339              :   z = 0.0;
     340            0 :   for (int j = 0; j < k; j++)
     341              :   {
     342            0 :     linalg::AXPBYPCZ(y(j).real(), X[j].Real(), -y(j).imag(), X[j].Imag(), 1.0, z.Real());
     343            0 :     linalg::AXPBYPCZ(y(j).imag(), X[j].Real(), y(j).real(), X[j].Imag(), 1.0, z.Imag());
     344              :   }
     345            0 :   return z;
     346              : }
     347              : 
     348              : }  // namespace
     349              : 
     350            0 : int QuasiNewtonSolver::Solve()
     351              : {
     352              :   // Quasi-Newton method for nonlinear eigenvalue problems.
     353              :   // Reference: Jarlebring, Koskela, Mele, Disguised and new quasi-Newton methods for
     354              :   //            nonlinear eigenvalue problems, Numerical Algorithms (2018).
     355              :   // Using the deflation scheme used by SLEPc's NEP solver with minimality index set to 1.
     356              :   // Reference: Effenberger, Robust successive computation of eigenpairs for nonlinear
     357              :   //            eigenvalue problems, SIAM J. Matrix Anal. Appl. (2013).
     358              :   // The deflation scheme solves an extended problem of size n + k, where n is the original
     359              :   // problem size and k is the number of converged eigenpairs. The extended operators are
     360              :   // never explicitly constructed and two separate vectors of length n and k are used to
     361              :   // store the extended solution: [v, v2] where v is a ComplexVector distributed across all
     362              :   // processes and v2 is an Eigen::VectorXcd stored redundantly on all processes.
     363              : 
     364              :   // Set initial guess from linear eigensolver.
     365            0 :   SetInitialGuess();
     366              : 
     367              :   // Return early if not refining the eigenmodes with Newton.
     368            0 :   if (!refine_nonlinear)
     369              :   {
     370            0 :     nev = eigenvalues.size();
     371            0 :     return nev;
     372              :   }
     373              : 
     374              :   // Palace ComplexVectors of size n.
     375            0 :   ComplexVector v, u, w, c, w0, z;
     376            0 :   v.SetSize(n);
     377            0 :   u.SetSize(n);
     378            0 :   w.SetSize(n);
     379            0 :   c.SetSize(n);
     380            0 :   w0.SetSize(n);
     381            0 :   z.SetSize(n);
     382            0 :   v.UseDevice(true);
     383            0 :   u.UseDevice(true);
     384            0 :   w.UseDevice(true);
     385            0 :   c.UseDevice(true);
     386            0 :   w0.UseDevice(true);
     387            0 :   z.UseDevice(true);
     388              : 
     389              :   // Eigen Matrix/Vectors for extended operator of size k.
     390              :   Eigen::MatrixXcd H;
     391              :   Eigen::VectorXcd u2, z2, c2, w2, v2;
     392              : 
     393              :   // Storage for eigenpairs.
     394              :   std::vector<ComplexVector> X;
     395              :   std::vector<std::complex<double>> eigs;
     396            0 :   X.reserve(nev);
     397              : 
     398              :   // Set defaults.
     399            0 :   if (nleps_it <= 0)
     400              :   {
     401            0 :     nleps_it = 100;
     402              :   }
     403              : 
     404              :   // Delta used in to compute divided difference Jacobian.
     405              :   const auto delta = std::sqrt(std::numeric_limits<double>::epsilon());
     406              : 
     407              :   // Set a seed and distribution for random Eigen vectors to ensure the same values on all
     408              :   // ranks.
     409              :   unsigned int seed = 111;
     410              :   std::mt19937 gen(seed);
     411              :   std::uniform_real_distribution<> dist_real(-1.0, 1.0);
     412              : 
     413            0 :   const int num_init_guess = eigenvalues.size();
     414            0 :   std::uniform_int_distribution<int> dist_int(0, num_init_guess - 1);
     415            0 :   int k = 0, restart = 0, guess_idx = 0;
     416            0 :   while (k < nev)
     417              :   {
     418              :     // If > max_restart with the same initial guess, skip to next initial guess.
     419              :     // If we tried all initial guesses and the random guess, end search even if k < nev.
     420            0 :     if (restart > max_restart)
     421              :     {
     422            0 :       if (guess_idx < num_init_guess)
     423              :       {
     424            0 :         guess_idx++;
     425            0 :         restart = 0;
     426              :       }
     427              :       else
     428              :       {
     429              :         break;
     430              :       }
     431              :     }
     432              : 
     433              :     // Set the eigenpair estimate to the initial guess.
     434            0 :     std::complex<double> eig, eig_opInv;
     435            0 :     if (guess_idx < num_init_guess)
     436              :     {
     437            0 :       eig = eigenvalues[guess_idx];
     438              :       v = eigenvectors[guess_idx];
     439              :     }
     440              :     else
     441              :     {
     442            0 :       eig = sigma;
     443            0 :       if (num_init_guess < 3)
     444              :       {
     445              :         // Set purely random vector.
     446            0 :         linalg::SetRandom(GetComm(), v);
     447            0 :         linalg::SetSubVector(
     448            0 :             v, *dynamic_cast<const ComplexParOperator *>(opK)->GetEssentialTrueDofs(), 0.0);
     449              :       }
     450              :       else
     451              :       {
     452              :         // Set random vector as the average of two distinct randomly-chosen initial guesses.
     453              :         int i1 = dist_int(gen);
     454              :         int i2;
     455              :         do
     456              :         {
     457              :           i2 = dist_int(gen);
     458            0 :         } while (i2 == i1);
     459            0 :         v.AXPBYPCZ(0.5, eigenvectors[i1], 0.5, eigenvectors[i2], 0.0);
     460              :       }
     461              :     }
     462            0 :     eig_opInv = eig;  // eigenvalue estimate used in the (lagged) preconditioner
     463              : 
     464              :     // Set the "random" c vector and the deflation component of the eigenpair initial guess.
     465            0 :     linalg::SetRandom(GetComm(), c, seed);  // set seed for deterministic behavior
     466            0 :     c2.conservativeResize(k);
     467            0 :     v2.conservativeResize(k);
     468            0 :     for (int i = 0; i < k; i++)
     469              :     {
     470            0 :       c2(i) = std::complex<double>(dist_real(gen), dist_real(gen));
     471            0 :       v2(i) = std::complex<double>(dist_real(gen), dist_real(gen));
     472              :     }
     473              : 
     474              :     // Normalize random c vector.
     475            0 :     double norm_c = std::sqrt(std::abs(linalg::Dot(GetComm(), c, c)) + c2.squaredNorm());
     476            0 :     c *= 1.0 / norm_c;
     477            0 :     c2 *= 1.0 / norm_c;
     478              : 
     479              :     // Normalize eigenvector estimate.
     480            0 :     double norm_v = std::sqrt(std::abs(linalg::Dot(GetComm(), v, v)) + v2.squaredNorm());
     481            0 :     v *= 1.0 / norm_v;
     482            0 :     v2 *= 1.0 / norm_v;
     483              : 
     484              :     // Set the linear solver operators.
     485            0 :     opA2 = (*funcA2)(std::abs(eig.imag()));
     486            0 :     opA = BuildParSumOperator({1.0 + 0.0i, eig, eig * eig, 1.0 + 0.0i},
     487            0 :                               {opK, opC, opM, opA2.get()}, true);
     488            0 :     opP = (*funcP)(1.0 + 0.0i, eig, eig * eig, eig.imag());
     489            0 :     opInv->SetOperators(*opA, *opP);
     490              : 
     491              :     // Linear solve with the extended operator of the deflated problem.
     492            0 :     auto deflated_solve = [&](const ComplexVector &b1, const Eigen::VectorXcd &b2,
     493              :                               ComplexVector &x1, Eigen::VectorXcd &x2)
     494              :     {
     495              :       // Solve the block linear system
     496              :       // |T(σ) U(σ)| |x1| = |b1|
     497              :       // |A(σ) B(σ)| |x2|   |b2|
     498              :       // x1 = T^-1 b1
     499              :       // x2 = SS^-1 (b2 - A x1) where SS = (B - A T^-1 U) = - X^* X S^-1
     500              :       // x1 = x1 - X S x2
     501            0 :       opInv->Mult(b1, x1);
     502            0 :       if (k == 0)  // no deflation
     503              :       {
     504            0 :         return;
     505              :       }
     506            0 :       x2.conservativeResize(k);
     507            0 :       for (int j = 0; j < k; j++)
     508              :       {
     509            0 :         x2(j) = b2(j) - linalg::Dot(GetComm(), x1, X[j]);
     510              :       }
     511            0 :       Eigen::MatrixXcd SS(k, k);
     512            0 :       for (int i = 0; i < k; i++)
     513              :       {
     514            0 :         for (int j = 0; j < k; j++)
     515              :         {
     516            0 :           SS(i, j) = linalg::Dot(GetComm(), X[i], X[j]);
     517              :         }
     518              :       }
     519            0 :       const Eigen::MatrixXcd S = eig_opInv * Eigen::MatrixXcd::Identity(k, k) - H;
     520            0 :       SS = -S.fullPivLu().solve(SS);
     521            0 :       x2 = SS.fullPivLu().solve(x2);
     522            0 :       const ComplexVector XSx2 = MatVecMult(X, S.fullPivLu().solve(x2));
     523            0 :       linalg::AXPY(-1.0, XSx2, x1);
     524            0 :     };
     525              : 
     526              :     // Compute w0 = T^-1 c and normalize it.
     527            0 :     deflated_solve(c, c2, w0, w2);
     528            0 :     double norm_w0 = std::sqrt(std::abs(linalg::Dot(GetComm(), w0, w0)) + w2.squaredNorm());
     529            0 :     w0 *= 1.0 / norm_w0;
     530            0 :     w2 *= 1.0 / norm_w0;
     531              : 
     532              :     // Newton iterations.
     533            0 :     double res = mfem::infinity();
     534            0 :     int it = 0, diverged_it = 0;
     535            0 :     while (it < nleps_it)
     536              :     {
     537              :       // Compute u = A * v.
     538            0 :       auto A2n = (*funcA2)(std::abs(eig.imag()));
     539            0 :       auto A = BuildParSumOperator({1.0 + 0.0i, eig, eig * eig, 1.0 + 0.0i},
     540            0 :                                    {opK, opC, opM, A2n.get()}, true);
     541            0 :       A->Mult(v, u);
     542            0 :       if (k > 0)  // Deflation
     543              :       {
     544              :         // u1 = T(l) v1 + U(l) v2 = T(l) v1 + T(l)X(lI - H)^-1 v2.
     545            0 :         const Eigen::MatrixXcd S = eig * Eigen::MatrixXcd::Identity(k, k) - H;
     546            0 :         const ComplexVector XSv2 = MatVecMult(X, S.fullPivLu().solve(v2));
     547            0 :         A->AddMult(XSv2, u, 1.0);
     548              :         // u2 = X^* v1.
     549            0 :         u2.conservativeResize(k);
     550            0 :         for (int j = 0; j < k; j++)
     551              :         {
     552            0 :           u2(j) = linalg::Dot(GetComm(), v, X[j]);
     553              :         }
     554              :       }
     555              : 
     556              :       // Compute residual.
     557            0 :       res = std::sqrt(std::abs(linalg::Dot(GetComm(), u, u)) + u2.squaredNorm());
     558            0 :       if (print > 0)
     559              :       {
     560            0 :         Mpi::Print(GetComm(),
     561              :                    "{:d} NLEPS (nconv={:d}, restart={:d}) residual norm {:.6e}\n", it, k,
     562              :                    restart, res);
     563              :       }
     564              : 
     565              :       // End if residual below tolerance and eigenvalue above the target.
     566            0 :       if (res < rtol)
     567              :       {
     568            0 :         if (print > 0)
     569              :         {
     570            0 :           Mpi::Print(GetComm(),
     571              :                      "Eigenvalue {:d}, Quasi-Newton converged in {:d} iterations "
     572              :                      "({:.3e}{:+.3e}i).\n",
     573            0 :                      k, it, eig.real(), eig.imag());
     574              :         }
     575              :         // Update the invariant pair with normalization.
     576            0 :         const auto scale = linalg::Norml2(GetComm(), v);
     577            0 :         v *= 1.0 / scale;
     578            0 :         eigs.resize(k + 1);
     579            0 :         eigs[k] = eig;
     580            0 :         X.resize(k + 1);
     581            0 :         X[k] = v;
     582            0 :         H.conservativeResizeLike(Eigen::MatrixXd::Zero(k + 1, k + 1));
     583            0 :         H.col(k).head(k) = v2 / scale;
     584            0 :         H(k, k) = eig;
     585            0 :         k++;
     586              :         // If the eigenvalue is inside the desired range, increment initial guess index
     587              :         // Otherwise, use the same initial guess again and increment number of desired
     588              :         // eigenvalues.
     589            0 :         if (eig.imag() > sigma.imag())
     590              :         {
     591            0 :           guess_idx++;
     592              :         }
     593              :         else
     594              :         {
     595            0 :           nev++;
     596              :         }
     597            0 :         restart = 0;  // reset restart counter
     598              :         break;
     599              :       }
     600              :       // Stop if large residual for 10 consecutive iterations.
     601            0 :       diverged_it = (res > 0.9) ? diverged_it + 1 : 0;
     602            0 :       if (diverged_it > 10)
     603              :       {
     604            0 :         if (print > 0)
     605              :         {
     606            0 :           Mpi::Print(GetComm(),
     607              :                      "Eigenvalue {:d}, Quasi-Newton not converging after {:d} iterations, "
     608              :                      "restarting.\n",
     609              :                      k, it);
     610              :         }
     611            0 :         restart++;
     612            0 :         break;
     613              :       }
     614              : 
     615              :       // Compute w = J * v.
     616            0 :       auto opA2p = (*funcA2)(std::abs(eig.imag()) * (1.0 + delta));
     617              :       const std::complex<double> denom =
     618            0 :           std::complex<double>(0.0, delta * std::abs(eig.imag()));
     619              :       std::unique_ptr<ComplexOperator> opAJ =
     620            0 :           BuildParSumOperator({1.0 / denom, -1.0 / denom}, {opA2p.get(), A2n.get()}, true);
     621            0 :       auto opJ = BuildParSumOperator({0.0 + 0.0i, 1.0 + 0.0i, 2.0 * eig, 1.0 + 0.0i},
     622            0 :                                      {opK, opC, opM, opAJ.get()}, true);
     623            0 :       opJ->Mult(v, w);
     624            0 :       if (k > 0)  // Deflation
     625              :       {
     626              :         // w1 = T'(l) v1 + U'(l) v2 = T'(l) v1 + T'(l)XS v2 - T(l)XS^2 v2.
     627            0 :         const Eigen::MatrixXcd S = eig * Eigen::MatrixXcd::Identity(k, k) - H;
     628            0 :         const Eigen::VectorXcd Sv2 = S.fullPivLu().solve(v2);
     629            0 :         const ComplexVector XSv2 = MatVecMult(X, Sv2);
     630            0 :         const ComplexVector XSSv2 = MatVecMult(X, S.fullPivLu().solve(Sv2));
     631            0 :         opJ->AddMult(XSv2, w, 1.0);
     632            0 :         A->AddMult(XSSv2, w, -1.0);
     633              :       }
     634              : 
     635              :       // Compute delta = - dot(w0, u) / dot(w0, w).
     636            0 :       const std::complex<double> u2_w0 = std::complex<double>(w2.adjoint() * u2);
     637              :       const std::complex<double> delta =
     638            0 :           -(linalg::Dot(GetComm(), u, w0) + u2_w0) / linalg::Dot(GetComm(), w, w0);
     639              : 
     640              :       // Update eigenvalue.
     641              :       eig += delta;
     642              : 
     643              :       // Compute z = -(delta * w + u).
     644            0 :       z.AXPBYPCZ(-delta, w, -1.0, u, 0.0);
     645            0 :       z2 = -u2;
     646              : 
     647              :       //  Update preconditioner if needed. Updating the preconditioner as infrequently as
     648              :       //  possible gives the best performance and robustness. Updating the preconditioner
     649              :       //  close to an eigenvalue can lead to numerical instability.
     650            0 :       if (it > 0 && it % preconditioner_lag == 0 && res > preconditioner_tol)
     651              :       {
     652            0 :         eig_opInv = eig;
     653            0 :         opA2 = (*funcA2)(std::abs(eig.imag()));
     654            0 :         opA = BuildParSumOperator({1.0 + 0.0i, eig, eig * eig, 1.0 + 0.0i},
     655            0 :                                   {opK, opC, opM, opA2.get()}, true);
     656            0 :         opP = (*funcP)(1.0 + 0.0i, eig, eig * eig, eig.imag());
     657            0 :         opInv->SetOperators(*opA, *opP);
     658              :         // Recompute w0 and normalize.
     659            0 :         deflated_solve(c, c2, w0, w2);
     660              :         double norm_w0 =
     661            0 :             std::sqrt(std::abs(linalg::Dot(GetComm(), w0, w0)) + w2.squaredNorm());
     662            0 :         w0 *= 1.0 / norm_w0;
     663            0 :         w2 *= 1.0 / norm_w0;
     664              :       }
     665              : 
     666              :       // Solve M (v_k+1 - v_k) = z.
     667            0 :       deflated_solve(z, z2, u, u2);
     668              : 
     669              :       // Update and normalize eigenvector estimate.
     670              :       v += u;
     671              :       v2 += u2;
     672            0 :       norm_v = std::sqrt(std::abs(linalg::Dot(GetComm(), v, v)) + v2.squaredNorm());
     673            0 :       v *= 1.0 / norm_v;
     674            0 :       v2 *= 1.0 / norm_v;
     675              : 
     676            0 :       it++;
     677            0 :       if (it == nleps_it)
     678              :       {
     679            0 :         if (print > 0)
     680              :         {
     681            0 :           Mpi::Print(GetComm(),
     682              :                      "Eigenvalue {:d}, Quasi-Newton did not converge in {:d} iterations, "
     683              :                      "restarting.\n",
     684            0 :                      k, nleps_it);
     685              :         }
     686            0 :         restart++;
     687              :       }
     688              :     }
     689              :   }
     690            0 :   nev = k;  // in case some guesses did not converge
     691              : 
     692              :   // Eigenpair extraction from the invariant pair (X, H).
     693            0 :   Eigen::ComplexEigenSolver<Eigen::MatrixXcd> eps;
     694            0 :   eps.compute(H);
     695              :   // H eigenvectors are ordered arbitrarily, need to match them to order of X.
     696            0 :   std::vector<int> order(nev), order_eigen(nev), order2(nev);
     697              :   std::iota(order.begin(), order.end(), 0);
     698              :   std::iota(order_eigen.begin(), order_eigen.end(), 0);
     699              :   std::iota(order2.begin(), order2.end(), 0);
     700            0 :   std::sort(order.begin(), order.end(),
     701            0 :             [&](auto l, auto r) { return eigs[l].imag() < eigs[r].imag(); });
     702            0 :   std::sort(order_eigen.begin(), order_eigen.end(),
     703              :             [&epseig = eps.eigenvalues()](auto l, auto r)
     704            0 :             { return epseig(l).imag() < epseig(r).imag(); });
     705            0 :   std::sort(order2.begin(), order2.end(),
     706            0 :             [&](auto l, auto r) { return order[l] < order[r]; });
     707              : 
     708              :   // Sort Eigen eigenvectors.
     709              :   std::vector<Eigen::VectorXcd> Xeig;
     710            0 :   for (int i = 0; i < nev; i++)
     711              :   {
     712            0 :     Xeig.push_back(eps.eigenvectors().col(order_eigen[i]));
     713              :   }
     714              : 
     715              :   // Recover the eigenvectors in the target range.
     716              :   eigenvalues.clear();
     717              :   eigenvectors.clear();
     718            0 :   for (int i = 0; i < nev; i++)
     719              :   {
     720            0 :     if (eigs[i].imag() > sigma.imag())
     721              :     {
     722            0 :       ComplexVector eigv = MatVecMult(X, Xeig[order2[i]]);
     723            0 :       eigenvalues.push_back(eigs[i]);
     724            0 :       eigenvectors.push_back(eigv);
     725              :     }
     726              :   }
     727            0 :   nev = eigenvalues.size();
     728              : 
     729              :   // Get ordering of the reported eigenpairs.
     730            0 :   perm = std::make_unique<int[]>(nev);
     731            0 :   std::iota(perm.get(), perm.get() + nev, 0);
     732            0 :   std::sort(perm.get(), perm.get() + nev, [&eig = this->eigenvalues](auto l, auto r)
     733            0 :             { return eig[l].imag() < eig[r].imag(); });
     734              : 
     735              :   // Compute the eigenpair residuals for eigenvalue λ.
     736            0 :   RescaleEigenvectors(nev);
     737              : 
     738            0 :   return nev;
     739            0 : }
     740              : 
     741            0 : double QuasiNewtonSolver::GetResidualNorm(std::complex<double> l, const ComplexVector &x,
     742              :                                           ComplexVector &r) const
     743              : {
     744              :   // Compute the i-th eigenpair residual: || P(λ) x ||₂ = || (K + λ C + λ² M + A2(λ)) x ||₂
     745              :   // for eigenvalue λ.
     746            0 :   opK->Mult(x, r);
     747            0 :   if (opC)
     748              :   {
     749            0 :     opC->AddMult(x, r, l);
     750              :   }
     751            0 :   opM->AddMult(x, r, l * l);
     752            0 :   auto A2 = (*funcA2)(std::abs(l.imag()));
     753            0 :   A2->AddMult(x, r, 1.0);
     754            0 :   return linalg::Norml2(comm, r);
     755              : }
     756              : 
     757            0 : double QuasiNewtonSolver::GetBackwardScaling(std::complex<double> l) const
     758              : {
     759              :   // Make sure not to use norms from scaling as this can be confusing if they are different.
     760              :   // Note that SLEPc uses ||.||∞, not the 2-norm.
     761            0 :   if (normK <= 0.0)
     762              :   {
     763            0 :     normK = linalg::SpectralNorm(comm, *opK, opK->IsReal());
     764              :   }
     765            0 :   if (normC <= 0.0 && opC)
     766              :   {
     767            0 :     normC = linalg::SpectralNorm(comm, *opC, opC->IsReal());
     768              :   }
     769            0 :   if (normM <= 0.0)
     770              :   {
     771            0 :     normM = linalg::SpectralNorm(comm, *opM, opM->IsReal());
     772              :   }
     773              :   double t = std::abs(l);
     774            0 :   return normK + t * normC + t * t * normM;
     775              : }
     776              : 
     777            0 : NewtonInterpolationOperator::NewtonInterpolationOperator(
     778            0 :     std::function<std::unique_ptr<ComplexOperator>(double)> funcA2, int size)
     779            0 :   : funcA2(funcA2)
     780              : {
     781            0 :   rhs.SetSize(size);
     782            0 :   rhs.UseDevice(true);
     783            0 : }
     784              : 
     785              : // Compute the elementary symmetric polynomial. Used to convert from Newton to monomial
     786              : // basis.
     787              : template <typename ScalarType>
     788            0 : ScalarType elementarySymmetric(const std::vector<ScalarType> &points, int k, int n)
     789              : {
     790            0 :   if (k == 0)
     791              :   {
     792            0 :     return 1.0;
     793              :   }
     794            0 :   if (k > n || k < 0 || n == 0)
     795              :   {
     796            0 :     return 0.0;
     797              :   }
     798            0 :   return elementarySymmetric(points, k, n - 1) +
     799            0 :          points[n - 1] * elementarySymmetric(points, k - 1, n - 1);
     800              : }
     801              : 
     802            0 : void NewtonInterpolationOperator::Interpolate(const std::complex<double> sigma_min,
     803              :                                               const std::complex<double> sigma_max)
     804              : {
     805              :   // Reset operators and sample points each time Interpolate is called.
     806              :   ops.clear();
     807            0 :   ops.resize(num_points);
     808              :   points.clear();
     809            0 :   points.resize(num_points);
     810              : 
     811              :   // Linearly spaced sample points.
     812            0 :   for (int j = 0; j < num_points; j++)
     813              :   {
     814            0 :     points[j] = sigma_min + (double)j * (sigma_max - sigma_min) / (double)(num_points - 1);
     815              :   }
     816              : 
     817              :   // Build divided difference matrices.
     818            0 :   for (int k = 0; k < num_points; k++)
     819              :   {
     820            0 :     for (int j = 0; j < num_points - k; j++)
     821              :     {
     822            0 :       if (k == 0)
     823              :       {
     824            0 :         auto A2j = (funcA2)(points[j].imag());
     825              :         ops[k].push_back(std::move(A2j));
     826              :       }
     827              :       else
     828              :       {
     829            0 :         std::complex<double> denom = points[j + k] - points[j];
     830              :         auto A2dd =
     831            0 :             BuildParSumOperator({1.0 / denom, -1.0 / denom},
     832            0 :                                 {ops[k - 1][j + 1].get(), ops[k - 1][j].get()}, true);
     833            0 :         ops[k].push_back(std::move(A2dd));
     834              :       }
     835              :     }
     836              :   }
     837              : 
     838              :   // Compute monomial coefficients as a function of the Newton polynomial coefficients.
     839              :   coeffs.clear();
     840            0 :   coeffs.assign(num_points, std::vector<std::complex<double>>(num_points, 0.0));
     841            0 :   for (int k = 0; k < num_points; k++)
     842              :   {
     843            0 :     for (int j = k; j < num_points; j++)
     844              :     {
     845            0 :       double sign = ((j - k) % 2 == 0) ? 1 : -1;
     846            0 :       coeffs[k][j] = sign * elementarySymmetric(points, j - k, j);
     847              :     }
     848              :   }
     849            0 : }
     850              : 
     851              : std::unique_ptr<ComplexOperator>
     852            0 : NewtonInterpolationOperator::GetInterpolationOperator(int order) const
     853              : {
     854            0 :   MFEM_VERIFY(order >= 0 && order < num_points,
     855              :               "Order must be greater than or equal to 0 and smaller than the number of "
     856              :               "interpolation points!");
     857            0 :   return BuildParSumOperator({coeffs[order][0], coeffs[order][1], coeffs[order][2]},
     858            0 :                              {ops[0][0].get(), ops[1][0].get(), ops[2][0].get()}, true);
     859              : }
     860              : 
     861            0 : void NewtonInterpolationOperator::Mult(int order, const ComplexVector &x,
     862              :                                        ComplexVector &y) const
     863              : {
     864            0 :   MFEM_VERIFY(order >= 0 && order < num_points,
     865              :               "Order must be greater than or equal to 0 and smaller than the number of "
     866              :               "interpolation points!");
     867              : 
     868              :   y = 0.0;
     869            0 :   for (int j = 0; j < num_points; j++)
     870              :   {
     871            0 :     if (coeffs[order][j] != 0.0)
     872              :     {
     873            0 :       ops[j][0]->AddMult(x, y, coeffs[order][j]);
     874              :     }
     875              :   }
     876            0 : }
     877              : 
     878            0 : void NewtonInterpolationOperator::AddMult(int order, const ComplexVector &x,
     879              :                                           ComplexVector &y, std::complex<double> a) const
     880              : {
     881            0 :   this->Mult(order, x, rhs);
     882            0 :   rhs *= a;
     883              :   y += rhs;
     884            0 : }
     885              : 
     886              : }  // namespace palace
        

Generated by: LCOV version 2.0-1