SpECTRE Documentation Coverage Report
Current view: top level - Evolution/DiscontinuousGalerkin/Limiters - HwenoImpl.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 50.0 % 2 1
Test Date: 2026-10-22 17:29:42
Legend: Lines:     hit not hit

            Line data    Source code
       1            0 : // Distributed under the MIT License.
       2              : // See LICENSE.txt for details.
       3              : 
       4              : #pragma once
       5              : 
       6              : #include <array>
       7              : #include <boost/functional/hash.hpp>
       8              : #include <cstddef>
       9              : #include <functional>
      10              : #include <limits>
      11              : #include <unordered_map>
      12              : #include <utility>
      13              : #include <vector>
      14              : 
      15              : #include "DataStructures/ApplyMatrices.hpp"
      16              : #include "DataStructures/DataVector.hpp"
      17              : #include "DataStructures/Index.hpp"
      18              : #include "DataStructures/Matrix.hpp"
      19              : #include "DataStructures/Tags.hpp"
      20              : #include "DataStructures/Variables.hpp"
      21              : #include "Domain/Structure/Direction.hpp"
      22              : #include "Domain/Structure/DirectionMap.hpp"
      23              : #include "Evolution/DiscontinuousGalerkin/Limiters/WenoGridHelpers.hpp"
      24              : #include "Evolution/DiscontinuousGalerkin/Limiters/WenoHelpers.hpp"
      25              : #include "Evolution/DiscontinuousGalerkin/Limiters/WenoOscillationIndicator.hpp"
      26              : #include "NumericalAlgorithms/LinearOperators/MeanValue.hpp"
      27              : #include "NumericalAlgorithms/Spectral/Basis.hpp"
      28              : #include "NumericalAlgorithms/Spectral/Mesh.hpp"
      29              : #include "NumericalAlgorithms/Spectral/Quadrature.hpp"
      30              : #include "Utilities/Algorithm.hpp"
      31              : #include "Utilities/EqualWithinRoundoff.hpp"
      32              : #include "Utilities/ErrorHandling/Assert.hpp"
      33              : #include "Utilities/Gsl.hpp"
      34              : 
      35              : /// \cond
      36              : template <size_t VolumeDim>
      37              : class Element;
      38              : template <size_t VolumeDim>
      39              : class ElementId;
      40              : template <size_t>
      41              : class Mesh;
      42              : /// \endcond
      43              : 
      44            1 : namespace Limiters::Weno_detail {
      45              : 
      46              : // Caching class that holds various precomputed terms used in the constrained-
      47              : // fit algebra on each element.
      48              : //
      49              : // The terms to precompute and cache depend on the configuration of neighbors to
      50              : // the element (how many neighbors, in which directions, of what resolution).
      51              : // Each instance of the caching class represents one particular neighbor
      52              : // configuration, and typical simulations will create many instances of
      53              : // the caching class: one instance for each neighbor-element configuration
      54              : // present in the computational domain.
      55              : //
      56              : // Because the current implementation of the HWENO fitting makes the simplifying
      57              : // restrictions of no h/p-refinement, the structure of the caching is also
      58              : // simplified. In particular, with no h/p-refinement, the allowable neighbor
      59              : // configurations satisfy,
      60              : // 1. the element has at most one neighbor per dimension, AND
      61              : // 2. the mesh on every neighbor is the same as on the element.
      62              : // With these restrictions, the number of independent configurations to be
      63              : // cached is greatly reduced. Because an element has 2*VolumeDim boundaries,
      64              : // it has 2^(2*VolumeDim) possible configurations of internal/external
      65              : // boundaries, and therefore there are 2^(2*VolumeDim) configurations to cache.
      66              : // Different elements with the same configuration of internal/external
      67              : // boundaries vs. direction can share the same caching-class instance.
      68              : //
      69              : // Each instance of the caching class holds several terms, some of which also
      70              : // depend on the neighbor configuration. The restriction of no h/p-refinement
      71              : // therefore simplifies each cache instance, as well as reducing the necessary
      72              : // number of instances.
      73              : //
      74              : // The most complicated term to cache is the A^{-1} matrix. The complexity
      75              : // arises because the matrix can take many values depending on (runtime) choices
      76              : // made for each individual HWNEO fit: which element is the primary neighbor,
      77              : // and which element(s) are the excluded neighbors.
      78              : // Fits with one excluded neighbor are overwhelmingly the most likely, and the
      79              : // cache is designed for this particular case. Fits with no excluded neighbors
      80              : // can arise in elements with only one neighbor (always the primary neighbor);
      81              : // these cases are also handled. However, the very rare case in which more than
      82              : // one neighbor is excluded is not handled by the cache; the caller must compute
      83              : // A^{-1} from scratch if this scenario arises.
      84              : template <size_t VolumeDim>
      85              : class ConstrainedFitCache {
      86              :  public:
      87              :   ConstrainedFitCache(const Mesh<VolumeDim>& mesh,
      88              :                       const Element<VolumeDim>& element);
      89              : 
      90              :   // Valid calls must satisfy these constraints on directions_to_exclude:
      91              :   // - the vector is empty, OR
      92              :   // - the vector contains one element, and this element is a direction
      93              :   //   different from the direction to the primary neighbor.
      94              :   // The very rare case where more than one neighbor is excluded from the fit is
      95              :   // not cached. In this case, A^{-1} must be computed from scratch.
      96              :   const Matrix& retrieve_inverse_a_matrix(
      97              :       const Direction<VolumeDim>& primary_direction,
      98              :       const std::vector<Direction<VolumeDim>>& directions_to_exclude) const;
      99              : 
     100              :   DataVector quadrature_weights;
     101              :   DirectionMap<VolumeDim, Matrix> interpolation_matrices;
     102              :   DirectionMap<VolumeDim, DataVector>
     103              :       quadrature_weights_dot_interpolation_matrices;
     104              :   // The many possible values of A^{-1} are stored in a map of maps. The outer
     105              :   // map indexes over the primary neighbor, and the inner map indexes over the
     106              :   // excluded neighbor.
     107              :   // This data structure is not perfect: configurations with only one neighbor
     108              :   // (always the primary neighbor) lead to no excluded neighbors, and there is
     109              :   // no natural place to hold A^{-1} in the maps. For this case, we simply store
     110              :   // the data in the normally-nonsensical slot where
     111              :   // excluded_neighbor == primary_neighbor.
     112              :   DirectionMap<VolumeDim, DirectionMap<VolumeDim, Matrix>> inverse_a_matrices;
     113              : };
     114              : 
     115              : // Return the appropriate cache for the given mesh and element.
     116              : template <size_t VolumeDim>
     117              : const ConstrainedFitCache<VolumeDim>& constrained_fit_cache(
     118              :     const Mesh<VolumeDim>& mesh, const Element<VolumeDim>& element);
     119              : 
     120              : // Compute the inverse of the matrix A_st for the constrained fit.
     121              : // See the documentation of `hweno_modified_neighbor_solution`.
     122              : template <size_t VolumeDim>
     123              : Matrix inverse_a_matrix(
     124              :     const Mesh<VolumeDim>& mesh, const Element<VolumeDim>& element,
     125              :     const DataVector& quadrature_weights,
     126              :     const DirectionMap<VolumeDim, Matrix>& interpolation_matrices,
     127              :     const DirectionMap<VolumeDim, DataVector>&
     128              :         quadrature_weights_dot_interpolation_matrices,
     129              :     const Direction<VolumeDim>& primary_direction,
     130              :     const std::vector<Direction<VolumeDim>>& directions_to_exclude);
     131              : 
     132              : // Not all secondary neighbors (i.e., neighbors that are not the primary) are
     133              : // included in the HWENO constrained fit. In particular, for the tensor
     134              : // component specified by `Tag` and `tensor_index`, the secondary neighbor whose
     135              : // mean is most different from the troubled cell's mean is excluded.
     136              : //
     137              : // Zhu2016 indicate that when multiple secondary neighbors share the property of
     138              : // being "most different" from the troubled cell, then all of these secondary
     139              : // neighbors should be excluded from the minimization. The probability of this
     140              : // occuring is exceedingly low, but we handle it anyway. We return the excluded
     141              : // secondary neighbors in a vector.
     142              : //
     143              : // Note that if there is only one neighbor, it is the primary neighbor, and so
     144              : // there are no secondary neighbors to exclude. We return an empty vector. This
     145              : // scenario can arise in various test cases, but is unlikely to arise in science
     146              : // cases.
     147              : template <typename Tag, size_t VolumeDim, typename Package>
     148              : std::vector<DirectionalId<VolumeDim>> secondary_neighbors_to_exclude_from_fit(
     149              :     const double local_mean, const size_t tensor_index,
     150              :     const std::unordered_map<DirectionalId<VolumeDim>, Package,
     151              :                              boost::hash<DirectionalId<VolumeDim>>>&
     152              :         neighbor_data,
     153              :     const DirectionalId<VolumeDim>& primary_neighbor) {
     154              :   // Rare case: with only one neighbor, there is no secondary to exclude
     155              :   if (UNLIKELY(neighbor_data.size() == 1)) {
     156              :     return std::vector<DirectionalId<VolumeDim>>{};
     157              :   }
     158              : 
     159              :   // Identify element with maximum mean difference
     160              :   const auto mean_difference =
     161              :       [&tensor_index,
     162              :        &local_mean](const std::pair<DirectionalId<VolumeDim>, Package>&
     163              :                         neighbor_and_data) {
     164              :         return fabs(get<::Tags::Mean<Tag>>(
     165              :                         neighbor_and_data.second.means)[tensor_index] -
     166              :                     local_mean);
     167              :       };
     168              : 
     169              :   double max_difference = std::numeric_limits<double>::lowest();
     170              :   DirectionalId<VolumeDim> neighbor_max_difference{};
     171              :   for (const auto& neighbor_and_data : neighbor_data) {
     172              :     const auto& neighbor = neighbor_and_data.first;
     173              :     if (neighbor == primary_neighbor) {
     174              :       continue;
     175              :     }
     176              :     const double difference = mean_difference(neighbor_and_data);
     177              :     if (difference > max_difference) {
     178              :       max_difference = difference;
     179              :       neighbor_max_difference = neighbor;
     180              :     }
     181              :   }
     182              : 
     183              :   std::vector<DirectionalId<VolumeDim>> neighbors_to_exclude{
     184              :       {neighbor_max_difference}};
     185              : 
     186              :   // See if other elements share this maximum mean difference. This loop should
     187              :   // only rarely find other neighbors with the same maximal mean difference to
     188              :   // add to the vector, so it will usually not change the vector.
     189              :   for (const auto& neighbor_and_data : neighbor_data) {
     190              :     const auto& neighbor = neighbor_and_data.first;
     191              :     if (neighbor == primary_neighbor or neighbor == neighbor_max_difference) {
     192              :       continue;
     193              :     }
     194              :     const double difference = mean_difference(neighbor_and_data);
     195              :     if (UNLIKELY(equal_within_roundoff(difference, max_difference))) {
     196              :       neighbors_to_exclude.push_back(neighbor);
     197              :     }
     198              :   }
     199              : 
     200              :   ASSERT(not alg::found(neighbors_to_exclude, primary_neighbor),
     201              :          "Logical inconsistency: trying to exclude the primary neighbor.");
     202              : 
     203              :   return neighbors_to_exclude;
     204              : }
     205              : 
     206              : // Compute the vector b_s for the constrained fit. For details, see the
     207              : // documentation of `hweno_modified_neighbor_solution` below.
     208              : template <typename Tag, size_t VolumeDim, typename Package>
     209              : DataVector b_vector(
     210              :     const size_t tensor_index, const Mesh<VolumeDim>& mesh,
     211              :     const DataVector& quadrature_weights,
     212              :     const DirectionMap<VolumeDim, Matrix>& interpolation_matrices,
     213              :     const DirectionMap<VolumeDim, DataVector>&
     214              :         quadrature_weights_dot_interpolation_matrices,
     215              :     const std::unordered_map<DirectionalId<VolumeDim>, Package,
     216              :                              boost::hash<DirectionalId<VolumeDim>>>&
     217              :         neighbor_data,
     218              :     const DirectionalId<VolumeDim>& primary_neighbor,
     219              :     const std::vector<DirectionalId<VolumeDim>>& neighbors_to_exclude) {
     220              :   const size_t number_of_grid_points = mesh.number_of_grid_points();
     221              :   DataVector b(number_of_grid_points, 0.);
     222              : 
     223              :   for (const auto& neighbor_and_data : neighbor_data) {
     224              :     // Generally, neighbors_to_exclude contains just one neighbor to exclude,
     225              :     // and the loop over neighbor_data will hit this (in 3D) roughly 1/6 times.
     226              :     // So the condition is somewhat, but not overwhelmingly, unlikely:
     227              :     if (UNLIKELY(alg::found(neighbors_to_exclude, neighbor_and_data.first))) {
     228              :       continue;
     229              :     }
     230              : 
     231              :     const auto& direction = neighbor_and_data.first.direction();
     232              :     ASSERT(interpolation_matrices.contains(direction),
     233              :            "interpolation_matrices does not contain key: " << direction);
     234              :     ASSERT(
     235              :         quadrature_weights_dot_interpolation_matrices.contains(direction),
     236              :         "quadrature_weights_dot_interpolation_matrices does not contain key: "
     237              :             << direction);
     238              : 
     239              :     const auto& neighbor_mesh = mesh;
     240              :     const auto& neighbor_quadrature_weights = quadrature_weights;
     241              :     const auto& interpolation_matrix = interpolation_matrices.at(direction);
     242              :     const auto& quadrature_weights_dot_interpolation_matrix =
     243              :         quadrature_weights_dot_interpolation_matrices.at(direction);
     244              : 
     245              :     const auto& neighbor_tensor_component =
     246              :         get<Tag>(neighbor_and_data.second.volume_data)[tensor_index];
     247              : 
     248              :     // Add terms from the primary neighbor
     249              :     if (neighbor_and_data.first == primary_neighbor) {
     250              :       for (size_t r = 0; r < neighbor_mesh.number_of_grid_points(); ++r) {
     251              :         for (size_t s = 0; s < number_of_grid_points; ++s) {
     252              :           b[s] += neighbor_tensor_component[r] *
     253              :                   neighbor_quadrature_weights[r] * interpolation_matrix(r, s);
     254              :         }
     255              :       }
     256              :     }
     257              :     // Add terms from the secondary neighbors
     258              :     else {
     259              :       const double quadrature_weights_dot_u = [&]() {
     260              :         double result = 0.;
     261              :         for (size_t r = 0; r < neighbor_mesh.number_of_grid_points(); ++r) {
     262              :           result +=
     263              :               neighbor_tensor_component[r] * neighbor_quadrature_weights[r];
     264              :         }
     265              :         return result;
     266              :       }();
     267              :       b += quadrature_weights_dot_u *
     268              :            quadrature_weights_dot_interpolation_matrix;
     269              :     }
     270              :   }
     271              :   return b;
     272              : }
     273              : 
     274              : // Solve the constrained fit problem that gives the HWENO modified solution,
     275              : // for one particular tensor component. For details, see documentation of
     276              : // `hweno_modified_neighbor_solution` below.
     277              : template <typename Tag, size_t VolumeDim, typename Package>
     278              : void solve_constrained_fit(
     279              :     const gsl::not_null<DataVector*> constrained_fit_result,
     280              :     const DataVector& u, const size_t tensor_index, const Mesh<VolumeDim>& mesh,
     281              :     const Element<VolumeDim>& element,
     282              :     const std::unordered_map<DirectionalId<VolumeDim>, Package,
     283              :                              boost::hash<DirectionalId<VolumeDim>>>&
     284              :         neighbor_data,
     285              :     const DirectionalId<VolumeDim>& primary_neighbor,
     286              :     const std::vector<DirectionalId<VolumeDim>>& neighbors_to_exclude) {
     287              :   ASSERT(not alg::found(neighbors_to_exclude, primary_neighbor),
     288              :          "Logical inconsistency: trying to exclude the primary neighbor.");
     289              :   ASSERT(not neighbors_to_exclude.empty() or neighbor_data.size() == 1,
     290              :          "The HWENO constrained fit algorithm expects at least one neighbor \n"
     291              :          "to exclude from the fit (unless if the element has a single \n"
     292              :          "neighbor, which would automatically be the primary neighbor).");
     293              : 
     294              :   // Get the cache of linear algebra quantities for this element
     295              :   const ConstrainedFitCache<VolumeDim>& cache =
     296              :       constrained_fit_cache(mesh, element);
     297              : 
     298              :   // Because we don't support h-refinement, the direction is the only piece
     299              :   // of the neighbor information that we actually need.
     300              :   const Direction<VolumeDim> primary_direction = primary_neighbor.direction();
     301              :   const std::vector<Direction<VolumeDim>> directions_to_exclude =
     302              :       [&neighbors_to_exclude]() {
     303              :         std::vector<Direction<VolumeDim>> result(neighbors_to_exclude.size());
     304              :         for (size_t i = 0; i < result.size(); ++i) {
     305              :           result[i] = neighbors_to_exclude[i].direction();
     306              :         }
     307              :         return result;
     308              :       }();
     309              : 
     310              :   const DataVector& w = cache.quadrature_weights;
     311              :   const DirectionMap<VolumeDim, Matrix>& interp_matrices =
     312              :       cache.interpolation_matrices;
     313              :   const DirectionMap<VolumeDim, DataVector>& w_dot_interp_matrices =
     314              :       cache.quadrature_weights_dot_interpolation_matrices;
     315              : 
     316              :   // Use cache if possible, or compute matrix if we are in edge case
     317              :   const Matrix& inverse_a =
     318              :       LIKELY(directions_to_exclude.size() < 2)
     319              :           ? cache.retrieve_inverse_a_matrix(primary_direction,
     320              :                                             directions_to_exclude)
     321              :           : inverse_a_matrix(mesh, element, w, interp_matrices,
     322              :                              w_dot_interp_matrices, primary_direction,
     323              :                              directions_to_exclude);
     324              : 
     325              :   const DataVector b = b_vector<Tag>(tensor_index, mesh, w, interp_matrices,
     326              :                                      w_dot_interp_matrices, neighbor_data,
     327              :                                      primary_neighbor, neighbors_to_exclude);
     328              : 
     329              :   const size_t number_of_points = b.size();
     330              :   const DataVector inverse_a_times_b = apply_matrices(
     331              :       std::array<std::reference_wrapper<const Matrix>, 1>{{inverse_a}}, b,
     332              :       Index<1>(number_of_points));
     333              :   const DataVector inverse_a_times_w = apply_matrices(
     334              :       std::array<std::reference_wrapper<const Matrix>, 1>{{inverse_a}}, w,
     335              :       Index<1>(number_of_points));
     336              : 
     337              :   // Compute Lagrange multiplier:
     338              :   // Note: we take w as an argument (instead of as a lambda capture), because
     339              :   //       some versions of Clang incorrectly warn about capturing w.
     340              :   const double lagrange_multiplier = [&number_of_points, &inverse_a_times_b,
     341              :                                       &inverse_a_times_w,
     342              :                                       &u](const DataVector& local_w) {
     343              :     double numerator = 0.;
     344              :     double denominator = 0.;
     345              :     for (size_t s = 0; s < number_of_points; ++s) {
     346              :       numerator += local_w[s] * (inverse_a_times_b[s] - u[s]);
     347              :       denominator += local_w[s] * inverse_a_times_w[s];
     348              :     }
     349              :     return -numerator / denominator;
     350              :   }(w);
     351              : 
     352              :   // Compute solution:
     353              :   *constrained_fit_result =
     354              :       inverse_a_times_b + lagrange_multiplier * inverse_a_times_w;
     355              : }
     356              : 
     357              : /*!
     358              :  * \ingroup LimitersGroup
     359              :  * \brief Compute the HWENO solution for one tensor
     360              :  *
     361              :  * The HWENO limiter reconstructs a new solution from the linear combination of
     362              :  * the local DG solution and a "modified" solution from each neighbor element.
     363              :  * This function computes the modified solution for a particular tensor and
     364              :  * neighbor, following Section 3 of \cite Zhu2016.
     365              :  *
     366              :  * The modified solution associated with a particular neighbor (the "primary"
     367              :  * neighbor) is obtained by solving a constrained fit over the local element,
     368              :  * the primary neighbor, and the other ("secondary") neighbors of the local
     369              :  * element. This fit seeks to minimize in a least-squared sense:
     370              :  * 1. The distance between the modified solution and the original solution on
     371              :  *    the primary neighbor.
     372              :  * 2. The distance between the cell average of the modified solution and the
     373              :  *    cell average of the original solution on each secondary neighbor. Note
     374              :  *    however that one secondary neighbor (or, rarely, several) is excluded from
     375              :  *    this minimization: the secondary neighbor(s) where the original solution
     376              :  *    has the most different cell average from the local element. This helps to
     377              :  *    prevent an outlier (e.g., near a shock) from biasing the fit.
     378              :  *
     379              :  * The constraint on the minimization is the following: the cell average of the
     380              :  * modified solution on the local element must equal the cell average of the
     381              :  * local element's original solution.
     382              :  *
     383              :  * Below we give the mathematical form of the constraints described above and
     384              :  * show how these are translated into a numerical algorithm.
     385              :  *
     386              :  * Consider an element \f$I_0\f$ with neighbors \f$I_1, I_2, ...\f$. For a
     387              :  * given tensor component \f$u\f$, the values on each of these elements are
     388              :  * \f$u^{(0)}, u^{(1)}, u^{(2)}, ...\f$. Taking for the sake of example the
     389              :  * primary neighbor to be \f$I_1\f$, the modified solution \f$\phi\f$ must
     390              :  * minimize
     391              :  *
     392              :  * \f[
     393              :  * \chi^2 = \int_{I_1} (\phi - u^{(1)})^2 dV
     394              :  *          + \sum_{\ell \in L} \left(
     395              :  *                              \int_{I_{\ell}} ( \phi - u^{(\ell)} ) dV
     396              :  *                              \right)^2,
     397              :  * \f]
     398              :  *
     399              :  * subject to the constaint
     400              :  *
     401              :  * \f[
     402              :  * C = \int_{I_0} ( \phi - u^{(0)} ) dV = 0.
     403              :  * \f]
     404              :  *
     405              :  * where \f$\ell\f$ ranges over a subset \f$L\f$ of the secondary neighbors.
     406              :  * \f$L\f$ excludes the one (or more) secondary neighbor(s) where the mean of
     407              :  * \f$u\f$ is the most different from the mean of \f$u^{(0)}\f$. Typically, only
     408              :  * one secondary neighbor is excluded, so \f$L\f$ contains two fewer neighbors
     409              :  * than the total number of neighbors to the element. Note that in 1D, this
     410              :  * implies that \f$L\f$ is the empty set; for each modified solution, one
     411              :  * neighbor is the primary neighbor and the other is the excluded neighbor.
     412              :  *
     413              :  * The integrals are evaluated by quadrature. We denote the quadrature weights
     414              :  * by \f$w_s\f$ and the values of some data \f$X\f$ at the quadrature
     415              :  * nodes by \f$X_s\f$. We use subscripts \f$r,s\f$ to denote quadrature nodes
     416              :  * on the neighbor and local elements, respectively. The minimization becomes
     417              :  *
     418              :  * \f[
     419              :  * \chi^2 = \sum_r w^{(1)}_r ( \phi_r - u^{(1)}_r )^2
     420              :  *          + \sum_{\ell \in L} \left(
     421              :  *                              \sum_r w^{(\ell)}_r ( \phi_r - u^{(\ell)}_r )
     422              :  *                              \right)^2,
     423              :  * \f]
     424              :  *
     425              :  * subject to the constraint
     426              :  *
     427              :  * \f[
     428              :  * C = \sum_s w^{(0)}_s ( \phi_s - u^{(0)}_s ) = 0.
     429              :  * \f]
     430              :  *
     431              :  * Note that \f$\phi\f$ is a function defined on the local element \f$I_0\f$,
     432              :  * and so is fully represented by its values \f$\phi_s\f$ at the quadrature
     433              :  * points on this element. When evaluating \f$\phi\f$ on element \f$I_{\ell}\f$,
     434              :  * we obtain the function values \f$\phi_r\f$ by polynomial extrapolation,
     435              :  * \f$\phi_r = \sum_s \mathcal{I}^{(\ell)}_{rs} \phi_s\f$, where
     436              :  * \f$\mathcal{I}^{(\ell)}_{rs}\f$ is the interpolation/extrapolation matrix
     437              :  * that interpolates data defined at grid points \f$x_s\f$ and evaluates it at
     438              :  * grid points \f$x_r\f$ of \f$I_{\ell}\f$. Thus,
     439              :  *
     440              :  * \f[
     441              :  * \chi^2 = \sum_r w^{(1)}_r
     442              :  *                 \left(
     443              :  *                 \sum_s \mathcal{I}^{(1)}_{rs} \phi_s - u^{(1)}_r
     444              :  *                 \right)^2
     445              :  *          + \sum_{\ell \in L} \left(
     446              :  *                              \sum_r w^{(\ell)}_r
     447              :  *                                     \left(
     448              :  *                                     \sum_s \mathcal{I}^{(\ell)}_{rs} \phi_s
     449              :  *                                             - u^{(\ell)}_r
     450              :  *                                     \right)
     451              :  *                      \right)^2.
     452              :  * \f]
     453              :  *
     454              :  * The solution to this optimization problem is found in the standard way,
     455              :  * using a Lagrange multiplier \f$\lambda\f$ to impose the constraint:
     456              :  *
     457              :  * \f[
     458              :  * 0 = \frac{d}{d \phi_s} \left( \chi^2 + \lambda C \right).
     459              :  * \f]
     460              :  *
     461              :  * Working out the differentiation with respect to \f$\phi_s\f$ leads to the
     462              :  * linear problem that must be inverted to obtain the solution,
     463              :  *
     464              :  * \f[
     465              :  * 0 = A_{st} \phi_t - b_s - \lambda w^{(0)}_s,
     466              :  * \f]
     467              :  *
     468              :  * where
     469              :  *
     470              :  * \f{align*}{
     471              :  * A_{st} &= \sum_r \left( w^{(1)}_r
     472              :  *                  \mathcal{I}^{(1)}_{rs} \mathcal{I}^{(1)}_{rt}
     473              :  *                  \right)
     474              :  *          + \sum_{\ell \in L} \left(
     475              :  *                              \sum_r \left(
     476              :  *                                     w^{(\ell)}_r \mathcal{I}^{(\ell)}_{rt}
     477              :  *                                     \right)
     478              :  *                              \cdot
     479              :  *                              \sum_r \left(
     480              :  *                                     w^{(\ell)}_r \mathcal{I}^{(\ell)}_{rs}
     481              :  *                                     \right)
     482              :  *                              \right)
     483              :  * \\
     484              :  * b_s &= \sum_r \left( w^{(1)}_r u^{(1)}_r \mathcal{I}^{(1)}_{rs} \right)
     485              :  *         + \sum_{\ell \in L} \left(
     486              :  *                             \sum_r \left( w^{(\ell)}_r u^{(\ell)}_r \right)
     487              :  *                             \cdot
     488              :  *                             \sum_r \left(
     489              :  *                                    w^{(\ell)}_r \mathcal{I}^{(\ell)}_{rs}
     490              :  *                                    \right)
     491              :  *                             \right).
     492              :  * \f}
     493              :  *
     494              :  * Finally, the solution to the constrained fit is
     495              :  *
     496              :  * \f{align*}{
     497              :  * \lambda &= - \frac{ \sum_s w^{(0)}_s
     498              :  *                            \left( (A^{-1})_{st} b_t - u^{(0)}_s \right)
     499              :  *              }{ \sum_s w^{(0)}_s (A^{-1})_{st} w^{(0)}_t }
     500              :  * \\
     501              :  * \phi_s &= (A^{-1})_{st} ( b_t + \lambda w^{(0)}_t ).
     502              :  * \f}
     503              :  *
     504              :  * Note that the matrix \f$A\f$ does not depend on the values of the tensor
     505              :  * \f$u\f$, so its inverse \f$A^{-1}\f$ can be precomputed and stored.
     506              :  *
     507              :  * \warning
     508              :  * Note also that the implementation currently does not support h- or
     509              :  * p-refinement; this is checked by some assertions. The implementation is
     510              :  * untested for grids where elements are curved, and it should not be expected
     511              :  * to work in these cases.
     512              :  *
     513              :  * When calling `hweno_impl`, `modified_neighbor_solution_buffer` should contain
     514              :  * one DataVector for each neighboring element (i.e. for each entry in
     515              :  * `neighbor_data`).
     516              :  */
     517              : template <typename Tag, size_t VolumeDim, typename PackagedData>
     518              : void hweno_impl(const gsl::not_null<
     519              :                     std::unordered_map<DirectionalId<VolumeDim>, DataVector,
     520              :                                        boost::hash<DirectionalId<VolumeDim>>>*>
     521              :                     modified_neighbor_solution_buffer,
     522              :                 const gsl::not_null<typename Tag::type*> tensor,
     523              :                 const double neighbor_linear_weight,
     524              :                 const Mesh<VolumeDim>& mesh, const Element<VolumeDim>& element,
     525              :                 const std::unordered_map<DirectionalId<VolumeDim>, PackagedData,
     526              :                                          boost::hash<DirectionalId<VolumeDim>>>&
     527              :                     neighbor_data) {
     528              :   // Check that basis is LGL or LG
     529              :   // The Hweno reconstruction implemented here is specialized to the case of a
     530              :   // Legendre basis. While the underlying algorithm should work for other bases
     531              :   // as well, the matrix computations would need to be generalized.
     532              :   ASSERT(mesh.basis() == make_array<VolumeDim>(Spectral::Basis::Legendre),
     533              :          "Unsupported basis: " << mesh);
     534              :   ASSERT(mesh.quadrature() ==
     535              :                  make_array<VolumeDim>(Spectral::Quadrature::GaussLobatto) or
     536              :              mesh.quadrature() ==
     537              :                  make_array<VolumeDim>(Spectral::Quadrature::Gauss),
     538              :          "Unsupported quadrature: " << mesh);
     539              : 
     540              :   ASSERT(
     541              :       modified_neighbor_solution_buffer->size() == neighbor_data.size(),
     542              :       "modified_neighbor_solution_buffer->size() = "
     543              :           << modified_neighbor_solution_buffer->size()
     544              :           << "\nneighbor_data.size() = " << neighbor_data.size()
     545              :           << "\nmodified_neighbor_solution_buffer was incorrectly initialized "
     546              :              "before calling hweno_impl.");
     547              : 
     548              :   alg::for_each(
     549              :       neighbor_data, [&mesh, &element](const auto& neighbor_and_data) {
     550              :         ASSERT(Weno_detail::check_element_has_one_similar_neighbor_in_direction(
     551              :                    element, neighbor_and_data.first.direction()),
     552              :                "Found some amount of h-refinement; this is not supported");
     553              :         ASSERT(neighbor_and_data.second.mesh == mesh,
     554              :                "Found some amount of p-refinement; this is not supported");
     555              :       });
     556              : 
     557              :   for (size_t tensor_index = 0; tensor_index < tensor->size(); ++tensor_index) {
     558              :     const auto& tensor_component = (*tensor)[tensor_index];
     559              :     for (const auto& neighbor_and_data : neighbor_data) {
     560              :       const auto& primary_neighbor = neighbor_and_data.first;
     561              :       const auto neighbors_to_exclude =
     562              :           secondary_neighbors_to_exclude_from_fit<Tag>(
     563              :               mean_value(tensor_component, mesh), tensor_index, neighbor_data,
     564              :               primary_neighbor);
     565              : 
     566              :       DataVector& buffer =
     567              :           modified_neighbor_solution_buffer->at(primary_neighbor);
     568              :       solve_constrained_fit<Tag>(make_not_null(&buffer), tensor_component,
     569              :                                  tensor_index, mesh, element, neighbor_data,
     570              :                                  primary_neighbor, neighbors_to_exclude);
     571              :     }
     572              : 
     573              :     // Sum local and modified neighbor polynomials for the WENO reconstruction
     574              :     Weno_detail::reconstruct_from_weighted_sum(
     575              :         make_not_null(&((*tensor)[tensor_index])), neighbor_linear_weight,
     576              :         Weno_detail::DerivativeWeight::PowTwoEllOverEllFactorial, mesh,
     577              :         *modified_neighbor_solution_buffer);
     578              :   }
     579              : }
     580              : 
     581              : }  // namespace Limiters::Weno_detail
        

Generated by: LCOV version 2.0-1