SpECTRE Documentation Coverage Report
Current view: top level - Evolution/Systems/NewtonianMhd/Limiters - KxrcfTci.hpp Coverage Total Hit
Commit: a37d7b3c1803b88b537d2aef1c27ca5e6b499398 Lines: 33.3 % 3 1
Test Date: 2026-11-12 04:47:19
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 <string>
      10              : #include <unordered_map>
      11              : #include <utility>
      12              : 
      13              : #include "DataStructures/SliceVariables.hpp"
      14              : #include "DataStructures/Tags.hpp"
      15              : #include "DataStructures/Tensor/EagerMath/DotProduct.hpp"
      16              : #include "DataStructures/Tensor/EagerMath/Magnitude.hpp"
      17              : #include "DataStructures/Tensor/Slice.hpp"
      18              : #include "DataStructures/Tensor/Tensor.hpp"
      19              : #include "DataStructures/Variables.hpp"
      20              : #include "Domain/SizeOfElement.hpp"
      21              : #include "Domain/Structure/Direction.hpp"
      22              : #include "Domain/Structure/DirectionalId.hpp"
      23              : #include "Domain/Structure/Element.hpp"
      24              : #include "Domain/Tags.hpp"
      25              : #include "Evolution/DiscontinuousGalerkin/NormalVectorTags.hpp"
      26              : #include "Evolution/Systems/NewtonianMhd/Tags.hpp"
      27              : #include "NumericalAlgorithms/LinearOperators/DefiniteIntegral.hpp"
      28              : #include "NumericalAlgorithms/LinearOperators/MeanValue.hpp"
      29              : #include "NumericalAlgorithms/Spectral/Basis.hpp"
      30              : #include "NumericalAlgorithms/Spectral/Mesh.hpp"
      31              : #include "NumericalAlgorithms/Spectral/Quadrature.hpp"
      32              : #include "Utilities/ErrorHandling/Assert.hpp"
      33              : 
      34              : /// \cond
      35              : template <size_t VolumeDim>
      36              : class ElementId;
      37              : /// \endcond
      38              : 
      39            0 : namespace NewtonianMhd::Limiters::Tci {
      40              : 
      41              : /// \ingroup LimitersGroup
      42              : /// \brief Implements the troubled-cell indicator from Krivodonova et al, 2004.
      43              : ///
      44              : /// The KXRCF (these are the author initials) TCI is described in
      45              : /// \cite Krivodonova2004.
      46              : ///
      47              : /// In summary, this TCI uses the size of discontinuities between neighboring DG
      48              : /// elements to determine the smoothness of the solution. This works because the
      49              : /// discontinuities converge rapidly for smooth solutions, therefore a large
      50              : /// discontinuity suggests a lack of smoothness and the need to apply a limiter.
      51              : ///
      52              : /// The reference sets the constant we call `kxrcf_constant` to 1. This should
      53              : /// generally be a good threshold to use, though it might not be the optimal
      54              : /// value (in balancing robustness vs accuracy) for any particular problem.
      55              : ///
      56              : /// This implementation
      57              : /// - does not support h- or p-refinement; this is checked by assertion.
      58              : /// - chooses not to check external boundaries, because this adds complexity.
      59              : ///   However, by not checking external boundaries, the implementation may not
      60              : ///   be robust for problems that feed in shocks through boundary conditions.
      61              : template <size_t VolumeDim, typename PackagedData>
      62            1 : bool kxrcf_indicator(
      63              :     const double kxrcf_constant, const Scalar<DataVector>& cons_mass_density,
      64              :     const tnsr::I<DataVector, VolumeDim>& cons_momentum_density,
      65              :     const Scalar<DataVector>& cons_energy_density, const Mesh<VolumeDim>& mesh,
      66              :     const Element<VolumeDim>& element,
      67              :     const std::array<double, VolumeDim>& element_size,
      68              :     const Scalar<DataVector>& det_logical_to_inertial_jacobian,
      69              :     const typename evolution::dg::Tags::NormalCovectorAndMagnitude<
      70              :         VolumeDim>::type& normals_and_magnitudes,
      71              :     const std::unordered_map<DirectionalId<VolumeDim>, PackagedData,
      72              :                              boost::hash<DirectionalId<VolumeDim>>>&
      73              :         neighbor_data) {
      74              :   // Enforce restrictions on h-refinement, p-refinement
      75              :   if (UNLIKELY(
      76              :           alg::any_of(element.neighbors(), [](const auto& direction_neighbors) {
      77              :             return direction_neighbors.second.size() != 1;
      78              :           }))) {
      79              :     ERROR("The Kxrcf TCI does not yet support h-refinement");
      80              :     // Removing this limitation will require adapting the surface integrals to
      81              :     // correctly acount for,
      82              :     // - multiple (smaller) neighbors contributing to the integral
      83              :     // - only a portion of a (larger) neighbor contributing to the integral
      84              :   }
      85              :   alg::for_each(neighbor_data, [&mesh](const auto& neighbor_and_data) {
      86              :     if (UNLIKELY(neighbor_and_data.second.mesh != mesh)) {
      87              :       ERROR("The Kxrcf TCI does not yet support p-refinement");
      88              :       // Removing this limitation will require generalizing the surface
      89              :       // integrals to make sure the meshes are consistent.
      90              :     }
      91              :   });
      92              :   // Check the mesh matches expectations:
      93              :   // - the extents must be uniform, because the TCI expects a unique "order" for
      94              :   //   the DG scheme. This shows up when computing the threshold parameter,
      95              :   //   h^(degree+1)/2
      96              :   // - the quadrature must be GL, because the current implementation doesn't
      97              :   //   handle extrapolation to the boundary, though this could be changed.
      98              :   // - the basis in principle could be changed, but until we need to change it
      99              :   //   we just check it's the expected Legendre for simplicity
     100              :   ASSERT(mesh == Mesh<VolumeDim>(mesh.extents(0), Spectral::Basis::Legendre,
     101              :                                  Spectral::Quadrature::GaussLobatto),
     102              :          "The Kxrcf TCI expects a uniform LGL mesh, but got mesh = " << mesh);
     103              : 
     104              :   bool inflow_boundaries_present = false;
     105              :   double inflow_area = 0.;
     106              :   double inflow_delta_density = 0.;
     107              :   double inflow_delta_energy = 0.;
     108              : 
     109              :   // Skip boundary integrations on external boundaries. This choice might be
     110              :   // problematic for evolutions (likely only simple test cases) that feed in
     111              :   // shocks through the boundary condition: the limiter might fail to activate
     112              :   // in the cell that touches the boundary.
     113              :   //
     114              :   // To properly compute the limiter at external boundaries we would need the
     115              :   // limiter to know about the boundary condition, which may be difficult to
     116              :   // do in a general way.
     117              :   for (const auto& [neighbor, data] : neighbor_data) {
     118              :     const auto& dir = neighbor.direction();
     119              : 
     120              :     // Check consistency of neighbor_data with element and normals
     121              :     ASSERT(element.neighbors().contains(dir),
     122              :            "Received neighbor data from dir = "
     123              :                << dir << ", but element has no neighbor in this dir");
     124              :     ASSERT(normals_and_magnitudes.contains(dir),
     125              :            "Received neighbor data from dir = "
     126              :                << dir
     127              :                << ", but normals_and_magnitudes has no normal in this dir");
     128              :     ASSERT(normals_and_magnitudes.at(dir).has_value(),
     129              :            "The normals_and_magnitudes are not up-to-date in dir = " << dir);
     130              :     const auto& normal = get<evolution::dg::Tags::NormalCovector<VolumeDim>>(
     131              :         normals_and_magnitudes.at(dir).value());
     132              :     const auto& magnitude_of_normal =
     133              :         get<evolution::dg::Tags::MagnitudeOfNormal>(
     134              :             normals_and_magnitudes.at(dir).value());
     135              : 
     136              :     const size_t sliced_dim = dir.dimension();
     137              :     const size_t index_of_slice =
     138              :         (dir.side() == Side::Lower ? 0 : mesh.extents()[sliced_dim] - 1);
     139              :     const auto momentum_on_slice = data_on_slice(
     140              :         cons_momentum_density, mesh.extents(), sliced_dim, index_of_slice);
     141              :     const auto momentum_dot_normal = dot_product(momentum_on_slice, normal);
     142              : 
     143              :     // Skip boundaries with no significant inflow
     144              :     // Note: the cutoff value here is small but arbitrarily chosen.
     145              :     if (min(get(momentum_dot_normal)) > -1e-12) {
     146              :       continue;
     147              :     }
     148              :     inflow_boundaries_present = true;
     149              : 
     150              :     // This mask has value 1. for momentum_dot_normal < 0.
     151              :     //                     0. for momentum_dot_normal >= 0.
     152              :     const DataVector inflow_mask = 1. - step_function(get(momentum_dot_normal));
     153              :     // Mask is then weighted pointwise by the Jacobian determinant giving
     154              :     // surface integrals in inertial coordinates. This Jacobian determinant is
     155              :     // given by the product of the volume Jacobian determinant with the
     156              :     // magnitude of the unnormalized normal covectors.
     157              :     const DataVector weighted_inflow_mask =
     158              :         inflow_mask *
     159              :         get(data_on_slice(det_logical_to_inertial_jacobian, mesh.extents(),
     160              :                           sliced_dim, index_of_slice)) *
     161              :         get(magnitude_of_normal);
     162              : 
     163              :     inflow_area +=
     164              :         definite_integral(weighted_inflow_mask, mesh.slice_away(sliced_dim));
     165              : 
     166              :     // This is the step that is incompatible with h/p refinement. For use with
     167              :     // h/p refinement, would need to correctly obtain the neighbor solution on
     168              :     // the local grid points.
     169              :     const auto neighbor_vars_on_slice = data_on_slice(
     170              :         data.volume_data, mesh.extents(), sliced_dim, index_of_slice);
     171              : 
     172              :     const auto density_on_slice = data_on_slice(
     173              :         cons_mass_density, mesh.extents(), sliced_dim, index_of_slice);
     174              :     const auto& neighbor_density_on_slice =
     175              :         get<NewtonianMhd::Tags::MassDensityCons>(neighbor_vars_on_slice);
     176              :     inflow_delta_density += definite_integral(
     177              :         (get(density_on_slice) - get(neighbor_density_on_slice)) *
     178              :             weighted_inflow_mask,
     179              :         mesh.slice_away(sliced_dim));
     180              : 
     181              :     const auto energy_on_slice = data_on_slice(
     182              :         cons_energy_density, mesh.extents(), sliced_dim, index_of_slice);
     183              :     const auto& neighbor_energy_on_slice =
     184              :         get<NewtonianMhd::Tags::EnergyDensity>(neighbor_vars_on_slice);
     185              :     inflow_delta_energy += definite_integral(
     186              :         (get(energy_on_slice) - get(neighbor_energy_on_slice)) *
     187              :             weighted_inflow_mask,
     188              :         mesh.slice_away(sliced_dim));
     189              :   }
     190              : 
     191              :   if (not inflow_boundaries_present) {
     192              :     // No boundaries had inflow, so not a troubled cell
     193              :     return false;
     194              :   }
     195              : 
     196              :   // KXRCF take h to be the radius of the circumscribed circle
     197              :   const double h = 0.5 * magnitude(element_size);
     198              :   const double h_pow = pow(h, 0.5 * mesh.extents(0));
     199              : 
     200              :   ASSERT(inflow_area > 0.,
     201              :          "Sanity check failed: negative area of inflow boundaries");
     202              : 
     203              :   const double norm_squared_density = mean_value(
     204              :       get(det_logical_to_inertial_jacobian) * square(get(cons_mass_density)),
     205              :       mesh);
     206              :   ASSERT(norm_squared_density > 0.,
     207              :          "Sanity check failed: negative density norm over element");
     208              :   const double norm_density = sqrt(norm_squared_density);
     209              :   const double ratio_for_density =
     210              :       abs(inflow_delta_density) / (h_pow * inflow_area * norm_density);
     211              : 
     212              :   const double norm_squared_energy = mean_value(
     213              :       get(det_logical_to_inertial_jacobian) * square(get(cons_energy_density)),
     214              :       mesh);
     215              :   ASSERT(norm_squared_energy > 0.,
     216              :          "Sanity check failed: negative energy norm over element");
     217              :   const double norm_energy = sqrt(norm_squared_energy);
     218              :   const double ratio_for_energy =
     219              :       abs(inflow_delta_energy) / (h_pow * inflow_area * norm_energy);
     220              : 
     221              :   return (ratio_for_density > kxrcf_constant or
     222              :           ratio_for_energy > kxrcf_constant);
     223              : }
     224              : 
     225              : }  // namespace NewtonianMhd::Limiters::Tci
        

Generated by: LCOV version 2.0-1