LCOV - code coverage report
Current view: top level - src/LinearSolvers - PCG.h (source / functions) Coverage Total Hit
Test: final_report.info Lines: 19.6 % 163 32
Test Date: 2025-09-04 13:52:53 Functions: 6.4 % 47 3

            Line data    Source code
       1              : //
       2              : // Class PCG
       3              : //   Preconditioned Conjugate Gradient solver algorithm
       4              : //
       5              : 
       6              : #ifndef IPPL_PCG_H
       7              : #define IPPL_PCG_H
       8              : 
       9              : #include "Preconditioner.h"
      10              : #include "SolverAlgorithm.h"
      11              : #include "FEM/FEMVector.h"
      12              : 
      13              : namespace ippl {
      14              :     template <typename OperatorRet, typename LowerRet, typename UpperRet, typename UpperLowerRet,
      15              :               typename InverseDiagRet, typename DiagRet, typename FieldLHS,
      16              :               typename FieldRHS = FieldLHS>
      17              :     class CG : public SolverAlgorithm<FieldLHS, FieldRHS> {
      18              :         using Base = SolverAlgorithm<FieldLHS, FieldRHS>;
      19              :         typedef typename Base::lhs_type::value_type T;
      20              : 
      21              :     public:
      22              :         using typename Base::lhs_type, typename Base::rhs_type;
      23              :         using OperatorF    = std::function<OperatorRet(lhs_type)>;
      24              :         using LowerF       = std::function<LowerRet(lhs_type)>;
      25              :         using UpperF       = std::function<UpperRet(lhs_type)>;
      26              :         using UpperLowerF  = std::function<UpperLowerRet(lhs_type)>;
      27              :         using InverseDiagF = std::function<InverseDiagRet(lhs_type)>;
      28              :         using DiagF        = std::function<DiagRet(lhs_type)>;
      29              : 
      30            0 :         virtual ~CG() = default;
      31              : 
      32              :         /*!
      33              :          * Sets the differential operator for the conjugate gradient algorithm
      34              :          * @param op A function that returns OpRet and takes a field of the LHS type
      35              :          */
      36            0 :         virtual void setOperator(OperatorF op) { op_m = std::move(op); }
      37            0 :         virtual void setPreconditioner(
      38              :             [[maybe_unused]] OperatorF&& op,  // Operator passed to chebyshev and newton
      39              :             [[maybe_unused]] LowerF&& lower,  // Operator passed to 2-step gauss-seidel and ssor
      40              :             [[maybe_unused]] UpperF&& upper,  // Operator passed to 2-step gauss-seidel and ssor
      41              :             [[maybe_unused]] UpperLowerF&&
      42              :                 upper_and_lower,  // Operator passed to 2-step gauss-seidel
      43              :             [[maybe_unused]] InverseDiagF&&
      44              :                 inverse_diagonal,  // Operator passed to jacobi, 2-step gauss-seidel and ssor
      45              :             [[maybe_unused]] DiagF&& diagonal,  // Operator passed to SSOR
      46              :             [[maybe_unused]] double alpha,      // smallest eigenvalue of the operator
      47              :             [[maybe_unused]] double beta,       // largest eigenvalue of the operator
      48              :             [[maybe_unused]] std::string preconditioner_type =
      49              :                 "",  // Name of the preconditioner that should be used
      50              :             [[maybe_unused]] int level =
      51              :                 5,  // This is a dummy default parameter, actual default parameter should be
      52              :             // set in main
      53              :             [[maybe_unused]] int degree =
      54              :                 31,  // This is a dummy default parameter, actual default parameter should
      55              :             // be set in main
      56              :             [[maybe_unused]] int richardson_iterations =
      57              :                 1,  // This is a dummy default parameter, actual default
      58              :             // parameter should be set in main
      59              :             [[maybe_unused]] int inner =
      60              :                 5,  // This is a dummy default parameter, actual default parameter should be
      61              :             // set in main
      62              :             [[maybe_unused]] int outer =
      63              :                 1,  // This is a dummy default parameter, actual default parameter should be
      64              :             [[maybe_unused]] double omega =
      65              :                 1  // This is a dummy default parameter, actual default parameter should be
      66              :                    // set in main
      67            0 :         ) {}
      68              :         /*!
      69              :          * Query how many iterations were required to obtain the solution
      70              :          * the last time this solver was used
      71              :          * @return Iteration count of last solve
      72              :          */
      73            0 :         virtual int getIterationCount() { return iterations_m; }
      74              : 
      75            0 :         virtual void operator()(lhs_type& lhs, rhs_type& rhs,
      76              :                                 const ParameterList& params) override {
      77            0 :             constexpr unsigned Dim             = lhs_type::dim;
      78            0 :             typename lhs_type::Mesh_t mesh     = lhs.get_mesh();
      79            0 :             typename lhs_type::Layout_t layout = lhs.getLayout();
      80              : 
      81            0 :             iterations_m            = 0;
      82            0 :             const int maxIterations = params.get<int>("max_iterations");
      83              : 
      84              :             // Variable names mostly based on description in
      85              :             // https://www.cs.cmu.edu/~quake-papers/painless-conjugate-gradient.pdf
      86            0 :             lhs_type r(mesh, layout);
      87            0 :             lhs_type d(mesh, layout);
      88              : 
      89              :             using bc_type  = BConds<lhs_type, Dim>;
      90            0 :             bc_type lhsBCs = lhs.getFieldBC();
      91            0 :             bc_type bc;
      92              : 
      93            0 :             bool allFacesPeriodic = true;
      94            0 :             for (unsigned int i = 0; i < 2 * Dim; ++i) {
      95            0 :                 FieldBC bcType = lhsBCs[i]->getBCType();
      96            0 :                 if (bcType == PERIODIC_FACE) {
      97              :                     // If the LHS has periodic BCs, so does the residue
      98            0 :                     bc[i] = std::make_shared<PeriodicFace<lhs_type>>(i);
      99            0 :                 } else if (bcType & CONSTANT_FACE) {
     100              :                     // If the LHS has constant BCs, the residue is zero on the BCs
     101              :                     // Bitwise AND with CONSTANT_FACE will succeed for ZeroFace or ConstantFace
     102            0 :                     bc[i]            = std::make_shared<ZeroFace<lhs_type>>(i);
     103            0 :                     allFacesPeriodic = false;
     104              :                 } else {
     105            0 :                     throw IpplException("PCG::operator()",
     106              :                                         "Only periodic or constant BCs for LHS supported.");
     107              :                     return;
     108              :                 }
     109              :             }
     110              : 
     111            0 :             r = rhs - op_m(lhs);
     112            0 :             d = r.deepCopy();
     113            0 :             d.setFieldBC(bc);
     114              : 
     115            0 :             T delta1          = innerProduct(r, d);
     116            0 :             T delta0          = delta1;
     117            0 :             residueNorm       = std::sqrt(delta1);
     118            0 :             const T tolerance = params.get<T>("tolerance") * norm(rhs);
     119              : 
     120            0 :             lhs_type q(mesh, layout);
     121              : 
     122            0 :             while (iterations_m < maxIterations && residueNorm > tolerance) {
     123            0 :                 q = op_m(d);
     124              : 
     125            0 :                 T alpha = delta1 / innerProduct(d, q);
     126            0 :                 lhs     = lhs + alpha * d;
     127              : 
     128              :                 // The exact residue is given by
     129              :                 // r = rhs - op_m(lhs);
     130              :                 // This correction is generally not used in practice because
     131              :                 // applying the Laplacian is computationally expensive and
     132              :                 // the correction does not have a significant effect on accuracy;
     133              :                 // in some implementations, the correction may be applied every few
     134              :                 // iterations to offset accumulated floating point errors
     135            0 :                 r      = r - alpha * q;
     136            0 :                 delta0 = delta1;
     137            0 :                 delta1 = innerProduct(r, r);
     138            0 :                 T beta = delta1 / delta0;
     139              : 
     140            0 :                 residueNorm = std::sqrt(delta1);
     141            0 :                 d           = r + beta * d;
     142            0 :                 ++iterations_m;
     143              :             }
     144              : 
     145            0 :             if (allFacesPeriodic) {
     146            0 :                 T avg = lhs.getVolumeAverage();
     147            0 :                 lhs   = lhs - avg;
     148              :             }
     149            0 :         }
     150              : 
     151            0 :         virtual T getResidue() const { return residueNorm; }
     152              : 
     153              :     protected:
     154              :         OperatorF op_m;
     155              :         T residueNorm    = 0;
     156              :         int iterations_m = 0;
     157              :     };
     158              : 
     159              : 
     160              :     template <typename OperatorRet, typename LowerRet, typename UpperRet, typename UpperLowerRet,
     161              :               typename InverseDiagRet, typename T>
     162              :     class CG<OperatorRet, LowerRet, UpperRet, UpperLowerRet, InverseDiagRet, FEMVector<T>, FEMVector<T> >
     163              :             : public SolverAlgorithm<FEMVector<T>, FEMVector<T>> {
     164              :         using Base = SolverAlgorithm<FEMVector<T>, FEMVector<T>>;
     165              : 
     166              :     public:
     167              :         using typename Base::lhs_type, typename Base::rhs_type;
     168              :         using OperatorF    = std::function<OperatorRet(lhs_type)>;
     169              :         using LowerF       = std::function<LowerRet(lhs_type)>;
     170              :         using UpperF       = std::function<UpperRet(lhs_type)>;
     171              :         using UpperLowerF  = std::function<UpperLowerRet(lhs_type)>;
     172              :         using InverseDiagF = std::function<InverseDiagRet(lhs_type)>;
     173              : 
     174            2 :         virtual ~CG() = default;
     175              : 
     176              :         /*!
     177              :          * Sets the differential operator for the conjugate gradient algorithm
     178              :          * @param op A function that returns OpRet and takes a field of the LHS type
     179              :          */
     180            6 :         virtual void setOperator(OperatorF op) { op_m = std::move(op); }
     181            0 :         virtual void setPreconditioner(
     182              :             [[maybe_unused]] OperatorF&& op,  // Operator passed to chebyshev and newton
     183              :             [[maybe_unused]] LowerF&& lower,  // Operator passed to 2-step gauss-seidel
     184              :             [[maybe_unused]] UpperF&& upper,  // Operator passed to 2-step gauss-seidel
     185              :             [[maybe_unused]] UpperLowerF&&
     186              :                 upper_and_lower,  // Operator passed to 2-step gauss-seidel
     187              :             [[maybe_unused]] InverseDiagF&&
     188              :                 inverse_diagonal,           // Operator passed to jacobi and 2-step gauss-seidel
     189              :             [[maybe_unused]] double alpha,  // smallest eigenvalue of the operator
     190              :             [[maybe_unused]] double beta,   // largest eigenvalue of the operator
     191              :             [[maybe_unused]] std::string preconditioner_type =
     192              :                 "",  // Name of the preconditioner that should be used
     193              :             [[maybe_unused]] int level =
     194              :                 5,  // This is a dummy default parameter, actual default parameter should be
     195              :             // set in main
     196              :             [[maybe_unused]] int degree =
     197              :                 31,  // This is a dummy default parameter, actual default parameter should
     198              :             // be set in main
     199              :             [[maybe_unused]] int richardson_iterations =
     200              :                 1,  // This is a dummy default parameter, actual default
     201              :             // parameter should be set in main
     202              :             [[maybe_unused]] int inner =
     203              :                 5,  // This is a dummy default parameter, actual default parameter should be
     204              :             // set in main
     205              :             [[maybe_unused]] int outer = 1  // This is a dummy default parameter, actual default
     206              :                                             // parameter should be set in main
     207            0 :         ) {}
     208              :         /*!
     209              :          * Query how many iterations were required to obtain the solution
     210              :          * the last time this solver was used
     211              :          * @return Iteration count of last solve
     212              :          */
     213            0 :         virtual int getIterationCount() { return iterations_m; }
     214              :         
     215            6 :         virtual void operator()(lhs_type& lhs, rhs_type& rhs,
     216              :                                 const ParameterList& params) override {
     217              :             
     218              :             //constexpr unsigned Dim             = lhs_type::dim;
     219              :             //typename lhs_type::Mesh_t mesh     = lhs.get_mesh();
     220              :             //typename lhs_type::Layout_t layout = lhs.getLayout();
     221              : 
     222            6 :             iterations_m            = 0;
     223            6 :             const int maxIterations = params.get<int>("max_iterations");
     224              : 
     225              :             // Variable names mostly based on description in
     226              :             // https://www.cs.cmu.edu/~quake-papers/painless-conjugate-gradient.pdf
     227            6 :             lhs_type r = lhs.deepCopy();
     228            6 :             r = 0;
     229            6 :             lhs_type d = lhs.deepCopy();
     230            6 :             d = 0;
     231              :             
     232              :            
     233            6 :             r = rhs - op_m(lhs);
     234            6 :             r.setHalo(0);
     235            6 :             d = r; //.deepCopy();
     236              :             //d.setFieldBC(bc);
     237            6 :             T delta1          = innerProduct(r, d);
     238            6 :             T delta0          = delta1;
     239            6 :             residueNorm       = std::sqrt(delta1);
     240            6 :             const T tolerance = params.get<T>("tolerance") * norm(rhs);
     241              : 
     242            6 :             lhs_type q = lhs.deepCopy();
     243            6 :             q = 0;
     244              :             
     245              : 
     246           54 :             while (iterations_m < maxIterations && residueNorm > tolerance) {
     247           48 :                 q = op_m(d);
     248           48 :                 d.setHalo(0);
     249           48 :                 T alpha = delta1 / innerProduct(d, q);
     250           48 :                 lhs     = lhs + alpha * d;
     251              : 
     252              :                 // The exact residue is given by
     253              :                 // r = rhs - op_m(lhs);
     254              :                 // This correction is generally not used in practice because
     255              :                 // applying the Laplacian is computationally expensive and
     256              :                 // the correction does not have a significant effect on accuracy;
     257              :                 // in some implementations, the correction may be applied every few
     258              :                 // iterations to offset accumulated floating point errors
     259           48 :                 r      = r - alpha * q;
     260           48 :                 delta0 = delta1;
     261           48 :                 r.setHalo(0);
     262           48 :                 delta1 = innerProduct(r, r);
     263           48 :                 T beta = delta1 / delta0;
     264              : 
     265           48 :                 residueNorm = std::sqrt(delta1);
     266           48 :                 d           = r + beta * d;
     267           48 :                 ++iterations_m;
     268              : 
     269              :                 
     270              :             }
     271            6 :         }
     272              : 
     273            0 :         virtual T getResidue() const { return residueNorm; }
     274              : 
     275              :     protected:
     276              :         OperatorF op_m;
     277              :         T residueNorm    = 0;
     278              :         int iterations_m = 0;
     279              :     };
     280              : 
     281              : 
     282              :     template <typename OperatorRet, typename LowerRet, typename UpperRet, typename UpperLowerRet,
     283              :               typename InverseDiagRet, typename DiagRet, typename FieldLHS,
     284              :               typename FieldRHS = FieldLHS>
     285              :     class PCG : public CG<OperatorRet, LowerRet, UpperRet, UpperLowerRet, InverseDiagRet, DiagRet,
     286              :                           FieldLHS, FieldRHS> {
     287              :         using Base = SolverAlgorithm<FieldLHS, FieldRHS>;
     288              :         typedef typename Base::lhs_type::value_type T;
     289              : 
     290              :     public:
     291              :         using typename Base::lhs_type, typename Base::rhs_type;
     292              :         using OperatorF    = std::function<OperatorRet(lhs_type)>;
     293              :         using LowerF       = std::function<LowerRet(lhs_type)>;
     294              :         using UpperF       = std::function<UpperRet(lhs_type)>;
     295              :         using UpperLowerF  = std::function<UpperLowerRet(lhs_type)>;
     296              :         using InverseDiagF = std::function<InverseDiagRet(lhs_type)>;
     297              :         using DiagF        = std::function<DiagRet(lhs_type)>;
     298              : 
     299            0 :         PCG()
     300              :             : CG<OperatorRet, LowerRet, UpperRet, UpperLowerRet, InverseDiagRet, DiagRet, FieldLHS,
     301              :                  FieldRHS>()
     302            0 :             , preconditioner_m(nullptr){};
     303              : 
     304              :         /*!
     305              :          * Sets the differential operator for the conjugate gradient algorithm
     306              :          * @param op A function that returns OpRet and takes a field of the LHS type
     307              :          */
     308            0 :         void setPreconditioner(
     309              :             OperatorF&& op,                   // Operator passed to chebyshev and newton
     310              :             LowerF&& lower,                   // Operator passed to 2-step gauss-seidel
     311              :             UpperF&& upper,                   // Operator passed to 2-step gauss-seidel
     312              :             UpperLowerF&& upper_and_lower,    // Operator passed to 2-step gauss-seidel
     313              :             InverseDiagF&& inverse_diagonal,  // Operator passed to jacobi and 2-step gauss-seidel
     314              :             DiagF&& diagonal,                 // Operator passed to ssor
     315              :             double alpha,                     // smallest eigenvalue of the operator
     316              :             double beta,                      // largest eigenvalue of the operator
     317              :             std::string preconditioner_type = "",  // Name of the preconditioner that should be used
     318              :             int level = 5,  // This is a dummy default parameter, actual default parameter should be
     319              :             // set in main
     320              :             int degree = 31,  // This is a dummy default parameter, actual default parameter should
     321              :             // be set in main
     322              :             int richardson_iterations = 4,  // This is a dummy default parameter, actual default
     323              :             // parameter should be set in main
     324              :             int inner = 2,  // This is a dummy default parameter, actual default parameter should be
     325              :             // set in main
     326              :             int outer = 2,  // This is a dummy default parameter, actual default parameter should be
     327              :             // set in main
     328              :             double omega = 1.57079632679  // This is a dummy default parameter, actual default
     329              :             // parameter should be set in main
     330              :             // default = pi/2 as this was found optimal during hyperparameter scan for test case 
     331              :             // (see https://amas.web.psi.ch/people/aadelmann/ETH-Accel-Lecture-1/projectscompleted/cse/BSc-mbolliger.pdf)
     332              :             ) override {
     333            0 :             if (preconditioner_type == "jacobi") {
     334              :                 // Turn on damping parameter
     335              :                 /*
     336              :                 double w = 2.0 / ((alpha + beta));
     337              :                 preconditioner_m = std::move(std::make_unique<jacobi_preconditioner<FieldLHS ,
     338              :                 InverseDiagF>>(std::move(inverse_diagonal), w));
     339              :                 */
     340            0 :                 preconditioner_m =
     341            0 :                     std::move(std::make_unique<jacobi_preconditioner<FieldLHS, InverseDiagF>>(
     342            0 :                         std::move(inverse_diagonal)));
     343            0 :             } else if (preconditioner_type == "newton") {
     344            0 :                 preconditioner_m = std::move(
     345            0 :                     std::make_unique<polynomial_newton_preconditioner<FieldLHS, OperatorF>>(
     346            0 :                         std::move(op), alpha, beta, level, 1e-3));
     347            0 :             } else if (preconditioner_type == "chebyshev") {
     348            0 :                 preconditioner_m = std::move(
     349            0 :                     std::make_unique<polynomial_chebyshev_preconditioner<FieldLHS, OperatorF>>(
     350            0 :                         std::move(op), alpha, beta, degree, 1e-3));
     351            0 :             } else if (preconditioner_type == "richardson") {
     352            0 :                 preconditioner_m =
     353            0 :                     std::move(std::make_unique<
     354            0 :                               richardson_preconditioner<FieldLHS, UpperLowerF, InverseDiagF>>(
     355            0 :                         std::move(upper_and_lower), std::move(inverse_diagonal),
     356              :                         richardson_iterations));
     357            0 :             } else if (preconditioner_type == "gauss-seidel") {
     358            0 :                 preconditioner_m = std::move(
     359            0 :                     std::make_unique<gs_preconditioner<FieldLHS, LowerF, UpperF, InverseDiagF>>(
     360            0 :                         std::move(lower), std::move(upper), std::move(inverse_diagonal), inner,
     361              :                         outer));
     362            0 :             } else if (preconditioner_type == "ssor") {
     363            0 :                 preconditioner_m =
     364            0 :                     std::move(std::make_unique<
     365            0 :                               ssor_preconditioner<FieldLHS, LowerF, UpperF, InverseDiagF, DiagF>>(
     366            0 :                         std::move(lower), std::move(upper), std::move(inverse_diagonal),
     367            0 :                         std::move(diagonal), inner, outer, omega));
     368              :             } else {
     369            0 :                 preconditioner_m = std::move(std::make_unique<preconditioner<FieldLHS>>());
     370              :             }
     371            0 :         }
     372              : 
     373            0 :         void operator()(lhs_type& lhs, rhs_type& rhs, const ParameterList& params) override {
     374            0 :             constexpr unsigned Dim = lhs_type::dim;
     375              : 
     376            0 :             if (preconditioner_m == nullptr) {
     377            0 :                 throw IpplException("PCG::operator()",
     378              :                                     "Preconditioner has not been set for PCG solver");
     379              :             }
     380              : 
     381            0 :             typename lhs_type::Mesh_t mesh     = lhs.get_mesh();
     382            0 :             typename lhs_type::Layout_t layout = lhs.getLayout();
     383              : 
     384            0 :             this->iterations_m      = 0;
     385            0 :             const int maxIterations = params.get<int>("max_iterations");
     386              : 
     387              :             // Variable names mostly based on description in
     388              :             // https://www.cs.cmu.edu/~quake-papers/painless-conjugate-gradient.pdf
     389            0 :             lhs_type r(mesh, layout);
     390            0 :             lhs_type d(mesh, layout);
     391            0 :             lhs_type s(mesh, layout);
     392            0 :             lhs_type q(mesh, layout);
     393              : 
     394            0 :             preconditioner_m->init_fields(lhs);
     395              : 
     396              :             using bc_type  = BConds<lhs_type, Dim>;
     397            0 :             bc_type lhsBCs = lhs.getFieldBC();
     398            0 :             bc_type bc;
     399              : 
     400            0 :             bool allFacesPeriodic = true;
     401            0 :             for (unsigned int i = 0; i < 2 * Dim; ++i) {
     402            0 :                 FieldBC bcType = lhsBCs[i]->getBCType();
     403            0 :                 if (bcType == PERIODIC_FACE) {
     404              :                     // If the LHS has periodic BCs, so does the residue
     405            0 :                     bc[i] = std::make_shared<PeriodicFace<lhs_type>>(i);
     406            0 :                 } else if (bcType & CONSTANT_FACE) {
     407              :                     // If the LHS has constant BCs, the residue is zero on the BCs
     408              :                     // Bitwise AND with CONSTANT_FACE will succeed for ZeroFace or ConstantFace
     409            0 :                     bc[i]            = std::make_shared<ZeroFace<lhs_type>>(i);
     410            0 :                     allFacesPeriodic = false;
     411              :                 } else {
     412            0 :                     throw IpplException("PCG::operator()",
     413              :                                         "Only periodic or constant BCs for LHS supported.");
     414              :                     return;
     415              :                 }
     416              :             }
     417              : 
     418            0 :             r = rhs - this->op_m(lhs);
     419            0 :             d = preconditioner_m->operator()(r);
     420            0 :             d.setFieldBC(bc);
     421              : 
     422            0 :             T delta1          = innerProduct(r, d);
     423            0 :             T delta0          = delta1;
     424            0 :             this->residueNorm = Kokkos::sqrt(Kokkos::abs(delta1));
     425            0 :             const T tolerance = params.get<T>("tolerance") * this->residueNorm;
     426              : 
     427            0 :             while (this->iterations_m < maxIterations && this->residueNorm > tolerance) {
     428            0 :                 q       = this->op_m(d);
     429            0 :                 T alpha = delta1 / innerProduct(d, q);
     430            0 :                 lhs     = lhs + alpha * d;
     431              : 
     432              :                 // The exact residue is given by
     433              :                 // r = rhs - BaseCG::op_m(lhs);
     434              :                 // This correction is generally not used in practice because
     435              :                 // applying the Laplacian is computationally expensive and
     436              :                 // the correction does not have a significant effect on accuracy;
     437              :                 // in some implementations, the correction may be applied every few
     438              :                 // iterations to offset accumulated floating point errors
     439            0 :                 r = r - alpha * q;
     440            0 :                 s = preconditioner_m->operator()(r);
     441              : 
     442            0 :                 delta0 = delta1;
     443            0 :                 delta1 = innerProduct(r, s);
     444              : 
     445            0 :                 T beta            = delta1 / delta0;
     446            0 :                 this->residueNorm = Kokkos::sqrt(Kokkos::abs(delta1));
     447              : 
     448            0 :                 d = s + beta * d;
     449            0 :                 ++this->iterations_m;
     450              :             }
     451              : 
     452            0 :             if (allFacesPeriodic) {
     453            0 :                 T avg = lhs.getVolumeAverage();
     454            0 :                 lhs   = lhs - avg;
     455              :             }
     456            0 :         }
     457              : 
     458              :     protected:
     459              :         std::unique_ptr<preconditioner<FieldLHS>> preconditioner_m;
     460              :     };
     461              : 
     462              : };  // namespace ippl
     463              : 
     464              : #endif
        

Generated by: LCOV version 2.0-1