LCOV - code coverage report
Current view: top level - src/FEM - LagrangeSpace.hpp (source / functions) Coverage Total Hit
Test: final_report.info Lines: 55.1 % 379 209
Test Date: 2025-07-18 17:15:09 Functions: 56.7 % 240 136

            Line data    Source code
       1              : 
       2              : namespace ippl {
       3              : 
       4              :     // LagrangeSpace constructor, which calls the FiniteElementSpace constructor,
       5              :     // and decomposes the elements among ranks according to layout.
       6              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
       7              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
       8          396 :     LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS, FieldRHS>::LagrangeSpace(
       9              :         UniformCartesian<T, Dim>& mesh, ElementType& ref_element, const QuadratureType& quadrature,
      10              :         const Layout_t& layout)
      11              :         : FiniteElementSpace<T, Dim, getLagrangeNumElementDOFs(Dim, Order), ElementType,
      12          396 :                              QuadratureType, FieldLHS, FieldRHS>(mesh, ref_element, quadrature) {
      13              :         // Assert that the dimension is either 1, 2 or 3.
      14              :         static_assert(Dim >= 1 && Dim <= 3,
      15              :                       "Finite Element space only supports 1D, 2D and 3D meshes");
      16              : 
      17              :         // Initialize the elementIndices view
      18          396 :         initializeElementIndices(layout);
      19          396 :     }
      20              : 
      21              :     // LagrangeSpace constructor, which calls the FiniteElementSpace constructor.
      22              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
      23              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
      24              :     LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS, FieldRHS>::LagrangeSpace(
      25              :         UniformCartesian<T, Dim>& mesh, ElementType& ref_element, const QuadratureType& quadrature)
      26              :         : FiniteElementSpace<T, Dim, getLagrangeNumElementDOFs(Dim, Order), ElementType,
      27              :                              QuadratureType, FieldLHS, FieldRHS>(mesh, ref_element, quadrature) {
      28              :         // Assert that the dimension is either 1, 2 or 3.
      29              :         static_assert(Dim >= 1 && Dim <= 3,
      30              :                       "Finite Element space only supports 1D, 2D and 3D meshes");
      31              :     }
      32              : 
      33              :     // LagrangeSpace initializer, to be made available to the FEMPoissonSolver 
      34              :     // such that we can call it from setRhs.
      35              :     // Sets the correct mesh ad decomposes the elements among ranks according to layout.
      36              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
      37              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
      38            0 :     void LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS, FieldRHS>::initialize(
      39              :         UniformCartesian<T, Dim>& mesh, const Layout_t& layout)
      40              :     {
      41              :         FiniteElementSpace<T, Dim, getLagrangeNumElementDOFs(Dim, Order), ElementType,
      42            0 :                            QuadratureType, FieldLHS, FieldRHS>::setMesh(mesh);
      43              : 
      44              :         // Initialize the elementIndices view
      45            0 :         initializeElementIndices(layout);
      46            0 :     }
      47              : 
      48              :     // Initialize element indices Kokkos View by distributing elements among MPI ranks.
      49              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
      50              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
      51          396 :     void LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS,
      52              :                        FieldRHS>::initializeElementIndices(const Layout_t& layout) {
      53          396 :         const auto& ldom = layout.getLocalNDIndex();
      54          396 :         int npoints      = ldom.size();
      55          396 :         auto first       = ldom.first();
      56          396 :         auto last        = ldom.last();
      57          396 :         ippl::Vector<double, Dim> bounds;
      58              : 
      59         1224 :         for (size_t d = 0; d < Dim; ++d) {
      60          828 :             bounds[d] = this->nr_m[d] - 1;
      61              :         }
      62              : 
      63          396 :         int upperBoundaryPoints = -1;
      64              : 
      65              :         // We iterate over the local domain points, getting the corresponding elements, 
      66              :         // while tagging upper boundary points such that they can be removed after.
      67          396 :         Kokkos::View<size_t*> points("npoints", npoints);
      68          396 :         Kokkos::parallel_reduce(
      69            0 :             "ComputePoints", npoints,
      70         8514 :             KOKKOS_CLASS_LAMBDA(const int i, int& local) {
      71         7722 :                 int idx = i;
      72         7722 :                 indices_t val;
      73         7722 :                 bool isBoundary = false;
      74        29070 :                 for (unsigned int d = 0; d < Dim; ++d) {
      75        21348 :                     int range = last[d] - first[d] + 1;
      76        21348 :                     val[d]    = first[d] + (idx % range);
      77        21348 :                     idx /= range;
      78        21348 :                     if (val[d] == bounds[d]) {
      79         4716 :                         isBoundary = true;
      80              :                     }
      81              :                 }
      82         7722 :                 points(i) = (!isBoundary) * (this->getElementIndex(val));
      83         7722 :                 local += isBoundary;
      84         7722 :             },
      85          792 :             Kokkos::Sum<int>(upperBoundaryPoints));
      86          396 :         Kokkos::fence();
      87              : 
      88              :         // The elementIndices will be the same array as computed above,
      89              :         // with the tagged upper boundary points removed.
      90          396 :         int elementsPerRank = npoints - upperBoundaryPoints;
      91          396 :         elementIndices      = Kokkos::View<size_t*>("i", elementsPerRank);
      92          396 :         Kokkos::View<size_t> index("index");
      93              : 
      94          396 :         Kokkos::parallel_for(
      95        16236 :             "RemoveNaNs", npoints, KOKKOS_CLASS_LAMBDA(const int i) {
      96        15444 :                 if ((points(i) != 0) || (i == 0)) {
      97         7848 :                     const size_t idx    = Kokkos::atomic_fetch_add(&index(), 1);
      98        11772 :                     elementIndices(idx) = points(i);
      99              :                 }
     100              :             });
     101          396 :     }
     102              : 
     103              :     ///////////////////////////////////////////////////////////////////////
     104              :     /// Degree of Freedom operations //////////////////////////////////////
     105              :     ///////////////////////////////////////////////////////////////////////
     106              : 
     107              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
     108              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
     109           24 :     KOKKOS_FUNCTION size_t LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS,
     110              :                                          FieldRHS>::numGlobalDOFs() const {
     111           24 :         size_t num_global_dofs = 1;
     112           72 :         for (size_t d = 0; d < Dim; ++d) {
     113           48 :             num_global_dofs *= this->nr_m[d] * Order;
     114              :         }
     115              : 
     116           24 :         return num_global_dofs;
     117              :     }
     118              : 
     119              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
     120              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
     121              :     KOKKOS_FUNCTION
     122         1032 :     size_t LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS, FieldRHS>::getLocalDOFIndex
     123              :     (const size_t& elementIndex, const size_t& globalDOFIndex) const {
     124              :         static_assert(Dim == 1 || Dim == 2 || Dim == 3, "Dim must be 1, 2 or 3");
     125              :         // TODO fix not order independent, only works for order 1
     126              :         static_assert(Order == 1, "Only order 1 is supported at the moment");
     127              : 
     128              :         // Get all the global DOFs for the element
     129         1032 :         const Vector<size_t, numElementDOFs> global_dofs =
     130         1032 :             this->getGlobalDOFIndices(elementIndex);
     131              : 
     132         1032 :         ippl::Vector<size_t, numElementDOFs> dof_mapping;
     133              :         if (Dim == 1) {
     134           24 :             dof_mapping = {0, 1};
     135              :         } else if (Dim == 2) {
     136          144 :             dof_mapping = {0, 1, 3, 2};
     137              :         } else if (Dim == 3) {
     138          864 :             dof_mapping = {0, 1, 3, 2, 4, 5, 7, 6};
     139              :         }
     140              : 
     141              :         // Find the global DOF in the vector and return the local DOF index
     142              :         // TODO this can be done faster since the global DOFs are sorted
     143         7232 :         for (size_t i = 0; i < dof_mapping.dim; ++i) {
     144         6536 :             if (global_dofs[dof_mapping[i]] == globalDOFIndex) {
     145          336 :                 return dof_mapping[i];
     146              :             }
     147              :         }
     148          696 :         return std::numeric_limits<size_t>::quiet_NaN();
     149              :         //throw IpplException("LagrangeSpace::getLocalDOFIndex()",
     150              :         //                    "FEM Lagrange Space: Global DOF not found in specified element");
     151         1032 :     }
     152              : 
     153              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
     154              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
     155              :     KOKKOS_FUNCTION size_t
     156          336 :     LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS,
     157              :                   FieldRHS>::getGlobalDOFIndex(const size_t& elementIndex,
     158              :                                                const size_t& localDOFIndex) const {
     159          336 :         const auto global_dofs = this->getGlobalDOFIndices(elementIndex);
     160              : 
     161          672 :         return global_dofs[localDOFIndex];
     162          336 :     }
     163              : 
     164              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
     165              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
     166              :     KOKKOS_FUNCTION Vector<size_t, LagrangeSpace<T, Dim, Order, ElementType, QuadratureType,
     167              :                                                  FieldLHS, FieldRHS>::numElementDOFs>
     168          452 :     LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS,
     169              :                   FieldRHS>::getLocalDOFIndices() const {
     170          452 :         Vector<size_t, numElementDOFs> localDOFs;
     171              : 
     172         3756 :         for (size_t dof = 0; dof < numElementDOFs; ++dof) {
     173         3304 :             localDOFs[dof] = dof;
     174              :         }
     175              : 
     176          452 :         return localDOFs;
     177              :     }
     178              : 
     179              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
     180              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
     181              :     KOKKOS_FUNCTION Vector<size_t, LagrangeSpace<T, Dim, Order, ElementType, QuadratureType,
     182              :                                                  FieldLHS, FieldRHS>::numElementDOFs>
     183         1820 :     LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS,
     184              :                   FieldRHS>::getGlobalDOFIndices(const size_t& elementIndex) const {
     185         1820 :         Vector<size_t, numElementDOFs> globalDOFs(0);
     186              : 
     187              :         // get element pos
     188         1820 :         indices_t elementPos = this->getElementNDIndex(elementIndex);
     189              : 
     190              :         // Compute the vector to multiply the ndindex with
     191         1820 :         ippl::Vector<size_t, Dim> vec(1);
     192         5080 :         for (size_t d = 1; d < dim; ++d) {
     193         8028 :             for (size_t d2 = d; d2 < Dim; ++d2) {
     194         4768 :                 vec[d2] *= this->nr_m[d - 1];
     195              :             }
     196              :         }
     197         1820 :         vec *= Order;  // Multiply each dimension by the order
     198         1820 :         size_t smallestGlobalDOF = elementPos.dot(vec);
     199              : 
     200              :         // Add the vertex DOFs
     201         1820 :         globalDOFs[0] = smallestGlobalDOF;
     202         1820 :         globalDOFs[1] = smallestGlobalDOF + Order;
     203              : 
     204              :         if (Dim >= 2) {
     205         1752 :             globalDOFs[2] = globalDOFs[1] + this->nr_m[1] * Order;
     206         1752 :             globalDOFs[3] = globalDOFs[0] + this->nr_m[1] * Order;
     207              :         }
     208              :         if (Dim >= 3) {
     209         1508 :             globalDOFs[4] = globalDOFs[0] + this->nr_m[1] * this->nr_m[2] * Order;
     210         1508 :             globalDOFs[5] = globalDOFs[1] + this->nr_m[1] * this->nr_m[2] * Order;
     211         1508 :             globalDOFs[6] = globalDOFs[2] + this->nr_m[1] * this->nr_m[2] * Order;
     212         1508 :             globalDOFs[7] = globalDOFs[3] + this->nr_m[1] * this->nr_m[2] * Order;
     213              :         }
     214              : 
     215              :         if (Order > 1) {
     216              :             // If the order is greater than 1, there are edge and face DOFs, otherwise the work is
     217              :             // done
     218              : 
     219              :             // Add the edge DOFs
     220              :             if (Dim >= 2) {
     221              :                 for (size_t i = 0; i < Order - 1; ++i) {
     222              :                     globalDOFs[8 + i]                   = globalDOFs[0] + i + 1;
     223              :                     globalDOFs[8 + Order - 1 + i]       = globalDOFs[1] + (i + 1) * this->nr_m[1];
     224              :                     globalDOFs[8 + 2 * (Order - 1) + i] = globalDOFs[2] - (i + 1);
     225              :                     globalDOFs[8 + 3 * (Order - 1) + i] = globalDOFs[3] - (i + 1) * this->nr_m[1];
     226              :                 }
     227              :             }
     228              :             if (Dim >= 3) {
     229              :                 // TODO
     230              :             }
     231              : 
     232              :             // Add the face DOFs
     233              :             if (Dim >= 2) {
     234              :                 for (size_t i = 0; i < Order - 1; ++i) {
     235              :                     for (size_t j = 0; j < Order - 1; ++j) {
     236              :                         // TODO CHECK
     237              :                         globalDOFs[8 + 4 * (Order - 1) + i * (Order - 1) + j] =
     238              :                             globalDOFs[0] + (i + 1) + (j + 1) * this->nr_m[1];
     239              :                         globalDOFs[8 + 4 * (Order - 1) + (Order - 1) * (Order - 1) + i * (Order - 1)
     240              :                                    + j] = globalDOFs[1] + (i + 1) + (j + 1) * this->nr_m[1];
     241              :                         globalDOFs[8 + 4 * (Order - 1) + 2 * (Order - 1) * (Order - 1)
     242              :                                    + i * (Order - 1) + j] =
     243              :                             globalDOFs[2] - (i + 1) + (j + 1) * this->nr_m[1];
     244              :                         globalDOFs[8 + 4 * (Order - 1) + 3 * (Order - 1) * (Order - 1)
     245              :                                    + i * (Order - 1) + j] =
     246              :                             globalDOFs[3] - (i + 1) + (j + 1) * this->nr_m[1];
     247              :                     }
     248              :                 }
     249              :             }
     250              :         }
     251              : 
     252         1820 :         return globalDOFs;
     253         1820 :     }
     254              : 
     255              :     ///////////////////////////////////////////////////////////////////////
     256              :     /// Basis functions and gradients /////////////////////////////////////
     257              :     ///////////////////////////////////////////////////////////////////////
     258              : 
     259              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
     260              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
     261              :     KOKKOS_FUNCTION T
     262       267040 :     LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS, FieldRHS>::
     263              :         evaluateRefElementShapeFunction(
     264              :             const size_t& localDOF,
     265              :             const LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS,
     266              :                                 FieldRHS>::point_t& localPoint) const {
     267              :         static_assert(Order == 1, "Only order 1 is supported at the moment");
     268              :         // Assert that the local vertex index is valid.
     269       267040 :         assert(localDOF < numElementDOFs
     270              :                && "The local vertex index is invalid");  // TODO assumes 1st order Lagrange
     271              : 
     272       267040 :         assert(this->ref_element_m.isPointInRefElement(localPoint)
     273              :                && "Point is not in reference element");
     274              : 
     275              :         // Get the local vertex indices for the local vertex index.
     276              :         // TODO fix not order independent, only works for order 1
     277       267040 :         const point_t ref_element_point = this->ref_element_m.getLocalVertices()[localDOF];
     278              : 
     279              :         // The variable that accumulates the product of the shape functions.
     280       267040 :         T product = 1;
     281              : 
     282      1060944 :         for (size_t d = 0; d < Dim; d++) {
     283       793904 :             if (localPoint[d] < ref_element_point[d]) {
     284       396952 :                 product *= localPoint[d];
     285              :             } else {
     286       396952 :                 product *= 1.0 - localPoint[d];
     287              :             }
     288              :         }
     289              : 
     290       267040 :         return product;
     291       267040 :     }
     292              : 
     293              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
     294              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
     295              :     KOKKOS_FUNCTION typename LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS,
     296              :                                            FieldRHS>::point_t
     297       262600 :     LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS, FieldRHS>::
     298              :         evaluateRefElementShapeFunctionGradient(
     299              :             const size_t& localDOF,
     300              :             const LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS,
     301              :                                 FieldRHS>::point_t& localPoint) const {
     302              :         // TODO fix not order independent, only works for order 1
     303              :         static_assert(Order == 1 && "Only order 1 is supported at the moment");
     304              : 
     305              :         // Assert that the local vertex index is valid.
     306       262600 :         assert(localDOF < numElementDOFs && "The local vertex index is invalid");
     307              : 
     308       262600 :         assert(this->ref_element_m.isPointInRefElement(localPoint)
     309              :                && "Point is not in reference element");
     310              : 
     311              :         // Get the local dof nd_index
     312       262600 :         const vertex_points_t local_vertex_points = this->ref_element_m.getLocalVertices();
     313              : 
     314       262600 :         const point_t& local_vertex_point = local_vertex_points[localDOF];
     315              : 
     316       262600 :         point_t gradient(1);
     317              : 
     318              :         // To construct the gradient we need to loop over the dimensions and multiply the
     319              :         // shape functions in each dimension except the current one. The one of the current
     320              :         // dimension is replaced by the derivative of the shape function in that dimension,
     321              :         // which is either 1 or -1.
     322      1043664 :         for (size_t d = 0; d < Dim; d++) {
     323              :             // The variable that accumulates the product of the shape functions.
     324       781064 :             T product = 1;
     325              : 
     326      3111120 :             for (size_t d2 = 0; d2 < Dim; d2++) {
     327      2330056 :                 if (d2 == d) {
     328       781064 :                     if (localPoint[d] < local_vertex_point[d]) {
     329       390532 :                         product *= 1;
     330              :                     } else {
     331       390532 :                         product *= -1;
     332              :                     }
     333              :                 } else {
     334      1548992 :                     if (localPoint[d2] < local_vertex_point[d2]) {
     335       774496 :                         product *= localPoint[d2];
     336              :                     } else {
     337       774496 :                         product *= 1.0 - localPoint[d2];
     338              :                     }
     339              :                 }
     340              :             }
     341              : 
     342       781064 :             gradient[d] = product;
     343              :         }
     344              : 
     345       262600 :         return gradient;
     346       262600 :     }
     347              : 
     348              :     ///////////////////////////////////////////////////////////////////////
     349              :     /// Assembly operations ///////////////////////////////////////////////
     350              :     ///////////////////////////////////////////////////////////////////////
     351              : 
     352              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
     353              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
     354              :     template <typename F>
     355            8 :     FieldLHS LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS,
     356              :                            FieldRHS>::evaluateAx(FieldLHS& field, F& evalFunction) const {
     357            8 :         Inform m("");
     358              : 
     359              :         // start a timer
     360            8 :         static IpplTimings::TimerRef evalAx = IpplTimings::getTimer("evaluateAx");
     361            8 :         IpplTimings::startTimer(evalAx);
     362              : 
     363              :         // get number of ghost cells in field
     364            8 :         const int nghost = field.getNghost();
     365              : 
     366              :         // create a new field for result with view initialized to zero (views are initialized to
     367              :         // zero by default)
     368            8 :         FieldLHS resultField(field.get_mesh(), field.getLayout(), nghost);
     369              : 
     370              :         // List of quadrature weights
     371            8 :         const Vector<T, QuadratureType::numElementNodes> w =
     372            8 :             this->quadrature_m.getWeightsForRefElement();
     373              : 
     374              :         // List of quadrature nodes
     375            8 :         const Vector<point_t, QuadratureType::numElementNodes> q =
     376            8 :             this->quadrature_m.getIntegrationNodesForRefElement();
     377              : 
     378              :         // TODO move outside of evaluateAx (I think it is possible for other problems as well)
     379              :         // Gradients of the basis functions for the DOF at the quadrature nodes
     380            8 :         Vector<Vector<point_t, numElementDOFs>, QuadratureType::numElementNodes> grad_b_q;
     381           16 :         for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
     382           48 :             for (size_t i = 0; i < numElementDOFs; ++i) {
     383           40 :                 grad_b_q[k][i] = this->evaluateRefElementShapeFunctionGradient(i, q[k]);
     384              :             }
     385              :         }
     386              : 
     387              :         // Get field data and atomic result data,
     388              :         // since it will be added to during the kokkos loop
     389            8 :         ViewType view             = field.getView();
     390            8 :         AtomicViewType resultView = resultField.getView();
     391              : 
     392              :         // Get boundary conditions from field
     393            8 :         BConds<FieldLHS, Dim>& bcField = field.getFieldBC();
     394            8 :         FieldBC bcType = bcField[0]->getBCType();
     395              : 
     396              :         // Get domain information
     397            8 :         auto ldom = (field.getLayout()).getLocalNDIndex();
     398              : 
     399              :         using exec_space  = typename Kokkos::View<const size_t*>::execution_space;
     400              :         using policy_type = Kokkos::RangePolicy<exec_space>;
     401              : 
     402              :         // start a timer
     403            8 :         static IpplTimings::TimerRef outer_loop = IpplTimings::getTimer("evaluateAx: outer loop");
     404            8 :         IpplTimings::startTimer(outer_loop);
     405              : 
     406              :         // Loop over elements to compute contributions
     407            8 :         Kokkos::parallel_for(
     408           16 :             "Loop over elements", policy_type(0, elementIndices.extent(0)),
     409          152 :             KOKKOS_CLASS_LAMBDA(const size_t index) {
     410            0 :                 const size_t elementIndex                            = elementIndices(index);
     411          136 :                 const Vector<size_t, numElementDOFs> local_dof = this->getLocalDOFIndices();
     412            0 :                 const Vector<size_t, numElementDOFs> global_dofs =
     413          136 :                     this->getGlobalDOFIndices(elementIndex);
     414          136 :                 Vector<indices_t, numElementDOFs> global_dof_ndindices;
     415              : 
     416         1176 :                 for (size_t i = 0; i < numElementDOFs; ++i) {
     417         1040 :                     global_dof_ndindices[i] = this->getMeshVertexNDIndex(global_dofs[i]);
     418              :                 }
     419              : 
     420              :                 // local DOF indices
     421              :                 size_t i, j;
     422              : 
     423              :                 // Element matrix
     424          136 :                 Vector<Vector<T, numElementDOFs>, numElementDOFs> A_K;
     425              : 
     426              :                 // 1. Compute the Galerkin element matrix A_K
     427         1176 :                 for (i = 0; i < numElementDOFs; ++i) {
     428         9264 :                     for (j = 0; j < numElementDOFs; ++j) {
     429            0 :                         A_K[i][j] = 0.0;
     430        16448 :                         for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
     431         8224 :                             A_K[i][j] += w[k] * evalFunction(i, j, grad_b_q[k]);
     432              :                         }
     433              :                     }
     434              :                 }
     435              : 
     436              :                 // global DOF n-dimensional indices (Vector of N indices representing indices in
     437              :                 // each dimension)
     438          136 :                 indices_t I_nd, J_nd;
     439              : 
     440              :                 // 2. Compute the contribution to resultAx = A*x with A_K
     441         1176 :                 for (i = 0; i < numElementDOFs; ++i) {
     442            0 :                     I_nd = global_dof_ndindices[i];
     443              : 
     444              :                     // Handle boundary DOFs
     445              :                     // If Zero Dirichlet BCs, skip this DOF
     446              :                     // If Constant Dirichlet BCs, identity
     447         1040 :                     if ((bcType == CONSTANT_FACE) && (this->isDOFOnBoundary(I_nd))) {
     448            0 :                         for (unsigned d = 0; d < Dim; ++d) {
     449            0 :                             I_nd[d] = I_nd[d] - ldom[d].first() + nghost;
     450              :                         }
     451            0 :                         apply(resultView, I_nd) =  apply(view, I_nd);
     452            0 :                         continue;
     453         1040 :                     } else if ((bcType == ZERO_FACE) && (this->isDOFOnBoundary(I_nd))) {
     454            0 :                         continue;
     455              :                     }
     456              : 
     457              :                     // get the appropriate index for the Kokkos view of the field
     458         1752 :                     for (unsigned d = 0; d < Dim; ++d) {
     459            0 :                         I_nd[d] = I_nd[d] - ldom[d].first() + nghost;
     460              :                     }
     461              : 
     462         3924 :                     for (j = 0; j < numElementDOFs; ++j) {
     463            0 :                         J_nd = global_dof_ndindices[j];
     464              : 
     465              :                         // Skip boundary DOFs (Zero & Constant Dirichlet BCs)
     466            0 :                         if (((bcType == ZERO_FACE) || (bcType == CONSTANT_FACE)) 
     467         3480 :                             && this->isDOFOnBoundary(J_nd)) {
     468            0 :                             continue;
     469              :                         }
     470              : 
     471              :                         // get the appropriate index for the Kokkos view of the field
     472         8040 :                         for (unsigned d = 0; d < Dim; ++d) {
     473            0 :                             J_nd[d] = J_nd[d] - ldom[d].first() + nghost;
     474              :                         }
     475              : 
     476         2020 :                         apply(resultView, I_nd) += A_K[i][j] * apply(view, J_nd);
     477              :                     }
     478              :                 }
     479            0 :             });
     480            8 :         IpplTimings::stopTimer(outer_loop);
     481              : 
     482            8 :         if (bcType == PERIODIC_FACE) {
     483            0 :             resultField.accumulateHalo();
     484            0 :             bcField.apply(resultField);
     485            0 :             bcField.assignGhostToPhysical(resultField);
     486              :         } else {
     487            8 :             resultField.accumulateHalo_noghost();
     488              :         }
     489              : 
     490            8 :         IpplTimings::stopTimer(evalAx);
     491              : 
     492            8 :         return resultField;
     493            8 :     }
     494              : 
     495              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
     496              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
     497              :     template <typename F>
     498            0 :     FieldLHS LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS,
     499              :                            FieldRHS>::evaluateAx_lift(FieldLHS& field, F& evalFunction) const {
     500            0 :         Inform m("");
     501              : 
     502              :         // get number of ghost cells in field
     503            0 :         const int nghost = field.getNghost();
     504              : 
     505              :         // create a new field for result with view initialized to zero (views are initialized to
     506              :         // zero by default)
     507            0 :         FieldLHS resultField(field.get_mesh(), field.getLayout(), nghost);
     508              : 
     509              :         // List of quadrature weights
     510            0 :         const Vector<T, QuadratureType::numElementNodes> w =
     511            0 :             this->quadrature_m.getWeightsForRefElement();
     512              : 
     513              :         // List of quadrature nodes
     514            0 :         const Vector<point_t, QuadratureType::numElementNodes> q =
     515            0 :             this->quadrature_m.getIntegrationNodesForRefElement();
     516              : 
     517              :         // TODO move outside of evaluateAx (I think it is possible for other problems as well)
     518              :         // Gradients of the basis functions for the DOF at the quadrature nodes
     519            0 :         Vector<Vector<point_t, numElementDOFs>, QuadratureType::numElementNodes> grad_b_q;
     520            0 :         for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
     521            0 :             for (size_t i = 0; i < numElementDOFs; ++i) {
     522            0 :                 grad_b_q[k][i] = this->evaluateRefElementShapeFunctionGradient(i, q[k]);
     523              :             }
     524              :         }
     525              : 
     526              :         // Get field data and atomic result data,
     527              :         // since it will be added to during the kokkos loop
     528            0 :         ViewType view             = field.getView();
     529            0 :         AtomicViewType resultView = resultField.getView();
     530              : 
     531              :         // Get domain information
     532            0 :         auto ldom = (field.getLayout()).getLocalNDIndex();
     533              : 
     534              :         using exec_space  = typename Kokkos::View<const size_t*>::execution_space;
     535              :         using policy_type = Kokkos::RangePolicy<exec_space>;
     536              : 
     537              :         // Loop over elements to compute contributions
     538            0 :         Kokkos::parallel_for(
     539            0 :             "Loop over elements", policy_type(0, elementIndices.extent(0)),
     540            0 :             KOKKOS_CLASS_LAMBDA(const size_t index) {
     541            0 :                 const size_t elementIndex                            = elementIndices(index);
     542            0 :                 const Vector<size_t, numElementDOFs> local_dof = this->getLocalDOFIndices();
     543            0 :                 const Vector<size_t, numElementDOFs> global_dofs =
     544            0 :                     this->getGlobalDOFIndices(elementIndex);
     545            0 :                 Vector<indices_t, numElementDOFs> global_dof_ndindices;
     546              : 
     547            0 :                 for (size_t i = 0; i < numElementDOFs; ++i) {
     548            0 :                     global_dof_ndindices[i] = this->getMeshVertexNDIndex(global_dofs[i]);
     549              :                 }
     550              : 
     551              :                 // local DOF indices
     552              :                 size_t i, j;
     553              : 
     554              :                 // Element matrix
     555            0 :                 Vector<Vector<T, numElementDOFs>, numElementDOFs> A_K;
     556              : 
     557              :                 // 1. Compute the Galerkin element matrix A_K
     558            0 :                 for (i = 0; i < numElementDOFs; ++i) {
     559            0 :                     for (j = 0; j < numElementDOFs; ++j) {
     560            0 :                         A_K[i][j] = 0.0;
     561            0 :                         for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
     562            0 :                             A_K[i][j] += w[k] * evalFunction(i, j, grad_b_q[k]);
     563              :                         }
     564              :                     }
     565              :                 }
     566              : 
     567              :                 // global DOF n-dimensional indices (Vector of N indices representing indices in
     568              :                 // each dimension)
     569            0 :                 indices_t I_nd, J_nd;
     570              : 
     571              :                 // 2. Compute the contribution to resultAx = A*x with A_K
     572            0 :                 for (i = 0; i < numElementDOFs; ++i) {
     573            0 :                     I_nd = global_dof_ndindices[i];
     574              : 
     575              :                     // Skip if on a row of the matrix
     576            0 :                     if (this->isDOFOnBoundary(I_nd)) {
     577            0 :                         continue;
     578              :                     }
     579              : 
     580              :                     // get the appropriate index for the Kokkos view of the field
     581            0 :                     for (unsigned d = 0; d < Dim; ++d) {
     582            0 :                         I_nd[d] = I_nd[d] - ldom[d].first() + nghost;
     583              :                     }
     584              : 
     585            0 :                     for (j = 0; j < numElementDOFs; ++j) {
     586            0 :                         J_nd = global_dof_ndindices[j];
     587              : 
     588              :                         // Contribute to lifting only if on a boundary DOF
     589            0 :                         if (this->isDOFOnBoundary(J_nd)) {
     590              :                             // get the appropriate index for the Kokkos view of the field
     591            0 :                             for (unsigned d = 0; d < Dim; ++d) {
     592            0 :                                 J_nd[d] = J_nd[d] - ldom[d].first() + nghost;
     593              :                             }
     594            0 :                             apply(resultView, I_nd) += A_K[i][j] * apply(view, J_nd);
     595            0 :                             continue;
     596            0 :                         }
     597              : 
     598              :                     }
     599              :                 }
     600            0 :             });
     601            0 :         resultField.accumulateHalo();
     602              : 
     603            0 :         return resultField;
     604            0 :     }
     605              : 
     606              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
     607              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
     608           20 :     void LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS,
     609              :                        FieldRHS>::evaluateLoadVector(FieldRHS& field) const {
     610           20 :         Inform m("");
     611              : 
     612              :         // start a timer
     613           20 :         static IpplTimings::TimerRef evalLoadV = IpplTimings::getTimer("evaluateLoadVector");
     614           20 :         IpplTimings::startTimer(evalLoadV);
     615              : 
     616              :         // List of quadrature weights
     617           20 :         const Vector<T, QuadratureType::numElementNodes> w =
     618           20 :             this->quadrature_m.getWeightsForRefElement();
     619              : 
     620              :         // List of quadrature nodes
     621           20 :         const Vector<point_t, QuadratureType::numElementNodes> q =
     622           20 :             this->quadrature_m.getIntegrationNodesForRefElement();
     623              : 
     624           20 :         const indices_t zeroNdIndex = Vector<size_t, Dim>(0);
     625              : 
     626              :         // Evaluate the basis functions for the DOF at the quadrature nodes
     627           20 :         Vector<Vector<T, numElementDOFs>, QuadratureType::numElementNodes> basis_q;
     628          648 :         for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
     629         5108 :             for (size_t i = 0; i < numElementDOFs; ++i) {
     630         4480 :                 basis_q[k][i] = this->evaluateRefElementShapeFunction(i, q[k]);
     631              :             }
     632              :         }
     633              : 
     634              :         // Absolute value of det Phi_K
     635           20 :         const T absDetDPhi = Kokkos::abs(this->ref_element_m.getDeterminantOfTransformationJacobian(
     636           40 :             this->getElementMeshVertexPoints(zeroNdIndex)));
     637              : 
     638              :         // Get domain information and ghost cells
     639           20 :         auto ldom        = (field.getLayout()).getLocalNDIndex();
     640           20 :         const int nghost = field.getNghost();
     641              : 
     642              :         // Get boundary conditions from field
     643           20 :         BConds<FieldRHS, Dim>& bcField = field.getFieldBC();
     644           20 :         FieldBC bcType = bcField[0]->getBCType();
     645              : 
     646           20 :         FieldRHS temp_field(field.get_mesh(), field.getLayout(), nghost);
     647           20 :         temp_field.setFieldBC(bcField);
     648              : 
     649              :         // Get field data and make it atomic,
     650              :         // since it will be added to during the kokkos loop
     651              :         // We work with a temporary field since we need to use field
     652              :         // to evaluate the load vector; then we assign temp to RHS field
     653           20 :         AtomicViewType atomic_view = temp_field.getView();
     654              : 
     655              :         using exec_space  = typename Kokkos::View<const size_t*>::execution_space;
     656              :         using policy_type = Kokkos::RangePolicy<exec_space>;
     657              : 
     658              :         // start a timer
     659           20 :         static IpplTimings::TimerRef outer_loop =
     660           20 :             IpplTimings::getTimer("evaluateLoadVec: outer loop");
     661           20 :         IpplTimings::startTimer(outer_loop);
     662              : 
     663              :         // Loop over elements to compute contributions
     664           20 :         Kokkos::parallel_for(
     665           40 :             "Loop over elements", policy_type(0, elementIndices.extent(0)),
     666          344 :             KOKKOS_CLASS_LAMBDA(size_t index) {
     667            0 :                 const size_t elementIndex                              = elementIndices(index);
     668          304 :                 const Vector<size_t, numElementDOFs> local_dofs  = this->getLocalDOFIndices();
     669            0 :                 const Vector<size_t, numElementDOFs> global_dofs =
     670          304 :                     this->getGlobalDOFIndices(elementIndex);
     671              : 
     672              :                 size_t i, I;
     673              : 
     674              :                 // 1. Compute b_K
     675         4720 :                 for (i = 0; i < numElementDOFs; ++i) {
     676            0 :                     I = global_dofs[i];
     677              : 
     678              :                     // TODO fix for higher order
     679         2208 :                     auto dof_ndindex_I = this->getMeshVertexNDIndex(I);
     680              : 
     681              :                     // Skip boundary DOFs (Zero and Constant Dirichlet BCs)
     682            0 :                     if (((bcType == ZERO_FACE) || (bcType == CONSTANT_FACE))
     683         2208 :                         && (this->isDOFOnBoundary(dof_ndindex_I))) {
     684            0 :                         continue;
     685              :                     }
     686              : 
     687              :                     // calculate the contribution of this element
     688            0 :                     T contrib = 0;
     689        57264 :                     for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
     690            0 :                         T val = 0;
     691       499104 :                         for (size_t j = 0; j < numElementDOFs; ++j) {
     692              :                             // get field index corresponding to this DOF
     693            0 :                             size_t J           = global_dofs[j];
     694       442800 :                             auto dof_ndindex_J = this->getMeshVertexNDIndex(J);
     695      1763712 :                             for (unsigned d = 0; d < Dim; ++d) {
     696            0 :                                 dof_ndindex_J[d] = dof_ndindex_J[d] - ldom[d].first() + nghost;
     697              :                             }
     698              : 
     699              :                             // get field value at DOF and interpolate to q_k
     700       442800 :                             val += basis_q[k][j] * apply(field, dof_ndindex_J);
     701              :                         }
     702              : 
     703            0 :                         contrib += w[k] * basis_q[k][i] * absDetDPhi * val;
     704              :                     }
     705              : 
     706              :                     // get the appropriate index for the Kokkos view of the field
     707         3720 :                     for (unsigned d = 0; d < Dim; ++d) {
     708            0 :                         dof_ndindex_I[d] = dof_ndindex_I[d] - ldom[d].first() + nghost;
     709              :                     }
     710              : 
     711              :                     // add the contribution of the element to the field
     712          960 :                     apply(atomic_view, dof_ndindex_I) += contrib;
     713              : 
     714              :                 }
     715            0 :             });
     716           20 :         IpplTimings::stopTimer(outer_loop);
     717              : 
     718           20 :         temp_field.accumulateHalo();
     719              : 
     720           20 :         if ((bcType == PERIODIC_FACE) || (bcType == CONSTANT_FACE)) {
     721            0 :             bcField.apply(temp_field);
     722            0 :             bcField.assignGhostToPhysical(temp_field);
     723              :         }
     724              : 
     725           20 :         field = temp_field;
     726              : 
     727           20 :         IpplTimings::stopTimer(evalLoadV);
     728           20 :     }
     729              : 
     730              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
     731              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
     732              :     template <typename F>
     733            0 :     T LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS, FieldRHS>::computeErrorL2(
     734              :         const FieldLHS& u_h, const F& u_sol) const {
     735            0 :         if (this->quadrature_m.getOrder() < (2 * Order + 1)) {
     736              :             // throw exception
     737            0 :             throw IpplException(
     738              :                 "LagrangeSpace::computeErrorL2()",
     739              :                 "Order of quadrature rule for error computation should be > 2*p + 1");
     740              :         }
     741              : 
     742              :         // List of quadrature weights
     743            0 :         const Vector<T, QuadratureType::numElementNodes> w =
     744            0 :             this->quadrature_m.getWeightsForRefElement();
     745              : 
     746              :         // List of quadrature nodes
     747            0 :         const Vector<point_t, QuadratureType::numElementNodes> q =
     748            0 :             this->quadrature_m.getIntegrationNodesForRefElement();
     749              : 
     750              :         // Evaluate the basis functions for the DOF at the quadrature nodes
     751            0 :         Vector<Vector<T, numElementDOFs>, QuadratureType::numElementNodes> basis_q;
     752            0 :         for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
     753            0 :             for (size_t i = 0; i < numElementDOFs; ++i) {
     754            0 :                 basis_q[k][i] = this->evaluateRefElementShapeFunction(i, q[k]);
     755              :             }
     756              :         }
     757              : 
     758            0 :         const indices_t zeroNdIndex = Vector<size_t, Dim>(0);
     759              : 
     760              :         // Absolute value of det Phi_K
     761            0 :         const T absDetDPhi = Kokkos::abs(this->ref_element_m.getDeterminantOfTransformationJacobian(
     762            0 :             this->getElementMeshVertexPoints(zeroNdIndex)));
     763              : 
     764              :         // Variable to sum the error to
     765            0 :         T error = 0;
     766              : 
     767              :         // Get domain information and ghost cells
     768            0 :         auto ldom        = (u_h.getLayout()).getLocalNDIndex();
     769            0 :         const int nghost = u_h.getNghost();
     770              : 
     771              :         using exec_space  = typename Kokkos::View<const size_t*>::execution_space;
     772              :         using policy_type = Kokkos::RangePolicy<exec_space>;
     773              : 
     774              :         // Loop over elements to compute contributions
     775            0 :         Kokkos::parallel_reduce(
     776            0 :             "Compute error over elements", policy_type(0, elementIndices.extent(0)),
     777            0 :             KOKKOS_CLASS_LAMBDA(size_t index, double& local) {
     778            0 :                 const size_t elementIndex = elementIndices(index);
     779            0 :                 const Vector<size_t, numElementDOFs> global_dofs =
     780            0 :                     this->getGlobalDOFIndices(elementIndex);
     781              : 
     782              :                 // contribution of this element to the error
     783            0 :                 T contrib = 0;
     784            0 :                 for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
     785            0 :                     T val_u_sol = u_sol(this->ref_element_m.localToGlobal(
     786            0 :                         this->getElementMeshVertexPoints(this->getElementNDIndex(elementIndex)),
     787            0 :                         q[k]));
     788              : 
     789            0 :                     T val_u_h = 0;
     790            0 :                     for (size_t i = 0; i < numElementDOFs; ++i) {
     791              :                         // get field index corresponding to this DOF
     792            0 :                         size_t I           = global_dofs[i];
     793            0 :                         auto dof_ndindex_I = this->getMeshVertexNDIndex(I);
     794            0 :                         for (unsigned d = 0; d < Dim; ++d) {
     795            0 :                             dof_ndindex_I[d] = dof_ndindex_I[d] - ldom[d].first() + nghost;
     796              :                         }
     797              : 
     798              :                         // get field value at DOF and interpolate to q_k
     799            0 :                         val_u_h += basis_q[k][i] * apply(u_h, dof_ndindex_I);
     800              :                     }
     801              : 
     802            0 :                     contrib += w[k] * Kokkos::pow(val_u_sol - val_u_h, 2) * absDetDPhi;
     803              :                 }
     804            0 :                 local += contrib;
     805            0 :             },
     806            0 :             Kokkos::Sum<double>(error));
     807              : 
     808              :         // MPI reduce
     809            0 :         T global_error = 0.0;
     810            0 :         Comm->allreduce(error, global_error, 1, std::plus<T>());
     811              : 
     812            0 :         return Kokkos::sqrt(global_error);
     813            0 :     }
     814              : 
     815              :     template <typename T, unsigned Dim, unsigned Order, typename ElementType,
     816              :               typename QuadratureType, typename FieldLHS, typename FieldRHS>
     817            0 :     T LagrangeSpace<T, Dim, Order, ElementType, QuadratureType, FieldLHS, FieldRHS>::computeAvg(
     818              :         const FieldLHS& u_h) const {
     819            0 :         if (this->quadrature_m.getOrder() < (2 * Order + 1)) {
     820              :             // throw exception
     821            0 :             throw IpplException(
     822              :                 "LagrangeSpace::computeAvg()",
     823              :                 "Order of quadrature rule for error computation should be > 2*p + 1");
     824              :         }
     825              : 
     826              :         // List of quadrature weights
     827            0 :         const Vector<T, QuadratureType::numElementNodes> w =
     828            0 :             this->quadrature_m.getWeightsForRefElement();
     829              : 
     830              :         // List of quadrature nodes
     831            0 :         const Vector<point_t, QuadratureType::numElementNodes> q =
     832            0 :             this->quadrature_m.getIntegrationNodesForRefElement();
     833              : 
     834              :         // Evaluate the basis functions for the DOF at the quadrature nodes
     835            0 :         Vector<Vector<T, numElementDOFs>, QuadratureType::numElementNodes> basis_q;
     836            0 :         for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
     837            0 :             for (size_t i = 0; i < numElementDOFs; ++i) {
     838            0 :                 basis_q[k][i] = this->evaluateRefElementShapeFunction(i, q[k]);
     839              :             }
     840              :         }
     841              : 
     842            0 :         const indices_t zeroNdIndex = Vector<size_t, Dim>(0);
     843              : 
     844              :         // Absolute value of det Phi_K
     845            0 :         const T absDetDPhi = Kokkos::abs(this->ref_element_m.getDeterminantOfTransformationJacobian(
     846            0 :             this->getElementMeshVertexPoints(zeroNdIndex)));
     847              : 
     848              :         // Variable to sum the error to
     849            0 :         T avg = 0;
     850              : 
     851              :         // Get domain information and ghost cells
     852            0 :         auto ldom        = (u_h.getLayout()).getLocalNDIndex();
     853            0 :         const int nghost = u_h.getNghost();
     854              : 
     855              :         using exec_space  = typename Kokkos::View<const size_t*>::execution_space;
     856              :         using policy_type = Kokkos::RangePolicy<exec_space>;
     857              : 
     858              :         // Loop over elements to compute contributions
     859            0 :         Kokkos::parallel_reduce(
     860            0 :             "Compute average over elements", policy_type(0, elementIndices.extent(0)),
     861            0 :             KOKKOS_CLASS_LAMBDA(size_t index, double& local) {
     862            0 :                 const size_t elementIndex = elementIndices(index);
     863            0 :                 const Vector<size_t, numElementDOFs> global_dofs =
     864            0 :                     this->getGlobalDOFIndices(elementIndex);
     865              : 
     866              :                 // contribution of this element to the error
     867            0 :                 T contrib = 0;
     868            0 :                 for (size_t k = 0; k < QuadratureType::numElementNodes; ++k) {
     869            0 :                     T val_u_h = 0;
     870            0 :                     for (size_t i = 0; i < numElementDOFs; ++i) {
     871              :                         // get field index corresponding to this DOF
     872            0 :                         size_t I           = global_dofs[i];
     873            0 :                         auto dof_ndindex_I = this->getMeshVertexNDIndex(I);
     874            0 :                         for (unsigned d = 0; d < Dim; ++d) {
     875            0 :                             dof_ndindex_I[d] = dof_ndindex_I[d] - ldom[d].first() + nghost;
     876              :                         }
     877              : 
     878              :                         // get field value at DOF and interpolate to q_k
     879            0 :                         val_u_h += basis_q[k][i] * apply(u_h, dof_ndindex_I);
     880              :                     }
     881              : 
     882            0 :                     contrib += w[k] * val_u_h * absDetDPhi;
     883              :                 }
     884            0 :                 local += contrib;
     885            0 :             },
     886            0 :             Kokkos::Sum<double>(avg));
     887              : 
     888              :         // MPI reduce
     889            0 :         T global_avg = 0.0;
     890            0 :         Comm->allreduce(avg, global_avg, 1, std::plus<T>());
     891              : 
     892            0 :         return global_avg;
     893            0 :     }
     894              : 
     895              : }  // namespace ippl
        

Generated by: LCOV version 2.0-1