SpECTRE Documentation Coverage Report
Current view: top level - Evolution/DgSubcell/Actions - TciAndSwitchToDg.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 33.3 % 3 1
Test Date: 2026-10-22 17:29:42
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 <cstddef>
       7              : #include <deque>
       8              : #include <optional>
       9              : #include <tuple>
      10              : #include <type_traits>
      11              : #include <utility>
      12              : 
      13              : #include "DataStructures/DataBox/DataBox.hpp"
      14              : #include "DataStructures/DataBox/PrefixHelpers.hpp"
      15              : #include "DataStructures/DataBox/Prefixes.hpp"
      16              : #include "DataStructures/Variables.hpp"
      17              : #include "DataStructures/VariablesTag.hpp"
      18              : #include "Domain/Structure/Direction.hpp"
      19              : #include "Domain/Structure/ElementId.hpp"
      20              : #include "Domain/Tags.hpp"
      21              : #include "Evolution/DgSubcell/ActiveGrid.hpp"
      22              : #include "Evolution/DgSubcell/RdmpTci.hpp"
      23              : #include "Evolution/DgSubcell/RdmpTciData.hpp"
      24              : #include "Evolution/DgSubcell/Reconstruction.hpp"
      25              : #include "Evolution/DgSubcell/ReconstructionMethod.hpp"
      26              : #include "Evolution/DgSubcell/SubcellOptions.hpp"
      27              : #include "Evolution/DgSubcell/Tags/ActiveGrid.hpp"
      28              : #include "Evolution/DgSubcell/Tags/CellCenteredFlux.hpp"
      29              : #include "Evolution/DgSubcell/Tags/DataForRdmpTci.hpp"
      30              : #include "Evolution/DgSubcell/Tags/DidRollback.hpp"
      31              : #include "Evolution/DgSubcell/Tags/GhostDataForReconstruction.hpp"
      32              : #include "Evolution/DgSubcell/Tags/Mesh.hpp"
      33              : #include "Evolution/DgSubcell/Tags/StepsSinceTciCall.hpp"
      34              : #include "Evolution/DgSubcell/Tags/SubcellOptions.hpp"
      35              : #include "Evolution/DgSubcell/Tags/TciCallsSinceRollback.hpp"
      36              : #include "Evolution/DgSubcell/Tags/TciGridHistory.hpp"
      37              : #include "Evolution/DgSubcell/Tags/TciStatus.hpp"
      38              : #include "Evolution/DiscontinuousGalerkin/OnlyDgBlockIds.hpp"
      39              : #include "NumericalAlgorithms/Spectral/Mesh.hpp"
      40              : #include "Parallel/AlgorithmExecution.hpp"
      41              : #include "Time/History.hpp"
      42              : #include "Time/TimeStepId.hpp"
      43              : #include "Utilities/Algorithm.hpp"
      44              : #include "Utilities/ContainerHelpers.hpp"
      45              : #include "Utilities/ErrorHandling/Assert.hpp"
      46              : #include "Utilities/Gsl.hpp"
      47              : #include "Utilities/TMPL.hpp"
      48              : 
      49              : /// \cond
      50              : namespace Parallel {
      51              : template <typename Metavariables>
      52              : class GlobalCache;
      53              : }  // namespace Parallel
      54              : namespace Tags {
      55              : template <typename Tag>
      56              : struct HistoryEvolvedVariables;
      57              : struct TimeStepId;
      58              : template <typename StepperInterface>
      59              : struct TimeStepper;
      60              : }  // namespace Tags
      61              : class TimeStepper;
      62              : namespace tuples {
      63              : template <typename...>
      64              : class TaggedTuple;
      65              : }  // namespace tuples
      66              : /// \endcond
      67              : 
      68              : namespace evolution::dg::subcell::Actions {
      69              : /*!
      70              :  * \brief Run the troubled-cell indicator on the subcell solution to see if it
      71              :  * is safe to switch back to DG.
      72              :  *
      73              :  * In terms of the DG-subcell/FD hybrid solver, this action is run after the FD
      74              :  * step has calculated the solution at \f$t^{n+1}\f$. At this point we check if
      75              :  * the FD solution at the new time \f$t^{n+1}\f$ is representable on the DG
      76              :  * grid.
      77              :  *
      78              :  * The algorithm proceeds as follows:
      79              :  * 1. If we are using a substep time integrator and are not at the end of a
      80              :  *    step, or we are in the self-starting stage of a multistep method, or the
      81              :  *    `subcell_options.always_use_subcells() == true`, then we do not run any
      82              :  *    TCI or try to go back to DG. We need to avoid reconstructing (in the sense
      83              :  *    of the inverse of projecting the DG solution to the subcells) the time
      84              :  *    stepper history if there are shocks present in the history, and for
      85              :  *    substep methods this is most easily handled by only switching back at the
      86              :  *    end of a full time step. During the self-start phase of the multistep time
      87              :  *    integrators we integrate over the same region of time at increasingly
      88              :  *    higher order, which means if we were on subcell "previously" (since we use
      89              :  *    a forward-in-time self-start method the time history is actually in the
      90              :  *    future of the current step) then we will very likely need to again switch
      91              :  *    to subcell.
      92              :  * 2. Reconstruct the subcell solution to the DG grid.
      93              :  * 3. Run the relaxed discrete maximum principle (RDMP) troubled-cell indicator
      94              :  *    (TCI), checking both the subcell solution at \f$t^{n+1}\f$ and the
      95              :  *    reconstructed DG solution at \f$t^{n+1}\f$ to make sure they are
      96              :  *    admissible.
      97              :  * 4. If the RDMP TCI marked the DG solution as admissible, run the
      98              :  *    user-specified mutator TCI `TciMutator`.
      99              :  * 5. If the cell is not troubled, and the time integrator type is substep or
     100              :  *    the TCI history indicates the entire history for the multistep method is
     101              :  *    free of discontinuities, then we can switch back to DG. Switching back to
     102              :  *    DG requires reconstructing dg variables, reconstructing the time stepper
     103              :  *    history, marking the active grid as `ActiveGrid::Dg`, and clearing the
     104              :  *    subcell neighbor data.
     105              :  * 6. If we are not using a substep method, then record the TCI decision in the
     106              :  *    `subcell::Tags::TciGridHistory`.
     107              :  *
     108              :  * \note Unlike `Actions::TciAndRollback`, this action does _not_ jump back to
     109              :  * `Labels::BeginDg`. This is because users may add actions after a time step
     110              :  * has been completed. In that sense, it may be more proper to actually check
     111              :  * the TCI and switch back to DG at the start of the step rather than the end.
     112              :  *
     113              :  * \note This action always sets `subcell::Tags::DidRollback` to `false` at the
     114              :  * very beginning since this action is called after an FD step has completed.
     115              :  *
     116              :  * \note If `subcell::Tags::DidRollback=True` i.e. the grid has been switched
     117              :  * from DG to FD in the current time step by preceding actions, this action is
     118              :  * skipped except setting `DidRollback` to `false`. Stated differently, if an
     119              :  * element switched from DG to FD it needs to remain at least one time step on
     120              :  * the FD grid.
     121              :  *
     122              :  * GlobalCache:
     123              :  * - Uses:
     124              :  *   - `subcell::Tags::SubcellOptions`
     125              :  *
     126              :  * DataBox:
     127              :  * - Uses:
     128              :  *   - `domain::Tags::Mesh<Dim>`
     129              :  *   - `subcell::Tags::Mesh<Dim>`
     130              :  *   - `Tags::TimeStepId`
     131              :  *   - `subcell::Tags::ActiveGrid`
     132              :  *   - `subcell::Tags::DataForRdmpTci`
     133              :  *   - `subcell::Tags::TciGridHistory`
     134              :  * - Adds: nothing
     135              :  * - Removes: nothing
     136              :  * - Modifies:
     137              :  *   - `System::variables_tag` if the cell is not troubled
     138              :  *   - `Tags::HistoryEvolvedVariables` if the cell is not troubled
     139              :  *   - `subcell::Tags::ActiveGrid` if the cell is not troubled
     140              :  *   - `subcell::Tags::DidRollback` sets to `false`
     141              :  *   - `subcell::Tags::TciDecision` is set to an integer value according to the
     142              :  *     return of TciMutator.
     143              :  *   - `subcell::Tags::GhostDataForReconstruction<Dim>`
     144              :  *     if the cell is not troubled
     145              :  *   - `subcell::Tags::TciGridHistory` if the time stepper is a multistep method
     146              :  */
     147              : template <typename TciMutator>
     148            1 : struct TciAndSwitchToDg {
     149              :   template <typename DbTags, typename... InboxTags, typename Metavariables,
     150              :             typename ArrayIndex, typename ActionList,
     151              :             typename ParallelComponent, size_t Dim = Metavariables::volume_dim>
     152            0 :   static Parallel::iterable_action_return_t apply(
     153              :       db::DataBox<DbTags>& box,
     154              :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     155              :       const Parallel::GlobalCache<Metavariables>& /*cache*/,
     156              :       const ArrayIndex& /*array_index*/, const ActionList /*meta*/,
     157              :       const ParallelComponent* const /*meta*/) {
     158              :     static_assert(
     159              :         tmpl::count_if<
     160              :             ActionList,
     161              :             std::is_same<tmpl::_1, tmpl::pin<TciAndSwitchToDg>>>::value == 1,
     162              :         "Must have the TciAndSwitchToDg action exactly once in the action list "
     163              :         "of a phase.");
     164              : 
     165              :     using variables_tag = typename Metavariables::system::variables_tag;
     166              :     using flux_variables = typename Metavariables::system::flux_variables;
     167              : 
     168              :     ASSERT(db::get<Tags::ActiveGrid>(box) == ActiveGrid::Subcell,
     169              :            "Must be using subcells when calling TciAndSwitchToDg action.");
     170              :     const Mesh<Dim>& dg_mesh = db::get<::domain::Tags::Mesh<Dim>>(box);
     171              :     const Mesh<Dim>& subcell_mesh = db::get<subcell::Tags::Mesh<Dim>>(box);
     172              :     const SubcellOptions& subcell_options =
     173              :         db::get<Tags::SubcellOptions<Dim>>(box);
     174              :     const TimeStepId& time_step_id = db::get<::Tags::TimeStepId>(box);
     175              : 
     176              :     // This should never be run if we are prohibited from using subcell on this
     177              :     // element.
     178              :     ASSERT(not alg::found(
     179              :                db::get<evolution::dg::Tags::OnlyDgBlockIds<Dim>>(box),
     180              :                db::get<::domain::Tags::Element<Dim>>(box).id().block_id()),
     181              :            "Should never use subcell on element "
     182              :                << db::get<domain::Tags::Element<Dim>>(box).id());
     183              : 
     184              :     // The first condition is that for substep time integrators we only allow
     185              :     // switching back to DG on step boundaries. This is the easiest way to
     186              :     // avoid having a shock in the time stepper history, since there is no
     187              :     // history at step boundaries.
     188              :     //
     189              :     // The second condition is that if we are in the self-start procedure of
     190              :     // the time stepper, and we don't want to switch from subcell back to DG
     191              :     // during self-start since we integrate over the same temporal region at
     192              :     // increasingly higher order.
     193              :     //
     194              :     // The third condition is that the user has requested we always do
     195              :     // subcell, so effectively a finite difference/volume code.
     196              :     const bool only_need_rdmp_data =
     197              :         db::get<subcell::Tags::DidRollback>(box) or
     198              :         (time_step_id.substep() != 0 or time_step_id.slab_number() < 0)
     199              : 
     200              :         or db::get<evolution::dg::subcell::Tags::StepsSinceTciCall>(box) + 1 <
     201              :                subcell_options.number_of_steps_between_tci_calls();
     202              :     if (UNLIKELY(db::get<subcell::Tags::DidRollback>(box))) {
     203              :       db::mutate<subcell::Tags::DidRollback>(
     204              :           [](const gsl::not_null<bool*> did_rollback) {
     205              :             *did_rollback = false;
     206              :           },
     207              :           make_not_null(&box));
     208              :     }
     209              : 
     210              :     if (subcell_options.always_use_subcells()) {
     211              :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     212              :     }
     213              : 
     214              :     std::tuple<int, evolution::dg::subcell::RdmpTciData> tci_result =
     215              :         db::mutate_apply<TciMutator>(make_not_null(&box),
     216              :                                      subcell_options.persson_exponent() + 1.0,
     217              :                                      only_need_rdmp_data);
     218              : 
     219              :     db::mutate<evolution::dg::subcell::Tags::DataForRdmpTci,
     220              :                evolution::dg::subcell::Tags::TciCallsSinceRollback,
     221              :                evolution::dg::subcell::Tags::StepsSinceTciCall>(
     222              :         [only_need_rdmp_data, &subcell_options, &tci_result](
     223              :             const auto rdmp_data_ptr,
     224              :             const gsl::not_null<size_t*> tci_calls_since_rollback_ptr,
     225              :             const gsl::not_null<size_t*> steps_since_tci_call_ptr) {
     226              :           *rdmp_data_ptr = std::move(std::get<1>(std::move(tci_result)));
     227              :           (*tci_calls_since_rollback_ptr) += (only_need_rdmp_data ? 0 : 1);
     228              :           if ((*steps_since_tci_call_ptr) + 1 <
     229              :               subcell_options.number_of_steps_between_tci_calls()) {
     230              :             (*steps_since_tci_call_ptr) += 1;
     231              :           } else {
     232              :             (*steps_since_tci_call_ptr) = 0;
     233              :           }
     234              :         },
     235              :         make_not_null(&box));
     236              : 
     237              :     // Use <= for TciCallsSinceRollback since we've already incremented the
     238              :     // count in a mutate call above. This means that if this is the first TCI
     239              :     // call after a rollback, TciCallsSinceRollback will be 1, not 0. We also
     240              :     // require that `subcell_options.min_tci_calls_after_rollback() >= 1`, so
     241              :     // 1 TCI call means only one step was taken after a rollback.
     242              :     if (only_need_rdmp_data or
     243              :         db::get<evolution::dg::subcell::Tags::TciCallsSinceRollback>(box) <=
     244              :             subcell_options.min_tci_calls_after_rollback()) {
     245              :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     246              :     }
     247              : 
     248              :     const int tci_decision = std::get<0>(tci_result);
     249              :     const bool cell_is_troubled =
     250              :         tci_decision != 0 or (subcell_options.use_halo() and [&box]() -> bool {
     251              :           for (const auto& [_, neighbor_decision] :
     252              :                db::get<evolution::dg::subcell::Tags::NeighborTciDecisions<Dim>>(
     253              :                    box)) {
     254              :             if (neighbor_decision != 0) {
     255              :               return true;
     256              :             }
     257              :           }
     258              :           return false;
     259              :         }());
     260              : 
     261              :     db::mutate<Tags::TciDecision>(
     262              :         [&tci_decision](const gsl::not_null<int*> tci_decision_ptr) {
     263              :           *tci_decision_ptr = tci_decision;
     264              :         },
     265              :         make_not_null(&box));
     266              : 
     267              :     // If the cell is not troubled, then we _might_ be able to switch back to
     268              :     // DG. This depends on the type of time stepper we are using:
     269              :     // - ADER: Not yet implemented, but here the TCI history is irrelevant
     270              :     //         because it is a one-step scheme, so we can always switch.
     271              :     // - Multistep: the TCI must have marked the entire evolved variable history
     272              :     //              as free of shocks. In practice for LMMs this means the TCI
     273              :     //              history is as long as the evolved variables history and that
     274              :     //              the entire TCI history is `ActiveGrid::Dg`.
     275              :     // - Substep: the easiest is to restrict switching back to DG to step
     276              :     //            boundaries where there is no history.
     277              :     const auto& time_stepper = db::get<::Tags::TimeStepper<TimeStepper>>(box);
     278              :     const bool is_substep_method = time_stepper.number_of_substeps() != 1;
     279              :     const bool is_multistep_method = time_stepper.number_of_past_steps() != 0;
     280              :     ASSERT(time_stepper.number_of_substeps() != 0,
     281              :            "Don't know how to handle a time stepper with zero substeps. This "
     282              :            "might be totally fine, but the case should be thought about.");
     283              :     ASSERT(subcell_options.min_clear_tci_before_dg() > 0,
     284              :            "The value of 'subcell_options.min_clear_tci_before_dg()' must be "
     285              :            "at least 1");
     286              :     if (const auto& tci_history = db::get<subcell::Tags::TciGridHistory>(box);
     287              :         not cell_is_troubled and
     288              : 
     289              :         (((is_substep_method and
     290              :            tci_history.size() >= subcell_options.min_clear_tci_before_dg() - 1)
     291              : 
     292              :           or
     293              : 
     294              :           (is_multistep_method and
     295              :            tci_history.size() ==
     296              :                std::max(subcell_options.min_clear_tci_before_dg(),
     297              :                         // Use `number_of_past_steps()` instead of
     298              :                         // `number_of_past_steps()+number_of_substeps()`
     299              :                         // since we only perform this check if we are on
     300              :                         // substep 0 (i.e. starting a new step).
     301              :                         time_stepper.number_of_past_steps() + 1)))
     302              : 
     303              :          and alg::all_of(tci_history,
     304              :                          [](const ActiveGrid tci_grid_decision) {
     305              :                            return tci_grid_decision == ActiveGrid::Dg;
     306              :                          })
     307              : 
     308              :              )) {
     309              :       db::mutate<
     310              :           variables_tag, ::Tags::HistoryEvolvedVariables<variables_tag>,
     311              :           Tags::ActiveGrid, subcell::Tags::GhostDataForReconstruction<Dim>,
     312              :           evolution::dg::subcell::Tags::TciGridHistory,
     313              :           evolution::dg::subcell::Tags::TciCallsSinceRollback,
     314              :           evolution::dg::subcell::Tags::CellCenteredFlux<flux_variables, Dim>>(
     315              :           [&dg_mesh, &subcell_mesh, &subcell_options](
     316              :               const auto active_vars_ptr, const auto active_history_ptr,
     317              :               const gsl::not_null<ActiveGrid*> active_grid_ptr,
     318              :               const auto subcell_ghost_data_ptr,
     319              :               const gsl::not_null<
     320              :                   std::deque<evolution::dg::subcell::ActiveGrid>*>
     321              :                   tci_grid_history_ptr,
     322              :               const gsl::not_null<size_t*> tci_calls_since_rollback_ptr,
     323              :               const auto subcell_cell_centered_fluxes) {
     324              :             // Note: strictly speaking, to be conservative this should
     325              :             // reconstruct uJ instead of u.
     326              :             *active_vars_ptr = fd::reconstruct(
     327              :                 *active_vars_ptr, dg_mesh, subcell_mesh.extents(),
     328              :                 subcell_options.reconstruction_method());
     329              : 
     330              :             // Reconstruct the DG solution for each time in the time stepper
     331              :             // history
     332              :             active_history_ptr->map_entries(
     333              :                 [&dg_mesh, &subcell_mesh, &subcell_options](const auto entry) {
     334              :                   *entry =
     335              :                       fd::reconstruct(*entry, dg_mesh, subcell_mesh.extents(),
     336              :                                       subcell_options.reconstruction_method());
     337              :                 });
     338              :             *active_grid_ptr = ActiveGrid::Dg;
     339              : 
     340              :             // Clear the neighbor data needed for subcell reconstruction since
     341              :             // we have now completed the time step.
     342              :             subcell_ghost_data_ptr->clear();
     343              : 
     344              :             // Clear the TCI grid history since we don't need to use it when on
     345              :             // the DG grid.
     346              :             tci_grid_history_ptr->clear();
     347              : 
     348              :             // Reset tci_calls_since_rollback
     349              :             *tci_calls_since_rollback_ptr = 0;
     350              : 
     351              :             // Clear the allocation for the cell-centered fluxes.
     352              :             *subcell_cell_centered_fluxes = std::nullopt;
     353              :           },
     354              :           make_not_null(&box));
     355              :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     356              :     }
     357              : 
     358              :     // We record the TCI history for a couple reasons.
     359              :     //
     360              :     // For multistep methods we track the TCI decision because we need the
     361              :     // discontinuity to clear the entire history before we can switch back to
     362              :     // DG.
     363              :     //
     364              :     // For substep methods tracking the TCI history allows us to stay on FD a
     365              :     // bit longer to allow the solution to smoothen a bit more before going
     366              :     // back to DG.
     367              :     db::mutate<evolution::dg::subcell::Tags::TciGridHistory>(
     368              :         [cell_is_troubled, &subcell_options, &time_stepper](
     369              :             const gsl::not_null<std::deque<evolution::dg::subcell::ActiveGrid>*>
     370              :                 tci_grid_history) {
     371              :           tci_grid_history->push_front(cell_is_troubled ? ActiveGrid::Subcell
     372              :                                                         : ActiveGrid::Dg);
     373              :           if (tci_grid_history->size() >
     374              :               std::max(subcell_options.min_clear_tci_before_dg() - 1,
     375              :                        // Use `number_of_past_steps()` instead of
     376              :                        // `number_of_past_steps()+number_of_substeps()`
     377              :                        // since we only perform this check if we are on
     378              :                        // substep 0 (i.e. starting a new step).
     379              :                        time_stepper.number_of_past_steps() + 1)) {
     380              :             tci_grid_history->pop_back();
     381              :           }
     382              :           ASSERT(tci_grid_history->size() <=
     383              :                      std::max(subcell_options.min_clear_tci_before_dg() - 1,
     384              :                               time_stepper.number_of_past_steps() + 1),
     385              :                  "The TCI history is not being correctly cleared. This is an "
     386              :                  "internal bug. Please file an issue.\n"
     387              :                  "tci_grid_history->size(): "
     388              :                      << tci_grid_history->size() << "\nExpected: "
     389              :                      << std::max(subcell_options.min_clear_tci_before_dg() - 1,
     390              :                                  time_stepper.number_of_past_steps() + 1));
     391              :         },
     392              :         make_not_null(&box));
     393              : 
     394              :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     395              :   }
     396              : };
     397              : }  // namespace evolution::dg::subcell::Actions
        

Generated by: LCOV version 2.0-1