SpECTRE Documentation Coverage Report
Current view: top level - NumericalAlgorithms/DiscontinuousGalerkin - ProjectToBoundary.hpp Hit Total Coverage
Commit: 1e29a35ad8559408f21493dc5db8a49a237bb2f0 Lines: 4 5 80.0 %
Date: 2026-03-31 22:27:51
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 <cstddef>
       7             : #include <type_traits>
       8             : 
       9             : #include "DataStructures/ApplyMatrices.hpp"
      10             : #include "DataStructures/DataVector.hpp"
      11             : #include "DataStructures/Matrix.hpp"
      12             : #include "DataStructures/SliceIterator.hpp"
      13             : #include "DataStructures/Variables.hpp"
      14             : #include "Domain/Structure/Direction.hpp"
      15             : #include "NumericalAlgorithms/Spectral/BoundaryInterpolationMatrices.hpp"
      16             : #include "NumericalAlgorithms/Spectral/Mesh.hpp"
      17             : #include "NumericalAlgorithms/Spectral/Quadrature.hpp"
      18             : #include "Utilities/ErrorHandling/Assert.hpp"
      19             : #include "Utilities/Gsl.hpp"
      20             : #include "Utilities/MakeArray.hpp"
      21             : #include "Utilities/TMPL.hpp"
      22             : 
      23             : namespace dg {
      24             : namespace detail {
      25             : template <typename TagsList>
      26             : struct NumberOfIndependentComponents;
      27             : 
      28             : template <typename... Tags>
      29             : struct NumberOfIndependentComponents<tmpl::list<Tags...>> {
      30             :   static constexpr size_t value = (... + Tags::type::size());
      31             : };
      32             : }  // namespace detail
      33             : 
      34             : /*!
      35             :  * \brief Projects a `Variables` of volume data to a contiguous subset of
      36             :  * a boundary `Variables`
      37             :  *
      38             :  * The `volume_fields` are all projected into the `face_fields` in the direction
      39             :  * `direction`. The tags in `VolumeVarsTagsList` must be a contiguous subset of
      40             :  * the tags in `FaceVarsTagsList`. That is, `FaceVarsTagsList` must be
      41             :  * equivalent to `tmpl::append<Before, VolumeVarsTagsList, After>` where
      42             :  * `Before` and `After` are `tmpl::list`s of arbitrary size. This is because the
      43             :  * projection is applied on all of the tensor components of the volume variables
      44             :  * and is written into contiguous memory on the boundary.
      45             :  *
      46             :  * In general, this function will be used for projecting all the evolved
      47             :  * variables or all the volume fluxes to the faces. The function
      48             :  * `dg::project_tensors_to_boundary()` should be used for projecting
      49             :  * individual tensors to the face.
      50             :  *
      51             :  * \note This function works for both Gauss and Gauss-Lobatto uniform meshes.
      52             :  */
      53             : template <typename VolumeVarsTagsList, typename FaceVarsTagsList, size_t Dim>
      54           1 : void project_contiguous_data_to_boundary(
      55             :     const gsl::not_null<Variables<FaceVarsTagsList>*> face_fields,
      56             :     const Variables<VolumeVarsTagsList>& volume_fields,
      57             :     const Mesh<Dim>& volume_mesh, const Direction<Dim>& direction) {
      58             :   static_assert(tmpl::size<VolumeVarsTagsList>::value != 0,
      59             :                 "Must have non-zero number of volume fields");
      60             :   static_assert(tmpl::size<FaceVarsTagsList>::value >=
      61             :                     tmpl::size<VolumeVarsTagsList>::value,
      62             :                 "There must not be more volume tags than there are face tags.");
      63             :   static_assert(
      64             :       tmpl::list_contains_v<FaceVarsTagsList, tmpl::front<VolumeVarsTagsList>>,
      65             :       "The first tag of VolumeVarsTagsList is not in the face tags. The "
      66             :       "VolumeVarsTagsList must be a subset of the FaceVarsTagsList");
      67             :   static_assert(
      68             :       tmpl::list_contains_v<FaceVarsTagsList, tmpl::back<VolumeVarsTagsList>>,
      69             :       "The last tag of VolumeVarsTagsList is not in the face tags. The "
      70             :       "VolumeVarsTagsList must be a subset of the FaceVarsTagsList");
      71             :   using face_vars_excluding_extras_at_end = tmpl::front<
      72             :       tmpl::split_at<FaceVarsTagsList,
      73             :                      tmpl::next<tmpl::index_of<
      74             :                          FaceVarsTagsList, tmpl::back<VolumeVarsTagsList>>>>>;
      75             :   using front_face_vars_split = tmpl::split_at<
      76             :       face_vars_excluding_extras_at_end,
      77             :       tmpl::index_of<FaceVarsTagsList, tmpl::front<VolumeVarsTagsList>>>;
      78             :   using volume_vars_face_subset_list = tmpl::back<front_face_vars_split>;
      79             :   static_assert(
      80             :       std::is_same_v<volume_vars_face_subset_list, VolumeVarsTagsList>,
      81             :       "The VolumeVarsTagsList must be a subset of the FaceVarsTagsList.");
      82             :   constexpr const size_t number_of_independent_components =
      83             :       Variables<VolumeVarsTagsList>::number_of_independent_components;
      84             :   using first_volume_tag = tmpl::front<VolumeVarsTagsList>;
      85             :   using VectorType = typename Variables<FaceVarsTagsList>::vector_type;
      86             :   using ValueType = typename VectorType::value_type;
      87             :   const size_t sliced_dim = direction.dimension();
      88             :   if (volume_mesh.quadrature(sliced_dim) == Spectral::Quadrature::Gauss) {
      89             :     const Matrix identity{};
      90             :     auto interpolation_matrices = make_array<Dim>(std::cref(identity));
      91             :     const auto& matrix = Spectral::boundary_interpolation_matrices(
      92             :         volume_mesh.slice_through(sliced_dim));
      93             :     gsl::at(interpolation_matrices, sliced_dim) =
      94             :         direction.side() == Side::Upper ? matrix.second : matrix.first;
      95             : 
      96             :     auto& first_face_field = get<first_volume_tag>(*face_fields);
      97             :     auto& first_volume_field = get<first_volume_tag>(volume_fields);
      98             : 
      99             :     // The size is the number of tensor components we are projecting times the
     100             :     // number of grid points on the face. Note that this is _not_ equal to the
     101             :     // size of face_fields->size() since face_fields is a superset of the
     102             :     // volume variables.
     103             :     VectorType face_view{
     104             :         first_face_field[0].data(),
     105             :         first_face_field[0].size() * number_of_independent_components};
     106             : 
     107             :     apply_matrices(
     108             :         make_not_null(&face_view), interpolation_matrices,
     109             :         VectorType{
     110             :             // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
     111             :             const_cast<ValueType*>(first_volume_field[0].data()),
     112             :             first_volume_field[0].size() * number_of_independent_components},
     113             :         volume_mesh.extents());
     114             :   } else {
     115             :     ASSERT(volume_mesh.quadrature(sliced_dim) ==
     116             :                    Spectral::Quadrature::GaussLobatto or
     117             :                (volume_mesh.quadrature(sliced_dim) ==
     118             :                     Spectral::Quadrature::GaussRadauUpper and
     119             :                 direction.side() == Side::Upper),
     120             :            "Got quadrature without boundary collocation point at "
     121             :                << direction << " with " << volume_mesh.quadrature(sliced_dim));
     122             :     const size_t fixed_index = direction.side() == Side::Upper
     123             :                                    ? volume_mesh.extents(sliced_dim) - 1
     124             :                                    : 0;
     125             : 
     126             :     const size_t interface_grid_points =
     127             :         volume_mesh.extents().slice_away(sliced_dim).product();
     128             :     const size_t volume_grid_points = volume_mesh.number_of_grid_points();
     129             : 
     130             :     const ValueType* vars_data = volume_fields.data();
     131             :     // Since the face fields are a superset of the volume tags we need to find
     132             :     // the first volume tag on the face and get the pointer for that.
     133             :     ValueType* interface_vars_data =
     134             :         get<first_volume_tag>(*face_fields)[0].data();
     135             : 
     136             :     // The reason we can't use data_on_slice is because the volume and face tags
     137             :     // are not the same, but data_on_slice assumes they are. In general, this
     138             :     // function should replace data_on_slice in the long term since in
     139             :     // additional to supporting different volume and face tags, it also supports
     140             :     // Gauss and Gauss-Lobatto points.
     141             :     //
     142             :     // Run the SliceIterator as the outer-most loop since incrementing the slice
     143             :     // iterator is surprisingly expensive.
     144             :     for (SliceIterator si(volume_mesh.extents(), sliced_dim, fixed_index); si;
     145             :          ++si) {
     146             :       for (size_t i = 0; i < number_of_independent_components; ++i) {
     147             :         // clang-tidy: do not use pointer arithmetic
     148             :         interface_vars_data[si.slice_offset() +                      // NOLINT
     149             :                             i * interface_grid_points] =             // NOLINT
     150             :             vars_data[si.volume_offset() + i * volume_grid_points];  // NOLINT
     151             :       }
     152             :     }
     153             :   }
     154             : }
     155             : 
     156             : /*!
     157             :  * \brief Projects a subset of the tensors in the `volume_fields` onto the face
     158             :  *
     159             :  * The tensors to project are listed in the `TagsToProjectList`.
     160             :  *
     161             :  * \note This function works for both Gauss and Gauss-Lobatto uniform meshes.
     162             :  */
     163             : template <typename TagsToProjectList, typename VolumeVarsTagsList,
     164             :           typename FaceVarsTagsList, size_t Dim>
     165           1 : void project_tensors_to_boundary(
     166             :     const gsl::not_null<Variables<FaceVarsTagsList>*> face_fields,
     167             :     const Variables<VolumeVarsTagsList>& volume_fields,
     168             :     const Mesh<Dim>& volume_mesh, const Direction<Dim>& direction) {
     169             :   static_assert(tmpl::size<VolumeVarsTagsList>::value != 0,
     170             :                 "Must have non-zero number of volume fields");
     171             :   static_assert(
     172             :       tmpl::size<
     173             :           tmpl::list_difference<TagsToProjectList, FaceVarsTagsList>>::value ==
     174             :           0,
     175             :       "All of the tags in TagsToProjectList must be in FaceVarsTagsList");
     176             :   static_assert(
     177             :       tmpl::size<tmpl::list_difference<TagsToProjectList,
     178             :                                        VolumeVarsTagsList>>::value == 0,
     179             :       "All of the tags in TagsToProjectList must be in VolumeVarsTagsList");
     180             :   const size_t sliced_dim = direction.dimension();
     181             :   if (volume_mesh.quadrature(sliced_dim) == Spectral::Quadrature::Gauss) {
     182             :     const Matrix identity{};
     183             :     auto interpolation_matrices = make_array<Dim>(std::cref(identity));
     184             :     const std::pair<Matrix, Matrix>& matrices =
     185             :         Spectral::boundary_interpolation_matrices(
     186             :             volume_mesh.slice_through(sliced_dim));
     187             :     gsl::at(interpolation_matrices, sliced_dim) =
     188             :         direction.side() == Side::Upper ? matrices.second : matrices.first;
     189             :     tmpl::for_each<TagsToProjectList>([&face_fields, &interpolation_matrices,
     190             :                                        &volume_fields,
     191             :                                        &volume_mesh](auto tag_v) {
     192             :       using tag = typename decltype(tag_v)::type;
     193             :       auto& face_field = get<tag>(*face_fields);
     194             :       const auto& volume_field = get<tag>(volume_fields);
     195             :       DataVector face_view{face_field[0].data(),
     196             :                            face_field[0].size() * face_field.size()};
     197             :       apply_matrices(make_not_null(&face_view), interpolation_matrices,
     198             :                      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
     199             :                      DataVector{const_cast<double*>(volume_field[0].data()),
     200             :                                 volume_field[0].size() * volume_field.size()},
     201             :                      volume_mesh.extents());
     202             :     });
     203             :   } else {
     204             :     ASSERT(volume_mesh.quadrature(sliced_dim) ==
     205             :                    Spectral::Quadrature::GaussLobatto or
     206             :                (volume_mesh.quadrature(sliced_dim) ==
     207             :                     Spectral::Quadrature::GaussRadauUpper and
     208             :                 direction.side() == Side::Upper),
     209             :            "Got quadrature without boundary collocation point at "
     210             :                << direction << " with " << volume_mesh.quadrature(sliced_dim));
     211             :     const size_t fixed_index = direction.side() == Side::Upper
     212             :                                    ? volume_mesh.extents(sliced_dim) - 1
     213             :                                    : 0;
     214             : 
     215             :     const size_t interface_grid_points =
     216             :         volume_mesh.extents().slice_away(sliced_dim).product();
     217             :     const size_t volume_grid_points = volume_mesh.number_of_grid_points();
     218             : 
     219             :     // Run the SliceIterator as the outer-most loop since incrementing the slice
     220             :     // iterator is surprisingly expensive.
     221             :     for (SliceIterator si(volume_mesh.extents(), sliced_dim, fixed_index); si;
     222             :          ++si) {
     223             :       tmpl::for_each<TagsToProjectList>([&face_fields, interface_grid_points,
     224             :                                          &si, &volume_fields,
     225             :                                          volume_grid_points](auto tag_v) {
     226             :         using tag = typename decltype(tag_v)::type;
     227             : 
     228             :         const double* vars_data = get<tag>(volume_fields)[0].data();
     229             :         double* interface_vars_data = get<tag>(*face_fields)[0].data();
     230             :         static constexpr size_t number_of_independent_components_in_tensor =
     231             :             std::decay_t<decltype(get<tag>(volume_fields))>::size();
     232             : 
     233             :         for (size_t i = 0; i < number_of_independent_components_in_tensor;
     234             :              ++i) {
     235             :           // clang-tidy: do not use pointer arithmetic
     236             :           interface_vars_data[si.slice_offset() +                      // NOLINT
     237             :                               i * interface_grid_points] =             // NOLINT
     238             :               vars_data[si.volume_offset() + i * volume_grid_points];  // NOLINT
     239             :         }
     240             :       });
     241             :     }
     242             :   }
     243             : }
     244             : 
     245             : /// @{
     246             : /*!
     247             :  * \brief Projects a tensor to the face
     248             :  *
     249             :  * \note This function works for both Gauss and Gauss-Lobatto uniform meshes.
     250             :  */
     251             : template <typename Symm, typename IndexList, size_t Dim>
     252           1 : void project_tensor_to_boundary(
     253             :     const gsl::not_null<Tensor<DataVector, Symm, IndexList>*> face_field,
     254             :     const Tensor<DataVector, Symm, IndexList>& volume_field,
     255             :     const Mesh<Dim>& volume_mesh, const Direction<Dim>& direction) {
     256             :   const size_t sliced_dim = direction.dimension();
     257             :   if (volume_mesh.quadrature(sliced_dim) == Spectral::Quadrature::Gauss) {
     258             :     const Matrix identity{};
     259             :     auto interpolation_matrices = make_array<Dim>(std::cref(identity));
     260             :     const std::pair<Matrix, Matrix>& matrices =
     261             :         Spectral::boundary_interpolation_matrices(
     262             :             volume_mesh.slice_through(sliced_dim));
     263             :     gsl::at(interpolation_matrices, sliced_dim) =
     264             :         direction.side() == Side::Upper ? matrices.second : matrices.first;
     265             :     for (size_t tensor_storage_index = 0;
     266             :          tensor_storage_index < volume_field.size(); ++tensor_storage_index) {
     267             :       apply_matrices(make_not_null(&(*face_field)[tensor_storage_index]),
     268             :                      interpolation_matrices, volume_field[tensor_storage_index],
     269             :                      volume_mesh.extents());
     270             :     }
     271             :   } else {
     272             :     ASSERT(volume_mesh.quadrature(sliced_dim) ==
     273             :                    Spectral::Quadrature::GaussLobatto or
     274             :                (volume_mesh.quadrature(sliced_dim) ==
     275             :                     Spectral::Quadrature::GaussRadauUpper and
     276             :                 direction.side() == Side::Upper),
     277             :            "Got quadrature without boundary collocation point at "
     278             :                << direction << " with " << volume_mesh.quadrature(sliced_dim));
     279             :     const size_t fixed_index = direction.side() == Side::Upper
     280             :                                    ? volume_mesh.extents(sliced_dim) - 1
     281             :                                    : 0;
     282             : 
     283             :     // Run the SliceIterator as the outer-most loop since incrementing the slice
     284             :     // iterator is surprisingly expensive.
     285             :     for (SliceIterator si(volume_mesh.extents(), sliced_dim, fixed_index); si;
     286             :          ++si) {
     287             :       for (size_t tensor_storage_index = 0;
     288             :            tensor_storage_index < volume_field.size(); ++tensor_storage_index) {
     289             :         (*face_field)[tensor_storage_index][si.slice_offset()] =
     290             :             volume_field[tensor_storage_index][si.volume_offset()];
     291             :       }
     292             :     }
     293             :   }
     294             : }
     295             : 
     296             : template <typename Symm, typename IndexList, size_t Dim>
     297           1 : Tensor<DataVector, Symm, IndexList> project_tensor_to_boundary(
     298             :     const Tensor<DataVector, Symm, IndexList>& volume_field,
     299             :     const Mesh<Dim>& volume_mesh, const Direction<Dim>& direction) {
     300             :   Tensor<DataVector, Symm, IndexList> face_field{
     301             :       volume_mesh.slice_away(direction.dimension()).number_of_grid_points()};
     302             :   project_tensor_to_boundary(make_not_null(&face_field), volume_field,
     303             :                              volume_mesh, direction);
     304             :   return face_field;
     305             : }
     306             : /// @}
     307             : 
     308             : }  // namespace dg

Generated by: LCOV version 1.14