LCOV - code coverage report
Current view: top level - src/LinearSolvers - PCG.h (source / functions) Coverage Total Hit
Test: report.info Lines: 0.0 % 127 0
Test Date: 2025-05-21 11:16:25 Functions: 0.0 % 31 0
Branches: 0.0 % 256 0

             Branch data     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