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 <cstddef>
8 : #include <deque>
9 : #include <iterator>
10 : #include <optional>
11 : #include <tuple>
12 : #include <type_traits>
13 : #include <utility>
14 :
15 : #include "DataStructures/DataBox/DataBox.hpp"
16 : #include "DataStructures/DataBox/PrefixHelpers.hpp"
17 : #include "DataStructures/DataBox/Prefixes.hpp"
18 : #include "DataStructures/DataVector.hpp"
19 : #include "Domain/Structure/Direction.hpp"
20 : #include "Domain/Structure/DirectionalId.hpp"
21 : #include "Domain/Structure/DirectionalIdMap.hpp"
22 : #include "Domain/Structure/Element.hpp"
23 : #include "Domain/Structure/ElementId.hpp"
24 : #include "Domain/Tags.hpp"
25 : #include "Evolution/DgSubcell/Actions/Labels.hpp"
26 : #include "Evolution/DgSubcell/ActiveGrid.hpp"
27 : #include "Evolution/DgSubcell/GhostData.hpp"
28 : #include "Evolution/DgSubcell/NeighborRdmpAndVolumeData.hpp"
29 : #include "Evolution/DgSubcell/Projection.hpp"
30 : #include "Evolution/DgSubcell/RdmpTci.hpp"
31 : #include "Evolution/DgSubcell/RdmpTciData.hpp"
32 : #include "Evolution/DgSubcell/SubcellOptions.hpp"
33 : #include "Evolution/DgSubcell/Tags/ActiveGrid.hpp"
34 : #include "Evolution/DgSubcell/Tags/Coordinates.hpp"
35 : #include "Evolution/DgSubcell/Tags/DataForRdmpTci.hpp"
36 : #include "Evolution/DgSubcell/Tags/DidRollback.hpp"
37 : #include "Evolution/DgSubcell/Tags/GhostDataForReconstruction.hpp"
38 : #include "Evolution/DgSubcell/Tags/Interpolators.hpp"
39 : #include "Evolution/DgSubcell/Tags/Mesh.hpp"
40 : #include "Evolution/DgSubcell/Tags/MeshForGhostData.hpp"
41 : #include "Evolution/DgSubcell/Tags/SubcellOptions.hpp"
42 : #include "Evolution/DgSubcell/Tags/TciStatus.hpp"
43 : #include "Evolution/DiscontinuousGalerkin/InboxTags.hpp"
44 : #include "NumericalAlgorithms/Interpolation/IrregularInterpolant.hpp"
45 : #include "NumericalAlgorithms/Spectral/Mesh.hpp"
46 : #include "Parallel/AlgorithmExecution.hpp"
47 : #include "Parallel/GlobalCache.hpp"
48 : #include "ParallelAlgorithms/Actions/Goto.hpp"
49 : #include "Time/Actions/SelfStartActions.hpp"
50 : #include "Time/History.hpp"
51 : #include "Utilities/Algorithm.hpp"
52 : #include "Utilities/ContainerHelpers.hpp"
53 : #include "Utilities/ErrorHandling/Assert.hpp"
54 : #include "Utilities/TMPL.hpp"
55 : #include "Utilities/TaggedTuple.hpp"
56 :
57 : /// \cond
58 : namespace Tags {
59 : template <typename Tag>
60 : struct HistoryEvolvedVariables;
61 : struct TimeStepId;
62 : } // namespace Tags
63 : /// \endcond
64 :
65 : namespace evolution::dg::subcell::Actions {
66 : /*!
67 : * \brief Run the troubled-cell indicator on the candidate solution and perform
68 : * the time step rollback if needed.
69 : *
70 : * Interior cells are marked as troubled if
71 : * `subcell_options.always_use_subcells()` is `true`, or if either the RDMP
72 : * troubled-cell indicator (TCI) or the TciMutator reports the cell is
73 : * troubled. Exterior cells are marked as troubled only if
74 : * `Metavariables::SubcellOptions::subcell_enabled_at_external_boundary` is
75 : * `true`.
76 : *
77 : * The troubled-cell indicator (TCI) given by the mutator `TciMutator` can
78 : * mutate tags in the DataBox, but should do so cautiously. The main reason that
79 : * this is a mutator is because primitive variables, such as the pressure, are
80 : * used to check if the solution is physical. In the relativistic case, even
81 : * just whether or not the primitive variables can be recovered is used as a
82 : * condition. Note that the evolved variables are projected to the subcells
83 : * _after_ the TCI is called and marks the cell as troubled.
84 : *
85 : * After rollback, the subcell scheme must project the DG boundary corrections
86 : * \f$G\f$ to the subcells for the scheme to be conservative. The subcell
87 : * actions know if a rollback was done because the local mortar data would
88 : * already be computed.
89 : */
90 : template <typename TciMutator>
91 1 : struct TciAndRollback {
92 : template <typename DbTags, typename... InboxTags, typename Metavariables,
93 : typename ArrayIndex, typename ActionList,
94 : typename ParallelComponent, size_t Dim = Metavariables::volume_dim>
95 0 : static Parallel::iterable_action_return_t apply(
96 : db::DataBox<DbTags>& box,
97 : const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
98 : const Parallel::GlobalCache<Metavariables>& /*cache*/,
99 : const ArrayIndex& /*array_index*/, const ActionList /*meta*/,
100 : const ParallelComponent* const /*meta*/) {
101 : static_assert(
102 : tmpl::count_if<
103 : ActionList,
104 : std::is_same<tmpl::_1, tmpl::pin<TciAndRollback>>>::value == 1,
105 : "Must have the TciAndRollback action exactly once in the action list "
106 : "of a phase.");
107 : static_assert(
108 : tmpl::count_if<
109 : ActionList,
110 : std::is_same<tmpl::_1,
111 : tmpl::pin<::Actions::Label<
112 : evolution::dg::subcell::Actions::Labels::
113 : BeginSubcellAfterDgRollback>>>>::value == 1,
114 : "Must have the BeginSubcellAfterDgRollback label exactly once in the "
115 : "action list of a phase.");
116 :
117 : using variables_tag = typename Metavariables::system::variables_tag;
118 :
119 : const ActiveGrid active_grid = db::get<Tags::ActiveGrid>(box);
120 : ASSERT(active_grid == ActiveGrid::Dg,
121 : "Must be using DG when calling TciAndRollback action.");
122 :
123 : const Element<Dim>& element = db::get<::domain::Tags::Element<Dim>>(box);
124 : const bool cell_has_external_boundary =
125 : not element.external_boundaries().empty();
126 :
127 : constexpr bool subcell_enabled_at_external_boundary =
128 : Metavariables::SubcellOptions::subcell_enabled_at_external_boundary;
129 : const Mesh<Dim>& dg_mesh = db::get<::domain::Tags::Mesh<Dim>>(box);
130 : const Mesh<Dim>& subcell_mesh = db::get<Tags::Mesh<Dim>>(box);
131 :
132 : const SubcellOptions& subcell_options =
133 : db::get<Tags::SubcellOptions<Dim>>(box);
134 : bool cell_is_troubled =
135 : subcell_options.always_use_subcells() or
136 : (subcell_options.use_halo() and [&box]() -> bool {
137 : for (const auto& [_, neighbor_decision] :
138 : db::get<evolution::dg::subcell::Tags::NeighborTciDecisions<Dim>>(
139 : box)) {
140 : if (neighbor_decision != 0) {
141 : return true;
142 : }
143 : }
144 : return false;
145 : }());
146 :
147 : // Loop over block neighbors and if neighbor id is inside of
148 : // subcell_options.only_dg_block_ids(), then bordering DG-only block
149 : const bool bordering_dg_block = alg::any_of(
150 : element.neighbors(),
151 : [&subcell_options](const auto& direction_and_neighbor) {
152 : const size_t first_block_id =
153 : direction_and_neighbor.second.ids().begin()->block_id();
154 : return alg::found(subcell_options.only_dg_block_ids(),
155 : first_block_id);
156 : });
157 :
158 : // Subcell is allowed in the element if 2 conditions are met:
159 : // (i) The current element block id is not marked as DG only
160 : // (ii) The current element is not bordering a DG only block.
161 : const bool subcell_allowed_in_element =
162 : not alg::found(subcell_options.only_dg_block_ids(),
163 : element.id().block_id()) and
164 : not bordering_dg_block;
165 :
166 : // The reason we pass in the persson_exponent explicitly instead of
167 : // leaving it to the user is because the value of the exponent that
168 : // should be used to decide if it is safe to switch back to DG should be
169 : // `persson_exponent+1` to prevent the code from rapidly switching back
170 : // and forth between DG and subcell. Rather than trying to enforce this
171 : // by documentation, the switching back to DG TCI gets passed in the
172 : // exponent it should use, and to keep the interface between the TCIs
173 : // consistent, we also pass the exponent in separately here.
174 : std::tuple<int, RdmpTciData> tci_result = db::mutate_apply<TciMutator>(
175 : make_not_null(&box), subcell_options.persson_exponent(),
176 : not subcell_allowed_in_element);
177 :
178 : const int tci_decision = std::get<0>(tci_result);
179 : db::mutate<Tags::TciDecision>(
180 : [&tci_decision](const gsl::not_null<int*> tci_decision_ptr) {
181 : *tci_decision_ptr = tci_decision;
182 : },
183 : make_not_null(&box));
184 :
185 : cell_is_troubled |= (tci_decision != 0);
186 :
187 : // If either:
188 : //
189 : // 1. we are not allowed to do subcell in this block
190 : // 2. the element is at an outer boundary _and_ we aren't allow to go to
191 : // subcell at an outer boundary.
192 : // 3. the cell is not troubled
193 : //
194 : // then we can remove the current neighbor data and update the RDMP TCI
195 : // data.
196 : if (not subcell_allowed_in_element or
197 : (cell_has_external_boundary and
198 : not subcell_enabled_at_external_boundary) or
199 : not cell_is_troubled) {
200 : db::mutate<subcell::Tags::GhostDataForReconstruction<Dim>,
201 : subcell::Tags::DataForRdmpTci>(
202 : [&tci_result](const auto neighbor_data_ptr,
203 : const gsl::not_null<RdmpTciData*> rdmp_tci_data_ptr) {
204 : neighbor_data_ptr->clear();
205 : *rdmp_tci_data_ptr = std::move(std::get<1>(std::move(tci_result)));
206 : },
207 : make_not_null(&box));
208 : return {Parallel::AlgorithmExecution::Continue, std::nullopt};
209 : }
210 :
211 : db::mutate<variables_tag, ::Tags::HistoryEvolvedVariables<variables_tag>,
212 : Tags::ActiveGrid, Tags::DidRollback,
213 : subcell::Tags::GhostDataForReconstruction<Dim>>(
214 : [&dg_mesh, &element, &subcell_mesh](
215 : const auto active_vars_ptr, const auto active_history_ptr,
216 : const gsl::not_null<ActiveGrid*> active_grid_ptr,
217 : const gsl::not_null<bool*> did_rollback_ptr,
218 : const gsl::not_null<DirectionalIdMap<Dim, GhostData>*>
219 : ghost_data_ptr,
220 : const DirectionalIdMap<Dim, Mesh<Dim>>& meshes_for_ghost_data,
221 : const size_t ghost_zone_size,
222 : const DirectionalIdMap<Dim, std::optional<intrp::Irregular<Dim>>>&
223 : neighbor_dg_to_fd_interpolants) {
224 : ASSERT(active_history_ptr->size() > 0,
225 : "We cannot have an empty history when unwinding, that's just "
226 : "nutty. Did you call the action too early in the action "
227 : "list?");
228 : // Rollback u^{n+1}* to u^n (undoing the candidate solution).
229 : //
230 : // Note: strictly speaking, to be conservative this should project
231 : // uJ instead of u.
232 : *active_vars_ptr = fd::project(active_history_ptr->latest_value(),
233 : dg_mesh, subcell_mesh.extents());
234 :
235 : // Project the time stepper history to the subcells, excluding the
236 : // most recent inadmissible history.
237 : active_history_ptr->undo_latest();
238 : active_history_ptr->map_entries(
239 : [&dg_mesh, &subcell_mesh](const auto entry) {
240 : *entry = fd::project(*entry, dg_mesh, subcell_mesh.extents());
241 : });
242 : *active_grid_ptr = ActiveGrid::Subcell;
243 : *did_rollback_ptr = true;
244 : // Project the neighbor data we were sent for reconstruction since
245 : // the neighbor might have sent DG volume data instead of ghost data
246 : // in order to elide projections when they aren't necessary.
247 : for (const auto& [directional_element_id, mesh_for_ghost_data] :
248 : meshes_for_ghost_data) {
249 : evolution::dg::subcell::insert_or_update_neighbor_volume_data<
250 : false>(ghost_data_ptr,
251 : ghost_data_ptr->at(directional_element_id)
252 : .neighbor_ghost_data_for_reconstruction(),
253 : 0, directional_element_id, mesh_for_ghost_data, element,
254 : subcell_mesh, ghost_zone_size,
255 : neighbor_dg_to_fd_interpolants);
256 : }
257 :
258 : // Note: We do _not_ project the boundary history here because
259 : // that needs to be done at the lifting stage of the subcell
260 : // method, since we need to lift G+D instead of the ingredients
261 : // that go into G+D, which is what we would be projecting here.
262 : },
263 : make_not_null(&box),
264 : db::get<evolution::dg::subcell::Tags::MeshForGhostData<Dim>>(box),
265 : Metavariables::SubcellOptions::ghost_zone_size(box),
266 : db::get<
267 : evolution::dg::subcell::Tags::InterpolatorsFromNeighborDgToFd<Dim>>(
268 : box));
269 :
270 : if (UNLIKELY(db::get<::Tags::TimeStepId>(box).slab_number() < 0)) {
271 : // If we are doing self start, then we need to project the initial
272 : // guess to the subcells as well.
273 : //
274 : // Warning: this unfortunately needs to be kept in sync with the
275 : // self-start procedure.
276 : //
277 : // Note: if we switch to the subcells then we might have an
278 : // inconsistent
279 : // state between the primitive and conservative variables on the
280 : // subcells. The most correct thing is to re-compute the
281 : // primitive variables on the subcells, since projecting the
282 : // conservative variables is conservative.
283 : if constexpr (Metavariables::system::
284 : has_primitive_and_conservative_vars) {
285 : db::mutate<
286 : SelfStart::Tags::InitialValue<variables_tag>,
287 : SelfStart::Tags::InitialValue<
288 : typename Metavariables::system::primitive_variables_tag>>(
289 : [&dg_mesh, &subcell_mesh](const auto initial_vars_ptr,
290 : const auto initial_prim_vars_ptr) {
291 : // Note: for strict conservation, we need to project uJ
292 : // instead of just u.
293 : std::get<0>(*initial_vars_ptr) =
294 : fd::project(std::get<0>(*initial_vars_ptr), dg_mesh,
295 : subcell_mesh.extents());
296 : std::get<0>(*initial_prim_vars_ptr) =
297 : fd::project(std::get<0>(*initial_prim_vars_ptr), dg_mesh,
298 : subcell_mesh.extents());
299 : },
300 : make_not_null(&box));
301 : } else {
302 : db::mutate<SelfStart::Tags::InitialValue<variables_tag>>(
303 : [&dg_mesh, &subcell_mesh](const auto initial_vars_ptr) {
304 : // Note: for strict conservation, we need to project uJ
305 : // instead of just u.
306 : std::get<0>(*initial_vars_ptr) =
307 : fd::project(std::get<0>(*initial_vars_ptr), dg_mesh,
308 : subcell_mesh.extents());
309 : },
310 : make_not_null(&box));
311 : }
312 : }
313 :
314 : return {Parallel::AlgorithmExecution::Continue,
315 : tmpl::index_of<
316 : ActionList,
317 : ::Actions::Label<evolution::dg::subcell::Actions::Labels::
318 : BeginSubcellAfterDgRollback>>::value +
319 : 1};
320 : }
321 : };
322 : } // namespace evolution::dg::subcell::Actions
|