SpECTRE Documentation Coverage Report
Current view: top level - Evolution/DgSubcell/Actions - ReconstructionCommunication.hpp Hit Total Coverage
Commit: 107e15b340886ae54549b1baa4bfc92e676f667e Lines: 3 9 33.3 %
Date: 2026-09-17 16:38:56
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 <algorithm>
       7             : #include <array>
       8             : #include <cstddef>
       9             : #include <iterator>
      10             : #include <limits>
      11             : #include <map>
      12             : #include <optional>
      13             : #include <tuple>
      14             : #include <unordered_set>
      15             : #include <utility>
      16             : 
      17             : #include "DataStructures/DataBox/DataBox.hpp"
      18             : #include "DataStructures/DataBox/Prefixes.hpp"
      19             : #include "DataStructures/DataVector.hpp"
      20             : #include "DataStructures/Index.hpp"
      21             : #include "DataStructures/Tensor/Tensor.hpp"
      22             : #include "DataStructures/Variables.hpp"
      23             : #include "DataStructures/VariablesTag.hpp"
      24             : #include "Domain/Structure/Direction.hpp"
      25             : #include "Domain/Structure/DirectionalId.hpp"
      26             : #include "Domain/Structure/DirectionalIdMap.hpp"
      27             : #include "Domain/Structure/Element.hpp"
      28             : #include "Domain/Structure/ElementId.hpp"
      29             : #include "Domain/Structure/FaceType.hpp"
      30             : #include "Domain/Structure/OrientationMapHelpers.hpp"
      31             : #include "Domain/Structure/TrimMap.hpp"
      32             : #include "Domain/Tags.hpp"
      33             : #include "Domain/Tags/NeighborMesh.hpp"
      34             : #include "Evolution/DgSubcell/ActiveGrid.hpp"
      35             : #include "Evolution/DgSubcell/CombineVolumeGhostData.hpp"
      36             : #include "Evolution/DgSubcell/GhostData.hpp"
      37             : #include "Evolution/DgSubcell/NeighborRdmpAndVolumeData.hpp"
      38             : #include "Evolution/DgSubcell/Projection.hpp"
      39             : #include "Evolution/DgSubcell/RdmpTci.hpp"
      40             : #include "Evolution/DgSubcell/RdmpTciData.hpp"
      41             : #include "Evolution/DgSubcell/SliceData.hpp"
      42             : #include "Evolution/DgSubcell/SubcellOptions.hpp"
      43             : #include "Evolution/DgSubcell/Tags/ActiveGrid.hpp"
      44             : #include "Evolution/DgSubcell/Tags/CellCenteredFlux.hpp"
      45             : #include "Evolution/DgSubcell/Tags/DataForRdmpTci.hpp"
      46             : #include "Evolution/DgSubcell/Tags/GhostDataForReconstruction.hpp"
      47             : #include "Evolution/DgSubcell/Tags/Interpolators.hpp"
      48             : #include "Evolution/DgSubcell/Tags/Mesh.hpp"
      49             : #include "Evolution/DgSubcell/Tags/MeshForGhostData.hpp"
      50             : #include "Evolution/DgSubcell/Tags/SubcellOptions.hpp"
      51             : #include "Evolution/DgSubcell/Tags/TciStatus.hpp"
      52             : #include "Evolution/DiscontinuousGalerkin/BoundaryData.hpp"
      53             : #include "Evolution/DiscontinuousGalerkin/InboxTags.hpp"
      54             : #include "Evolution/DiscontinuousGalerkin/MortarData.hpp"
      55             : #include "Evolution/DiscontinuousGalerkin/MortarDataHolder.hpp"
      56             : #include "Evolution/DiscontinuousGalerkin/MortarTags.hpp"
      57             : #include "Evolution/DiscontinuousGalerkin/TimeSteppingPolicy.hpp"
      58             : #include "NumericalAlgorithms/Interpolation/IrregularInterpolant.hpp"
      59             : #include "NumericalAlgorithms/Spectral/Mesh.hpp"
      60             : #include "Parallel/AlgorithmExecution.hpp"
      61             : #include "Parallel/ArrayCollection/IsDgElementCollection.hpp"
      62             : #include "Parallel/GlobalCache.hpp"
      63             : #include "Time/Tags/HistoryEvolvedVariables.hpp"
      64             : #include "Time/TimeStepId.hpp"
      65             : #include "Utilities/Algorithm.hpp"
      66             : #include "Utilities/ErrorHandling/Assert.hpp"
      67             : #include "Utilities/Gsl.hpp"
      68             : #include "Utilities/Literals.hpp"
      69             : #include "Utilities/MakeArray.hpp"
      70             : #include "Utilities/TMPL.hpp"
      71             : 
      72             : /// \cond
      73             : namespace Tags {
      74             : struct TimeStepId;
      75             : }  // namespace Tags
      76             : namespace evolution::dg::Tags {
      77             : template <size_t Dim>
      78             : struct MortarInfo;
      79             : }  // namespace evolution::dg::Tags
      80             : /// \endcond
      81             : 
      82             : namespace evolution::dg::subcell::Actions {
      83             : /*!
      84             :  * \brief Sets the local data from the relaxed discrete maximum principle
      85             :  * troubled-cell indicator and sends ghost zone data to neighboring elements.
      86             :  *
      87             :  * The action proceeds as follows:
      88             :  *
      89             :  * 1. Determine in which directions we have neighbors
      90             :  * 2. Slice the variables provided by GhostDataMutator to send to our neighbors
      91             :  *    for ghost zones
      92             :  * 3. Send the ghost zone data, appending the max/min for the TCI at the end of
      93             :  *    the `DataVector` we are sending.
      94             :  *
      95             :  * \warning This assumes the RDMP TCI data in the DataBox has been set, it does
      96             :  * not calculate it automatically. The reason is this way we can only calculate
      97             :  * the RDMP data when it's needed since computing it can be pretty expensive.
      98             :  *
      99             :  * Some notes:
     100             :  * - In the future we will need to send the cell-centered fluxes to do
     101             :  *   high-order FD without additional reconstruction being necessary.
     102             :  *
     103             :  * GlobalCache:
     104             :  * - Uses:
     105             :  *   - `ParallelComponent` proxy
     106             :  *
     107             :  * DataBox:
     108             :  * - Uses:
     109             :  *   - `domain::Tags::Mesh<Dim>`
     110             :  *   - `subcell::Tags::Mesh<Dim>`
     111             :  *   - `domain::Tags::Element<Dim>`
     112             :  *   - `Tags::TimeStepId`
     113             :  *   - `Tags::Next<Tags::TimeStepId>`
     114             :  *   - `subcell::Tags::ActiveGrid`
     115             :  *   - `System::variables_tag`
     116             :  *   - `subcell::Tags::DataForRdmpTci`
     117             :  * - Adds: nothing
     118             :  * - Removes: nothing
     119             :  * - Modifies:
     120             :  *   - `subcell::Tags::GhostDataForReconstruction<Dim>`
     121             :  */
     122             : template <size_t Dim, typename GhostDataMutator, bool UseNodegroupDgElements>
     123           1 : struct SendDataForReconstruction {
     124           0 :   using inbox_tags =
     125             :       tmpl::list<evolution::dg::Tags::BoundaryCorrectionAndGhostCellsInbox<
     126             :           Dim, UseNodegroupDgElements>>;
     127             : 
     128             :   template <typename DbTags, typename... InboxTags, typename ArrayIndex,
     129             :             typename ActionList, typename ParallelComponent,
     130             :             typename Metavariables>
     131           0 :   static Parallel::iterable_action_return_t apply(
     132             :       db::DataBox<DbTags>& box, tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     133             :       Parallel::GlobalCache<Metavariables>& cache,
     134             :       const ArrayIndex& /*array_index*/, const ActionList /*meta*/,
     135             :       const ParallelComponent* const /*meta*/) {
     136             :     static_assert(UseNodegroupDgElements ==
     137             :                       Parallel::is_dg_element_collection_v<ParallelComponent>,
     138             :                   "The action SendDataForReconstruction is told by the "
     139             :                   "template parameter UseNodegroupDgElements that it is being "
     140             :                   "used with a DgElementCollection, but the ParallelComponent "
     141             :                   "is not a DgElementCollection. You need to change the "
     142             :                   "template parameter on the SendDataForReconstruction action "
     143             :                   "in your action list.");
     144             : 
     145             :     ASSERT(db::get<Tags::ActiveGrid>(box) == ActiveGrid::Subcell,
     146             :            "The SendDataForReconstruction action can only be called when "
     147             :            "Subcell is the active scheme.");
     148             : 
     149             :     const Element<Dim>& element = db::get<::domain::Tags::Element<Dim>>(box);
     150             :     ASSERT(alg::all_of(db::get<evolution::dg::Tags::MortarInfo<Dim>>(box),
     151             :                        [](const auto& mortar) {
     152             :                          return mortar.second.time_stepping_policy() ==
     153             :                                 TimeSteppingPolicy::EqualRate;
     154             :                        }),
     155             :            "Cannot send subcell data from "
     156             :                << element.id() << " across an LTS mortar: "
     157             :                << db::get<evolution::dg::Tags::MortarInfo<Dim>>(box));
     158             : 
     159             :     using flux_variables = typename Metavariables::system::flux_variables;
     160             : 
     161             :     db::mutate<Tags::GhostDataForReconstruction<Dim>>(
     162             :         [](const auto ghost_data_ptr) {
     163             :           // Clear the previous neighbor data and add current local data
     164             :           ghost_data_ptr->clear();
     165             :         },
     166             :         make_not_null(&box));
     167             : 
     168             :     const Mesh<Dim>& dg_mesh = db::get<::domain::Tags::Mesh<Dim>>(box);
     169             :     const Mesh<Dim>& subcell_mesh = db::get<Tags::Mesh<Dim>>(box);
     170             :     const size_t ghost_zone_size =
     171             :         Metavariables::SubcellOptions::ghost_zone_size(box);
     172             : 
     173             :     // Optimization note: could save a copy+allocation if we moved
     174             :     // all_sliced_data when possible before sending.
     175             :     //
     176             :     // Note: RDMP size doesn't help here since we need to slice data after
     177             :     // anyway, so no way to save an allocation through that.
     178             :     const auto& cell_centered_flux =
     179             :         db::get<Tags::CellCenteredFlux<flux_variables, Dim>>(box);
     180             :     DataVector volume_data_to_slice = db::mutate_apply(
     181             :         GhostDataMutator{}, make_not_null(&box),
     182             :         cell_centered_flux.has_value() ? cell_centered_flux.value().size()
     183             :                                        : 0_st);
     184             :     if (cell_centered_flux.has_value()) {
     185             :       std::copy(
     186             :           cell_centered_flux.value().data(),
     187             :           std::next(
     188             :               cell_centered_flux.value().data(),
     189             :               static_cast<std::ptrdiff_t>(cell_centered_flux.value().size())),
     190             :           std::next(
     191             :               volume_data_to_slice.data(),
     192             :               static_cast<std::ptrdiff_t>(volume_data_to_slice.size() -
     193             :                                           cell_centered_flux.value().size())));
     194             :     }
     195             : 
     196             :     // When using enable_extension_directions, we send the ghost data
     197             :     // for problematic directions separately with the new action
     198             :     // ReceiveAndSendDataForReconstruction, so we only slice the data
     199             :     // for non-problematic directions here. (see the documentation of
     200             :     // ReceiveAndSendDataForReconstruction for what "problematic" means)
     201             :     const auto& extension_directions =
     202             :         db::get<evolution::dg::subcell::Tags::ExtensionDirections<Dim>>(box);
     203             :     std::unordered_set<Direction<Dim>> directions_to_work;
     204             :     for (const auto& internal_direction : element.internal_boundaries()) {
     205             :       if (extension_directions.contains(internal_direction)) {
     206             :         continue;
     207             :       } else {
     208             :         directions_to_work.insert(internal_direction);
     209             :       }
     210             :     }
     211             : 
     212             :     const DirectionMap<Dim, DataVector> all_sliced_data = slice_data(
     213             :         volume_data_to_slice, subcell_mesh.extents(), ghost_zone_size,
     214             :         directions_to_work, 0,
     215             :         db::get<
     216             :             evolution::dg::subcell::Tags::InterpolatorsFromFdToNeighborFd<Dim>>(
     217             :             box));
     218             : 
     219             :     auto& receiver_proxy =
     220             :         Parallel::get_parallel_component<ParallelComponent>(cache);
     221             :     const RdmpTciData& rdmp_tci_data = db::get<Tags::DataForRdmpTci>(box);
     222             :     const TimeStepId& time_step_id = db::get<::Tags::TimeStepId>(box);
     223             :     const TimeStepId& next_time_step_id =
     224             :         db::get<::Tags::Next<::Tags::TimeStepId>>(box);
     225             : 
     226             :     const int tci_decision =
     227             :         db::get<evolution::dg::subcell::Tags::TciDecision>(box);
     228             :     using history_tags = ::Tags::get_all_history_tags<DbTags>;
     229             :     static_assert(tmpl::size<history_tags>::value == 1);
     230             :     const auto& integration_order =
     231             :         db::get<tmpl::front<history_tags>>(box).integration_order();
     232             :     // Compute and send actual variables
     233             :     for (const auto& [direction, neighbors_in_direction] :
     234             :          element.neighbors()) {
     235             :       // Only need to send data for directions that are not
     236             :       // problematic directions (keys of extension_directions).
     237             :       if (not extension_directions.contains(direction)) {
     238             :         ASSERT(neighbors_in_direction.size() == 1,
     239             :                "AMR is not yet supported when using DG-subcell. Note that this "
     240             :                "condition could be relaxed to support AMR only where the "
     241             :                "evolution is using DG without any changes to subcell.");
     242             : 
     243             :         for (const ElementId<Dim>& neighbor : neighbors_in_direction) {
     244             :           const auto& orientation =
     245             :               neighbors_in_direction.orientation(neighbor);
     246             :           const auto direction_from_neighbor =
     247             :               orientation(direction.opposite());
     248             :           const size_t rdmp_size = rdmp_tci_data.max_variables_values.size() +
     249             :                                    rdmp_tci_data.min_variables_values.size();
     250             :           const auto& sliced_data_in_direction = all_sliced_data.at(direction);
     251             : 
     252             :           // Allocate with subcell data and rdmp data
     253             :           DataVector subcell_data_to_send{sliced_data_in_direction.size() +
     254             :                                           rdmp_size};
     255             :           // Note: Currently we interpolate our solution to our neighbor FD grid
     256             :           // even when grid points align but are oriented differently. There's a
     257             :           // possible optimization for the rare (almost never?) edge case where
     258             :           // two blocks have the same ghost zone coordinates but have different
     259             :           // orientations (e.g. RotatedBricks). Since this shouldn't ever happen
     260             :           // outside of tests, we currently don't bother with it. If we wanted
     261             :           // to, here's the code:
     262             :           //
     263             :           // if (not orientation.is_aligned()) {
     264             :           //   std::array<size_t, Dim> slice_extents{};
     265             :           //   for (size_t d = 0; d < Dim; ++d) {
     266             :           //     gsl::at(slice_extents, d) = subcell_mesh.extents(d);
     267             :           //   }
     268             :           //   gsl::at(slice_extents, direction.dimension()) = ghost_zone_size;
     269             :           //   // Need a view so we only get the subcell data and not the rdmp
     270             :           //   // data
     271             :           //   DataVector subcell_data_to_send_view{
     272             :           //       subcell_data_to_send.data(),
     273             :           //       subcell_data_to_send.size() - rdmp_size};
     274             :           //   orient_variables(make_not_null(&subcell_data_to_send_view),
     275             :           //                  sliced_data_in_direction,
     276             :           //                  Index<Dim>{slice_extents}, orientation);
     277             :           // } else { std::copy(...); }
     278             :           //
     279             : 
     280             :           // Copy over data since it's already oriented from interpolation
     281             :           std::copy(sliced_data_in_direction.begin(),
     282             :                     sliced_data_in_direction.end(),
     283             :                     subcell_data_to_send.begin());
     284             :           // Copy rdmp data to end of subcell_data_to_send
     285             :           std::copy(rdmp_tci_data.max_variables_values.cbegin(),
     286             :                     rdmp_tci_data.max_variables_values.cend(),
     287             :                     std::prev(subcell_data_to_send.end(),
     288             :                               static_cast<int>(rdmp_size)));
     289             :           std::copy(rdmp_tci_data.min_variables_values.cbegin(),
     290             :                     rdmp_tci_data.min_variables_values.cend(),
     291             :                     std::prev(subcell_data_to_send.end(),
     292             :                               static_cast<int>(
     293             :                                   rdmp_tci_data.min_variables_values.size())));
     294             : 
     295             :           evolution::dg::BoundaryData<Dim> data{
     296             :               dg_mesh,      subcell_mesh,
     297             :               std::nullopt, std::move(subcell_data_to_send),
     298             :               std::nullopt, next_time_step_id,
     299             :               tci_decision, integration_order};
     300             : 
     301             :           Parallel::receive_data<
     302             :               evolution::dg::Tags::BoundaryCorrectionAndGhostCellsInbox<
     303             :                   Dim,
     304             :                   Parallel::is_dg_element_collection_v<ParallelComponent>>>(
     305             :               receiver_proxy[neighbor], time_step_id,
     306             :               std::pair{
     307             :                   DirectionalId<Dim>{direction_from_neighbor, element.id()},
     308             :                   std::move(data)});
     309             :         }
     310             :       }
     311             :     }
     312             :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     313             :   }
     314             : };
     315             : 
     316             : /*!
     317             :  * \brief Handles ghost data at block boundaries to ensure interpolation
     318             :  * is (almost) always used instead of extrapolation.
     319             :  *
     320             :  * Since the coordinate maps are only continuous and not smooth at block
     321             :  * boundaries, the logical coordinate axes, and therefore grid point axes,
     322             :  * do not necessarily align. An example is given in the image below, which is
     323             :  * a snapshot of 2d circular domain built from one central square surrounded
     324             :  * by four deformed wedges. We zoom in on the upper right corner of the
     325             :  * cube for illustration purposes.
     326             :  *
     327             :  * \image html curved_mesh_illustration.png width=600px
     328             :  *
     329             :  * Blue circles denote the cell-centered FD points in the two elements whose
     330             :  * ghost points are being exchanged, bright red diamonds denote the ghost
     331             :  * points needed for reconstruction in the element on the right, and dark red
     332             :  * squares denote the cell-centered FD points in a neighboring element not
     333             :  * directly participating in the exchange. The dashed blue and dash-dotted
     334             :  * red lines show lines of constant logical coordinates in the left and
     335             :  * right elements, respectively. Notice that they intersect on the boundary,
     336             :  * but do not align.
     337             :  *
     338             :  * In this example, three lowest ghost points cannot be directly
     339             :  * interpolated from the element on the left. In such a case,
     340             :  * we flag the direction (from the perspective of the element on the left,
     341             :  * in this example, the direction the green arrow points to) as
     342             :  * problematic and the ghost points may be filled either
     343             :  * by extrapolation or interpolation. Extrapolation can lead to an unphysical
     344             :  * state like negative densities, so interpolation is generally preferred.
     345             :  * To enable interpolation, set `EnableExtensionDirections` to true in
     346             :  * `SubcellOptions` part of the input file. This enables the use of
     347             :  * ghost data from the neighboring element in the extension direction
     348             :  * (in this example, the direction the grey arrow points to) to fill the
     349             :  * ghost points.
     350             :  *
     351             :  * \warning This option is only available in the case where we are only
     352             :  * using FD scheme for the evolution, i.e. we are using true for
     353             :  * `AlwaysUseSubcell` in the `SubcellOptions` part of the input file.
     354             :  * Currently, the following cases are not supported:
     355             :  * 1. There are multiple neighbors in a direction
     356             :  * 2. There are multiple extension directions required for a
     357             :  *    single problematic direction.
     358             :  * 3. The extension direction is itself a problematic direction, which can
     359             :  *    result in a deadlock.
     360             :  *
     361             :  * When disabled, this action does nothing.
     362             :  *
     363             :  * When enabled, the action:
     364             :  * 1. Receives subcell ghost data from neighbors for all non-problematic
     365             :  *    directions.
     366             :  * 2. For “problematic directions” it extends the element’s volume data
     367             :  *    using ghost data from another neighbor (in the "extension direction").
     368             :  *    This extension ensures that the ghost data can be filled using
     369             :  *    interpolation.
     370             :  * 3. Interpolates to the requested ghost points, and then sends the completed
     371             :  *    ghost data to the original neighbor.
     372             :  */
     373             : template <size_t Dim, typename GhostDataMutator, bool UseNodegroupDgElements>
     374           1 : struct ReceiveAndSendDataForReconstruction {
     375           0 :   using inbox_tags =
     376             :       tmpl::list<evolution::dg::Tags::BoundaryCorrectionAndGhostCellsInbox<
     377             :           Dim, UseNodegroupDgElements>>;
     378             :   template <typename DbTags, typename... InboxTags, typename ArrayIndex,
     379             :             typename ActionList, typename ParallelComponent,
     380             :             typename Metavariables>
     381           0 :   static Parallel::iterable_action_return_t apply(
     382             :       db::DataBox<DbTags>& box, tuples::TaggedTuple<InboxTags...>& inboxes,
     383             :       Parallel::GlobalCache<Metavariables>& cache,
     384             :       const ArrayIndex& /*array_index*/, const ActionList /*meta*/,
     385             :       const ParallelComponent* const /*meta*/) {
     386             :     if (not db::get<evolution::dg::subcell::Tags::SubcellOptions<Dim>>(box)
     387             :                 .enable_extension_directions()) {
     388             :       // We are not using extension directions, so just continue without doing
     389             :       // any work.
     390             :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     391             :     }
     392             : 
     393             :     const auto& extension_directions =
     394             :         db::get<evolution::dg::subcell::Tags::ExtensionDirections<Dim>>(box);
     395             :     if (extension_directions.empty()) {
     396             :       // For this element, we have no extension directions, so just
     397             :       // continue without doing any work.
     398             :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     399             :     }
     400             : 
     401             :     const Element<Dim>& element = db::get<::domain::Tags::Element<Dim>>(box);
     402             :     // Need to subtract number of problematic directions.
     403             :     const auto number_of_expected_messages =
     404             :         element.neighbors().size() - extension_directions.size();
     405             : 
     406             :     if (UNLIKELY(number_of_expected_messages == 0)) {
     407             :       // We have no neighbors, so just continue without doing any work.
     408             :       // Technically, this could also happen if all of the neighbors are in
     409             :       // problematic directions, but this case is unlikely to ever happen.
     410             :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     411             :     }
     412             : 
     413             :     std::unordered_set<DirectionalId<Dim>> expected_keys;
     414             :     for (const auto& internal_direction : element.internal_boundaries()) {
     415             :       if (not extension_directions.contains(internal_direction)) {
     416             :         // Note here, we assume that we have only one neighbor per
     417             :         // direction, so we can just take the first one.
     418             :         ASSERT(element.neighbors().at(internal_direction).ids().size() == 1,
     419             :                "Assumption one neighbor per direction failed. "
     420             :                "direction: "
     421             :                    << internal_direction << ", neighbors.size() = "
     422             :                    << element.neighbors().at(internal_direction).ids().size()
     423             :                    << ", element: " << element.id());
     424             :         expected_keys.emplace(
     425             :             internal_direction,
     426             :             *element.neighbors().at(internal_direction).ids().begin());
     427             :       }
     428             :     }
     429             : 
     430             :     const auto& interpolants = db::get<
     431             :         evolution::dg::subcell::Tags::InterpolatorsFromFdToNeighborFd<Dim>>(
     432             :         box);
     433             : 
     434             :     const auto& current_time_step_id = db::get<::Tags::TimeStepId>(box);
     435             :     auto& inbox =
     436             :         tuples::get<evolution::dg::Tags::BoundaryCorrectionAndGhostCellsInbox<
     437             :             Metavariables::volume_dim,
     438             :             Parallel::is_dg_element_collection_v<ParallelComponent>>>(inboxes);
     439             :     inbox.collect_messages();
     440             :     const auto received = inbox.messages.find(current_time_step_id);
     441             :     // Check we have at least some data from correct time, and then check
     442             :     // we have received all data
     443             :     if (received == inbox.messages.end()) {
     444             :       inbox.set_missing_messages(expected_keys.size());
     445             :       return {Parallel::AlgorithmExecution::Retry, std::nullopt};
     446             :     }
     447             :     if (const auto found = static_cast<size_t>(
     448             :             alg::count_if(received->second,
     449             :                           [&expected_keys](const auto& message) {
     450             :                             return expected_keys.contains(message.first);
     451             :                           }));
     452             :         found < expected_keys.size()) {
     453             :       inbox.set_missing_messages(expected_keys.size() - found);
     454             :       return {Parallel::AlgorithmExecution::Retry, std::nullopt};
     455             :     }
     456             : 
     457             :     const size_t ghost_zone_size =
     458             :         Metavariables::SubcellOptions::ghost_zone_size(box);
     459             :     const Mesh<Dim>& dg_mesh = db::get<::domain::Tags::Mesh<Dim>>(box);
     460             :     const Mesh<Dim>& subcell_mesh = db::get<Tags::Mesh<Dim>>(box);
     461             :     const Index<Dim>& subcell_extents = subcell_mesh.extents();
     462             : 
     463             :     const auto& received_data = received->second;
     464             :     ASSERT(received_data.size() >= number_of_expected_messages,
     465             :            "received_data size: " << received_data.size()
     466             :                                   << " less than expected number of messages: "
     467             :                                   << number_of_expected_messages << " !");
     468             : 
     469             :     using flux_variables = typename Metavariables::system::flux_variables;
     470             :     const auto& cell_centered_flux =
     471             :         db::get<Tags::CellCenteredFlux<flux_variables, Dim>>(box);
     472             :     DataVector volume_data_to_slice = db::mutate_apply(
     473             :         GhostDataMutator{}, make_not_null(&box),
     474             :         cell_centered_flux.has_value() ? cell_centered_flux.value().size()
     475             :                                        : 0_st);
     476             :     if (cell_centered_flux.has_value()) {
     477             :       std::copy(
     478             :           cell_centered_flux.value().data(),
     479             :           std::next(
     480             :               cell_centered_flux.value().data(),
     481             :               static_cast<std::ptrdiff_t>(cell_centered_flux.value().size())),
     482             :           std::next(
     483             :               volume_data_to_slice.data(),
     484             :               static_cast<std::ptrdiff_t>(volume_data_to_slice.size() -
     485             :                                           cell_centered_flux.value().size())));
     486             :     }
     487             : 
     488             :     auto& receiver_proxy =
     489             :         Parallel::get_parallel_component<ParallelComponent>(cache);
     490             :     const RdmpTciData& rdmp_tci_data = db::get<Tags::DataForRdmpTci>(box);
     491             :     const TimeStepId& time_step_id = db::get<::Tags::TimeStepId>(box);
     492             :     const TimeStepId& next_time_step_id =
     493             :         db::get<::Tags::Next<::Tags::TimeStepId>>(box);
     494             : 
     495             :     const int tci_decision =
     496             :         db::get<evolution::dg::subcell::Tags::TciDecision>(box);
     497             :     using history_tags = ::Tags::get_all_history_tags<DbTags>;
     498             :     static_assert(tmpl::size<history_tags>::value == 1);
     499             :     const auto& integration_order =
     500             :         db::get<tmpl::front<history_tags>>(box).integration_order();
     501             : 
     502             :     const size_t number_of_points = subcell_mesh.extents().product();
     503             :     // Number of independent components per grid point.
     504             :     const size_t number_of_components =
     505             :         volume_data_to_slice.size() / number_of_points;
     506             : 
     507             :     // For each problematic direction (a direction for which the element
     508             :     // cannot directly provide ghost data):
     509             :     // 1. Receive ghost data from our neighbor in the direction specified by
     510             :     // extension_direction.direction_to_extend.
     511             :     // 2. Use this data to extend our own volume data, then interpolate to the
     512             :     // required ghost points.
     513             :     // 3. Send the final ghost data to the neighbor at problematic_direction.
     514             :     for (const auto& [problematic_direction, extension_direction] :
     515             :          extension_directions) {
     516             :       // Direction to extend the volume data.
     517             :       const Direction<Dim> direction_to_extend =
     518             :           extension_direction.direction_to_extend;
     519             : 
     520             :       // Check that direction_to_extend is not a problematic direction.
     521             :       ASSERT(problematic_direction != direction_to_extend,
     522             :              "The direction to extend must not be a problematic direction. "
     523             :              "problematic_direction: "
     524             :                  << problematic_direction
     525             :                  << ", direction_to_extend: " << direction_to_extend);
     526             : 
     527             :       // Only one neighbor per direction.
     528             :       ASSERT(element.neighbors().at(direction_to_extend).ids().size() == 1,
     529             :              "Assumption one neighbor per direction failed. "
     530             :              "direction_to_extend: "
     531             :                  << direction_to_extend << ", neighbors.size() = "
     532             :                  << element.neighbors().at(direction_to_extend).ids().size()
     533             :                  << ", element: " << element.id());
     534             : 
     535             :       // Only internal boundary for the extension.
     536             :       ASSERT(element.internal_boundaries().contains(direction_to_extend),
     537             :              "Direction to extend not an internal boundary! "
     538             :              "dir_to_extend = "
     539             :                  << direction_to_extend << ", element.internal_boundaries() = "
     540             :                  << element.internal_boundaries()
     541             :                  << ", element = " << element.id());
     542             : 
     543             :       // Again, we are assuming that we have only one neighbor per direction,
     544             :       // and we are just taking the first one.
     545             :       const ElementId<Dim> relevant_neighbor_id =
     546             :           *((element.neighbors()).at(direction_to_extend).ids().begin());
     547             : 
     548             :       const auto received_data_for_direction_it =
     549             :           alg::find_if(received_data, [&](const auto& entry) {
     550             :             return entry.first == DirectionalId<Dim>{direction_to_extend,
     551             :                                                      relevant_neighbor_id};
     552             :           });
     553             :       // Received data must have entry for direction to extend.
     554             :       ASSERT(received_data_for_direction_it != received_data.end(),
     555             :              "Received data missing entry for direction to extend."
     556             :                  << " direction_to_extend = " << direction_to_extend
     557             :                  << ", relevant_neighbor_id = " << relevant_neighbor_id
     558             :                  << ", problematic_direction = " << problematic_direction
     559             :                  << ", element = " << element.id());
     560             :       const auto& received_data_for_direction =
     561             :           received_data_for_direction_it->second;
     562             : 
     563             :       // Received data must have received ghost data for this extension
     564             :       // direction.
     565             :       ASSERT(received_data_for_direction.ghost_cell_data.has_value(),
     566             :              "Ghost data missing for this extension direction."
     567             :                  << " direction_to_extend = " << direction_to_extend
     568             :                  << ", relevant_neighbor_id = " << relevant_neighbor_id
     569             :                  << ", problematic_direction = " << problematic_direction
     570             :                  << ", element = " << element.id());
     571             : 
     572             :       const ElementId<Dim> problematic_neighbor_id =
     573             :           *((element.neighbors()).at(problematic_direction).ids().begin());
     574             : 
     575             :       const auto& orientation = (element.neighbors())
     576             :                                     .at(problematic_direction)
     577             :                                     .orientation(problematic_neighbor_id);
     578             :       const auto direction_from_neighbor =
     579             :           orientation(problematic_direction.opposite());
     580             : 
     581             :       const size_t rdmp_size = rdmp_tci_data.max_variables_values.size() +
     582             :                                rdmp_tci_data.min_variables_values.size();
     583             : 
     584             :       const DataVector& full_ghost_cell_data =
     585             :           received_data_for_direction.ghost_cell_data.value();
     586             :       const size_t relevant_ghost_data_size =
     587             :           full_ghost_cell_data.size() - rdmp_size;
     588             : 
     589             :       const DataVector relevant_ghost_data;
     590             :       make_const_view(make_not_null(&relevant_ghost_data), full_ghost_cell_data,
     591             :                       0, relevant_ghost_data_size);
     592             : 
     593             :       const auto& interpolant =
     594             :           (interpolants.at(DirectionalId<Dim>{problematic_direction,
     595             :                                               problematic_neighbor_id}))
     596             :               .value();
     597             :       const DataVector combined_data = combine_volume_ghost_data(
     598             :           volume_data_to_slice, relevant_ghost_data, subcell_extents,
     599             :           ghost_zone_size, direction_to_extend);
     600             :       const size_t result_size =
     601             :           ghost_zone_size * subcell_mesh.extents()
     602             :                                 .slice_away(problematic_direction.dimension())
     603             :                                 .product();
     604             :       const size_t span_size = result_size * number_of_components;
     605             : 
     606             :       DataVector subcell_data_to_send{span_size + rdmp_size};
     607             : 
     608             :       auto result_span = gsl::make_span(subcell_data_to_send.data(), span_size);
     609             :       interpolant.interpolate(
     610             :           make_not_null(&result_span),
     611             :           gsl::make_span(combined_data.data(), combined_data.size()));
     612             : 
     613             :       // Copy rdmp data to end of subcell_data_to_send
     614             :       std::copy(
     615             :           rdmp_tci_data.max_variables_values.cbegin(),
     616             :           rdmp_tci_data.max_variables_values.cend(),
     617             :           std::prev(subcell_data_to_send.end(), static_cast<int>(rdmp_size)));
     618             :       std::copy(rdmp_tci_data.min_variables_values.cbegin(),
     619             :                 rdmp_tci_data.min_variables_values.cend(),
     620             :                 std::prev(subcell_data_to_send.end(),
     621             :                           static_cast<int>(
     622             :                               rdmp_tci_data.min_variables_values.size())));
     623             :       evolution::dg::BoundaryData<Dim> data{
     624             :           dg_mesh,      subcell_mesh,
     625             :           std::nullopt, std::move(subcell_data_to_send),
     626             :           std::nullopt, next_time_step_id,
     627             :           tci_decision, integration_order};
     628             :       Parallel::receive_data<
     629             :           evolution::dg::Tags::BoundaryCorrectionAndGhostCellsInbox<
     630             :               Dim, Parallel::is_dg_element_collection_v<ParallelComponent>>>(
     631             :           receiver_proxy[problematic_neighbor_id], time_step_id,
     632             :           std::pair{DirectionalId<Dim>{direction_from_neighbor, element.id()},
     633             :                     std::move(data)});
     634             :     }
     635             :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     636             :   }
     637             : };
     638             : /*!
     639             :  * \brief Receive the subcell data from our neighbor, and accumulate the data
     640             :  * from the relaxed discrete maximum principle troubled-cell indicator.
     641             :  *
     642             :  * Note:
     643             :  * - Since we only care about the min/max over all neighbors and ourself at the
     644             :  *   past time, we accumulate all data immediately into the `RdmpTciData`.
     645             :  * - If the neighbor is using DG and therefore sends boundary correction data
     646             :  *   then that is added into the `evolution::dg::Tags::MortarData` tag
     647             :  * - The next `TimeStepId` is recorded, but we do not yet support local time
     648             :  *   stepping.
     649             :  * - This action will never care about what variables are sent for
     650             :  *   reconstruction. It is only responsible for receiving the data and storing
     651             :  *   it in the `NeighborData`.
     652             :  *
     653             :  * GlobalCache:
     654             :  * -Uses: nothing
     655             :  *
     656             :  * DataBox:
     657             :  * - Uses:
     658             :  *   - `domain::Tags::Element<Dim>`
     659             :  *   - `Tags::TimeStepId`
     660             :  *   - `domain::Tags::Mesh<Dim>`
     661             :  *   - `subcell::Tags::Mesh<Dim>`
     662             :  *   - `domain::Tags::Element<Dim>`
     663             :  *   - `Tags::Next<Tags::TimeStepId>`
     664             :  *   - `subcell::Tags::ActiveGrid`
     665             :  *   - `System::variables_tag`
     666             :  * - Adds: nothing
     667             :  * - Removes: nothing
     668             :  * - Modifies:
     669             :  *   - `subcell::Tags::GhostDataForReconstruction<Dim>`
     670             :  *   - `subcell::Tags::DataForRdmpTci`
     671             :  *   - `evolution::dg::Tags::MortarData`
     672             :  *   - `evolution::dg::Tags::MortarNextTemporalId`
     673             :  */
     674             : template <size_t Dim>
     675           1 : struct ReceiveDataForReconstruction {
     676             :   template <typename DbTags, typename... InboxTags, typename ArrayIndex,
     677             :             typename ActionList, typename ParallelComponent,
     678             :             typename Metavariables>
     679           0 :   static Parallel::iterable_action_return_t apply(
     680             :       db::DataBox<DbTags>& box, tuples::TaggedTuple<InboxTags...>& inboxes,
     681             :       const Parallel::GlobalCache<Metavariables>& /*cache*/,
     682             :       const ArrayIndex& /*array_index*/, const ActionList /*meta*/,
     683             :       const ParallelComponent* const /*meta*/) {
     684             :     const Element<Dim>& element = db::get<::domain::Tags::Element<Dim>>(box);
     685             :     const auto number_of_expected_messages = element.neighbors().size();
     686             :     if (UNLIKELY(number_of_expected_messages == 0)) {
     687             :       // We have no neighbors, so just continue without doing any work
     688             :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     689             :     }
     690             : 
     691             :     using ::operator<<;
     692             :     const auto& current_time_step_id = db::get<::Tags::TimeStepId>(box);
     693             :     auto& inbox =
     694             :         tuples::get<evolution::dg::Tags::BoundaryCorrectionAndGhostCellsInbox<
     695             :             Metavariables::volume_dim,
     696             :             Parallel::is_dg_element_collection_v<ParallelComponent>>>(inboxes);
     697             :     inbox.collect_messages();
     698             :     const auto received = inbox.messages.find(current_time_step_id);
     699             :     // Check we have at least some data from correct time, and then check that
     700             :     // we have received all data
     701             :     if (received == inbox.messages.end()) {
     702             :       inbox.set_missing_messages(number_of_expected_messages);
     703             :       return {Parallel::AlgorithmExecution::Retry, std::nullopt};
     704             :     }
     705             :     if (received->second.size() != number_of_expected_messages) {
     706             :       inbox.set_missing_messages(number_of_expected_messages -
     707             :                                  received->second.size());
     708             :       return {Parallel::AlgorithmExecution::Retry, std::nullopt};
     709             :     }
     710             : 
     711             :     // Now that we have received all the data, copy it over as needed.
     712             :     auto received_data = std::move(received->second);
     713             :     inbox.messages.erase(received);
     714             : 
     715             :     const Mesh<Dim>& subcell_mesh = db::get<Tags::Mesh<Dim>>(box);
     716             :     const auto& mortar_meshes = get<evolution::dg::Tags::MortarMesh<Dim>>(box);
     717             : 
     718             :     db::mutate<Tags::GhostDataForReconstruction<Dim>, Tags::DataForRdmpTci,
     719             :                evolution::dg::Tags::MortarData<Dim>,
     720             :                evolution::dg::Tags::MortarNextTemporalId<Dim>,
     721             :                domain::Tags::NeighborMesh<Dim>,
     722             :                evolution::dg::subcell::Tags::MeshForGhostData<Dim>,
     723             :                evolution::dg::subcell::Tags::NeighborTciDecisions<Dim>>(
     724             :         [&element,
     725             :          ghost_zone_size = Metavariables::SubcellOptions::ghost_zone_size(box),
     726             :          &received_data, &subcell_mesh, &mortar_meshes](
     727             :             const gsl::not_null<DirectionalIdMap<Dim, GhostData>*>
     728             :                 ghost_data_ptr,
     729             :             const gsl::not_null<RdmpTciData*> rdmp_tci_data_ptr,
     730             :             const gsl::not_null<
     731             :                 DirectionalIdMap<Dim, evolution::dg::MortarDataHolder<Dim>>*>
     732             :                 mortar_data,
     733             :             const gsl::not_null<DirectionalIdMap<Dim, TimeStepId>*>
     734             :                 mortar_next_time_step_id,
     735             :             const gsl::not_null<DirectionalIdMap<Dim, Mesh<Dim>>*>
     736             :                 neighbor_mesh,
     737             :             const gsl::not_null<DirectionalIdMap<Dim, Mesh<Dim>>*>
     738             :                 mesh_for_ghost_data,
     739             :             const auto neighbor_tci_decisions,
     740             :             const DirectionalIdMap<Dim, std::optional<intrp::Irregular<Dim>>>&
     741             :                 neighbor_dg_to_fd_interpolants) {
     742             :           // Remove neighbor meshes for neighbors that don't exist anymore
     743             :           domain::remove_nonexistent_neighbors(neighbor_mesh, element);
     744             :           domain::remove_nonexistent_neighbors(mesh_for_ghost_data, element);
     745             : 
     746             :           // Get the next time step id, and also the fluxes data if the neighbor
     747             :           // is doing DG.
     748             :           for (auto& received_mortar_data : received_data) {
     749             :             const auto& mortar_id = received_mortar_data.first;
     750             :             ASSERT(element.face_types().at(mortar_id.direction()) !=
     751             :                        domain::FaceType::MultipleNonconforming,
     752             :                    "This code needs updating to handle nonconforming blocks");
     753             :             try {
     754             :               mortar_next_time_step_id->at(mortar_id) =
     755             :                   received_mortar_data.second.validity_range;
     756             :             } catch (std::exception& e) {
     757             :               ERROR("Failed retrieving the MortarId: ("
     758             :                     << mortar_id.direction() << ',' << mortar_id.id()
     759             :                     << ") from the mortar_next_time_step_id. Got exception: "
     760             :                     << e.what());
     761             :             }
     762             :             if (received_mortar_data.second.boundary_correction_data
     763             :                     .has_value()) {
     764             :               mortar_data->at(mortar_id).neighbor().face_mesh =
     765             :                   received_mortar_data.second.volume_mesh.slice_away(
     766             :                       mortar_id.direction().dimension());
     767             :               mortar_data->at(mortar_id).neighbor().mortar_mesh =
     768             :                   mortar_meshes.at(mortar_id);
     769             :               mortar_data->at(mortar_id).neighbor().mortar_data = std::move(
     770             :                   *received_mortar_data.second.boundary_correction_data);
     771             :             }
     772             :             // Set new neighbor mesh
     773             :             neighbor_mesh->insert_or_assign(
     774             :                 mortar_id, received_mortar_data.second.volume_mesh);
     775             :             mesh_for_ghost_data->insert_or_assign(
     776             :                 mortar_id, received_mortar_data.second
     777             :                                .volume_mesh_ghost_cell_data.value());
     778             :           }
     779             : 
     780             :           ASSERT(ghost_data_ptr->empty(),
     781             :                  "Should have no elements in the neighbor data when "
     782             :                  "receiving neighbor data");
     783             :           const size_t number_of_rdmp_vars =
     784             :               rdmp_tci_data_ptr->max_variables_values.size();
     785             :           ASSERT(rdmp_tci_data_ptr->min_variables_values.size() ==
     786             :                      number_of_rdmp_vars,
     787             :                  "The number of RDMP variables for which we have a maximum "
     788             :                  "and minimum should be the same, but we have "
     789             :                      << number_of_rdmp_vars << " for the max and "
     790             :                      << rdmp_tci_data_ptr->min_variables_values.size()
     791             :                      << " for the min.");
     792             : 
     793             :           for (const auto& [directional_element_id, boundary_data] :
     794             :                received_data) {
     795             :             ASSERT(ghost_data_ptr->count(directional_element_id) == 0,
     796             :                    "Found neighbor already inserted in direction "
     797             :                        << directional_element_id.direction()
     798             :                        << " with ElementId " << directional_element_id.id());
     799             :             ASSERT(boundary_data.ghost_cell_data.has_value(),
     800             :                    "Received subcell data message that does not contain any "
     801             :                    "actual subcell data for reconstruction.");
     802             :             // Collect the max/min of u(t^n) for the RDMP as we receive data.
     803             :             // This reduces the memory footprint.
     804             : 
     805             :             evolution::dg::subcell::insert_neighbor_rdmp_and_volume_data(
     806             :                 rdmp_tci_data_ptr, ghost_data_ptr,
     807             :                 *boundary_data.ghost_cell_data, number_of_rdmp_vars,
     808             :                 directional_element_id,
     809             :                 mesh_for_ghost_data->at(directional_element_id), element,
     810             :                 subcell_mesh, ghost_zone_size, neighbor_dg_to_fd_interpolants,
     811             :                 typename Metavariables::SubcellOptions::GhostVariables::
     812             :                     ghost_variables_tag_list{});
     813             :             if (neighbor_tci_decisions->contains(directional_element_id)) {
     814             :               neighbor_tci_decisions->at(directional_element_id) =
     815             :                   boundary_data.tci_status;
     816             :             } else {
     817             :               // TCI decisions for MultipleNonconforming neighbors are not
     818             :               // tracked because those elements are forced to remain on DG.
     819             :               ASSERT(
     820             :                   element.face_types().at(directional_element_id.direction()) ==
     821             :                       domain::FaceType::MultipleNonconforming,
     822             :                   "NeighborTciDecisions does not contain the neighbor ("
     823             :                       << directional_element_id.direction() << ", "
     824             :                       << directional_element_id.id()
     825             :                       << ") but the face is not MultipleNonconforming. "
     826             :                          "This indicates a bug in the initialization of "
     827             :                          "NeighborTciDecisions.");
     828             :             }
     829             :           }
     830             :         },
     831             :         make_not_null(&box),
     832             :         db::get<
     833             :             evolution::dg::subcell::Tags::InterpolatorsFromNeighborDgToFd<Dim>>(
     834             :             box));
     835             :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     836             :   }
     837             : };
     838             : }  // namespace evolution::dg::subcell::Actions

Generated by: LCOV version 1.14