LCOV - code coverage report
Current view: top level - drivers - eigensolver.cpp (source / functions) Coverage Total Hit
Test: Palace Coverage Report Lines: 0.0 % 166 0
Test Date: 2025-10-23 22:45:05 Functions: 0.0 % 1 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 "eigensolver.hpp"
       5              : 
       6              : #include <complex>
       7              : #include <mfem.hpp>
       8              : #include "fem/errorindicator.hpp"
       9              : #include "fem/mesh.hpp"
      10              : #include "linalg/arpack.hpp"
      11              : #include "linalg/divfree.hpp"
      12              : #include "linalg/errorestimator.hpp"
      13              : #include "linalg/floquetcorrection.hpp"
      14              : #include "linalg/ksp.hpp"
      15              : #include "linalg/nleps.hpp"
      16              : #include "linalg/operator.hpp"
      17              : #include "linalg/rap.hpp"
      18              : #include "linalg/slepc.hpp"
      19              : #include "linalg/vector.hpp"
      20              : #include "models/lumpedportoperator.hpp"
      21              : #include "models/postoperator.hpp"
      22              : #include "models/spaceoperator.hpp"
      23              : #include "utils/communication.hpp"
      24              : #include "utils/iodata.hpp"
      25              : #include "utils/timer.hpp"
      26              : 
      27              : namespace palace
      28              : {
      29              : 
      30              : using namespace std::complex_literals;
      31              : 
      32              : std::pair<ErrorIndicator, long long int>
      33            0 : EigenSolver::Solve(const std::vector<std::unique_ptr<Mesh>> &mesh) const
      34              : {
      35              :   // Construct and extract the system matrices defining the eigenvalue problem. The diagonal
      36              :   // values for the mass matrix PEC dof shift the Dirichlet eigenvalues out of the
      37              :   // computational range. The damping matrix may be nullptr.
      38            0 :   BlockTimer bt0(Timer::CONSTRUCT);
      39            0 :   SpaceOperator space_op(iodata, mesh);
      40            0 :   auto K = space_op.GetStiffnessMatrix<ComplexOperator>(Operator::DIAG_ONE);
      41            0 :   auto C = space_op.GetDampingMatrix<ComplexOperator>(Operator::DIAG_ZERO);
      42            0 :   auto M = space_op.GetMassMatrix<ComplexOperator>(Operator::DIAG_ZERO);
      43              : 
      44              :   // Check if there are nonlinear terms and, if so, setup interpolation operator.
      45              :   auto funcA2 = [&space_op](double omega) -> std::unique_ptr<ComplexOperator>
      46            0 :   { return space_op.GetExtraSystemMatrix<ComplexOperator>(omega, Operator::DIAG_ZERO); };
      47              :   auto funcP = [&space_op](std::complex<double> a0, std::complex<double> a1,
      48              :                            std::complex<double> a2,
      49              :                            double omega) -> std::unique_ptr<ComplexOperator>
      50            0 :   { return space_op.GetPreconditionerMatrix<ComplexOperator>(a0, a1, a2, omega); };
      51            0 :   const double target = iodata.solver.eigenmode.target;
      52              :   auto A2 = funcA2(target);
      53              :   bool has_A2 = (A2 != nullptr);
      54              : 
      55              :   // Extend K, C, M operators with interpolated A2 operator.
      56              :   // K' = K + A2_0, C' = C + A2_1, M' = M + A2_2
      57              :   std::unique_ptr<ComplexOperator> Kp, Cp, Mp;
      58              :   std::unique_ptr<Interpolation> interp_op;
      59              :   std::unique_ptr<ComplexOperator> A2_0, A2_1, A2_2;
      60            0 :   NonlinearEigenSolver nonlinear_type = iodata.solver.eigenmode.nonlinear_type;
      61            0 :   if (has_A2 && nonlinear_type == NonlinearEigenSolver::HYBRID)
      62              :   {
      63            0 :     const double target_max = iodata.solver.eigenmode.target_upper;
      64            0 :     interp_op = std::make_unique<NewtonInterpolationOperator>(funcA2, A2->Width());
      65            0 :     interp_op->Interpolate(1i * target, 1i * target_max);
      66            0 :     A2_0 = interp_op->GetInterpolationOperator(0);
      67            0 :     A2_1 = interp_op->GetInterpolationOperator(1);
      68            0 :     A2_2 = interp_op->GetInterpolationOperator(2);
      69            0 :     Kp = BuildParSumOperator({1.0 + 0i, 1.0 + 0i}, {K.get(), A2_0.get()});
      70            0 :     Cp = BuildParSumOperator({1.0 + 0i, 1.0 + 0i}, {C.get(), A2_1.get()});
      71            0 :     Mp = BuildParSumOperator({1.0 + 0i, 1.0 + 0i}, {M.get(), A2_2.get()});
      72              :   }
      73              : 
      74              :   const auto &Curl = space_op.GetCurlMatrix();
      75            0 :   SaveMetadata(space_op.GetNDSpaces());
      76              : 
      77              :   // Configure objects for postprocessing.
      78            0 :   PostOperator<ProblemType::EIGENMODE> post_op(iodata, space_op);
      79            0 :   ComplexVector E(Curl.Width()), B(Curl.Height());
      80            0 :   E.UseDevice(true);
      81            0 :   B.UseDevice(true);
      82              : 
      83              :   // Define and configure the eigensolver to solve the eigenvalue problem:
      84              :   //         (K + λ C + λ² M) u = 0    or    K u = -λ² M u
      85              :   // with λ = iω. In general, the system matrices are complex and symmetric.
      86            0 :   std::unique_ptr<EigenvalueSolver> eigen;
      87            0 :   EigenSolverBackend type = iodata.solver.eigenmode.type;
      88              : 
      89              : #if defined(PALACE_WITH_ARPACK) && defined(PALACE_WITH_SLEPC)
      90              :   if (type == EigenSolverBackend::DEFAULT)
      91              :   {
      92              :     type = EigenSolverBackend::SLEPC;
      93              :   }
      94              : #elif defined(PALACE_WITH_ARPACK)
      95              :   if (type == EigenSolverBackend::SLEPC)
      96              :   {
      97              :     Mpi::Warning("SLEPc eigensolver not available, using ARPACK!\n");
      98              :   }
      99              :   type = EigenSolverBackend::ARPACK;
     100              :   if (nonlinear_type == NonlinearEigenSolver::SLP)
     101              :   {
     102              :     Mpi::Warning("SLP nonlinear eigensolver not available, using Hybrid!\n");
     103              :   }
     104              :   nonlinear_type = NonlinearEigenSolver::HYBRID;
     105              : #elif defined(PALACE_WITH_SLEPC)
     106            0 :   if (type == EigenSolverBackend::ARPACK)
     107              :   {
     108            0 :     Mpi::Warning("ARPACK eigensolver not available, using SLEPc!\n");
     109              :   }
     110              :   type = EigenSolverBackend::SLEPC;
     111              : #else
     112              : #error "Eigenmode solver requires building with ARPACK or SLEPc!"
     113              : #endif
     114              :   if (type == EigenSolverBackend::ARPACK)
     115              :   {
     116              : #if defined(PALACE_WITH_ARPACK)
     117              :     Mpi::Print("\nConfiguring ARPACK eigenvalue solver:\n");
     118              :     if (C || has_A2)
     119              :     {
     120              :       eigen = std::make_unique<arpack::ArpackPEPSolver>(space_op.GetComm(),
     121              :                                                         iodata.problem.verbose);
     122              :     }
     123              :     else
     124              :     {
     125              :       eigen = std::make_unique<arpack::ArpackEPSSolver>(space_op.GetComm(),
     126              :                                                         iodata.problem.verbose);
     127              :     }
     128              : #endif
     129              :   }
     130              :   else  // EigenSolverBackend::SLEPC
     131              :   {
     132              : #if defined(PALACE_WITH_SLEPC)
     133            0 :     Mpi::Print("\nConfiguring SLEPc eigenvalue solver:\n");
     134              :     std::unique_ptr<slepc::SlepcEigenvalueSolver> slepc;
     135            0 :     if (nonlinear_type == NonlinearEigenSolver::SLP)
     136              :     {
     137            0 :       slepc = std::make_unique<slepc::SlepcNEPSolver>(space_op.GetComm(),
     138            0 :                                                       iodata.problem.verbose);
     139            0 :       slepc->SetType(slepc::SlepcEigenvalueSolver::Type::SLP);
     140            0 :       slepc->SetProblemType(slepc::SlepcEigenvalueSolver::ProblemType::GENERAL);
     141              :     }
     142              :     else
     143              :     {
     144            0 :       if (C || has_A2)
     145              :       {
     146            0 :         if (!iodata.solver.eigenmode.pep_linear)
     147              :         {
     148            0 :           slepc = std::make_unique<slepc::SlepcPEPSolver>(space_op.GetComm(),
     149            0 :                                                           iodata.problem.verbose);
     150            0 :           slepc->SetType(slepc::SlepcEigenvalueSolver::Type::TOAR);
     151              :         }
     152              :         else
     153              :         {
     154            0 :           slepc = std::make_unique<slepc::SlepcPEPLinearSolver>(space_op.GetComm(),
     155            0 :                                                                 iodata.problem.verbose);
     156            0 :           slepc->SetType(slepc::SlepcEigenvalueSolver::Type::KRYLOVSCHUR);
     157              :         }
     158              :       }
     159              :       else
     160              :       {
     161            0 :         slepc = std::make_unique<slepc::SlepcEPSSolver>(space_op.GetComm(),
     162            0 :                                                         iodata.problem.verbose);
     163            0 :         slepc->SetType(slepc::SlepcEigenvalueSolver::Type::KRYLOVSCHUR);
     164              :       }
     165            0 :       slepc->SetProblemType(slepc::SlepcEigenvalueSolver::ProblemType::GEN_NON_HERMITIAN);
     166              :     }
     167            0 :     slepc->SetOrthogonalization(iodata.solver.linear.gs_orthog == Orthogonalization::MGS,
     168            0 :                                 iodata.solver.linear.gs_orthog == Orthogonalization::CGS2);
     169              :     eigen = std::move(slepc);
     170              : #endif
     171              :   }
     172            0 :   EigenvalueSolver::ScaleType scale = iodata.solver.eigenmode.scale
     173            0 :                                           ? EigenvalueSolver::ScaleType::NORM_2
     174              :                                           : EigenvalueSolver::ScaleType::NONE;
     175            0 :   if (nonlinear_type == NonlinearEigenSolver::SLP)
     176              :   {
     177            0 :     eigen->SetOperators(*K, *C, *M, EigenvalueSolver::ScaleType::NONE);
     178            0 :     eigen->SetExtraSystemMatrix(funcA2);
     179            0 :     eigen->SetPreconditionerUpdate(funcP);
     180              :   }
     181              :   else
     182              :   {
     183            0 :     if (has_A2)
     184              :     {
     185            0 :       eigen->SetOperators(*Kp, *Cp, *Mp, scale);
     186              :     }
     187            0 :     else if (C)
     188              :     {
     189            0 :       eigen->SetOperators(*K, *C, *M, scale);
     190              :     }
     191              :     else
     192              :     {
     193            0 :       eigen->SetOperators(*K, *M, scale);
     194              :     }
     195              :   }
     196            0 :   eigen->SetNumModes(iodata.solver.eigenmode.n, iodata.solver.eigenmode.max_size);
     197              :   const double tol = (has_A2 && nonlinear_type == NonlinearEigenSolver::HYBRID)
     198            0 :                          ? iodata.solver.eigenmode.linear_tol
     199            0 :                          : iodata.solver.eigenmode.tol;
     200            0 :   eigen->SetTol(tol);
     201            0 :   eigen->SetMaxIter(iodata.solver.eigenmode.max_it);
     202            0 :   Mpi::Print(" Scaling γ = {:.3e}, δ = {:.3e}\n", eigen->GetScalingGamma(),
     203            0 :              eigen->GetScalingDelta());
     204              : 
     205              :   // If desired, use an M-inner product for orthogonalizing the eigenvalue subspace. The
     206              :   // constructed matrix just references the real SPD part of the mass matrix (no copy is
     207              :   // performed). Boundary conditions don't need to be eliminated here.
     208              :   std::unique_ptr<Operator> KM;
     209            0 :   if (iodata.solver.eigenmode.mass_orthog)
     210              :   {
     211            0 :     Mpi::Print(" Basis uses M-inner product\n");
     212            0 :     KM = space_op.GetInnerProductMatrix(0.0, 1.0, nullptr, M.get());
     213            0 :     eigen->SetBMat(*KM);
     214              : 
     215              :     // Mpi::Print(" Basis uses (K + M)-inner product\n");
     216              :     // KM = space_op.GetInnerProductMatrix(1.0, 1.0, K.get(), M.get());
     217              :     // eigen->SetBMat(*KM);
     218              :   }
     219              : 
     220              :   // Construct a divergence-free projector so the eigenvalue solve is performed in the space
     221              :   // orthogonal to the zero eigenvalues of the stiffness matrix.
     222            0 :   std::unique_ptr<DivFreeSolver<ComplexVector>> divfree;
     223            0 :   if (iodata.solver.linear.divfree_max_it > 0 &&
     224            0 :       !space_op.GetMaterialOp().HasWaveVector() &&
     225              :       !space_op.GetMaterialOp().HasLondonDepth())
     226              :   {
     227            0 :     Mpi::Print(" Configuring divergence-free projection\n");
     228            0 :     constexpr int divfree_verbose = 0;
     229            0 :     divfree = std::make_unique<DivFreeSolver<ComplexVector>>(
     230              :         space_op.GetMaterialOp(), space_op.GetNDSpace(), space_op.GetH1Spaces(),
     231            0 :         space_op.GetAuxBdrTDofLists(), iodata.solver.linear.divfree_tol,
     232            0 :         iodata.solver.linear.divfree_max_it, divfree_verbose);
     233            0 :     eigen->SetDivFreeProjector(*divfree);
     234              :   }
     235              : 
     236              :   // If using Floquet BCs, a correction term (kp x E) needs to be added to the B field.
     237            0 :   std::unique_ptr<FloquetCorrSolver<ComplexVector>> floquet_corr;
     238            0 :   if (space_op.GetMaterialOp().HasWaveVector())
     239              :   {
     240            0 :     floquet_corr = std::make_unique<FloquetCorrSolver<ComplexVector>>(
     241              :         space_op.GetMaterialOp(), space_op.GetNDSpace(), space_op.GetRTSpace(),
     242            0 :         iodata.solver.linear.tol, iodata.solver.linear.max_it, 0);
     243              :   }
     244              : 
     245              :   // Set up the initial space for the eigenvalue solve. Satisfies boundary conditions and is
     246              :   // projected appropriately.
     247            0 :   if (iodata.solver.eigenmode.init_v0)
     248              :   {
     249            0 :     ComplexVector v0;
     250            0 :     if (iodata.solver.eigenmode.init_v0_const)
     251              :     {
     252            0 :       Mpi::Print(" Using constant starting vector\n");
     253            0 :       space_op.GetConstantInitialVector(v0);
     254              :     }
     255              :     else
     256              :     {
     257            0 :       Mpi::Print(" Using random starting vector\n");
     258            0 :       space_op.GetRandomInitialVector(v0);
     259              :     }
     260            0 :     if (divfree)
     261              :     {
     262            0 :       divfree->Mult(v0);
     263              :     }
     264            0 :     eigen->SetInitialSpace(v0);  // Copies the vector
     265              : 
     266              :     // Debug
     267              :     // const auto &Grad = space_op.GetGradMatrix();
     268              :     // ComplexVector r0(Grad->Width());
     269              :     // r0.UseDevice(true);
     270              :     // Grad.MultTranspose(v0.Real(), r0.Real());
     271              :     // Grad.MultTranspose(v0.Imag(), r0.Imag());
     272              :     // r0.Print();
     273              :   }
     274              : 
     275              :   // Configure the shift-and-invert strategy is employed to solve for the eigenvalues
     276              :   // closest to the specified target, σ.
     277              :   {
     278              :     const double f_target =
     279            0 :         iodata.units.Dimensionalize<Units::ValueType::FREQUENCY>(target);
     280            0 :     Mpi::Print(" Shift-and-invert σ = {:.3e} GHz ({:.3e})\n", f_target, target);
     281              :   }
     282            0 :   if (C || has_A2 || nonlinear_type == NonlinearEigenSolver::SLP)
     283              :   {
     284              :     // Search for eigenvalues closest to λ = iσ.
     285            0 :     eigen->SetShiftInvert(1i * target);
     286              :     if (type == EigenSolverBackend::ARPACK)
     287              :     {
     288              :       // ARPACK searches based on eigenvalues of the transformed problem. The eigenvalue
     289              :       // 1 / (λ - σ) will be a large-magnitude negative imaginary number for an eigenvalue
     290              :       // λ with frequency close to but not below the target σ.
     291              :       eigen->SetWhichEigenpairs(EigenvalueSolver::WhichType::SMALLEST_IMAGINARY);
     292              :     }
     293            0 :     else if (nonlinear_type == NonlinearEigenSolver::SLP)
     294              :     {
     295            0 :       eigen->SetWhichEigenpairs(EigenvalueSolver::WhichType::TARGET_MAGNITUDE);
     296              :     }
     297              :     else
     298              :     {
     299            0 :       eigen->SetWhichEigenpairs(EigenvalueSolver::WhichType::TARGET_IMAGINARY);
     300              :     }
     301              :   }
     302              :   else
     303              :   {
     304              :     // Linear EVP has eigenvalues μ = -λ² = ω². Search for eigenvalues closest to μ = σ².
     305            0 :     eigen->SetShiftInvert(target * target);
     306              :     if (type == EigenSolverBackend::ARPACK)
     307              :     {
     308              :       // ARPACK searches based on eigenvalues of the transformed problem. 1 / (μ - σ²)
     309              :       // will be a large-magnitude positive real number for an eigenvalue μ with frequency
     310              :       // close to but below the target σ².
     311              :       eigen->SetWhichEigenpairs(EigenvalueSolver::WhichType::LARGEST_REAL);
     312              :     }
     313              :     else
     314              :     {
     315            0 :       eigen->SetWhichEigenpairs(EigenvalueSolver::WhichType::TARGET_REAL);
     316              :     }
     317              :   }
     318              : 
     319              :   // Set up the linear solver required for solving systems involving the shifted operator
     320              :   // (K - σ² M) or P(iσ) = (K + iσ C - σ² M) during the eigenvalue solve. The
     321              :   // preconditioner for complex linear systems is constructed from a real approximation
     322              :   // to the complex system matrix.
     323            0 :   auto A = space_op.GetSystemMatrix(1.0 + 0.0i, 1i * target, -target * target + 0.0i,
     324            0 :                                     K.get(), C.get(), M.get(), A2.get());
     325              :   auto P = space_op.GetPreconditionerMatrix<ComplexOperator>(
     326            0 :       1.0 + 0.0i, 1i * target, -target * target + 0.0i, target);
     327              :   auto ksp = std::make_unique<ComplexKspSolver>(iodata, space_op.GetNDSpaces(),
     328            0 :                                                 &space_op.GetH1Spaces());
     329            0 :   ksp->SetOperators(*A, *P);
     330            0 :   eigen->SetLinearSolver(*ksp);
     331              : 
     332              :   // Initialize structures for storing and reducing the results of error estimation.
     333              :   TimeDependentFluxErrorEstimator<ComplexVector> estimator(
     334              :       space_op.GetMaterialOp(), space_op.GetNDSpaces(), space_op.GetRTSpaces(),
     335            0 :       iodata.solver.linear.estimator_tol, iodata.solver.linear.estimator_max_it, 0,
     336            0 :       iodata.solver.linear.estimator_mg);
     337              :   ErrorIndicator indicator;
     338              : 
     339              :   // Eigenvalue problem solve.
     340            0 :   BlockTimer bt1(Timer::EPS);
     341            0 :   Mpi::Print("\n");
     342            0 :   int num_conv = eigen->Solve();
     343              :   {
     344            0 :     std::complex<double> lambda = (num_conv > 0) ? eigen->GetEigenvalue(0) : 0.0;
     345            0 :     Mpi::Print(" Found {:d} converged eigenvalue{}{}\n", num_conv,
     346            0 :                (num_conv > 1) ? "s" : "",
     347              :                (num_conv > 0)
     348            0 :                    ? fmt::format(" (first = {:.3e}{:+.3e}i)", lambda.real(), lambda.imag())
     349            0 :                    : "");
     350              :   }
     351              : 
     352            0 :   if (has_A2 && nonlinear_type == NonlinearEigenSolver::HYBRID)
     353              :   {
     354            0 :     Mpi::Print("\n Refining eigenvalues with Quasi-Newton solver\n");
     355            0 :     auto qn = std::make_unique<QuasiNewtonSolver>(space_op.GetComm(), std::move(eigen),
     356            0 :                                                   num_conv, iodata.problem.verbose,
     357            0 :                                                   iodata.solver.eigenmode.refine_nonlinear);
     358            0 :     qn->SetTol(iodata.solver.eigenmode.tol);
     359            0 :     qn->SetMaxIter(iodata.solver.eigenmode.max_it);
     360            0 :     if (C)
     361              :     {
     362            0 :       qn->SetOperators(*K, *C, *M, EigenvalueSolver::ScaleType::NONE);
     363              :     }
     364              :     else
     365              :     {
     366            0 :       qn->SetOperators(*K, *M, EigenvalueSolver::ScaleType::NONE);
     367              :     }
     368            0 :     qn->SetExtraSystemMatrix(funcA2);
     369            0 :     qn->SetPreconditionerUpdate(funcP);
     370            0 :     qn->SetNumModes(iodata.solver.eigenmode.n, iodata.solver.eigenmode.max_size);
     371            0 :     qn->SetPreconditionerLag(iodata.solver.eigenmode.preconditioner_lag,
     372            0 :                              iodata.solver.eigenmode.preconditioner_lag_tol);
     373            0 :     qn->SetMaxRestart(iodata.solver.eigenmode.max_restart);
     374            0 :     qn->SetLinearSolver(*ksp);
     375            0 :     qn->SetShiftInvert(1i * target);
     376              :     eigen = std::move(qn);
     377              : 
     378              :     // Suppress wave port output during nonlinear eigensolver iterations.
     379              :     space_op.GetWavePortOp().SetSuppressOutput(true);
     380            0 :     num_conv = eigen->Solve();
     381              :     space_op.GetWavePortOp().SetSuppressOutput(false);
     382              :   }
     383              : 
     384            0 :   BlockTimer bt2(Timer::POSTPRO);
     385            0 :   SaveMetadata(*ksp);
     386              : 
     387              :   // Calculate and record the error indicators, and postprocess the results.
     388            0 :   Mpi::Print("\nComputing solution error estimates and performing postprocessing\n");
     389            0 :   if (!KM)
     390              :   {
     391              :     // Normalize the finalized eigenvectors with respect to mass matrix (unit electric field
     392              :     // energy) even if they are not computed to be orthogonal with respect to it.
     393            0 :     KM = space_op.GetInnerProductMatrix(0.0, 1.0, nullptr, M.get());
     394            0 :     eigen->SetBMat(*KM);
     395            0 :     eigen->RescaleEigenvectors(num_conv);
     396              :   }
     397            0 :   Mpi::Print("\n");
     398              : 
     399            0 :   for (int i = 0; i < num_conv; i++)
     400              :   {
     401              :     // Get the eigenvalue and relative error.
     402            0 :     std::complex<double> omega = eigen->GetEigenvalue(i);
     403            0 :     double error_bkwd = eigen->GetError(i, EigenvalueSolver::ErrorType::BACKWARD);
     404            0 :     double error_abs = eigen->GetError(i, EigenvalueSolver::ErrorType::ABSOLUTE);
     405            0 :     if (!C && !has_A2)
     406              :     {
     407              :       // Linear EVP has eigenvalue μ = -λ² = ω².
     408              :       omega = std::sqrt(omega);
     409              :     }
     410              :     else
     411              :     {
     412              :       // Quadratic EVP solves for eigenvalue λ = iω.
     413              :       omega /= 1i;
     414              :     }
     415              : 
     416              :     // Compute B = -1/(iω) ∇ x E on the true dofs, and set the internal GridFunctions in
     417              :     // PostOperator for all postprocessing operations.
     418            0 :     eigen->GetEigenvector(i, E);
     419              : 
     420            0 :     linalg::NormalizePhase(space_op.GetComm(), E);
     421              : 
     422            0 :     Curl.Mult(E.Real(), B.Real());
     423            0 :     Curl.Mult(E.Imag(), B.Imag());
     424            0 :     B *= -1.0 / (1i * omega);
     425            0 :     if (space_op.GetMaterialOp().HasWaveVector())
     426              :     {
     427              :       // Calculate B field correction for Floquet BCs.
     428              :       // B = -1/(iω) ∇ x E + 1/ω kp x E.
     429            0 :       floquet_corr->AddMult(E, B, 1.0 / omega);
     430              :     }
     431              : 
     432              :     auto total_domain_energy =
     433            0 :         post_op.MeasureAndPrintAll(i, E, B, omega, error_abs, error_bkwd, num_conv);
     434              : 
     435              :     // Calculate and record the error indicators.
     436            0 :     if (i < iodata.solver.eigenmode.n)
     437              :     {
     438            0 :       estimator.AddErrorIndicator(E, B, total_domain_energy, indicator);
     439              :     }
     440              : 
     441              :     // Final write: Different condition than end of loop (i = num_conv - 1).
     442            0 :     if (i == iodata.solver.eigenmode.n - 1)
     443              :     {
     444            0 :       post_op.MeasureFinalize(indicator);
     445              :     }
     446              :   }
     447            0 :   MFEM_VERIFY(num_conv >= iodata.solver.eigenmode.n, "Eigenmode solve only found "
     448              :                                                          << num_conv << " modes when "
     449              :                                                          << iodata.solver.eigenmode.n
     450              :                                                          << " were requested!");
     451            0 :   return {indicator, space_op.GlobalTrueVSize()};
     452            0 : }
     453              : 
     454              : }  // namespace palace
        

Generated by: LCOV version 2.0-1