LCOV - code coverage report
Current view: top level - src/LinearSolvers - PCG.h (source / functions) Coverage Total Hit
Test: final_report.info Lines: 0.0 % 127 0
Test Date: 2025-07-18 09:50:00 Functions: 0.0 % 31 0

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

Generated by: LCOV version 2.0-1