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