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

Generated by: LCOV version 1.14