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

Generated by: LCOV version 1.14