SpECTRE Documentation Coverage Report
Current view: top level - Evolution/DiscontinuousGalerkin/Actions - ApplyBoundaryCorrections.hpp Hit Total Coverage
Commit: c428a3e2e0ca78fe0364ec1b0e0493c627d428d4 Lines: 6 26 23.1 %
Date: 2026-04-26 20:20:36
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 "Domain/FaceNormal.hpp"
      24             : #include "Domain/Structure/DirectionalIdMap.hpp"
      25             : #include "Domain/Structure/Element.hpp"
      26             : #include "Domain/Structure/ElementId.hpp"
      27             : #include "Domain/Structure/Topology.hpp"
      28             : #include "Domain/Tags.hpp"
      29             : #include "Domain/Tags/NeighborMesh.hpp"
      30             : #include "Evolution/BoundaryCorrection.hpp"
      31             : #include "Evolution/BoundaryCorrectionTags.hpp"
      32             : #include "Evolution/DiscontinuousGalerkin/BoundaryData.hpp"
      33             : #include "Evolution/DiscontinuousGalerkin/InboxTags.hpp"
      34             : #include "Evolution/DiscontinuousGalerkin/MortarData.hpp"
      35             : #include "Evolution/DiscontinuousGalerkin/MortarDataHolder.hpp"
      36             : #include "Evolution/DiscontinuousGalerkin/MortarTags.hpp"
      37             : #include "Evolution/DiscontinuousGalerkin/NormalVectorTags.hpp"
      38             : #include "Evolution/DiscontinuousGalerkin/TimeSteppingPolicy.hpp"
      39             : #include "Evolution/DiscontinuousGalerkin/UsingSubcell.hpp"
      40             : #include "NumericalAlgorithms/DiscontinuousGalerkin/Formulation.hpp"
      41             : #include "NumericalAlgorithms/DiscontinuousGalerkin/LiftFlux.hpp"
      42             : #include "NumericalAlgorithms/DiscontinuousGalerkin/LiftFromBoundary.hpp"
      43             : #include "NumericalAlgorithms/DiscontinuousGalerkin/MortarHelpers.hpp"
      44             : #include "NumericalAlgorithms/DiscontinuousGalerkin/Tags/Formulation.hpp"
      45             : #include "NumericalAlgorithms/Spectral/BoundaryInterpolationMatrices.hpp"
      46             : #include "NumericalAlgorithms/Spectral/Mesh.hpp"
      47             : #include "NumericalAlgorithms/Spectral/Quadrature.hpp"
      48             : #include "NumericalAlgorithms/Spectral/SegmentSize.hpp"
      49             : #include "Parallel/AlgorithmExecution.hpp"
      50             : #include "Parallel/ArrayCollection/IsDgElementCollection.hpp"
      51             : #include "Parallel/GlobalCache.hpp"
      52             : #include "Time/BoundaryHistory.hpp"
      53             : #include "Time/EvolutionOrdering.hpp"
      54             : #include "Time/SelfStart.hpp"
      55             : #include "Time/Time.hpp"
      56             : #include "Time/TimeStepId.hpp"
      57             : #include "Time/TimeSteppers/LtsTimeStepper.hpp"
      58             : #include "Time/TimeSteppers/TimeStepper.hpp"
      59             : #include "Utilities/Algorithm.hpp"
      60             : #include "Utilities/CallWithDynamicType.hpp"
      61             : #include "Utilities/ErrorHandling/Assert.hpp"
      62             : #include "Utilities/ErrorHandling/Error.hpp"
      63             : #include "Utilities/Gsl.hpp"
      64             : #include "Utilities/MakeArray.hpp"
      65             : #include "Utilities/TMPL.hpp"
      66             : 
      67             : /// \cond
      68             : namespace Tags {
      69             : struct Time;
      70             : struct TimeStep;
      71             : struct TimeStepId;
      72             : template <typename StepperInterface>
      73             : struct TimeStepper;
      74             : }  // namespace Tags
      75             : 
      76             : namespace evolution::dg::subcell {
      77             : // We use a forward declaration instead of including a header file to avoid
      78             : // coupling to the DG-subcell libraries for executables that don't use subcell.
      79             : template <size_t VolumeDim, typename DgComputeSubcellNeighborPackagedData>
      80             : void neighbor_reconstructed_face_solution(gsl::not_null<db::Access*> box);
      81             : template <size_t Dim>
      82             : void neighbor_tci_decision(
      83             :     gsl::not_null<db::Access*> box,
      84             :     const DirectionalId<Dim>& directional_element_id,
      85             :     const evolution::dg::BoundaryData<Dim>& neighbor_data);
      86             : template <size_t VolumeDim>
      87             : void receive_subcell_data_for_dg(
      88             :     gsl::not_null<db::Access*> box, const DirectionalId<VolumeDim>& mortar_id,
      89             :     const evolution::dg::BoundaryData<VolumeDim>& received_mortar_data);
      90             : }  // namespace evolution::dg::subcell
      91             : /// \endcond
      92             : 
      93             : namespace evolution::dg {
      94             : namespace detail {
      95             : template <typename BoundaryCorrectionClass>
      96             : struct get_dg_boundary_terms {
      97             :   using type = typename BoundaryCorrectionClass::dg_boundary_terms_volume_tags;
      98             : };
      99             : 
     100             : template <typename Tag, typename Type = db::const_item_type<Tag, tmpl::list<>>>
     101             : struct TemporaryReference {
     102             :   using tag = Tag;
     103             :   using type = const Type&;
     104             : };
     105             : }  // namespace detail
     106             : 
     107             : /// Move boundary data from the inbox to the DataBox.  Returns true if
     108             : /// all necessary data has been received.
     109             : ///
     110             : /// Setting \p DenseOutput to true receives data required for output
     111             : /// at `::Tags::Time` instead of `::Tags::Next<::Tags::TimeStepId>`.
     112             : ///
     113             : /// If \p LocalTimeStepping is true, it will process all data
     114             : /// necessary for conservative LTS, otherwise it will process all data
     115             : /// necessary for GTS.  Some data for the other mode may also be
     116             : /// processed to simplify the message handling.
     117             : template <bool UseNodegroupDgElements, typename Metavariables,
     118             :           bool LocalTimeStepping, bool DenseOutput, typename DbTagsList,
     119             :           typename... InboxTags>
     120           1 : bool receive_boundary_data(
     121             :     const gsl::not_null<db::DataBox<DbTagsList>*> box,
     122             :     const gsl::not_null<tuples::TaggedTuple<InboxTags...>*> inboxes) {
     123             :   constexpr size_t volume_dim = Metavariables::system::volume_dim;
     124             :   constexpr size_t face_dim = volume_dim - 1;
     125             :   static_assert(LocalTimeStepping or not DenseOutput,
     126             :                 "Should not be receiving data for dense output with GTS.");
     127             : 
     128             :   auto& inbox =
     129             :       tuples::get<evolution::dg::Tags::BoundaryCorrectionAndGhostCellsInbox<
     130             :           volume_dim, UseNodegroupDgElements>>(*inboxes);
     131             : 
     132             :   const auto& volume_mesh = db::get<domain::Tags::Mesh<volume_dim>>(*box);
     133             :   const auto& mortar_infos = db::get<Tags::MortarInfo<volume_dim>>(*box);
     134             :   const auto& mortar_next_time_step_ids =
     135             :       db::get<evolution::dg::Tags::MortarNextTemporalId<volume_dim>>(*box);
     136             : 
     137             :   for (;;) {
     138             :     std::optional<TimeStepId> time_to_process{};
     139             :     for (const auto& [mortar_id, mortar_next_time_step_id] :
     140             :          mortar_next_time_step_ids) {
     141             :       if (time_to_process.has_value() and
     142             :           mortar_next_time_step_id > *time_to_process) {
     143             :         continue;
     144             :       }
     145             : 
     146             :       const auto& time_stepping_policy =
     147             :           mortar_infos.at(mortar_id).time_stepping_policy();
     148             :       switch (time_stepping_policy) {
     149             :         case TimeSteppingPolicy::EqualRate:
     150             :           if (LocalTimeStepping or
     151             :               mortar_next_time_step_id > db::get<::Tags::TimeStepId>(*box)) {
     152             :             continue;
     153             :           }
     154             :           break;
     155             :         case TimeSteppingPolicy::Conservative:
     156             :           if constexpr (not LocalTimeStepping) {
     157             :             continue;
     158             :           } else {
     159             :             const LtsTimeStepper& time_stepper =
     160             :                 db::get<::Tags::TimeStepper<LtsTimeStepper>>(*box);
     161             :             using goal_tag =
     162             :                 tmpl::conditional_t<DenseOutput, ::Tags::Time,
     163             :                                     ::Tags::Next<::Tags::TimeStepId>>;
     164             :             const auto& goal = db::get<goal_tag>(*box);
     165             :             if (not time_stepper.neighbor_data_required(
     166             :                     goal, mortar_next_time_step_id)) {
     167             :               continue;
     168             :             }
     169             :           }
     170             :           break;
     171             :         default:
     172             :           ERROR("Unhandled TimeSteppingPolicy: " << time_stepping_policy);
     173             :       }
     174             : 
     175             :       time_to_process.emplace(mortar_next_time_step_id);
     176             :     }
     177             : 
     178             :     if (not time_to_process.has_value()) {
     179             :       if constexpr (using_subcell_v<Metavariables> and not LocalTimeStepping) {
     180             :         evolution::dg::subcell::neighbor_reconstructed_face_solution<
     181             :             volume_dim, typename Metavariables::SubcellOptions::
     182             :                             DgComputeSubcellNeighborPackagedData>(
     183             :             &db::as_access(*box));
     184             :       }
     185             :       return true;
     186             :     }
     187             : 
     188             :     const auto expected_messages = static_cast<size_t>(alg::count_if(
     189             :         mortar_next_time_step_ids,
     190             :         [&](const auto& entry) { return entry.second == *time_to_process; }));
     191             : 
     192             :     // This is a
     193             :     //
     194             :     // std::map<TimeStepId,
     195             :     //          V<std::pair<DirectionalId<volume_dim>,
     196             :     //                      evolution::dg::BoundaryData<volume_dim>>>,
     197             :     //
     198             :     // where V<> is a vector-like type the details of which we don't
     199             :     // want to hardcode here.
     200             :     auto& inbox_data = inbox.messages;
     201             :     auto messages_to_process = inbox_data.end();
     202             : 
     203             :     {
     204             :       size_t missing_messages{};
     205             :       do {
     206             :         inbox.collect_messages();
     207             :         if (messages_to_process == inbox_data.end()) {
     208             :           messages_to_process = inbox_data.find(*time_to_process);
     209             :         }
     210             :         const size_t available_messages =
     211             :             messages_to_process == inbox_data.end()
     212             :                 ? 0
     213             :                 : messages_to_process->second.size();
     214             :         ASSERT(available_messages <= expected_messages,
     215             :                "Too many boundary messages at " << *time_to_process << ": "
     216             :                                                 << available_messages << "/"
     217             :                                                 << expected_messages);
     218             :         missing_messages = expected_messages - available_messages;
     219             :       } while (missing_messages != 0 and
     220             :                inbox.set_missing_messages(missing_messages));
     221             :       if (missing_messages != 0) {
     222             :         return false;
     223             :       }
     224             :     }
     225             : 
     226             :     // *time_to_process represents the same temporal event as this,
     227             :     // but may have an out-of-date slab size because the
     228             :     // MortarNextTemporalId data can be sent before the slab size is
     229             :     // chosen.  It is important that the corrected version be what is
     230             :     // inserted into the boundary history.
     231             :     const TimeStepId processing_time = messages_to_process->first;
     232             : 
     233             :     for (auto& mortar_id_and_data : messages_to_process->second) {
     234             :       const auto& mortar_id = mortar_id_and_data.first;
     235             :       auto& received_mortar_data = mortar_id_and_data.second;
     236             :       const size_t sliced_away_dim = mortar_id.direction().dimension();
     237             :       const Mesh<face_dim> face_mesh = volume_mesh.slice_away(sliced_away_dim);
     238             :       ASSERT(mortar_next_time_step_ids.at(mortar_id) == processing_time,
     239             :              "Processing wrong time for mortar "
     240             :                  << mortar_id << "\nExpected "
     241             :                  << mortar_next_time_step_ids.at(mortar_id)
     242             :                  << " but processing " << processing_time);
     243             : 
     244             :       const auto& time_stepping_policy =
     245             :           mortar_infos.at(mortar_id).time_stepping_policy();
     246             : 
     247             :       if constexpr (using_subcell_v<Metavariables>) {
     248             :         if (time_stepping_policy == TimeSteppingPolicy::EqualRate) {
     249             :           evolution::dg::subcell::receive_subcell_data_for_dg<volume_dim>(
     250             :               &db::as_access(*box), mortar_id, received_mortar_data);
     251             :           evolution::dg::subcell::neighbor_tci_decision<volume_dim>(
     252             :               make_not_null(&db::as_access(*box)), mortar_id,
     253             :               received_mortar_data);
     254             :         }
     255             :       }
     256             : 
     257             :       db::mutate<evolution::dg::Tags::MortarMesh<volume_dim>,
     258             :                  evolution::dg::Tags::MortarData<volume_dim>,
     259             :                  evolution::dg::Tags::MortarDataHistory<volume_dim>,
     260             :                  evolution::dg::Tags::MortarNextTemporalId<volume_dim>,
     261             :                  domain::Tags::NeighborMesh<volume_dim>>(
     262             :           [&](const gsl::not_null<DirectionalIdMap<volume_dim, Mesh<face_dim>>*>
     263             :                   mortar_meshes,
     264             :               const gsl::not_null<DirectionalIdMap<
     265             :                   volume_dim, evolution::dg::MortarDataHolder<volume_dim>>*>
     266             :                   gts_mortar_data,
     267             :               const gsl::not_null<DirectionalIdMap<
     268             :                   volume_dim,
     269             :                   TimeSteppers::BoundaryHistory<
     270             :                       evolution::dg::MortarData<volume_dim>,
     271             :                       evolution::dg::MortarData<volume_dim>, DataVector>>*>
     272             :                   boundary_data_history,
     273             :               const gsl::not_null<DirectionalIdMap<volume_dim, TimeStepId>*>
     274             :                   mortar_next_time_step_ids_mutable,
     275             :               const gsl::not_null<
     276             :                   DirectionalIdMap<volume_dim, Mesh<volume_dim>>*>
     277             :                   neighbor_mesh) {
     278             :             const Mesh<face_dim> neighbor_face_mesh =
     279             :                 received_mortar_data.volume_mesh.slice_away(sliced_away_dim);
     280             :             const Mesh<face_dim> mortar_mesh =
     281             :                 ::dg::mortar_mesh(face_mesh, neighbor_face_mesh);
     282             : 
     283             :             const auto project_boundary_mortar_data =
     284             :                 [&mortar_mesh](const TimeStepId& /*id*/,
     285             :                                const gsl::not_null<
     286             :                                    ::evolution::dg::MortarData<volume_dim>*>
     287             :                                    mortar_data) {
     288             :                   return p_project_mortar_data(mortar_data, mortar_mesh);
     289             :                 };
     290             : 
     291             :             mortar_meshes->at(mortar_id) = mortar_mesh;
     292             :             switch (time_stepping_policy) {
     293             :               case TimeSteppingPolicy::EqualRate:
     294             :                 p_project_mortar_data(
     295             :                     make_not_null(&gts_mortar_data->at(mortar_id).local()),
     296             :                     mortar_mesh);
     297             :                 break;
     298             :               case TimeSteppingPolicy::Conservative:
     299             :                 boundary_data_history->at(mortar_id).local().for_each(
     300             :                     project_boundary_mortar_data);
     301             :                 break;
     302             :               default:
     303             :                 ERROR("Unhandled TimeSteppingPolicy: " << time_stepping_policy);
     304             :             }
     305             : 
     306             :             neighbor_mesh->insert_or_assign(mortar_id,
     307             :                                             received_mortar_data.volume_mesh);
     308             :             mortar_next_time_step_ids_mutable->at(mortar_id) =
     309             :                 received_mortar_data.validity_range;
     310             : 
     311             :             ASSERT(
     312             :                 using_subcell_v<Metavariables> or
     313             :                     received_mortar_data.boundary_correction_data.has_value(),
     314             :                 "Must receive neighbor boundary correction data when "
     315             :                 "not using DG-subcell. Mortar ID is: ("
     316             :                     << mortar_id.direction() << "," << mortar_id.id()
     317             :                     << ") and TimeStepId is " << processing_time);
     318             :             MortarData<volume_dim> neighbor_mortar_data{};
     319             :             neighbor_mortar_data.face_mesh = neighbor_face_mesh;
     320             :             neighbor_mortar_data.mortar_mesh =
     321             :                 received_mortar_data.boundary_correction_mesh;
     322             :             neighbor_mortar_data.mortar_data =
     323             :                 std::move(received_mortar_data.boundary_correction_data);
     324             :             switch (time_stepping_policy) {
     325             :               case TimeSteppingPolicy::EqualRate:
     326             :                 if (neighbor_mortar_data.mortar_data.has_value()) {
     327             :                   p_project_mortar_data(make_not_null(&neighbor_mortar_data),
     328             :                                         mortar_mesh);
     329             :                 }
     330             :                 gts_mortar_data->at(mortar_id).neighbor() =
     331             :                     std::move(neighbor_mortar_data);
     332             :                 break;
     333             :               case TimeSteppingPolicy::Conservative:
     334             :                 ASSERT(neighbor_mortar_data.mortar_data.has_value(),
     335             :                        "Did not receive mortar data for " << mortar_id);
     336             :                 boundary_data_history->at(mortar_id).remote().insert(
     337             :                     processing_time, received_mortar_data.integration_order,
     338             :                     std::move(neighbor_mortar_data));
     339             :                 boundary_data_history->at(mortar_id).remote().for_each(
     340             :                     project_boundary_mortar_data);
     341             :                 break;
     342             :               default:
     343             :                 ERROR("Unhandled TimeSteppingPolicy: " << time_stepping_policy);
     344             :             }
     345             :           },
     346             :           box);
     347             :     }
     348             : 
     349             :     inbox_data.erase(messages_to_process);
     350             :   }
     351             : }
     352             : 
     353             : /// Apply corrections from boundary communication.
     354             : ///
     355             : /// This is usually used indirectly through
     356             : /// `ApplyBoundaryCorrectionsToTimeDerivative`,
     357             : /// `ApplyLtsBoundaryCorrections`, or `ApplyLtsDenseBoundaryCorrections`.
     358             : ///
     359             : /// If `LocalTimeStepping` is false, updates the derivative of the variables,
     360             : /// which should be done before taking a time step.  If
     361             : /// `LocalTimeStepping` is true, updates the variables themselves, which should
     362             : /// be done after the volume update.
     363             : ///
     364             : /// Setting \p DenseOutput to true receives data required for output
     365             : /// at ::Tags::Time instead of performing a full step.  This is only
     366             : /// used for local time-stepping.
     367             : template <bool LocalTimeStepping, typename Metavariables, bool DenseOutput>
     368           1 : struct ApplyBoundaryCorrections {
     369           0 :   static constexpr bool local_time_stepping = LocalTimeStepping;
     370             :   static_assert(local_time_stepping or not DenseOutput,
     371             :                 "GTS does not use ApplyBoundaryCorrections for dense output.");
     372             : 
     373           0 :   using system = typename Metavariables::system;
     374           0 :   static constexpr size_t volume_dim = system::volume_dim;
     375           0 :   using variables_tag = typename system::variables_tag;
     376           0 :   using dt_variables_tag = db::add_tag_prefix<::Tags::dt, variables_tag>;
     377           0 :   using DtVariables = typename dt_variables_tag::type;
     378           0 :   using derived_boundary_corrections =
     379             :       tmpl::at<typename Metavariables::factory_creation::factory_classes,
     380             :                evolution::BoundaryCorrection>;
     381           0 :   using volume_tags_for_dg_boundary_terms = tmpl::remove_duplicates<
     382             :       tmpl::flatten<tmpl::transform<derived_boundary_corrections,
     383             :                                     detail::get_dg_boundary_terms<tmpl::_1>>>>;
     384             : 
     385           0 :   using TimeStepperType =
     386             :       tmpl::conditional_t<local_time_stepping, LtsTimeStepper, TimeStepper>;
     387             : 
     388           0 :   using tag_to_update =
     389             :       tmpl::conditional_t<local_time_stepping, variables_tag, dt_variables_tag>;
     390           0 :   using mortar_data_tag =
     391             :       tmpl::conditional_t<local_time_stepping,
     392             :                           evolution::dg::Tags::MortarDataHistory<volume_dim>,
     393             :                           evolution::dg::Tags::MortarData<volume_dim>>;
     394             : 
     395           0 :   using return_tags = tmpl::list<tag_to_update>;
     396           0 :   using argument_tags = tmpl::append<
     397             :       tmpl::flatten<tmpl::list<
     398             :           mortar_data_tag, domain::Tags::Mesh<volume_dim>,
     399             :           domain::Tags::Element<volume_dim>, Tags::MortarMesh<volume_dim>,
     400             :           Tags::MortarInfo<volume_dim>, ::dg::Tags::Formulation,
     401             :           evolution::dg::Tags::NormalCovectorAndMagnitude<volume_dim>,
     402             :           ::Tags::TimeStepper<TimeStepperType>,
     403             :           evolution::Tags::BoundaryCorrection,
     404             :           tmpl::conditional_t<DenseOutput, ::Tags::Time, ::Tags::TimeStep>,
     405             :           tmpl::conditional_t<local_time_stepping, tmpl::list<>,
     406             :                               domain::Tags::DetInvJacobian<
     407             :                                   Frame::ElementLogical, Frame::Inertial>>>>,
     408             :       volume_tags_for_dg_boundary_terms>;
     409             : 
     410             :   // full step
     411             :   template <typename... VolumeArgs>
     412           0 :   static void apply(
     413             :       const gsl::not_null<typename tag_to_update::type*> vars_to_update,
     414             :       const typename mortar_data_tag::type& mortar_data,
     415             :       const Mesh<volume_dim>& volume_mesh, const Element<volume_dim>& element,
     416             :       const typename Tags::MortarMesh<volume_dim>::type& mortar_meshes,
     417             :       const typename Tags::MortarInfo<volume_dim>::type& mortar_infos,
     418             :       const ::dg::Formulation dg_formulation,
     419             :       const DirectionMap<
     420             :           volume_dim, std::optional<Variables<tmpl::list<
     421             :                           evolution::dg::Tags::MagnitudeOfNormal,
     422             :                           evolution::dg::Tags::NormalCovector<volume_dim>>>>>&
     423             :           face_normal_covector_and_magnitude,
     424             :       const TimeStepperType& time_stepper,
     425             :       const evolution::BoundaryCorrection& boundary_correction,
     426             :       const TimeDelta& time_step,
     427             :       const Scalar<DataVector>& gts_det_inv_jacobian,
     428             :       const VolumeArgs&... volume_args) {
     429             :     apply_impl(vars_to_update, mortar_data, volume_mesh, element, mortar_meshes,
     430             :                mortar_infos, dg_formulation, face_normal_covector_and_magnitude,
     431             :                time_stepper, boundary_correction, time_step,
     432             :                std::numeric_limits<double>::signaling_NaN(),
     433             :                gts_det_inv_jacobian, volume_args...);
     434             :   }
     435             : 
     436             :   template <typename... VolumeArgs>
     437           0 :   static void apply(
     438             :       const gsl::not_null<typename tag_to_update::type*> vars_to_update,
     439             :       const typename mortar_data_tag::type& mortar_data,
     440             :       const Mesh<volume_dim>& volume_mesh, const Element<volume_dim>& element,
     441             :       const typename Tags::MortarMesh<volume_dim>::type& mortar_meshes,
     442             :       const typename Tags::MortarInfo<volume_dim>::type& mortar_infos,
     443             :       const ::dg::Formulation dg_formulation,
     444             :       const DirectionMap<
     445             :           volume_dim, std::optional<Variables<tmpl::list<
     446             :                           evolution::dg::Tags::MagnitudeOfNormal,
     447             :                           evolution::dg::Tags::NormalCovector<volume_dim>>>>>&
     448             :           face_normal_covector_and_magnitude,
     449             :       const TimeStepperType& time_stepper,
     450             :       const evolution::BoundaryCorrection& boundary_correction,
     451             :       const TimeDelta& time_step, const VolumeArgs&... volume_args) {
     452             :     apply_impl(vars_to_update, mortar_data, volume_mesh, element, mortar_meshes,
     453             :                mortar_infos, dg_formulation, face_normal_covector_and_magnitude,
     454             :                time_stepper, boundary_correction, time_step,
     455             :                std::numeric_limits<double>::signaling_NaN(), {},
     456             :                volume_args...);
     457             :   }
     458             : 
     459             :   // dense output (LTS only)
     460             :   template <typename... VolumeArgs>
     461           0 :   static void apply(
     462             :       const gsl::not_null<typename variables_tag::type*> vars_to_update,
     463             :       const typename mortar_data_tag::type& mortar_data,
     464             :       const Mesh<volume_dim>& volume_mesh, const Element<volume_dim>& element,
     465             :       const typename Tags::MortarMesh<volume_dim>::type& mortar_meshes,
     466             :       const typename Tags::MortarInfo<volume_dim>::type& mortar_infos,
     467             :       const ::dg::Formulation dg_formulation,
     468             :       const DirectionMap<
     469             :           volume_dim, std::optional<Variables<tmpl::list<
     470             :                           evolution::dg::Tags::MagnitudeOfNormal,
     471             :                           evolution::dg::Tags::NormalCovector<volume_dim>>>>>&
     472             :           face_normal_covector_and_magnitude,
     473             :       const LtsTimeStepper& time_stepper,
     474             :       const evolution::BoundaryCorrection& boundary_correction,
     475             :       const double dense_output_time, const VolumeArgs&... volume_args) {
     476             :     apply_impl(vars_to_update, mortar_data, volume_mesh, element, mortar_meshes,
     477             :                mortar_infos, dg_formulation, face_normal_covector_and_magnitude,
     478             :                time_stepper, boundary_correction, TimeDelta{},
     479             :                dense_output_time, {}, volume_args...);
     480             :   }
     481             : 
     482             :   template <typename DbTagsList, typename... InboxTags, typename ArrayIndex,
     483             :             typename ParallelComponent>
     484           0 :   static bool is_ready(
     485             :       const gsl::not_null<db::DataBox<DbTagsList>*> box,
     486             :       const gsl::not_null<tuples::TaggedTuple<InboxTags...>*> inboxes,
     487             :       Parallel::GlobalCache<Metavariables>& /*cache*/,
     488             :       const ArrayIndex& /*array_index*/,
     489             :       const ParallelComponent* const /*component*/) {
     490             :     return receive_boundary_data<
     491             :         Parallel::is_dg_element_collection_v<ParallelComponent>, Metavariables,
     492             :         local_time_stepping, DenseOutput>(box, inboxes);
     493             :   }
     494             : 
     495             :  private:
     496             :   template <typename... VolumeArgs>
     497           0 :   static void apply_impl(
     498             :       const gsl::not_null<typename tag_to_update::type*> vars_to_update,
     499             :       const typename mortar_data_tag::type& mortar_data,
     500             :       const Mesh<volume_dim>& volume_mesh, const Element<volume_dim>& element,
     501             :       const typename Tags::MortarMesh<volume_dim>::type& mortar_meshes,
     502             :       const typename Tags::MortarInfo<volume_dim>::type& mortar_infos,
     503             :       const ::dg::Formulation dg_formulation,
     504             :       const DirectionMap<
     505             :           volume_dim, std::optional<Variables<tmpl::list<
     506             :                           evolution::dg::Tags::MagnitudeOfNormal,
     507             :                           evolution::dg::Tags::NormalCovector<volume_dim>>>>>&
     508             :           face_normal_covector_and_magnitude,
     509             :       const TimeStepperType& time_stepper,
     510             :       const evolution::BoundaryCorrection& boundary_correction,
     511             :       const TimeDelta& time_step, const double dense_output_time,
     512             :       const Scalar<DataVector>& gts_det_inv_jacobian,
     513             :       const VolumeArgs&... volume_args) {
     514             :     // We treat this as a set, but use a map because we don't have a
     515             :     // non-allocating set type.
     516             :     DirectionalIdMap<volume_dim, bool> mortars_to_act_on{};
     517             :     for (const auto& [mortar, info] : mortar_infos) {
     518             :       const auto& time_stepping_policy = info.time_stepping_policy();
     519             :       switch (time_stepping_policy) {
     520             :         case TimeSteppingPolicy::EqualRate:
     521             :           if (not local_time_stepping) {
     522             :             mortars_to_act_on.emplace(mortar, true);
     523             :           }
     524             :           break;
     525             :         case TimeSteppingPolicy::Conservative:
     526             :           if (local_time_stepping) {
     527             :             mortars_to_act_on.emplace(mortar, true);
     528             :           }
     529             :           break;
     530             :         default:
     531             :           ERROR("Unhandled TimeSteppingPolicy: " << time_stepping_policy);
     532             :       }
     533             :     }
     534             :     if (mortars_to_act_on.empty()) {
     535             :       return;
     536             :     }
     537             : 
     538             :     tuples::tagged_tuple_from_typelist<db::wrap_tags_in<
     539             :         detail::TemporaryReference, volume_tags_for_dg_boundary_terms>>
     540             :         volume_args_tuple{volume_args...};
     541             : 
     542             :     // Set up helper lambda that will compute and lift the boundary corrections
     543             :     ASSERT(
     544             :         volume_mesh.quadrature() ==
     545             :                 make_array<volume_dim>(volume_mesh.quadrature(0)) or
     546             :             element.topologies() != domain::topologies::hypercube<volume_dim>,
     547             :         "Must have isotropic quadrature, but got volume mesh: " << volume_mesh);
     548             :     Scalar<DataVector> volume_det_inv_jacobian{};
     549             :     Scalar<DataVector> volume_det_jacobian{};
     550             :     if constexpr (not local_time_stepping) {
     551             :       // Need volume Jacobian for any face whose normal direction uses Gauss
     552             :       // points (i.e. not GaussLobatto or GaussRadauUpper). This means
     553             :       // mixed-quadrature non-hypercube elements (e.g. full_cylinder) where
     554             :       // some directions have collocated face points and others do not.
     555             :       const bool any_direction_uses_gauss = alg::any_of(
     556             :           volume_mesh.quadrature(), [](const Spectral::Quadrature q) {
     557             :             return q == Spectral::Quadrature::Gauss;
     558             :           });
     559             :       if (any_direction_uses_gauss) {
     560             :         get(volume_det_inv_jacobian)
     561             :             .set_data_ref(make_not_null(
     562             :                 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
     563             :                 &const_cast<DataVector&>(get(gts_det_inv_jacobian))));
     564             :         get(volume_det_jacobian) = 1.0 / get(volume_det_inv_jacobian);
     565             :       }
     566             :     }
     567             : 
     568             :     static_assert(
     569             :         tmpl::all<derived_boundary_corrections, std::is_final<tmpl::_1>>::value,
     570             :         "All createable classes for boundary corrections must be marked "
     571             :         "final.");
     572             :     call_with_dynamic_type<void, derived_boundary_corrections>(
     573             :         &boundary_correction,
     574             :         [&dense_output_time, &dg_formulation, &element,
     575             :          &face_normal_covector_and_magnitude, &mortar_data, &mortar_meshes,
     576             :          &mortar_infos, &mortars_to_act_on, &time_step, &time_stepper,
     577             :          &vars_to_update, &volume_args_tuple, &volume_det_jacobian,
     578             :          &volume_det_inv_jacobian,
     579             :          &volume_mesh](auto* typed_boundary_correction) {
     580             :           using BcType = std::decay_t<decltype(*typed_boundary_correction)>;
     581             :           // Compute internal boundary quantities on the mortar for sides of
     582             :           // the element that have neighbors, i.e. they are not an external
     583             :           // side.
     584             :           using mortar_tags_list = typename BcType::dg_package_field_tags;
     585             : 
     586             :           // Variables for reusing allocations.  The actual values are
     587             :           // not reused.
     588             :           DtVariables dt_boundary_correction_on_mortar{};
     589             :           DtVariables volume_dt_correction{};
     590             :           // These variables may change size for each mortar and require
     591             :           // a new memory allocation, but they may also happen to need
     592             :           // to be the same size twice in a row, in which case holding
     593             :           // on to the allocation is a win.
     594             :           Scalar<DataVector> face_det_jacobian{};
     595             :           Variables<mortar_tags_list> local_data_on_mortar{};
     596             :           Variables<mortar_tags_list> neighbor_data_on_mortar{};
     597             : 
     598             :           for (const auto& mortar_id_and_data : mortar_data) {
     599             :             const auto& mortar_id = mortar_id_and_data.first;
     600             :             if (not mortars_to_act_on.contains(mortar_id)) {
     601             :               continue;
     602             :             }
     603             :             const auto& direction = mortar_id.direction();
     604             :             if (UNLIKELY(mortar_id.id() ==
     605             :                          ElementId<volume_dim>::external_boundary_id())) {
     606             :               ERROR(
     607             :                   "Cannot impose boundary conditions on external boundary in "
     608             :                   "direction "
     609             :                   << direction
     610             :                   << " in the ApplyBoundaryCorrections action. Boundary "
     611             :                      "conditions are applied in the ComputeTimeDerivative "
     612             :                      "action "
     613             :                      "instead. You may have unintentionally added external "
     614             :                      "mortars in one of the initialization actions.");
     615             :             }
     616             :             if (volume_mesh.basis(direction.dimension()) ==
     617             :                     Spectral::Basis::ZernikeB2 and
     618             :                 volume_mesh.quadrature(direction.dimension()) ==
     619             :                     Spectral::Quadrature::GaussRadauUpper and
     620             :                 direction.side() != Side::Upper) {
     621             :               ERROR(
     622             :                   "Trying to use ZernikeB2 basis with GaussRadauUpper "
     623             :                   "quadrature on the lower side: there is not a boundary here. "
     624             :                   "volume mesh: "
     625             :                   << volume_mesh << ", element ID " << element.id());
     626             :             }
     627             : 
     628             :             const Mesh<volume_dim - 1> face_mesh =
     629             :                 volume_mesh.slice_away(direction.dimension());
     630             : 
     631             :             // Whether the mesh has a collocation point on this face. True for
     632             :             // GaussLobatto (points on both faces) and GaussRadauUpper (point
     633             :             // on the upper face only). When true, lifting is done via
     634             :             // lift_flux on the slice; otherwise the full Gauss-point lifting
     635             :             // path is used.
     636             :             const bool using_points_on_face =
     637             :                 volume_mesh.quadrature(direction.dimension()) ==
     638             :                     Spectral::Quadrature::GaussLobatto or
     639             :                 volume_mesh.quadrature(direction.dimension()) ==
     640             :                     Spectral::Quadrature::GaussRadauUpper;
     641             : 
     642             :             const auto compute_correction_coupling =
     643             :                 [&typed_boundary_correction, &direction, dg_formulation,
     644             :                  &dt_boundary_correction_on_mortar, &face_det_jacobian,
     645             :                  &face_mesh, &face_normal_covector_and_magnitude,
     646             :                  &local_data_on_mortar, &mortar_id, &mortar_meshes,
     647             :                  &mortar_infos, &neighbor_data_on_mortar, using_points_on_face,
     648             :                  &volume_args_tuple, &volume_det_jacobian,
     649             :                  &volume_det_inv_jacobian, &volume_dt_correction, &volume_mesh](
     650             :                     const MortarData<volume_dim>& local_mortar_data,
     651             :                     const MortarData<volume_dim>& neighbor_mortar_data)
     652             :                 -> DtVariables {
     653             :               if (local_time_stepping and not using_points_on_face) {
     654             :                 // This needs to be updated every call because the Jacobian
     655             :                 // may be time-dependent. In the case of time-independent maps
     656             :                 // and local time stepping we could first perform the integral
     657             :                 // on the boundaries, and then lift to the volume. This is
     658             :                 // left as a future optimization.
     659             :                 volume_det_inv_jacobian =
     660             :                     local_mortar_data.volume_det_inv_jacobian.value();
     661             :                 get(volume_det_jacobian) = 1.0 / get(volume_det_inv_jacobian);
     662             :               }
     663             :               const auto& mortar_mesh = mortar_meshes.at(mortar_id);
     664             : 
     665             :               // Extract local and neighbor data, copy into Variables because
     666             :               // we store them in a std::vector for type erasure.
     667             :               ASSERT(*local_mortar_data.mortar_mesh ==
     668             :                              *neighbor_mortar_data.mortar_mesh and
     669             :                          *local_mortar_data.mortar_mesh == mortar_mesh,
     670             :                      "local mortar mesh: " << *local_mortar_data.mortar_mesh
     671             :                                            << "\nneighbor mortar mesh: "
     672             :                                            << *neighbor_mortar_data.mortar_mesh
     673             :                                            << "\nmortar mesh: " << mortar_mesh
     674             :                                            << "\n");
     675             :               const DataVector& local_data = *local_mortar_data.mortar_data;
     676             :               const DataVector& neighbor_data =
     677             :                   *neighbor_mortar_data.mortar_data;
     678             :               ASSERT(local_data.size() == neighbor_data.size(),
     679             :                      "local data size: "
     680             :                          << local_data.size()
     681             :                          << "\nneighbor_data: " << neighbor_data.size()
     682             :                          << "\n mortar_mesh: " << mortar_mesh << "\n");
     683             :               ASSERT(local_data_on_mortar.number_of_grid_points() ==
     684             :                          neighbor_data_on_mortar.number_of_grid_points(),
     685             :                      "Local data size = "
     686             :                          << local_data_on_mortar.number_of_grid_points()
     687             :                          << ", but neighbor size = "
     688             :                          << neighbor_data_on_mortar.number_of_grid_points());
     689             :               local_data_on_mortar.set_data_ref(
     690             :                   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
     691             :                   const_cast<double*>(local_data.data()), local_data.size());
     692             :               neighbor_data_on_mortar.set_data_ref(
     693             :                   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
     694             :                   const_cast<double*>(neighbor_data.data()),
     695             :                   neighbor_data.size());
     696             : 
     697             :               // The boundary computations and lifting can be further
     698             :               // optimized by in the h-refinement case having only one
     699             :               // allocation for the face and having the projection from the
     700             :               // mortar to the face be done in place. E.g.
     701             :               // local_data_on_mortar and neighbor_data_on_mortar could be
     702             :               // allocated fewer times, as well as `needs_projection` section
     703             :               // below could do an in-place projection.
     704             :               dt_boundary_correction_on_mortar.initialize(
     705             :                   mortar_mesh.number_of_grid_points());
     706             : 
     707             :               call_boundary_correction(
     708             :                   make_not_null(&dt_boundary_correction_on_mortar),
     709             :                   local_data_on_mortar, neighbor_data_on_mortar,
     710             :                   *typed_boundary_correction, dg_formulation, volume_args_tuple,
     711             :                   typename BcType::dg_boundary_terms_volume_tags{});
     712             : 
     713             :               const std::array<Spectral::SegmentSize, volume_dim - 1>&
     714             :                   mortar_size = mortar_infos.at(mortar_id).mortar_size();
     715             : 
     716             :               // This cannot reuse an allocation because it is initialized
     717             :               // via move-assignment.  (If it is used at all.)
     718             :               DtVariables dt_boundary_correction_projected_onto_face{};
     719             :               auto& dt_boundary_correction =
     720             :                   [&dt_boundary_correction_on_mortar,
     721             :                    &dt_boundary_correction_projected_onto_face, &face_mesh,
     722             :                    &mortar_mesh, &mortar_size]() -> DtVariables& {
     723             :                 if (Spectral::needs_projection(face_mesh, mortar_mesh,
     724             :                                                mortar_size)) {
     725             :                   dt_boundary_correction_projected_onto_face =
     726             :                       ::dg::project_from_mortar(
     727             :                           dt_boundary_correction_on_mortar, face_mesh,
     728             :                           mortar_mesh, mortar_size);
     729             :                   return dt_boundary_correction_projected_onto_face;
     730             :                 }
     731             :                 return dt_boundary_correction_on_mortar;
     732             :               }();
     733             : 
     734             :               // Both paths initialize this to be non-owning.
     735             :               Scalar<DataVector> magnitude_of_face_normal{};
     736             :               if constexpr (local_time_stepping) {
     737             :                 (void)face_normal_covector_and_magnitude;
     738             :                 get(magnitude_of_face_normal)
     739             :                     .set_data_ref(make_not_null(&const_cast<DataVector&>(
     740             :                         get(local_mortar_data.face_normal_magnitude.value()))));
     741             :               } else {
     742             :                 ASSERT(
     743             :                     face_normal_covector_and_magnitude.count(direction) == 1 and
     744             :                         face_normal_covector_and_magnitude.at(direction)
     745             :                             .has_value(),
     746             :                     "Face normal covector and magnitude not set in "
     747             :                     "direction: "
     748             :                         << direction);
     749             :                 get(magnitude_of_face_normal)
     750             :                     .set_data_ref(make_not_null(&const_cast<DataVector&>(
     751             :                         get(get<evolution::dg::Tags::MagnitudeOfNormal>(
     752             :                             *face_normal_covector_and_magnitude.at(
     753             :                                 direction))))));
     754             :               }
     755             : 
     756             :               if (using_points_on_face) {
     757             :                 // The lift_flux function lifts only on the slice, it does not
     758             :                 // add the contribution to the volume.
     759             :                 ::dg::lift_flux(make_not_null(&dt_boundary_correction),
     760             :                                 volume_mesh.extents(direction.dimension()),
     761             :                                 magnitude_of_face_normal,
     762             :                                 volume_mesh.basis(direction.dimension()));
     763             :                 return std::move(dt_boundary_correction);
     764             :               } else {
     765             :                 // We are using Gauss points.
     766             :                 //
     767             :                 // Notes:
     768             :                 // - We should really lift both sides simultaneously since this
     769             :                 //   reduces memory accesses. Lifting all sides at the same
     770             :                 //   time is unlikely to improve performance since we lift by
     771             :                 //   jumping through slices. There may also be compatibility
     772             :                 //   issues with local time stepping.
     773             :                 // - If we lift both sides at the same time we first need to
     774             :                 //   deal with projecting from mortars to the face, then lift
     775             :                 //   off the faces. With non-owning Variables memory
     776             :                 //   allocations could be significantly reduced in this code.
     777             :                 if constexpr (local_time_stepping) {
     778             :                   ASSERT(get(volume_det_inv_jacobian).size() > 0,
     779             :                          "For local time stepping the volume determinant of "
     780             :                          "the inverse Jacobian has not been set.");
     781             : 
     782             :                   get(face_det_jacobian)
     783             :                       .set_data_ref(make_not_null(&const_cast<DataVector&>(
     784             :                           get(local_mortar_data.face_det_jacobian.value()))));
     785             :                 } else {
     786             :                   // Project the determinant of the Jacobian to the face. This
     787             :                   // could be optimized by caching in the time-independent case.
     788             :                   get(face_det_jacobian)
     789             :                       .destructive_resize(face_mesh.number_of_grid_points());
     790             :                   const Matrix identity{};
     791             :                   auto interpolation_matrices =
     792             :                       make_array<volume_dim>(std::cref(identity));
     793             :                   const std::pair<Matrix, Matrix>& matrices =
     794             :                       Spectral::boundary_interpolation_matrices(
     795             :                           volume_mesh.slice_through(direction.dimension()));
     796             :                   gsl::at(interpolation_matrices, direction.dimension()) =
     797             :                       direction.side() == Side::Upper ? matrices.second
     798             :                                                       : matrices.first;
     799             :                   apply_matrices(make_not_null(&get(face_det_jacobian)),
     800             :                                  interpolation_matrices,
     801             :                                  get(volume_det_jacobian),
     802             :                                  volume_mesh.extents());
     803             :                 }
     804             : 
     805             :                 volume_dt_correction.initialize(
     806             :                     volume_mesh.number_of_grid_points(), 0.0);
     807             :                 ::dg::lift_boundary_terms_gauss_points(
     808             :                     make_not_null(&volume_dt_correction),
     809             :                     volume_det_inv_jacobian, volume_mesh, direction,
     810             :                     dt_boundary_correction, magnitude_of_face_normal,
     811             :                     face_det_jacobian);
     812             :                 return std::move(volume_dt_correction);
     813             :               }
     814             :             };
     815             : 
     816             :             if constexpr (local_time_stepping) {
     817             :               typename variables_tag::type boundary_lifted_data{};
     818             :               auto& lifted_data =
     819             :                   using_points_on_face ? boundary_lifted_data : *vars_to_update;
     820             :               if (using_points_on_face) {
     821             :                 lifted_data.initialize(face_mesh.number_of_grid_points(), 0.0);
     822             :               }
     823             : 
     824             :               const auto& mortar_data_history = mortar_id_and_data.second;
     825             :               if constexpr (DenseOutput) {
     826             :                 (void)time_step;
     827             :                 time_stepper.boundary_dense_output(
     828             :                     &lifted_data, mortar_data_history, dense_output_time,
     829             :                     compute_correction_coupling);
     830             :               } else {
     831             :                 (void)dense_output_time;
     832             :                 time_stepper.add_boundary_delta(&lifted_data,
     833             :                                                 mortar_data_history, time_step,
     834             :                                                 compute_correction_coupling);
     835             :               }
     836             : 
     837             :               if (using_points_on_face) {
     838             :                 // Add the flux contribution to the volume data
     839             :                 add_slice_to_data(
     840             :                     vars_to_update, lifted_data, volume_mesh.extents(),
     841             :                     direction.dimension(),
     842             :                     index_to_slice_at(volume_mesh.extents(), direction));
     843             :               }
     844             :             } else {
     845             :               (void)time_step;
     846             :               (void)time_stepper;
     847             :               (void)dense_output_time;
     848             : 
     849             :               // Choose an allocation cache that may be empty, so we
     850             :               // might be able to reuse the allocation obtained for the
     851             :               // lifted data.  This may result in a self assignment,
     852             :               // depending on the code paths taken, but handling the
     853             :               // results this way makes the GTS and LTS paths more
     854             :               // similar because the LTS code always stores the result
     855             :               // in the history and so sometimes benefits from moving
     856             :               // into the return value of compute_correction_coupling.
     857             :               auto& lifted_data = using_points_on_face
     858             :                                       ? dt_boundary_correction_on_mortar
     859             :                                       : volume_dt_correction;
     860             :               lifted_data = compute_correction_coupling(
     861             :                   mortar_id_and_data.second.local(),
     862             :                   mortar_id_and_data.second.neighbor());
     863             : 
     864             :               if (using_points_on_face) {
     865             :                 // Add the flux contribution to the volume data
     866             :                 add_slice_to_data(
     867             :                     vars_to_update, lifted_data, volume_mesh.extents(),
     868             :                     direction.dimension(),
     869             :                     index_to_slice_at(volume_mesh.extents(), direction));
     870             :               } else {
     871             :                 *vars_to_update += lifted_data;
     872             :               }
     873             :             }
     874             :           }
     875             :         });
     876             :   }
     877             : 
     878             :   template <typename... BoundaryCorrectionTags, typename... Tags,
     879             :             typename BoundaryCorrection, typename... AllVolumeArgs,
     880             :             typename... VolumeTagsForCorrection>
     881           0 :   static void call_boundary_correction(
     882             :       const gsl::not_null<Variables<tmpl::list<BoundaryCorrectionTags...>>*>
     883             :           boundary_corrections_on_mortar,
     884             :       const Variables<tmpl::list<Tags...>>& local_boundary_data,
     885             :       const Variables<tmpl::list<Tags...>>& neighbor_boundary_data,
     886             :       const BoundaryCorrection& boundary_correction,
     887             :       const ::dg::Formulation dg_formulation,
     888             :       const tuples::TaggedTuple<detail::TemporaryReference<AllVolumeArgs>...>&
     889             :           volume_args_tuple,
     890             :       tmpl::list<VolumeTagsForCorrection...> /*meta*/) {
     891             :     boundary_correction.dg_boundary_terms(
     892             :         make_not_null(
     893             :             &get<BoundaryCorrectionTags>(*boundary_corrections_on_mortar))...,
     894             :         get<Tags>(local_boundary_data)..., get<Tags>(neighbor_boundary_data)...,
     895             :         dg_formulation,
     896             :         tuples::get<detail::TemporaryReference<VolumeTagsForCorrection>>(
     897             :             volume_args_tuple)...);
     898             :   }
     899             : };
     900             : 
     901             : /// Apply corrections from boundary communication for LTS dense output.
     902             : template <typename Metavariables>
     903           1 : struct ApplyLtsDenseBoundaryCorrections
     904             :     : ApplyBoundaryCorrections<true, Metavariables, true> {};
     905             : 
     906           1 : namespace Actions {
     907             : namespace ApplyBoundaryCorrections_detail {
     908             : template <bool LocalTimeStepping, size_t VolumeDim, bool DenseOutput,
     909             :           bool UseNodegroupDgElements>
     910             : struct ActionImpl {
     911             :   using inbox_tags =
     912             :       tmpl::list<evolution::dg::Tags::BoundaryCorrectionAndGhostCellsInbox<
     913             :           VolumeDim, UseNodegroupDgElements>>;
     914             :   using const_global_cache_tags =
     915             :       tmpl::list<evolution::Tags::BoundaryCorrection, ::dg::Tags::Formulation>;
     916             : 
     917             :   template <typename DbTagsList, typename... InboxTags, typename Metavariables,
     918             :             typename ArrayIndex, typename ActionList,
     919             :             typename ParallelComponent>
     920             :   static Parallel::iterable_action_return_t apply(
     921             :       db::DataBox<DbTagsList>& box, tuples::TaggedTuple<InboxTags...>& inboxes,
     922             :       const Parallel::GlobalCache<Metavariables>& /*cache*/,
     923             :       const ArrayIndex& /*array_index*/, ActionList /*meta*/,
     924             :       const ParallelComponent* const /*meta*/) {
     925             :     static_assert(Metavariables::system::volume_dim == VolumeDim);
     926             :     static_assert(
     927             :         UseNodegroupDgElements ==
     928             :             Parallel::is_dg_element_collection_v<ParallelComponent>,
     929             :         "The action is told by the template parameter UseNodegroupDgElements "
     930             :         "that it is being used with a DgElementCollection, but the "
     931             :         "ParallelComponent is not a DgElementCollection. You need to change "
     932             :         "the template parameter on the action in your action list.");
     933             :     constexpr size_t volume_dim = Metavariables::system::volume_dim;
     934             :     const Element<volume_dim>& element =
     935             :         db::get<domain::Tags::Element<volume_dim>>(box);
     936             : 
     937             :     if (UNLIKELY(element.number_of_neighbors() == 0)) {
     938             :       // We have no neighbors, yay!
     939             :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     940             :     }
     941             : 
     942             :     if (not receive_boundary_data<
     943             :             Parallel::is_dg_element_collection_v<ParallelComponent>,
     944             :             Metavariables, LocalTimeStepping, false>(make_not_null(&box),
     945             :                                                      make_not_null(&inboxes))) {
     946             :       return {Parallel::AlgorithmExecution::Retry, std::nullopt};
     947             :     }
     948             : 
     949             :     // LTS updates the evolved variables, so we can skip that if they
     950             :     // are unused.  GTS updates the derivatives, which are always
     951             :     // needed to update the history.
     952             :     if (LocalTimeStepping and
     953             :         ::SelfStart::step_unused(
     954             :             db::get<::Tags::TimeStepId>(box),
     955             :             db::get<::Tags::Next<::Tags::TimeStepId>>(box))) {
     956             :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     957             :     }
     958             : 
     959             :     db::mutate_apply<ApplyBoundaryCorrections<LocalTimeStepping, Metavariables,
     960             :                                               DenseOutput>>(
     961             :         make_not_null(&box));
     962             :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     963             :   }
     964             : };
     965             : }  // namespace ApplyBoundaryCorrections_detail
     966             : 
     967             : /*!
     968             :  * \brief Computes the boundary corrections for global time-stepping
     969             :  * and adds them to the time derivative.
     970             :  */
     971             : template <size_t VolumeDim, bool UseNodegroupDgElements>
     972           1 : struct ApplyBoundaryCorrectionsToTimeDerivative
     973             :     : ApplyBoundaryCorrections_detail::ActionImpl<false, VolumeDim, false,
     974             :                                                   UseNodegroupDgElements> {};
     975             : 
     976             : /*!
     977             :  * \brief Computes the boundary corrections for local time-stepping
     978             :  * and adds them to the variables.
     979             :  *
     980             :  * When using local time stepping the neighbor sends data at the neighbor's
     981             :  * current temporal id. Along with the boundary data, the next temporal id at
     982             :  * which the neighbor will send data is also sent. This is equal to the
     983             :  * neighbor's `::Tags::Next<::Tags::TimeStepId>`. When inserting into the mortar
     984             :  * data history, we insert the received temporal id, that is, the current time
     985             :  * of the neighbor, along with the boundary correction data.
     986             :  */
     987             : template <size_t VolumeDim, bool UseNodegroupDgElements>
     988           1 : struct ApplyLtsBoundaryCorrections
     989             :     : ApplyBoundaryCorrections_detail::ActionImpl<true, VolumeDim, false,
     990             :                                                   UseNodegroupDgElements> {};
     991             : }  // namespace Actions
     992             : }  // namespace evolution::dg

Generated by: LCOV version 1.14