LCOV - code coverage report
Current view: top level - src/FEM/Quadrature - GaussJacobiQuadrature.hpp (source / functions) Coverage Total Hit
Test: final_report.info Lines: 63.0 % 100 63
Test Date: 2025-07-17 18:19:47 Functions: 75.0 % 120 90

            Line data    Source code
       1              : 
       2              : #include <cmath>
       3              : 
       4              : namespace ippl {
       5              :     template <typename T, unsigned NumNodes1D, typename ElementType>
       6          146 :     GaussJacobiQuadrature<T, NumNodes1D, ElementType>::GaussJacobiQuadrature(
       7              :         const ElementType& ref_element, const T& alpha, const T& beta,
       8              :         const size_t& max_newton_iterations, const size_t& min_newton_iterations)
       9              :         : Quadrature<T, NumNodes1D, ElementType>(ref_element)
      10          146 :         , alpha_m(alpha)
      11          146 :         , beta_m(beta)
      12          146 :         , max_newton_iterations_m(max_newton_iterations)
      13          146 :         , min_newton_iterations_m(min_newton_iterations) {
      14          146 :         assert(alpha > -1.0 && "alpha >= -1.0 is not satisfied");
      15          146 :         assert(beta > -1.0 && "beta >= -1.0 is not satisfied");
      16          146 :         assert(max_newton_iterations >= 1 && "max_newton_iterations >= 1 is not satisfied");
      17          146 :         assert(min_newton_iterations_m >= 1 && "min_newton_iterations_m >= 1 is not satisfied");
      18          146 :         assert(min_newton_iterations_m <= max_newton_iterations_m
      19              :                && "min_newton_iterations_m <= max_newton_iterations_m is not satisfied");
      20              : 
      21          146 :         this->degree_m = 2 * NumNodes1D - 1;
      22              : 
      23          146 :         this->a_m = -1.0;  // start of the domain
      24          146 :         this->b_m = 1.0;   // end of the domain
      25              : 
      26          146 :         this->integration_nodes_m = Vector<T, NumNodes1D>();
      27          146 :         this->weights_m           = Vector<T, NumNodes1D>();
      28              : 
      29          146 :         this->computeNodesAndWeights();
      30          146 :     }
      31              : 
      32              :     template <typename T, unsigned NumNodes1D, typename ElementType>
      33              :     typename GaussJacobiQuadrature<T, NumNodes1D, ElementType>::scalar_t
      34          622 :     GaussJacobiQuadrature<T, NumNodes1D, ElementType>::getChebyshevNodes(const size_t& i) const {
      35          622 :         return -Kokkos::cos((2.0 * static_cast<scalar_t>(i) + 1.0) * Kokkos::numbers::pi_v<scalar_t>
      36          622 :                             / (2.0 * NumNodes1D));
      37              :     }
      38              : 
      39              :     template <typename T, unsigned NumNodes1D, typename ElementType>
      40              :     typename GaussJacobiQuadrature<T, NumNodes1D, ElementType>::scalar_t
      41            0 :     GaussJacobiQuadrature<T, NumNodes1D, ElementType>::getLehrFEMInitialGuess(
      42              :         const size_t& i,
      43              :         const Vector<GaussJacobiQuadrature<T, NumNodes1D, ElementType>::scalar_t, NumNodes1D>&
      44              :             integration_nodes) const {
      45            0 :         const scalar_t alpha = this->alpha_m;
      46            0 :         const scalar_t beta  = this->beta_m;
      47              :         scalar_t r1;
      48              :         scalar_t r2;
      49              :         scalar_t r3;
      50            0 :         scalar_t z = (i > 0) ? integration_nodes[i - 1] : 0.0;
      51              : 
      52            0 :         if (i == 0) {
      53              :             // initial guess for the largest root
      54            0 :             const scalar_t an = alpha / NumNodes1D;
      55            0 :             const scalar_t bn = beta / NumNodes1D;
      56            0 :             r1 = (1.0 + alpha) * (2.78 / (4.0 + NumNodes1D * NumNodes1D) + 0.768 * an / NumNodes1D);
      57            0 :             r2 = 1.0 + 1.48 * an + 0.96 * bn + 0.452 * an * an + 0.83 * an * bn;
      58            0 :             z  = 1.0 - r1 / r2;
      59            0 :         } else if (i == 1) {
      60              :             // initial guess for the second largest root
      61            0 :             r1 = (4.1 + alpha) / ((1.0 + alpha) * (1.0 + 0.156 * alpha));
      62            0 :             r2 = 1.0 + 0.06 * (NumNodes1D - 8.0) * (1.0 + 0.12 * alpha) / NumNodes1D;
      63            0 :             r3 = 1.0 + 0.012 * beta * (1.0 + 0.25 * Kokkos::abs(alpha)) / NumNodes1D;
      64            0 :             z -= (1.0 - z) * r1 * r2 * r3;
      65            0 :         } else if (i == 2) {
      66              :             // initial guess for the third largest root
      67            0 :             r1 = (1.67 + 0.28 * alpha) / (1.0 + 0.37 * alpha);
      68            0 :             r2 = 1.0 + 0.22 * (NumNodes1D - 8.0) / NumNodes1D;
      69            0 :             r3 = 1.0 + 8.0 * beta / ((6.28 + beta) * NumNodes1D * NumNodes1D);
      70            0 :             z -= (integration_nodes[0] - z) * r1 * r2 * r3;
      71            0 :         } else if (i == NumNodes1D - 2) {
      72              :             // initial guess for the second smallest root
      73            0 :             r1 = (1.0 + 0.235 * beta) / (0.766 + 0.119 * beta);
      74            0 :             r2 = 1.0 / (1.0 + 0.639 * (NumNodes1D - 4.0) / (1.0 + 0.71 * (NumNodes1D - 4.0)));
      75            0 :             r3 = 1.0 / (1.0 + 20.0 * alpha / ((7.5 + alpha) * NumNodes1D * NumNodes1D));
      76            0 :             z += (z - integration_nodes[NumNodes1D - 4]) * r1 * r2 * r3;
      77            0 :         } else if (i == NumNodes1D - 1) {
      78              :             // initial guess for the smallest root
      79            0 :             r1 = (1.0 + 0.37 * beta) / (1.67 + 0.28 * beta);
      80            0 :             r2 = 1.0 / (1.0 + 0.22 * (NumNodes1D - 8.0) / NumNodes1D);
      81            0 :             r3 = 1.0 / (1.0 + 8.0 * alpha / ((6.28 + alpha) * NumNodes1D * NumNodes1D));
      82            0 :             z += (z - integration_nodes[NumNodes1D - 3]) * r1 * r2 * r3;
      83              :         } else {
      84              :             // initial guess for the other integration_nodes
      85            0 :             z = 3.0 * z - 3.0 * integration_nodes[i - 2] + integration_nodes[i - 3];
      86              :         }
      87            0 :         return z;
      88              :     }
      89              : 
      90              :     template <typename T, unsigned NumNodes1D, typename ElementType>
      91          146 :     void GaussJacobiQuadrature<T, NumNodes1D, ElementType>::computeNodesAndWeights() {
      92              :         // set the initial guess type
      93          146 :         const InitialGuessType& initial_guess_type = InitialGuessType::Chebyshev;
      94              : 
      95              :         /**
      96              :          * the following algorithm for computing the roots and weights for the Gauss-Jacobi
      97              :          * quadrature has been taken from LehrFEM++ (MIT License)
      98              :          * https://craffael.github.io/lehrfempp/gauss__quadrature_8cc_source.html
      99              :          */
     100              : 
     101          146 :         const scalar_t tolerance = 2e-16;
     102              : 
     103          146 :         Vector<scalar_t, NumNodes1D> integration_nodes;
     104          146 :         Vector<scalar_t, NumNodes1D> weights;
     105              : 
     106          146 :         const scalar_t alpha = this->alpha_m;
     107          146 :         const scalar_t beta  = this->beta_m;
     108              : 
     109          146 :         const scalar_t alfbet = alpha + beta;
     110              : 
     111              :         scalar_t a;
     112              :         scalar_t b;
     113              :         scalar_t c;
     114              :         scalar_t p1;
     115              :         scalar_t p2;
     116              :         scalar_t p3;
     117              :         scalar_t pp;
     118              :         scalar_t temp;
     119              :         scalar_t z;
     120              :         scalar_t z1;
     121              : 
     122              :         // Compute the root of the Jacobi polynomial
     123              : 
     124          712 :         for (size_t i = 0; i < NumNodes1D; ++i) {
     125              :             // initial guess depending on which root we are computing
     126          566 :             if (initial_guess_type == InitialGuessType::LehrFEM) {
     127            0 :                 z = this->getLehrFEMInitialGuess(i, integration_nodes);
     128          566 :             } else if (initial_guess_type == InitialGuessType::Chebyshev) {
     129          566 :                 z = -this->getChebyshevNodes(i);
     130              :             } else {
     131            0 :                 throw IpplException("GaussJacobiQuadrature::computeNodesAndWeights",
     132              :                                     "Unknown initial guess type");
     133              :             }
     134              : 
     135              :             // std::cout << NumNodes1D - i - 1 << ", initial guess: " << z << " with "
     136              :             //           << initial_guess_type << std::endl;
     137              : 
     138          566 :             size_t its = 1;
     139              :             do {
     140              :                 // refinement by Newton's method (from LehrFEM++)
     141         2352 :                 temp = 2.0 + alfbet;
     142              : 
     143              :                 // Start the recurrence with P_0 and P1 to avoid a division by zero when
     144              :                 // alpha * beta = 0 or -1
     145         2352 :                 p1 = (alpha - beta + temp * z) / 2.0;
     146         2352 :                 p2 = 1.0;
     147        10984 :                 for (size_t j = 2; j <= NumNodes1D; ++j) {
     148         8632 :                     p3   = p2;
     149         8632 :                     p2   = p1;
     150         8632 :                     temp = 2 * j + alfbet;
     151         8632 :                     a    = 2 * j * (j + alfbet) * (temp - 2.0);
     152         8632 :                     b    = (temp - 1.0) * (alpha * alpha - beta * beta + temp * (temp - 2.0) * z);
     153         8632 :                     c    = 2.0 * (j - 1 + alpha) * (j - 1 + beta) * temp;
     154         8632 :                     p1   = (b * p2 - c * p3) / a;
     155              :                 }
     156         2352 :                 pp = (NumNodes1D * (alpha - beta - temp * z) * p1
     157         2352 :                       + 2.0 * (NumNodes1D + alpha) * (NumNodes1D + beta) * p2)
     158         2352 :                      / (temp * (1.0 - z * z));
     159              :                 // p1 is now the desired jacobian polynomial. We next compute pp, its
     160              :                 // derivative, by a standard relation involving p2, the polynomial of one
     161              :                 // lower order
     162         2352 :                 z1 = z;
     163         2352 :                 z  = z1 - p1 / pp;  // Newtons Formula
     164              : 
     165              :                 // std::cout << "it = " << its << ", i = " << i << ", error: " << Kokkos::abs(z -
     166              :                 // z1)
     167              :                 //           << std::endl;
     168         2352 :                 if (its > this->min_newton_iterations_m && Kokkos::abs(z - z1) <= tolerance) {
     169          566 :                     break;
     170              :                 }
     171         1786 :                 ++its;
     172         1786 :             } while (its <= this->max_newton_iterations_m);
     173              : 
     174          566 :             if (its > this->max_newton_iterations_m) {
     175              :                 //  TODO switch to inform
     176            0 :                 std::cout << "Root " << NumNodes1D - i - 1
     177            0 :                           << " didn't converge. Tolerance may be too high for data type"
     178            0 :                           << std::endl;
     179              :             }
     180              : 
     181              :             // std::cout << "i = " << i << ", result after " << its << " iterations: " << z
     182              :             //           << std::endl;
     183              : 
     184          566 :             integration_nodes[i] = z;
     185              : 
     186              :             // Compute the weight of the Gauss-Jacobi quadrature
     187         1132 :             weights[i] =
     188         1132 :                 Kokkos::exp(Kokkos::lgamma(alpha + NumNodes1D) + Kokkos::lgamma(beta + NumNodes1D)
     189          566 :                             - Kokkos::lgamma(NumNodes1D + 1.)
     190          566 :                             - Kokkos::lgamma(static_cast<double>(NumNodes1D) + alfbet + 1.0))
     191          566 :                 * temp * Kokkos::pow(2.0, alfbet) / (pp * p2);
     192              : 
     193              :             // store the integration nodes and weights in the correct order
     194          566 :             this->integration_nodes_m[i] = static_cast<T>(-integration_nodes[i]);
     195          566 :             this->weights_m[i]           = static_cast<T>(weights[i]);
     196              :         }
     197          146 :     }
     198              : 
     199              : }  // namespace ippl
        

Generated by: LCOV version 2.0-1