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

Generated by: LCOV version 1.14