SpECTRE Documentation Coverage Report
Current view: top level - Evolution/DiscontinuousGalerkin/Actions - ApplyBoundaryCorrections.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 23.3 % 30 7
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 <atomic>
       7              : #include <cstddef>
       8              : #include <limits>
       9              : #include <map>
      10              : #include <mutex>
      11              : #include <optional>
      12              : #include <tuple>
      13              : #include <type_traits>
      14              : #include <utility>
      15              : #include <vector>
      16              : 
      17              : #include "DataStructures/DataBox/AsAccess.hpp"
      18              : #include "DataStructures/DataBox/DataBox.hpp"
      19              : #include "DataStructures/DataBox/PrefixHelpers.hpp"
      20              : #include "DataStructures/DataBox/Prefixes.hpp"
      21              : #include "DataStructures/TaggedTuple.hpp"
      22              : #include "DataStructures/Tensor/EagerMath/Magnitude.hpp"
      23              : #include "DataStructures/VariablesTag.hpp"
      24              : #include "Domain/FaceNormal.hpp"
      25              : #include "Domain/Structure/DirectionalIdMap.hpp"
      26              : #include "Domain/Structure/Element.hpp"
      27              : #include "Domain/Structure/ElementId.hpp"
      28              : #include "Domain/Structure/Topology.hpp"
      29              : #include "Domain/Tags.hpp"
      30              : #include "Domain/Tags/NeighborMesh.hpp"
      31              : #include "Evolution/BoundaryCorrection.hpp"
      32              : #include "Evolution/BoundaryCorrectionTags.hpp"
      33              : #include "Evolution/DiscontinuousGalerkin/Actions/ComputeTimeDerivativeHelpers.hpp"
      34              : #include "Evolution/DiscontinuousGalerkin/BoundaryData.hpp"
      35              : #include "Evolution/DiscontinuousGalerkin/InboxTags.hpp"
      36              : #include "Evolution/DiscontinuousGalerkin/InterfaceDataPolicy.hpp"
      37              : #include "Evolution/DiscontinuousGalerkin/MortarData.hpp"
      38              : #include "Evolution/DiscontinuousGalerkin/MortarDataHolder.hpp"
      39              : #include "Evolution/DiscontinuousGalerkin/MortarTags.hpp"
      40              : #include "Evolution/DiscontinuousGalerkin/NormalVectorTags.hpp"
      41              : #include "Evolution/DiscontinuousGalerkin/UsingSubcell.hpp"
      42              : #include "NumericalAlgorithms/DiscontinuousGalerkin/Formulation.hpp"
      43              : #include "NumericalAlgorithms/DiscontinuousGalerkin/LiftFlux.hpp"
      44              : #include "NumericalAlgorithms/DiscontinuousGalerkin/LiftFromBoundary.hpp"
      45              : #include "NumericalAlgorithms/DiscontinuousGalerkin/MortarHelpers.hpp"
      46              : #include "NumericalAlgorithms/DiscontinuousGalerkin/ProjectToBoundary.hpp"
      47              : #include "NumericalAlgorithms/DiscontinuousGalerkin/Tags/Formulation.hpp"
      48              : #include "NumericalAlgorithms/LinearOperators/Filters/Filter.hpp"
      49              : #include "NumericalAlgorithms/LinearOperators/Filters/None.hpp"
      50              : #include "NumericalAlgorithms/LinearOperators/Filters/Tag.hpp"
      51              : #include "NumericalAlgorithms/Spectral/BoundaryInterpolationMatrices.hpp"
      52              : #include "NumericalAlgorithms/Spectral/Mesh.hpp"
      53              : #include "NumericalAlgorithms/Spectral/Quadrature.hpp"
      54              : #include "NumericalAlgorithms/Spectral/SegmentSize.hpp"
      55              : #include "Parallel/AlgorithmExecution.hpp"
      56              : #include "Parallel/ArrayCollection/IsDgElementCollection.hpp"
      57              : #include "Parallel/GlobalCache.hpp"
      58              : #include "Time/BoundaryHistory.hpp"
      59              : #include "Time/EvolutionOrdering.hpp"
      60              : #include "Time/LtsMode.hpp"
      61              : #include "Time/SelfStart.hpp"
      62              : #include "Time/Tags/StepNumberWithinSlab.hpp"
      63              : #include "Time/Time.hpp"
      64              : #include "Time/TimeStepId.hpp"
      65              : #include "Time/TimeSteppers/LtsTimeStepper.hpp"
      66              : #include "Time/TimeSteppers/TimeStepper.hpp"
      67              : #include "Utilities/Algorithm.hpp"
      68              : #include "Utilities/CallWithDynamicType.hpp"
      69              : #include "Utilities/ErrorHandling/Assert.hpp"
      70              : #include "Utilities/ErrorHandling/Error.hpp"
      71              : #include "Utilities/Gsl.hpp"
      72              : #include "Utilities/MakeArray.hpp"
      73              : #include "Utilities/MemoryHelpers.hpp"
      74              : #include "Utilities/TMPL.hpp"
      75              : 
      76              : /// \cond
      77              : namespace Tags {
      78              : struct Time;
      79              : struct TimeStep;
      80              : struct TimeStepId;
      81              : template <typename StepperInterface>
      82              : struct TimeStepper;
      83              : }  // namespace Tags
      84              : 
      85              : namespace evolution::dg::subcell {
      86              : // We use a forward declaration instead of including a header file to avoid
      87              : // coupling to the DG-subcell libraries for executables that don't use subcell.
      88              : template <size_t VolumeDim, typename DgComputeSubcellNeighborPackagedData>
      89              : void neighbor_reconstructed_face_solution(gsl::not_null<db::Access*> box);
      90              : template <size_t Dim>
      91              : void neighbor_tci_decision(
      92              :     gsl::not_null<db::Access*> box,
      93              :     const DirectionalId<Dim>& directional_element_id,
      94              :     const evolution::dg::BoundaryData<Dim>& neighbor_data);
      95              : template <size_t VolumeDim>
      96              : void receive_subcell_data_for_dg(
      97              :     gsl::not_null<db::Access*> box, const DirectionalId<VolumeDim>& mortar_id,
      98              :     const evolution::dg::BoundaryData<VolumeDim>& received_mortar_data);
      99              : }  // namespace evolution::dg::subcell
     100              : /// \endcond
     101              : 
     102              : namespace evolution::dg {
     103              : namespace detail {
     104              : template <typename BoundaryCorrectionClass>
     105              : struct get_dg_boundary_terms {
     106              :   using type = typename BoundaryCorrectionClass::dg_boundary_terms_volume_tags;
     107              : };
     108              : 
     109              : template <typename BoundaryCorrectionClass>
     110              : struct get_dg_auxiliary_boundary_terms {
     111              :   using type = evolution::dg::Actions::detail::
     112              :       get_dg_auxiliary_boundary_terms_volume_tags_or_default_t<
     113              :           BoundaryCorrectionClass, tmpl::list<>>;
     114              : };
     115              : 
     116              : template <typename Tag, typename Type = db::const_item_type<Tag, tmpl::list<>>>
     117              : struct TemporaryReference {
     118              :   using tag = Tag;
     119              :   using type = const Type&;
     120              : };
     121              : }  // namespace detail
     122              : 
     123              : /// Move boundary data from the inbox to the DataBox.  Returns true if
     124              : /// all necessary data has been received.
     125              : ///
     126              : /// Setting \p DenseOutput to true receives data required for output
     127              : /// at `::Tags::Time` instead of `::Tags::Next<::Tags::TimeStepId>`.
     128              : ///
     129              : /// If \p LocalTimeStepping is true, it will process all data
     130              : /// necessary for conservative LTS, otherwise it will process all data
     131              : /// necessary for GTS.  Some data for the other mode may also be
     132              : /// processed to simplify the message handling.
     133              : template <bool UseNodegroupDgElements, typename Metavariables,
     134              :           bool LocalTimeStepping, bool DenseOutput,
     135              :           bool ComputeAuxiliary = false, typename DbTagsList,
     136              :           typename... InboxTags>
     137            1 : bool receive_boundary_data(
     138              :     const gsl::not_null<db::DataBox<DbTagsList>*> box,
     139              :     const gsl::not_null<tuples::TaggedTuple<InboxTags...>*> inboxes) {
     140              :   constexpr size_t volume_dim = Metavariables::system::volume_dim;
     141              :   constexpr size_t face_dim = volume_dim - 1;
     142              :   static_assert(LocalTimeStepping or not DenseOutput,
     143              :                 "Should not be receiving data for dense output with GTS.");
     144              : 
     145              :   auto& inbox =
     146              :       tuples::get<evolution::dg::Tags::BoundaryCorrectionAndGhostCellsInbox<
     147              :           volume_dim, UseNodegroupDgElements, ComputeAuxiliary>>(*inboxes);
     148              : 
     149              :   const auto& volume_mesh = db::get<domain::Tags::Mesh<volume_dim>>(*box);
     150              :   const auto& mortar_infos = db::get<Tags::MortarInfo<volume_dim>>(*box);
     151              :   const auto& mortar_next_time_step_ids =
     152              :       db::get<evolution::dg::Tags::MortarNextTemporalId<volume_dim>>(*box);
     153              : 
     154              :   for (;;) {
     155              :     std::optional<TimeStepId> time_to_process{};
     156              :     for (const auto& [mortar_id, mortar_next_time_step_id] :
     157              :          mortar_next_time_step_ids) {
     158              :       if (time_to_process.has_value() and
     159              :           mortar_next_time_step_id > *time_to_process) {
     160              :         continue;
     161              :       }
     162              : 
     163              :       const auto& time_stepping_policy =
     164              :           mortar_infos.at(mortar_id).time_stepping_policy();
     165              :       switch (time_stepping_policy) {
     166              :         case LtsMode::Off:
     167              :           if (LocalTimeStepping or
     168              :               mortar_next_time_step_id > db::get<::Tags::TimeStepId>(*box)) {
     169              :             continue;
     170              :           }
     171              :           break;
     172              :         case LtsMode::Conservative:
     173              :           if constexpr (not LocalTimeStepping) {
     174              :             continue;
     175              :           } else {
     176              :             const LtsTimeStepper& time_stepper =
     177              :                 db::get<::Tags::TimeStepper<LtsTimeStepper>>(*box);
     178              :             using goal_tag =
     179              :                 tmpl::conditional_t<DenseOutput, ::Tags::Time,
     180              :                                     ::Tags::Next<::Tags::TimeStepId>>;
     181              :             const auto& goal = db::get<goal_tag>(*box);
     182              :             if (not time_stepper.neighbor_data_required(
     183              :                     goal, mortar_next_time_step_id)) {
     184              :               continue;
     185              :             }
     186              :           }
     187              :           break;
     188              :         default:
     189              :           ERROR("Unhandled time-stepping policy: " << time_stepping_policy);
     190              :       }
     191              : 
     192              :       time_to_process.emplace(mortar_next_time_step_id);
     193              :     }
     194              : 
     195              :     if (not time_to_process.has_value()) {
     196              :       if constexpr (using_subcell_v<Metavariables> and not LocalTimeStepping) {
     197              :         evolution::dg::subcell::neighbor_reconstructed_face_solution<
     198              :             volume_dim, typename Metavariables::SubcellOptions::
     199              :                             DgComputeSubcellNeighborPackagedData>(
     200              :             &db::as_access(*box));
     201              :       }
     202              :       return true;
     203              :     }
     204              : 
     205              :     const auto& element = db::get<domain::Tags::Element<volume_dim>>(*box);
     206              :     const auto expected_messages = static_cast<size_t>(alg::accumulate(
     207              :         mortar_next_time_step_ids, 0,
     208              :         [&mortar_infos, &element, &time_to_process](const size_t total,
     209              :                                                     const auto& entry) {
     210              :           if (entry.second != *time_to_process) {
     211              :             return total;
     212              :           } else if (mortar_infos.at(entry.first).interface_data_policy() !=
     213              :                      InterfaceDataPolicy::NonconformingNeighborInterpolates) {
     214              :             return total + 1;
     215              :           } else {
     216              :             return total +
     217              :                    element.neighbors().at(entry.first.direction()).size();
     218              :           }
     219              :         }));
     220              : 
     221              :     // This is a
     222              :     //
     223              :     // std::map<TimeStepId,
     224              :     //          V<std::pair<DirectionalId<volume_dim>,
     225              :     //                      evolution::dg::BoundaryData<volume_dim>>>,
     226              :     //
     227              :     // where V<> is a vector-like type the details of which we don't
     228              :     // want to hardcode here.
     229              :     auto& inbox_data = inbox.messages;
     230              :     auto messages_to_process = inbox_data.end();
     231              : 
     232              :     {
     233              :       size_t missing_messages{};
     234              :       do {
     235              :         inbox.collect_messages();
     236              :         if (messages_to_process == inbox_data.end()) {
     237              :           messages_to_process = inbox_data.find(*time_to_process);
     238              :         }
     239              :         const size_t available_messages =
     240              :             messages_to_process == inbox_data.end()
     241              :                 ? 0
     242              :                 : messages_to_process->second.size();
     243              :         ASSERT(available_messages <= expected_messages,
     244              :                "Too many boundary messages at " << *time_to_process << ": "
     245              :                                                 << available_messages << "/"
     246              :                                                 << expected_messages);
     247              :         missing_messages = expected_messages - available_messages;
     248              :       } while (missing_messages != 0 and
     249              :                inbox.set_missing_messages(missing_messages));
     250              :       if (missing_messages != 0) {
     251              :         return false;
     252              :       }
     253              :     }
     254              : 
     255              :     ASSERT(time_to_process->step_time().slab() ==
     256              :                messages_to_process->first.step_time().slab(),
     257              :            "Inconsistent slabs in " << *time_to_process << " and "
     258              :                                     << messages_to_process->first);
     259              :     std::unordered_map<Direction<volume_dim>, std::vector<size_t>>
     260              :         contributors_multiple_non_conforming_neighbors{};
     261              : 
     262              :     for (auto& mortar_id_and_data : messages_to_process->second) {
     263              :       const auto& received_mortar_id = mortar_id_and_data.first;
     264              :       auto& received_mortar_data = mortar_id_and_data.second;
     265              :       const auto& direction = received_mortar_id.direction();
     266              :       const auto& neighbor_mesh = received_mortar_data.volume_mesh;
     267              :       const size_t sliced_away_dim = direction.dimension();
     268              :       const Mesh<face_dim> face_mesh =
     269              :           volume_mesh.on_interface(sliced_away_dim);
     270              :       // If there are multiple non-conforming neighbors, there is only a
     271              :       // single mortar labeled by the host ElementId.  This is done
     272              :       // because the data from all neighbors will be combined onto a
     273              :       // single mortar as it makes no sense to have multiple mortars
     274              :       // between non-conforming Elements.
     275              :       const DirectionalId<volume_dim> mortar_id =
     276              :           mortar_infos.contains(received_mortar_id)
     277              :               ? received_mortar_id
     278              :               : DirectionalId<volume_dim>{direction, element.id()};
     279              : 
     280              :       ASSERT(mortar_next_time_step_ids.at(mortar_id) == *time_to_process or
     281              :                  contributors_multiple_non_conforming_neighbors.contains(
     282              :                      direction),
     283              :              "Processing wrong time for mortar "
     284              :                  << mortar_id << "\nExpected "
     285              :                  << mortar_next_time_step_ids.at(mortar_id)
     286              :                  << " but processing " << *time_to_process);
     287              : 
     288              :       const auto& time_stepping_policy =
     289              :           mortar_infos.at(mortar_id).time_stepping_policy();
     290              : 
     291              :       if constexpr (using_subcell_v<Metavariables>) {
     292              :         if (time_stepping_policy == LtsMode::Off) {
     293              :           evolution::dg::subcell::receive_subcell_data_for_dg<volume_dim>(
     294              :               &db::as_access(*box), received_mortar_id, received_mortar_data);
     295              :           evolution::dg::subcell::neighbor_tci_decision<volume_dim>(
     296              :               make_not_null(&db::as_access(*box)), received_mortar_id,
     297              :               received_mortar_data);
     298              :         }
     299              :       }
     300              : 
     301              :       db::mutate<evolution::dg::Tags::MortarMesh<volume_dim>,
     302              :                  evolution::dg::Tags::MortarData<volume_dim>,
     303              :                  evolution::dg::Tags::MortarDataHistory<volume_dim>,
     304              :                  evolution::dg::Tags::MortarNextTemporalId<volume_dim>,
     305              :                  domain::Tags::NeighborMesh<volume_dim>>(
     306              :           [&](const gsl::not_null<DirectionalIdMap<volume_dim, Mesh<face_dim>>*>
     307              :                   mortar_meshes,
     308              :               const gsl::not_null<DirectionalIdMap<
     309              :                   volume_dim, evolution::dg::MortarDataHolder<volume_dim>>*>
     310              :                   gts_mortar_data,
     311              :               const gsl::not_null<DirectionalIdMap<
     312              :                   volume_dim,
     313              :                   TimeSteppers::BoundaryHistory<
     314              :                       evolution::dg::MortarData<volume_dim>,
     315              :                       evolution::dg::MortarData<volume_dim>, DataVector>>*>
     316              :                   boundary_data_history,
     317              :               [[maybe_unused]] const gsl::not_null<
     318              :                   DirectionalIdMap<volume_dim, TimeStepId>*>
     319              :                   mortar_next_time_step_ids_mutable,
     320              :               const gsl::not_null<
     321              :                   DirectionalIdMap<volume_dim, Mesh<volume_dim>>*>
     322              :                   neighbor_meshes) {
     323              :             switch (mortar_infos.at(mortar_id).interface_data_policy()) {
     324              :               case InterfaceDataPolicy::CopyProject:
     325              :                 [[fallthrough]];
     326              :               case InterfaceDataPolicy::OrientCopyProject: {
     327              :                 neighbor_meshes->insert_or_assign(received_mortar_id,
     328              :                                                   neighbor_mesh);
     329              :                 const Mesh<face_dim> neighbor_face_mesh =
     330              :                     received_mortar_data.volume_mesh.on_interface(
     331              :                         sliced_away_dim);
     332              :                 const Mesh<face_dim> mortar_mesh =
     333              :                     ::dg::mortar_mesh(face_mesh, neighbor_face_mesh);
     334              : 
     335              :                 const auto project_boundary_mortar_data =
     336              :                     [&mortar_mesh](const TimeStepId& /*id*/,
     337              :                                    const gsl::not_null<
     338              :                                        ::evolution::dg::MortarData<volume_dim>*>
     339              :                                        mortar_data) {
     340              :                       return p_project_mortar_data(mortar_data, mortar_mesh);
     341              :                     };
     342              : 
     343              :                 mortar_meshes->at(mortar_id) = mortar_mesh;
     344              :                 switch (time_stepping_policy) {
     345              :                   case LtsMode::Off:
     346              :                     p_project_mortar_data(
     347              :                         make_not_null(&gts_mortar_data->at(mortar_id).local()),
     348              :                         mortar_mesh);
     349              :                     break;
     350              :                   case LtsMode::Conservative:
     351              :                     boundary_data_history->at(mortar_id).local().for_each(
     352              :                         project_boundary_mortar_data);
     353              :                     break;
     354              :                   default:
     355              :                     ERROR("Unhandled time-stepping policy: "
     356              :                           << time_stepping_policy);
     357              :                 }
     358              : 
     359              :                 // The auxiliary pass must not advance the mortar clock: the
     360              :                 // physical receive later in the same step still reads it.
     361              :                 if constexpr (not ComputeAuxiliary) {
     362              :                   mortar_next_time_step_ids_mutable->at(mortar_id) =
     363              :                       received_mortar_data.validity_range;
     364              :                 }
     365              : 
     366              :                 ASSERT(using_subcell_v<Metavariables> or
     367              :                            received_mortar_data.boundary_correction_data
     368              :                                .has_value(),
     369              :                        "Must receive neighbor boundary correction data when "
     370              :                        "not using DG-subcell. Mortar ID is: ("
     371              :                            << mortar_id.direction() << "," << mortar_id.id()
     372              :                            << ") and TimeStepId is " << *time_to_process);
     373              :                 MortarData<volume_dim> neighbor_mortar_data{};
     374              :                 neighbor_mortar_data.face_mesh = neighbor_face_mesh;
     375              :                 neighbor_mortar_data.mortar_mesh =
     376              :                     received_mortar_data.boundary_correction_mesh;
     377              :                 neighbor_mortar_data.mortar_data =
     378              :                     std::move(received_mortar_data.boundary_correction_data);
     379              :                 switch (time_stepping_policy) {
     380              :                   case LtsMode::Off:
     381              :                     if (neighbor_mortar_data.mortar_data.has_value()) {
     382              :                       p_project_mortar_data(
     383              :                           make_not_null(&neighbor_mortar_data), mortar_mesh);
     384              :                     }
     385              :                     gts_mortar_data->at(mortar_id).neighbor() =
     386              :                         std::move(neighbor_mortar_data);
     387              :                     break;
     388              :                   case LtsMode::Conservative:
     389              :                     ASSERT(neighbor_mortar_data.mortar_data.has_value(),
     390              :                            "Did not receive mortar data for " << mortar_id);
     391              :                     boundary_data_history->at(mortar_id).remote().insert(
     392              :                         *time_to_process,
     393              :                         received_mortar_data.integration_order,
     394              :                         std::move(neighbor_mortar_data));
     395              :                     boundary_data_history->at(mortar_id).remote().for_each(
     396              :                         project_boundary_mortar_data);
     397              :                     break;
     398              :                   default:
     399              :                     ERROR("Unhandled time-stepping policy: "
     400              :                           << time_stepping_policy);
     401              :                 }
     402              :                 break;
     403              :               }
     404              :               case InterfaceDataPolicy::NonconformingSelfInterpolates: {
     405              :                 if constexpr (volume_dim > 1) {
     406              :                   neighbor_meshes->insert_or_assign(received_mortar_id,
     407              :                                                     neighbor_mesh);
     408              :                   if constexpr (not ComputeAuxiliary) {
     409              :                     mortar_next_time_step_ids_mutable->at(mortar_id) =
     410              :                         received_mortar_data.validity_range;
     411              :                   }
     412              :                   mortar_meshes->at(mortar_id) = face_mesh;
     413              :                   gts_mortar_data->at(mortar_id).neighbor().face_mesh =
     414              :                       face_mesh;
     415              :                   gts_mortar_data->at(mortar_id).neighbor().mortar_mesh =
     416              :                       face_mesh;
     417              :                   const auto& interpolator =
     418              :                       mortar_infos.at(mortar_id).interpolator().value();
     419              :                   const auto& received_data =
     420              :                       received_mortar_data.boundary_correction_data.value();
     421              :                   DataVector interpolated_data =
     422              :                       interpolator.interpolate_to_host(received_data);
     423              :                   gts_mortar_data->at(mortar_id).neighbor().mortar_data =
     424              :                       std::move(interpolated_data);
     425              :                 } else {
     426              :                   ERROR("Cannot have non-conforming neighbors in 1D");
     427              :                 }
     428              :                 break;
     429              :               }
     430              :               case InterfaceDataPolicy::NonconformingNeighborInterpolates: {
     431              :                 if constexpr (volume_dim > 1) {
     432              :                   // We do not insert the neighbor mesh into neighbor_meshes
     433              :                   // as this could overflow the FixedHashMap size
     434              :                   const size_t npts_mortar = face_mesh.number_of_grid_points();
     435              :                   const size_t mortar_data_size = gts_mortar_data->at(mortar_id)
     436              :                                                       .local()
     437              :                                                       .mortar_data.value()
     438              :                                                       .size();
     439              :                   const size_t number_of_components =
     440              :                       mortar_data_size / npts_mortar;
     441              :                   // The data received from each neighbor has been interpolated
     442              :                   // to a subset of points of the single mortar mesh of the host
     443              :                   // If this is the first neighbor processed,
     444              :                   if (not contributors_multiple_non_conforming_neighbors
     445              :                               .contains(direction)) {
     446              :                     contributors_multiple_non_conforming_neighbors.emplace(
     447              :                         direction, std::vector<size_t>(
     448              :                                        face_mesh.number_of_grid_points(), 0));
     449              :                     if constexpr (not ComputeAuxiliary) {
     450              :                       mortar_next_time_step_ids_mutable->at(mortar_id) =
     451              :                           received_mortar_data.validity_range;
     452              :                     }
     453              :                     mortar_meshes->at(mortar_id) = face_mesh;
     454              :                     gts_mortar_data->at(mortar_id).neighbor().face_mesh =
     455              :                         face_mesh;
     456              :                     gts_mortar_data->at(mortar_id).neighbor().mortar_mesh =
     457              :                         face_mesh;
     458              :                     gts_mortar_data->at(mortar_id).neighbor().mortar_data =
     459              :                         DataVector{mortar_data_size, 0.0};
     460              :                   }
     461              :                   if constexpr (not ComputeAuxiliary) {
     462              :                     ASSERT(mortar_next_time_step_ids_mutable->at(mortar_id) ==
     463              :                                received_mortar_data.validity_range,
     464              :                            "Inconsistent validity range "
     465              :                                << received_mortar_data.validity_range
     466              :                                << " received from " << received_mortar_id
     467              :                                << "; expected "
     468              :                                << mortar_next_time_step_ids_mutable->at(
     469              :                                       mortar_id));
     470              :                   }
     471              :                   const auto& interpolated_boundary_data =
     472              :                       received_mortar_data.interpolated_boundary_data.value();
     473              :                   const auto& interpolated_data =
     474              :                       interpolated_boundary_data.boundary_data();
     475              :                   const size_t interpolated_data_size =
     476              :                       interpolated_data.size();
     477              :                   const auto& offsets = interpolated_boundary_data.offsets();
     478              :                   const size_t npts_interpolated = offsets.size();
     479              :                   ASSERT(npts_interpolated * number_of_components ==
     480              :                              interpolated_data_size,
     481              :                          "Size mismatch!  Number of interpolated points "
     482              :                              << npts_interpolated
     483              :                              << " times number of components "
     484              :                              << number_of_components
     485              :                              << " is not interpolated data size "
     486              :                              << interpolated_data_size);
     487              :                   auto& target_mortar_data = gts_mortar_data->at(mortar_id)
     488              :                                                  .neighbor()
     489              :                                                  .mortar_data.value();
     490              :                   auto& contributors =
     491              :                       contributors_multiple_non_conforming_neighbors.at(
     492              :                           direction);
     493              :                   for (size_t i = 0; i < npts_interpolated; ++i) {
     494              :                     ++contributors[offsets[i]];
     495              :                     for (size_t c = 0; c < number_of_components; ++c) {
     496              :                       target_mortar_data[offsets[i] + c * npts_mortar] +=
     497              :                           interpolated_data[i + c * npts_interpolated];
     498              :                     }
     499              :                   }
     500              :                 } else {
     501              :                   ERROR("Cannot have non-conforming neighbors in 1D");
     502              :                 }
     503              :                 break;
     504              :               }
     505              :               default:
     506              :                 ERROR("InterfaceDataPolicy "
     507              :                       << mortar_infos.at(mortar_id).interface_data_policy()
     508              :                       << " is not handled yet, id = " << mortar_id);
     509              :             }
     510              :           },
     511              :           box);
     512              :     }
     513              : 
     514              :     db::mutate<evolution::dg::Tags::MortarData<volume_dim>>(
     515              :         [&contributors_multiple_non_conforming_neighbors, &element](
     516              :             const gsl::not_null<DirectionalIdMap<
     517              :                 volume_dim, evolution::dg::MortarDataHolder<volume_dim>>*>
     518              :                 gts_mortar_data) {
     519              :           for (const auto& [direction, contributors] :
     520              :                contributors_multiple_non_conforming_neighbors) {
     521              :             const DirectionalId<volume_dim> mortar_id =
     522              :                 DirectionalId<volume_dim>{direction, element.id()};
     523              :             auto& target_mortar_data =
     524              :                 gts_mortar_data->at(mortar_id).neighbor().mortar_data.value();
     525              :             const size_t npts_mortar = contributors.size();
     526              :             const size_t number_of_components =
     527              :                 target_mortar_data.size() / npts_mortar;
     528              :             ASSERT(alg::none_of(contributors,
     529              :                                 [](const size_t n) { return n == 0; }),
     530              :                    "Not all points were interpolated.  Direction = "
     531              :                        << direction << " ElementId = " << element.id() << "\n"
     532              :                        << "target_mortar_data = " << target_mortar_data);
     533              :             for (size_t i = 0; i < npts_mortar; ++i) {
     534              :               for (size_t c = 0; c < number_of_components; ++c) {
     535              :                 target_mortar_data[i + c * npts_mortar] /=
     536              :                     static_cast<double>(contributors[i]);
     537              :               }
     538              :             }
     539              :           }
     540              :         },
     541              :         box);
     542              : 
     543              :     inbox_data.erase(messages_to_process);
     544              :     if constexpr (ComputeAuxiliary) {
     545              :       // The auxiliary pass processes a single temporal id; return rather than
     546              :       // looping for more ready times.
     547              :       return true;
     548              :     }
     549              :   }
     550              : }
     551              : 
     552              : struct FetchFromMetavariables;
     553              : 
     554              : /// Apply corrections from boundary communication.
     555              : ///
     556              : /// This is usually used indirectly through
     557              : /// `ApplyBoundaryCorrectionsToTimeDerivative`,
     558              : /// `ApplyLtsBoundaryCorrections`, or `ApplyLtsDenseBoundaryCorrections`.
     559              : ///
     560              : /// If `LocalTimeStepping` is false, updates the derivative of the variables,
     561              : /// which should be done before taking a time step.  If
     562              : /// `LocalTimeStepping` is true, updates the variables themselves, which should
     563              : /// be done after the volume update.
     564              : ///
     565              : /// Setting \p DenseOutput to true receives data required for output
     566              : /// at ::Tags::Time instead of performing a full step.  This is only
     567              : /// used for local time-stepping.
     568              : template <bool LocalTimeStepping, typename Metavariables, bool DenseOutput,
     569              :           bool ComputeAuxiliary, typename VariablesTag>
     570            1 : struct ApplyBoundaryCorrections {
     571            0 :   static constexpr bool local_time_stepping = LocalTimeStepping;
     572              :   static_assert(local_time_stepping or not DenseOutput,
     573              :                 "GTS does not use ApplyBoundaryCorrections for dense output.");
     574              :   static_assert(not(ComputeAuxiliary and local_time_stepping),
     575              :                 "Auxiliary boundary corrections are not supported with LTS.");
     576              : 
     577            0 :   using system = typename Metavariables::system;
     578            0 :   static constexpr size_t volume_dim = system::volume_dim;
     579            0 :   using variables_tag =
     580              :       tmpl::conditional_t<std::is_same_v<VariablesTag, FetchFromMetavariables>,
     581              :                           typename Metavariables::system::variables_tag,
     582              :                           VariablesTag>;
     583              : 
     584            0 :   using FilterTagList = typename variables_tag::tags_list;
     585            0 :   using dt_variables_tag = db::add_tag_prefix<::Tags::dt, variables_tag>;
     586            0 :   using auxiliary_variables_tag = ::Tags::Variables<
     587              :       evolution::dg::Actions::detail::get_auxiliary_variables_or_default_t<
     588              :           system, tmpl::list<>>>;
     589              :   // The correction-buffer type. The physical/LTS paths hold time derivatives
     590              :   // (`dt_variables_tag`), lifted into `dt_variables_tag` (GTS) or
     591              :   // `variables_tag` (LTS, via the prefix-agnostic `Variables` arithmetic). The
     592              :   // auxiliary variables are not time-evolved, so the auxiliary pass's buffer is
     593              :   // shaped by `auxiliary_variables` directly (no `dt` prefix); it holds the
     594              :   // correction added to the auxiliary variables.
     595            0 :   using DtVariables =
     596              :       tmpl::conditional_t<ComputeAuxiliary,
     597              :                           typename auxiliary_variables_tag::type,
     598              :                           typename dt_variables_tag::type>;
     599            0 :   using derived_boundary_corrections =
     600              :       tmpl::at<typename Metavariables::factory_creation::factory_classes,
     601              :                evolution::BoundaryCorrection>;
     602              : 
     603            0 :   using volume_tags_for_dg_boundary_terms =
     604              :       tmpl::remove_duplicates<tmpl::flatten<tmpl::transform<
     605              :           derived_boundary_corrections,
     606              :           tmpl::conditional_t<ComputeAuxiliary,
     607              :                               detail::get_dg_auxiliary_boundary_terms<tmpl::_1>,
     608              :                               detail::get_dg_boundary_terms<tmpl::_1>>>>>;
     609              : 
     610            0 :   using TimeStepperType =
     611              :       tmpl::conditional_t<local_time_stepping, LtsTimeStepper, TimeStepper>;
     612              : 
     613              :   // The auxiliary pass writes the corrected auxiliary variables into their own
     614              :   // storage (`auxiliary_variables_tag`); LTS updates `variables_tag`; the GTS
     615              :   // physical pass updates `dt_variables_tag`.
     616            0 :   using tag_to_update =
     617              :       tmpl::conditional_t<ComputeAuxiliary, auxiliary_variables_tag,
     618              :                           tmpl::conditional_t<local_time_stepping,
     619              :                                               variables_tag, dt_variables_tag>>;
     620            0 :   using mortar_data_tag =
     621              :       tmpl::conditional_t<local_time_stepping,
     622              :                           evolution::dg::Tags::MortarDataHistory<volume_dim>,
     623              :                           evolution::dg::Tags::MortarData<volume_dim>>;
     624              : 
     625            0 :   using return_tags = tmpl::list<tag_to_update>;
     626            0 :   using argument_tags = tmpl::append<
     627              :       tmpl::flatten<tmpl::list<
     628              :           mortar_data_tag, domain::Tags::Mesh<volume_dim>,
     629              :           domain::Tags::Element<volume_dim>, Tags::MortarMesh<volume_dim>,
     630              :           Tags::MortarInfo<volume_dim>, ::dg::Tags::Formulation,
     631              :           evolution::dg::Tags::NormalCovectorAndMagnitude<volume_dim>,
     632              :           ::Tags::TimeStepper<TimeStepperType>,
     633              :           evolution::Tags::BoundaryCorrection,
     634              :           tmpl::conditional_t<
     635              :               DenseOutput, ::Tags::Time,
     636              :               tmpl::list<
     637              :                   ::Tags::TimeStep,
     638              :                   Filters::Tags::SpectralFilter<volume_dim, FilterTagList>,
     639              :                   ::Tags::StepNumberWithinSlab,
     640              :                   domain::Tags::Jacobian<volume_dim, Frame::Grid,
     641              :                                          Frame::Inertial>,
     642              :                   domain::Tags::InverseJacobian<volume_dim, Frame::Grid,
     643              :                                                 Frame::Inertial>>>,
     644              :           tmpl::conditional_t<local_time_stepping, tmpl::list<>,
     645              :                               domain::Tags::DetInvJacobian<
     646              :                                   Frame::ElementLogical, Frame::Inertial>>>>,
     647              :       volume_tags_for_dg_boundary_terms>;
     648              : 
     649              :   // full step (GTS: local_time_stepping=false, DenseOutput=false)
     650              :   template <typename... VolumeArgs>
     651            0 :   static void apply(
     652              :       const gsl::not_null<typename tag_to_update::type*> vars_to_update,
     653              :       const typename mortar_data_tag::type& mortar_data,
     654              :       const Mesh<volume_dim>& volume_mesh, const Element<volume_dim>& element,
     655              :       const typename Tags::MortarMesh<volume_dim>::type& mortar_meshes,
     656              :       const typename Tags::MortarInfo<volume_dim>::type& mortar_infos,
     657              :       const ::dg::Formulation dg_formulation,
     658              :       const DirectionMap<
     659              :           volume_dim, std::optional<Variables<tmpl::list<
     660              :                           evolution::dg::Tags::MagnitudeOfNormal,
     661              :                           evolution::dg::Tags::NormalCovector<volume_dim>>>>>&
     662              :           face_normal_covector_and_magnitude,
     663              :       const TimeStepperType& time_stepper,
     664              :       const evolution::BoundaryCorrection& boundary_correction,
     665              :       const TimeDelta& time_step,
     666              :       const Filters::Filter<volume_dim, FilterTagList>& boundary_filter,
     667              :       const uint64_t step_number_within_slab,
     668              :       const Jacobian<DataVector, volume_dim, Frame::Grid, Frame::Inertial>&
     669              :           volume_jac_grid_to_inertial,
     670              :       const InverseJacobian<DataVector, volume_dim, Frame::Grid,
     671              :                             Frame::Inertial>& volume_inv_jac_grid_to_inertial,
     672              :       const Scalar<DataVector>& gts_det_inv_jacobian,
     673              :       const VolumeArgs&... volume_args) {
     674              :     apply_impl(vars_to_update, mortar_data, volume_mesh, element, mortar_meshes,
     675              :                mortar_infos, dg_formulation, face_normal_covector_and_magnitude,
     676              :                time_stepper, boundary_correction, time_step,
     677              :                std::numeric_limits<double>::signaling_NaN(), &boundary_filter,
     678              :                step_number_within_slab, volume_jac_grid_to_inertial,
     679              :                volume_inv_jac_grid_to_inertial, gts_det_inv_jacobian,
     680              :                volume_args...);
     681              :   }
     682              : 
     683              :   // full step (LTS: local_time_stepping=true, DenseOutput=false)
     684              :   template <typename... VolumeArgs>
     685            0 :   static void apply(
     686              :       const gsl::not_null<typename tag_to_update::type*> vars_to_update,
     687              :       const typename mortar_data_tag::type& mortar_data,
     688              :       const Mesh<volume_dim>& volume_mesh, const Element<volume_dim>& element,
     689              :       const typename Tags::MortarMesh<volume_dim>::type& mortar_meshes,
     690              :       const typename Tags::MortarInfo<volume_dim>::type& mortar_infos,
     691              :       const ::dg::Formulation dg_formulation,
     692              :       const DirectionMap<
     693              :           volume_dim, std::optional<Variables<tmpl::list<
     694              :                           evolution::dg::Tags::MagnitudeOfNormal,
     695              :                           evolution::dg::Tags::NormalCovector<volume_dim>>>>>&
     696              :           face_normal_covector_and_magnitude,
     697              :       const TimeStepperType& time_stepper,
     698              :       const evolution::BoundaryCorrection& boundary_correction,
     699              :       const TimeDelta& time_step,
     700              :       const Filters::Filter<volume_dim, FilterTagList>& boundary_filter,
     701              :       const uint64_t step_number_within_slab,
     702              :       const Jacobian<DataVector, volume_dim, Frame::Grid, Frame::Inertial>&
     703              :           volume_jac_grid_to_inertial,
     704              :       const InverseJacobian<DataVector, volume_dim, Frame::Grid,
     705              :                             Frame::Inertial>& volume_inv_jac_grid_to_inertial,
     706              :       const VolumeArgs&... volume_args) {
     707              :     apply_impl(vars_to_update, mortar_data, volume_mesh, element, mortar_meshes,
     708              :                mortar_infos, dg_formulation, face_normal_covector_and_magnitude,
     709              :                time_stepper, boundary_correction, time_step,
     710              :                std::numeric_limits<double>::signaling_NaN(), &boundary_filter,
     711              :                step_number_within_slab, volume_jac_grid_to_inertial,
     712              :                volume_inv_jac_grid_to_inertial, {}, volume_args...);
     713              :   }
     714              : 
     715              :   // dense output (LTS only, DenseOutput=true)
     716              :   template <typename... VolumeArgs>
     717            0 :   static void apply(
     718              :       const gsl::not_null<typename variables_tag::type*> vars_to_update,
     719              :       const typename mortar_data_tag::type& mortar_data,
     720              :       const Mesh<volume_dim>& volume_mesh, const Element<volume_dim>& element,
     721              :       const typename Tags::MortarMesh<volume_dim>::type& mortar_meshes,
     722              :       const typename Tags::MortarInfo<volume_dim>::type& mortar_infos,
     723              :       const ::dg::Formulation dg_formulation,
     724              :       const DirectionMap<
     725              :           volume_dim, std::optional<Variables<tmpl::list<
     726              :                           evolution::dg::Tags::MagnitudeOfNormal,
     727              :                           evolution::dg::Tags::NormalCovector<volume_dim>>>>>&
     728              :           face_normal_covector_and_magnitude,
     729              :       const LtsTimeStepper& time_stepper,
     730              :       const evolution::BoundaryCorrection& boundary_correction,
     731              :       const double dense_output_time, const VolumeArgs&... volume_args) {
     732              :     apply_impl(
     733              :         vars_to_update, mortar_data, volume_mesh, element, mortar_meshes,
     734              :         mortar_infos, dg_formulation, face_normal_covector_and_magnitude,
     735              :         time_stepper, boundary_correction, TimeDelta{}, dense_output_time,
     736              :         nullptr, static_cast<uint64_t>(0),
     737              :         Jacobian<DataVector, volume_dim, Frame::Grid, Frame::Inertial>{},
     738              :         InverseJacobian<DataVector, volume_dim, Frame::Grid, Frame::Inertial>{},
     739              :         {}, volume_args...);
     740              :   }
     741              : 
     742              :   template <typename DbTagsList, typename... InboxTags, typename ArrayIndex,
     743              :             typename ParallelComponent>
     744            0 :   static bool is_ready(
     745              :       const gsl::not_null<db::DataBox<DbTagsList>*> box,
     746              :       const gsl::not_null<tuples::TaggedTuple<InboxTags...>*> inboxes,
     747              :       Parallel::GlobalCache<Metavariables>& /*cache*/,
     748              :       const ArrayIndex& /*array_index*/,
     749              :       const ParallelComponent* const /*component*/) {
     750              :     return receive_boundary_data<
     751              :         Parallel::is_dg_element_collection_v<ParallelComponent>, Metavariables,
     752              :         local_time_stepping, DenseOutput, ComputeAuxiliary>(box, inboxes);
     753              :   }
     754              : 
     755              :  private:
     756              :   template <typename... VolumeArgs>
     757            0 :   static void apply_impl(
     758              :       const gsl::not_null<typename tag_to_update::type*> vars_to_update,
     759              :       const typename mortar_data_tag::type& mortar_data,
     760              :       const Mesh<volume_dim>& volume_mesh, const Element<volume_dim>& element,
     761              :       const typename Tags::MortarMesh<volume_dim>::type& mortar_meshes,
     762              :       const typename Tags::MortarInfo<volume_dim>::type& mortar_infos,
     763              :       const ::dg::Formulation dg_formulation,
     764              :       const DirectionMap<
     765              :           volume_dim, std::optional<Variables<tmpl::list<
     766              :                           evolution::dg::Tags::MagnitudeOfNormal,
     767              :                           evolution::dg::Tags::NormalCovector<volume_dim>>>>>&
     768              :           face_normal_covector_and_magnitude,
     769              :       const TimeStepperType& time_stepper,
     770              :       const evolution::BoundaryCorrection& boundary_correction,
     771              :       const TimeDelta& time_step, const double dense_output_time,
     772              :       const Filters::Filter<volume_dim, FilterTagList>* const filter_ptr,
     773              :       const uint64_t step_number_within_slab,
     774              :       const Jacobian<DataVector, volume_dim, Frame::Grid, Frame::Inertial>&
     775              :           volume_jac_grid_to_inertial,
     776              :       const InverseJacobian<DataVector, volume_dim, Frame::Grid,
     777              :                             Frame::Inertial>& volume_inv_jac_grid_to_inertial,
     778              :       const Scalar<DataVector>& gts_det_inv_jacobian,
     779              :       const VolumeArgs&... volume_args) {
     780              :     // We treat this as a set, but use a map because we don't have a
     781              :     // non-allocating set type.
     782              :     DirectionalIdMap<volume_dim, bool> mortars_to_act_on{};
     783              :     for (const auto& [mortar, info] : mortar_infos) {
     784              :       const auto& time_stepping_policy = info.time_stepping_policy();
     785              :       switch (time_stepping_policy) {
     786              :         case LtsMode::Off:
     787              :           if (not local_time_stepping) {
     788              :             mortars_to_act_on.emplace(mortar, true);
     789              :           }
     790              :           break;
     791              :         case LtsMode::Conservative:
     792              :           if (local_time_stepping) {
     793              :             mortars_to_act_on.emplace(mortar, true);
     794              :           }
     795              :           break;
     796              :         default:
     797              :           ERROR("Unhandled time-stepping policy: " << time_stepping_policy);
     798              :       }
     799              :     }
     800              :     if (mortars_to_act_on.empty()) {
     801              :       return;
     802              :     }
     803              : 
     804              :     bool boundary_filter_active = false;
     805              :     // The auxiliary boundary correction is not filtered currently (see the
     806              :     // filter application below), so the auxiliary pass skips the
     807              :     // filter-activity check and the face-Jacobian setup it would trigger.
     808              :     if constexpr (not DenseOutput and not ComputeAuxiliary) {
     809              :       if (filter_ptr != nullptr and
     810              :           dynamic_cast<const Filters::None<volume_dim, FilterTagList>*>(
     811              :               filter_ptr) == nullptr) {
     812              :         const auto step_number = static_cast<size_t>(step_number_within_slab);
     813              :         boundary_filter_active =
     814              :             filter_ptr->apply_boundary_filter_on_substep() or
     815              :             filter_ptr->apply_boundary_filter_on_this_step(step_number);
     816              :       }
     817              :     }
     818              : 
     819              :     const bool need_face_jacobians = [&]() {
     820              :       if constexpr (DenseOutput) {
     821              :         return false;
     822              :       } else {
     823              :         return boundary_filter_active and filter_ptr != nullptr and
     824              :                filter_ptr->need_jacobians();
     825              :       }
     826              :     }();
     827              : 
     828              :     size_t max_face_grid_points = 0;
     829              :     if (need_face_jacobians) {
     830              :       for (const auto& [mortar_id, _info] : mortar_infos) {
     831              :         if (not mortars_to_act_on.contains(mortar_id) or
     832              :             mortar_id.id() == ElementId<volume_dim>::external_boundary_id()) {
     833              :           continue;
     834              :         }
     835              :         max_face_grid_points =
     836              :             std::max(max_face_grid_points,
     837              :                      volume_mesh.slice_away(mortar_id.direction().dimension())
     838              :                          .number_of_grid_points());
     839              :       }
     840              :     }
     841              : 
     842              :     // NOLINTNEXTLINE(modernize-avoid-c-arrays)
     843              :     std::unique_ptr<double[]> face_jac_buffer{nullptr};
     844              :     if (max_face_grid_points > 0) {
     845              :       constexpr size_t jac_components =
     846              :           Jacobian<DataVector, volume_dim, Frame::Grid,
     847              :                    Frame::Inertial>::size();
     848              :       // NOLINTNEXTLINE(modernize-avoid-c-arrays)
     849              :       face_jac_buffer = cpp20::make_unique_for_overwrite<double[]>(
     850              :           2 * jac_components * max_face_grid_points);
     851              :     }
     852              : 
     853              :     std::optional<
     854              :         Jacobian<DataVector, volume_dim, Frame::Grid, Frame::Inertial>>
     855              :         face_jac_grid_to_inertial{};
     856              :     std::optional<
     857              :         InverseJacobian<DataVector, volume_dim, Frame::Grid, Frame::Inertial>>
     858              :         face_inv_jac_grid_to_inertial{};
     859              :     if (face_jac_buffer != nullptr) {
     860              :       face_jac_grid_to_inertial.emplace();
     861              :       face_inv_jac_grid_to_inertial.emplace();
     862              :     }
     863              :     std::optional<Direction<volume_dim>> cached_face_jac_direction{};
     864              : 
     865              :     tuples::tagged_tuple_from_typelist<db::wrap_tags_in<
     866              :         detail::TemporaryReference, volume_tags_for_dg_boundary_terms>>
     867              :         volume_args_tuple{volume_args...};
     868              : 
     869              :     // Set up helper lambda that will compute and lift the boundary corrections
     870              :     ASSERT(
     871              :         volume_mesh.quadrature() ==
     872              :                 make_array<volume_dim>(volume_mesh.quadrature(0)) or
     873              :             element.topologies() != domain::topologies::hypercube<volume_dim>,
     874              :         "Must have isotropic quadrature, but got volume mesh: " << volume_mesh);
     875              :     Scalar<DataVector> volume_det_inv_jacobian{};
     876              :     Scalar<DataVector> volume_det_jacobian{};
     877              :     if constexpr (not local_time_stepping) {
     878              :       // Need volume Jacobian for any face whose normal direction uses Gauss
     879              :       // points (i.e. not GaussLobatto or GaussRadauUpper). This means
     880              :       // mixed-quadrature non-hypercube elements (e.g. full_cylinder) where
     881              :       // some directions have collocated face points and others do not.
     882              :       const bool any_direction_uses_gauss = alg::any_of(
     883              :           volume_mesh.quadrature(), [](const Spectral::Quadrature q) {
     884              :             return q == Spectral::Quadrature::Gauss;
     885              :           });
     886              :       if (any_direction_uses_gauss) {
     887              :         get(volume_det_inv_jacobian)
     888              :             .set_data_ref(make_not_null(
     889              :                 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
     890              :                 &const_cast<DataVector&>(get(gts_det_inv_jacobian))));
     891              :         get(volume_det_jacobian) = 1.0 / get(volume_det_inv_jacobian);
     892              :       }
     893              :     }
     894              : 
     895              :     static_assert(
     896              :         tmpl::all<derived_boundary_corrections, std::is_final<tmpl::_1>>::value,
     897              :         "All createable classes for boundary corrections must be marked "
     898              :         "final.");
     899              :     call_with_dynamic_type<void, derived_boundary_corrections>(
     900              :         &boundary_correction,
     901              :         [&cached_face_jac_direction, &dense_output_time, &dg_formulation,
     902              :          &element, &face_inv_jac_grid_to_inertial,
     903              :          &face_jac_buffer,  // NOLINT(modernize-avoid-c-arrays)
     904              :          &face_jac_grid_to_inertial, &face_normal_covector_and_magnitude,
     905              :          boundary_filter_active, filter_ptr, max_face_grid_points,
     906              :          need_face_jacobians, &mortar_data, &mortar_meshes, &mortar_infos,
     907              :          &mortars_to_act_on, &time_step, &time_stepper, &vars_to_update,
     908              :          &volume_args_tuple, &volume_det_jacobian, &volume_det_inv_jacobian,
     909              :          &volume_inv_jac_grid_to_inertial, &volume_jac_grid_to_inertial,
     910              :          &volume_mesh](auto* typed_boundary_correction) {
     911              :           (void)need_face_jacobians;
     912              :           using BcType = std::decay_t<decltype(*typed_boundary_correction)>;
     913              :           // Compute internal boundary quantities on the mortar for sides of
     914              :           // the element that have neighbors, i.e. they are not an external
     915              :           // side.  The auxiliary arm uses the detect-or-default helper so it
     916              :           // remains well-formed for any correction, even those lacking the
     917              :           // alias; this keeps the change additive.
     918              :           using mortar_tags_list = tmpl::conditional_t<
     919              :               ComputeAuxiliary,
     920              :               evolution::dg::Actions::detail::
     921              :                   get_dg_auxiliary_package_field_tags_or_default_t<
     922              :                       BcType, tmpl::list<>>,
     923              :               typename BcType::dg_package_field_tags>;
     924              : 
     925              :           // Variables for reusing allocations.  The actual values are
     926              :           // not reused.
     927              :           DtVariables dt_boundary_correction_on_mortar{};
     928              :           DtVariables volume_dt_correction{};
     929              :           // These variables may change size for each mortar and require
     930              :           // a new memory allocation, but they may also happen to need
     931              :           // to be the same size twice in a row, in which case holding
     932              :           // on to the allocation is a win.
     933              :           Scalar<DataVector> face_det_jacobian{};
     934              :           Variables<mortar_tags_list> local_data_on_mortar{};
     935              :           Variables<mortar_tags_list> neighbor_data_on_mortar{};
     936              : 
     937              :           for (const auto& mortar_id_and_data : mortar_data) {
     938              :             const auto& mortar_id = mortar_id_and_data.first;
     939              :             if (not mortars_to_act_on.contains(mortar_id)) {
     940              :               continue;
     941              :             }
     942              :             const auto& direction = mortar_id.direction();
     943              :             if (UNLIKELY(mortar_id.id() ==
     944              :                          ElementId<volume_dim>::external_boundary_id())) {
     945              :               ERROR(
     946              :                   "Cannot impose boundary conditions on external boundary in "
     947              :                   "direction "
     948              :                   << direction
     949              :                   << " in the ApplyBoundaryCorrections action. Boundary "
     950              :                      "conditions are applied in the ComputeTimeDerivative "
     951              :                      "action "
     952              :                      "instead. You may have unintentionally added external "
     953              :                      "mortars in one of the initialization actions.");
     954              :             }
     955              :             if (UNLIKELY((volume_mesh.basis(direction.dimension()) ==
     956              :                               Spectral::Basis::ZernikeB2 or
     957              :                           volume_mesh.basis(direction.dimension()) ==
     958              :                               Spectral::Basis::ZernikeB3) and
     959              :                          volume_mesh.quadrature(direction.dimension()) ==
     960              :                              Spectral::Quadrature::GaussRadauUpper and
     961              :                          direction.side() != Side::Upper)) {
     962              :               ERROR(
     963              :                   "Trying to use ZernikeB2 or ZernikeB3 basis with "
     964              :                   "GaussRadauUpper quadrature on the lower side: there is not "
     965              :                   "a boundary here. volume mesh: "
     966              :                   << volume_mesh << ", element ID " << element.id());
     967              :             }
     968              : 
     969              :             const Mesh<volume_dim - 1> face_mesh =
     970              :                 volume_mesh.on_interface(direction.dimension());
     971              : 
     972              :             // Whether the mesh has a collocation point on this face. True for
     973              :             // GaussLobatto (points on both faces) and GaussRadauUpper (point
     974              :             // on the upper face only). When true, lifting is done via
     975              :             // lift_flux on the slice; otherwise the full Gauss-point lifting
     976              :             // path is used.
     977              :             const bool using_points_on_face =
     978              :                 volume_mesh.quadrature(direction.dimension()) ==
     979              :                     Spectral::Quadrature::GaussLobatto or
     980              :                 volume_mesh.quadrature(direction.dimension()) ==
     981              :                     Spectral::Quadrature::GaussRadauUpper;
     982              : 
     983              :             if (need_face_jacobians and
     984              :                 (not cached_face_jac_direction.has_value() or
     985              :                  *cached_face_jac_direction != direction)) {
     986              :               constexpr size_t jac_components =
     987              :                   Jacobian<DataVector, volume_dim, Frame::Grid,
     988              :                            Frame::Inertial>::size();
     989              :               const size_t current_face_size =
     990              :                   face_mesh.number_of_grid_points();
     991              :               for (size_t i = 0; i < jac_components; ++i) {
     992              :                 (*face_jac_grid_to_inertial)[i].set_data_ref(
     993              :                     &face_jac_buffer[i * max_face_grid_points],
     994              :                     current_face_size);
     995              :                 (*face_inv_jac_grid_to_inertial)[i].set_data_ref(
     996              :                     &face_jac_buffer[(jac_components + i) *
     997              :                                      max_face_grid_points],
     998              :                     current_face_size);
     999              :               }
    1000              :               ::dg::project_tensor_to_boundary(
    1001              :                   make_not_null(&*face_jac_grid_to_inertial),
    1002              :                   volume_jac_grid_to_inertial, volume_mesh, direction);
    1003              :               ::dg::project_tensor_to_boundary(
    1004              :                   make_not_null(&*face_inv_jac_grid_to_inertial),
    1005              :                   volume_inv_jac_grid_to_inertial, volume_mesh, direction);
    1006              :               cached_face_jac_direction = direction;
    1007              :             }
    1008              : 
    1009              :             const auto compute_correction_coupling =
    1010              :                 [&typed_boundary_correction, boundary_filter_active, &direction,
    1011              :                  dg_formulation, &dt_boundary_correction_on_mortar,
    1012              :                  &face_det_jacobian, &face_inv_jac_grid_to_inertial,
    1013              :                  &face_jac_grid_to_inertial, &face_mesh,
    1014              :                  &face_normal_covector_and_magnitude, filter_ptr,
    1015              :                  &local_data_on_mortar, &mortar_id, &mortar_meshes,
    1016              :                  &mortar_infos, &neighbor_data_on_mortar, using_points_on_face,
    1017              :                  &volume_args_tuple, &volume_det_jacobian,
    1018              :                  &volume_det_inv_jacobian, &volume_dt_correction, &volume_mesh,
    1019              :                  &element](const MortarData<volume_dim>& local_mortar_data,
    1020              :                            const MortarData<volume_dim>& neighbor_mortar_data)
    1021              :                 -> DtVariables {
    1022              :               if (local_time_stepping and not using_points_on_face) {
    1023              :                 // This needs to be updated every call because the Jacobian
    1024              :                 // may be time-dependent. In the case of time-independent maps
    1025              :                 // and local time stepping we could first perform the integral
    1026              :                 // on the boundaries, and then lift to the volume. This is
    1027              :                 // left as a future optimization.
    1028              :                 volume_det_inv_jacobian =
    1029              :                     local_mortar_data.volume_det_inv_jacobian.value();
    1030              :                 get(volume_det_jacobian) = 1.0 / get(volume_det_inv_jacobian);
    1031              :               }
    1032              :               const auto& mortar_mesh = mortar_meshes.at(mortar_id);
    1033              : 
    1034              :               // Extract local and neighbor data, copy into Variables because
    1035              :               // we store them in a std::vector for type erasure.
    1036              :               ASSERT(*local_mortar_data.mortar_mesh ==
    1037              :                              *neighbor_mortar_data.mortar_mesh and
    1038              :                          *local_mortar_data.mortar_mesh == mortar_mesh,
    1039              :                      "local mortar mesh: " << *local_mortar_data.mortar_mesh
    1040              :                                            << "\nneighbor mortar mesh: "
    1041              :                                            << *neighbor_mortar_data.mortar_mesh
    1042              :                                            << "\nmortar mesh: " << mortar_mesh
    1043              :                                            << "\n");
    1044              :               const DataVector& local_data = *local_mortar_data.mortar_data;
    1045              :               const DataVector& neighbor_data =
    1046              :                   *neighbor_mortar_data.mortar_data;
    1047              :               ASSERT(local_data.size() == neighbor_data.size(),
    1048              :                      "local data size: "
    1049              :                          << local_data.size()
    1050              :                          << "\nneighbor_data: " << neighbor_data.size()
    1051              :                          << "\n mortar_mesh: " << mortar_mesh << "\n");
    1052              :               ASSERT(local_data_on_mortar.number_of_grid_points() ==
    1053              :                          neighbor_data_on_mortar.number_of_grid_points(),
    1054              :                      "Local data size = "
    1055              :                          << local_data_on_mortar.number_of_grid_points()
    1056              :                          << ", but neighbor size = "
    1057              :                          << neighbor_data_on_mortar.number_of_grid_points());
    1058              :               local_data_on_mortar.set_data_ref(
    1059              :                   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
    1060              :                   const_cast<double*>(local_data.data()), local_data.size());
    1061              :               neighbor_data_on_mortar.set_data_ref(
    1062              :                   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
    1063              :                   const_cast<double*>(neighbor_data.data()),
    1064              :                   neighbor_data.size());
    1065              : 
    1066              :               // The boundary computations and lifting can be further
    1067              :               // optimized by in the h-refinement case having only one
    1068              :               // allocation for the face and having the projection from the
    1069              :               // mortar to the face be done in place. E.g.
    1070              :               // local_data_on_mortar and neighbor_data_on_mortar could be
    1071              :               // allocated fewer times, as well as `needs_projection` section
    1072              :               // below could do an in-place projection.
    1073              :               dt_boundary_correction_on_mortar.initialize(
    1074              :                   mortar_mesh.number_of_grid_points());
    1075              : 
    1076              :               if constexpr (ComputeAuxiliary) {
    1077              :                 // `dt_boundary_correction_on_mortar` is used purely as a
    1078              :                 // scratch container for the packaged auxiliary correction on
    1079              :                 // the mortar.  The correction is ultimately applied to the
    1080              :                 // auxiliary-variable storage, not to a time derivative:
    1081              :                 // `tag_to_update` is `auxiliary_variables_tag` for the
    1082              :                 // auxiliary pass, and this `dt`-prefixed buffer reconciles with
    1083              :                 // it via the prefix-agnostic `Variables::operator+=` /
    1084              :                 // `add_slice_to_data` used when the lifted result is added to
    1085              :                 // `vars_to_update`.
    1086              :                 using aux_volume_tags = evolution::dg::Actions::detail::
    1087              :                     get_dg_auxiliary_boundary_terms_volume_tags_or_default_t<
    1088              :                         BcType, tmpl::list<>>;
    1089              :                 call_auxiliary_boundary_correction(
    1090              :                     make_not_null(&dt_boundary_correction_on_mortar),
    1091              :                     local_data_on_mortar, neighbor_data_on_mortar,
    1092              :                     *typed_boundary_correction, dg_formulation,
    1093              :                     volume_args_tuple, aux_volume_tags{});
    1094              :               } else {
    1095              :                 call_boundary_correction(
    1096              :                     make_not_null(&dt_boundary_correction_on_mortar),
    1097              :                     local_data_on_mortar, neighbor_data_on_mortar,
    1098              :                     *typed_boundary_correction, dg_formulation,
    1099              :                     volume_args_tuple,
    1100              :                     typename BcType::dg_boundary_terms_volume_tags{});
    1101              :               }
    1102              : 
    1103              :               const std::array<Spectral::SegmentSize, volume_dim - 1>&
    1104              :                   mortar_size = mortar_infos.at(mortar_id).mortar_size();
    1105              : 
    1106              :               // This cannot reuse an allocation because it is initialized
    1107              :               // via move-assignment.  (If it is used at all.)
    1108              :               DtVariables dt_boundary_correction_projected_onto_face{};
    1109              :               auto& dt_boundary_correction =
    1110              :                   [&dt_boundary_correction_on_mortar,
    1111              :                    &dt_boundary_correction_projected_onto_face, &face_mesh,
    1112              :                    &mortar_mesh, &mortar_size, &element,
    1113              :                    &direction]() -> DtVariables& {
    1114              :                 if (element.neighbors().at(direction).are_conforming() and
    1115              :                     Spectral::needs_projection(face_mesh, mortar_mesh,
    1116              :                                                mortar_size)) {
    1117              :                   dt_boundary_correction_projected_onto_face =
    1118              :                       ::dg::project_from_mortar(
    1119              :                           dt_boundary_correction_on_mortar, face_mesh,
    1120              :                           mortar_mesh, mortar_size);
    1121              :                   return dt_boundary_correction_projected_onto_face;
    1122              :                 }
    1123              :                 return dt_boundary_correction_on_mortar;
    1124              :               }();
    1125              :               // The auxiliary boundary correction is not filtered currently
    1126              :               if constexpr (not DenseOutput and not ComputeAuxiliary) {
    1127              :                 // Filter the boundary correction on the mortar before it is
    1128              :                 // lifted into the volume.
    1129              :                 if (boundary_filter_active) {
    1130              :                   using BoundaryFilterVars = Variables<FilterTagList>;
    1131              :                   auto boundary_filter_view =
    1132              :                       dt_boundary_correction
    1133              :                           .template reference_with_different_prefixes<
    1134              :                               BoundaryFilterVars>();
    1135              :                   filter_ptr->apply_on_boundary(
    1136              :                       make_not_null(&boundary_filter_view), face_mesh,
    1137              :                       face_inv_jac_grid_to_inertial, face_jac_grid_to_inertial);
    1138              :                 }
    1139              :               } else {
    1140              :                 (void)boundary_filter_active;
    1141              :                 (void)face_inv_jac_grid_to_inertial;
    1142              :                 (void)face_jac_grid_to_inertial;
    1143              :                 (void)filter_ptr;
    1144              :               }
    1145              : 
    1146              :               // Both paths initialize this to be non-owning.
    1147              :               Scalar<DataVector> magnitude_of_face_normal{};
    1148              :               if constexpr (local_time_stepping) {
    1149              :                 (void)face_normal_covector_and_magnitude;
    1150              :                 get(magnitude_of_face_normal)
    1151              :                     .set_data_ref(make_not_null(&const_cast<DataVector&>(
    1152              :                         get(local_mortar_data.face_normal_magnitude.value()))));
    1153              :               } else {
    1154              :                 ASSERT(
    1155              :                     face_normal_covector_and_magnitude.count(direction) == 1 and
    1156              :                         face_normal_covector_and_magnitude.at(direction)
    1157              :                             .has_value(),
    1158              :                     "Face normal covector and magnitude not set in "
    1159              :                     "direction: "
    1160              :                         << direction);
    1161              :                 get(magnitude_of_face_normal)
    1162              :                     .set_data_ref(make_not_null(&const_cast<DataVector&>(
    1163              :                         get(get<evolution::dg::Tags::MagnitudeOfNormal>(
    1164              :                             *face_normal_covector_and_magnitude.at(
    1165              :                                 direction))))));
    1166              :               }
    1167              : 
    1168              :               if (using_points_on_face) {
    1169              :                 // The lift_flux function lifts only on the slice, it does not
    1170              :                 // add the contribution to the volume.
    1171              :                 ::dg::lift_flux(make_not_null(&dt_boundary_correction),
    1172              :                                 volume_mesh.extents(direction.dimension()),
    1173              :                                 magnitude_of_face_normal,
    1174              :                                 volume_mesh.basis(direction.dimension()));
    1175              :                 return std::move(dt_boundary_correction);
    1176              :               } else {
    1177              :                 // We are using Gauss points.
    1178              :                 //
    1179              :                 // Notes:
    1180              :                 // - We should really lift both sides simultaneously since this
    1181              :                 //   reduces memory accesses. Lifting all sides at the same
    1182              :                 //   time is unlikely to improve performance since we lift by
    1183              :                 //   jumping through slices. There may also be compatibility
    1184              :                 //   issues with local time stepping.
    1185              :                 // - If we lift both sides at the same time we first need to
    1186              :                 //   deal with projecting from mortars to the face, then lift
    1187              :                 //   off the faces. With non-owning Variables memory
    1188              :                 //   allocations could be significantly reduced in this code.
    1189              :                 if constexpr (local_time_stepping) {
    1190              :                   ASSERT(get(volume_det_inv_jacobian).size() > 0,
    1191              :                          "For local time stepping the volume determinant of "
    1192              :                          "the inverse Jacobian has not been set.");
    1193              : 
    1194              :                   get(face_det_jacobian)
    1195              :                       .set_data_ref(make_not_null(&const_cast<DataVector&>(
    1196              :                           get(local_mortar_data.face_det_jacobian.value()))));
    1197              :                 } else {
    1198              :                   // Project the determinant of the Jacobian to the face. This
    1199              :                   // could be optimized by caching in the time-independent case.
    1200              :                   get(face_det_jacobian)
    1201              :                       .destructive_resize(face_mesh.number_of_grid_points());
    1202              :                   const Matrix identity{};
    1203              :                   auto interpolation_matrices =
    1204              :                       make_array<volume_dim>(std::cref(identity));
    1205              :                   const std::pair<Matrix, Matrix>& matrices =
    1206              :                       Spectral::boundary_interpolation_matrices(
    1207              :                           volume_mesh.slice_through(direction.dimension()));
    1208              :                   gsl::at(interpolation_matrices, direction.dimension()) =
    1209              :                       direction.side() == Side::Upper ? matrices.second
    1210              :                                                       : matrices.first;
    1211              :                   apply_matrices(make_not_null(&get(face_det_jacobian)),
    1212              :                                  interpolation_matrices,
    1213              :                                  get(volume_det_jacobian),
    1214              :                                  volume_mesh.extents());
    1215              :                 }
    1216              : 
    1217              :                 volume_dt_correction.initialize(
    1218              :                     volume_mesh.number_of_grid_points(), 0.0);
    1219              :                 ::dg::lift_boundary_terms_gauss_points(
    1220              :                     make_not_null(&volume_dt_correction),
    1221              :                     volume_det_inv_jacobian, volume_mesh, direction,
    1222              :                     dt_boundary_correction, magnitude_of_face_normal,
    1223              :                     face_det_jacobian);
    1224              :                 return std::move(volume_dt_correction);
    1225              :               }
    1226              :             };
    1227              : 
    1228              :             if constexpr (local_time_stepping) {
    1229              :               typename variables_tag::type boundary_lifted_data{};
    1230              :               auto& lifted_data =
    1231              :                   using_points_on_face ? boundary_lifted_data : *vars_to_update;
    1232              :               if (using_points_on_face) {
    1233              :                 lifted_data.initialize(face_mesh.number_of_grid_points(), 0.0);
    1234              :               }
    1235              : 
    1236              :               const auto& mortar_data_history = mortar_id_and_data.second;
    1237              :               if constexpr (DenseOutput) {
    1238              :                 (void)time_step;
    1239              :                 time_stepper.boundary_dense_output(
    1240              :                     &lifted_data, mortar_data_history, dense_output_time,
    1241              :                     compute_correction_coupling);
    1242              :               } else {
    1243              :                 (void)dense_output_time;
    1244              :                 time_stepper.add_boundary_delta(&lifted_data,
    1245              :                                                 mortar_data_history, time_step,
    1246              :                                                 compute_correction_coupling);
    1247              :               }
    1248              : 
    1249              :               if (using_points_on_face) {
    1250              :                 // Add the flux contribution to the volume data
    1251              :                 add_slice_to_data(
    1252              :                     vars_to_update, lifted_data, volume_mesh.extents(),
    1253              :                     direction.dimension(),
    1254              :                     index_to_slice_at(volume_mesh.extents(), direction));
    1255              :               }
    1256              :             } else {
    1257              :               (void)time_step;
    1258              :               (void)time_stepper;
    1259              :               (void)dense_output_time;
    1260              : 
    1261              :               // Choose an allocation cache that may be empty, so we
    1262              :               // might be able to reuse the allocation obtained for the
    1263              :               // lifted data.  This may result in a self assignment,
    1264              :               // depending on the code paths taken, but handling the
    1265              :               // results this way makes the GTS and LTS paths more
    1266              :               // similar because the LTS code always stores the result
    1267              :               // in the history and so sometimes benefits from moving
    1268              :               // into the return value of compute_correction_coupling.
    1269              :               auto& lifted_data = using_points_on_face
    1270              :                                       ? dt_boundary_correction_on_mortar
    1271              :                                       : volume_dt_correction;
    1272              :               lifted_data = compute_correction_coupling(
    1273              :                   mortar_id_and_data.second.local(),
    1274              :                   mortar_id_and_data.second.neighbor());
    1275              : 
    1276              :               if (using_points_on_face) {
    1277              :                 // Add the flux contribution to the volume data
    1278              :                 add_slice_to_data(
    1279              :                     vars_to_update, lifted_data, volume_mesh.extents(),
    1280              :                     direction.dimension(),
    1281              :                     index_to_slice_at(volume_mesh.extents(), direction));
    1282              :               } else {
    1283              :                 *vars_to_update += lifted_data;
    1284              :               }
    1285              :             }
    1286              :           }
    1287              :         });
    1288              :   }
    1289              : 
    1290              :   template <typename... BoundaryCorrectionTags, typename... Tags,
    1291              :             typename BoundaryCorrection, typename... AllVolumeArgs,
    1292              :             typename... VolumeTagsForCorrection>
    1293            0 :   static void call_boundary_correction(
    1294              :       const gsl::not_null<Variables<tmpl::list<BoundaryCorrectionTags...>>*>
    1295              :           boundary_corrections_on_mortar,
    1296              :       const Variables<tmpl::list<Tags...>>& local_boundary_data,
    1297              :       const Variables<tmpl::list<Tags...>>& neighbor_boundary_data,
    1298              :       const BoundaryCorrection& boundary_correction,
    1299              :       const ::dg::Formulation dg_formulation,
    1300              :       const tuples::TaggedTuple<detail::TemporaryReference<AllVolumeArgs>...>&
    1301              :           volume_args_tuple,
    1302              :       tmpl::list<VolumeTagsForCorrection...> /*meta*/) {
    1303              :     boundary_correction.dg_boundary_terms(
    1304              :         make_not_null(
    1305              :             &get<BoundaryCorrectionTags>(*boundary_corrections_on_mortar))...,
    1306              :         get<Tags>(local_boundary_data)..., get<Tags>(neighbor_boundary_data)...,
    1307              :         dg_formulation,
    1308              :         tuples::get<detail::TemporaryReference<VolumeTagsForCorrection>>(
    1309              :             volume_args_tuple)...);
    1310              :   }
    1311              : 
    1312              :   template <typename... BoundaryCorrectionTags, typename... Tags,
    1313              :             typename BoundaryCorrection, typename... AllVolumeArgs,
    1314              :             typename... VolumeTagsForCorrection>
    1315            0 :   static void call_auxiliary_boundary_correction(
    1316              :       const gsl::not_null<Variables<tmpl::list<BoundaryCorrectionTags...>>*>
    1317              :           boundary_corrections_on_mortar,
    1318              :       const Variables<tmpl::list<Tags...>>& local_boundary_data,
    1319              :       const Variables<tmpl::list<Tags...>>& neighbor_boundary_data,
    1320              :       const BoundaryCorrection& boundary_correction,
    1321              :       const ::dg::Formulation dg_formulation,
    1322              :       const tuples::TaggedTuple<detail::TemporaryReference<AllVolumeArgs>...>&
    1323              :           volume_args_tuple,
    1324              :       tmpl::list<VolumeTagsForCorrection...> /*meta*/) {
    1325              :     boundary_correction.dg_auxiliary_boundary_terms(
    1326              :         make_not_null(
    1327              :             &get<BoundaryCorrectionTags>(*boundary_corrections_on_mortar))...,
    1328              :         get<Tags>(local_boundary_data)..., get<Tags>(neighbor_boundary_data)...,
    1329              :         dg_formulation,
    1330              :         tuples::get<detail::TemporaryReference<VolumeTagsForCorrection>>(
    1331              :             volume_args_tuple)...);
    1332              :   }
    1333              : };
    1334              : 
    1335              : /// Apply corrections from boundary communication for LTS dense output.
    1336              : template <typename Metavariables,
    1337              :           typename VariablesTag = FetchFromMetavariables>
    1338            1 : struct ApplyLtsDenseBoundaryCorrections
    1339              :     : ApplyBoundaryCorrections<true, Metavariables, true, false, VariablesTag> {
    1340              : };
    1341              : 
    1342            1 : namespace Actions {
    1343              : namespace ApplyBoundaryCorrections_detail {
    1344              : template <bool LocalTimeStepping, size_t VolumeDim, bool DenseOutput,
    1345              :           bool UseNodegroupDgElements, bool ComputeAuxiliary,
    1346              :           typename VariablesTag>
    1347              : struct ActionImpl {
    1348              :   using inbox_tags =
    1349              :       tmpl::list<evolution::dg::Tags::BoundaryCorrectionAndGhostCellsInbox<
    1350              :           VolumeDim, UseNodegroupDgElements, ComputeAuxiliary>>;
    1351              :   using const_global_cache_tags =
    1352              :       tmpl::list<evolution::Tags::BoundaryCorrection, ::dg::Tags::Formulation>;
    1353              : 
    1354              :   template <typename DbTagsList, typename... InboxTags, typename Metavariables,
    1355              :             typename ArrayIndex, typename ActionList,
    1356              :             typename ParallelComponent>
    1357              :   static Parallel::iterable_action_return_t apply(
    1358              :       db::DataBox<DbTagsList>& box, tuples::TaggedTuple<InboxTags...>& inboxes,
    1359              :       const Parallel::GlobalCache<Metavariables>& /*cache*/,
    1360              :       const ArrayIndex& /*array_index*/, ActionList /*meta*/,
    1361              :       const ParallelComponent* const /*meta*/) {
    1362              :     static_assert(Metavariables::system::volume_dim == VolumeDim);
    1363              :     // The LDG auxiliary pass is implemented for DG elements only for now;
    1364              :     // DG-subcell support is deferred.
    1365              :     static_assert(
    1366              :         not(ComputeAuxiliary and evolution::dg::using_subcell_v<Metavariables>),
    1367              :         "LDG auxiliary boundary corrections do not support DG-subcell yet.");
    1368              :     static_assert(
    1369              :         UseNodegroupDgElements ==
    1370              :             Parallel::is_dg_element_collection_v<ParallelComponent>,
    1371              :         "The action is told by the template parameter UseNodegroupDgElements "
    1372              :         "that it is being used with a DgElementCollection, but the "
    1373              :         "ParallelComponent is not a DgElementCollection. You need to change "
    1374              :         "the template parameter on the action in your action list.");
    1375              :     constexpr size_t volume_dim = Metavariables::system::volume_dim;
    1376              :     const Element<volume_dim>& element =
    1377              :         db::get<domain::Tags::Element<volume_dim>>(box);
    1378              : 
    1379              :     if (UNLIKELY(element.number_of_neighbors() == 0)) {
    1380              :       // We have no neighbors, yay!
    1381              :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
    1382              :     }
    1383              : 
    1384              :     if (not receive_boundary_data<
    1385              :             Parallel::is_dg_element_collection_v<ParallelComponent>,
    1386              :             Metavariables, LocalTimeStepping, false, ComputeAuxiliary>(
    1387              :             make_not_null(&box), make_not_null(&inboxes))) {
    1388              :       return {Parallel::AlgorithmExecution::Retry, std::nullopt};
    1389              :     }
    1390              : 
    1391              :     // LTS updates the evolved variables, so we can skip that if they
    1392              :     // are unused.  GTS updates the derivatives, which are always
    1393              :     // needed to update the history.
    1394              :     if (LocalTimeStepping and
    1395              :         ::SelfStart::step_unused(
    1396              :             db::get<::Tags::TimeStepId>(box),
    1397              :             db::get<::Tags::Next<::Tags::TimeStepId>>(box))) {
    1398              :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
    1399              :     }
    1400              : 
    1401              :     db::mutate_apply<
    1402              :         ApplyBoundaryCorrections<LocalTimeStepping, Metavariables, DenseOutput,
    1403              :                                  ComputeAuxiliary, VariablesTag>>(
    1404              :         make_not_null(&box));
    1405              :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
    1406              :   }
    1407              : };
    1408              : }  // namespace ApplyBoundaryCorrections_detail
    1409              : 
    1410              : /*!
    1411              :  * \brief Computes the boundary corrections for global time-stepping
    1412              :  * and adds them to the time derivative.
    1413              :  */
    1414              : template <size_t VolumeDim, bool UseNodegroupDgElements,
    1415              :           typename VariablesTag = FetchFromMetavariables>
    1416            1 : struct ApplyBoundaryCorrectionsToTimeDerivative
    1417              :     : ApplyBoundaryCorrections_detail::ActionImpl<false, VolumeDim, false,
    1418              :                                                   UseNodegroupDgElements, false,
    1419              :                                                   VariablesTag> {};
    1420              : 
    1421              : /*!
    1422              :  * \brief Receives and lifts the LDG auxiliary boundary corrections into the
    1423              :  * auxiliary variables.
    1424              :  *
    1425              :  * This is the "receive" counterpart of the LDG auxiliary send action. It is the
    1426              :  * first communication step of the LDG two-communication scheme: after this
    1427              :  * action runs, the auxiliary variables have been corrected with the numerical
    1428              :  * flux. The second step (the physical boundary correction) is done by
    1429              :  * `ApplyBoundaryCorrectionsToTimeDerivative`.
    1430              :  */
    1431              : template <size_t VolumeDim, bool UseNodegroupDgElements,
    1432              :           typename VariablesTag = FetchFromMetavariables>
    1433            1 : struct ApplyAuxiliaryBoundaryCorrectionsToVariables
    1434              :     : ApplyBoundaryCorrections_detail::ActionImpl<
    1435              :           false, VolumeDim, false, UseNodegroupDgElements,
    1436              :           /*ComputeAuxiliary=*/true, VariablesTag> {};
    1437              : 
    1438              : /*!
    1439              :  * \brief Computes the boundary corrections for local time-stepping
    1440              :  * and adds them to the variables.
    1441              :  *
    1442              :  * When using local time stepping the neighbor sends data at the neighbor's
    1443              :  * current temporal id. Along with the boundary data, the next temporal id at
    1444              :  * which the neighbor will send data is also sent. This is equal to the
    1445              :  * neighbor's `::Tags::Next<::Tags::TimeStepId>`. When inserting into the mortar
    1446              :  * data history, we insert the received temporal id, that is, the current time
    1447              :  * of the neighbor, along with the boundary correction data.
    1448              :  */
    1449              : template <size_t VolumeDim, bool UseNodegroupDgElements,
    1450              :           typename VariablesTag = FetchFromMetavariables>
    1451            1 : struct ApplyLtsBoundaryCorrections
    1452              :     : ApplyBoundaryCorrections_detail::ActionImpl<
    1453              :           true, VolumeDim, false, UseNodegroupDgElements, false, VariablesTag> {
    1454              : };
    1455              : }  // namespace Actions
    1456              : }  // namespace evolution::dg
        

Generated by: LCOV version 2.0-1