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

Generated by: LCOV version 2.0-1