Loading...
Searching...
No Matches
fct_advection_diffusion.hpp
Go to the documentation of this file.
1
2#pragma once
3
5#include "fv/hex/helpers.hpp"
7#include "grid/grid_types.hpp"
10#include "linalg/vector_q1.hpp"
11#include "mpi/mpi.hpp"
12#include "util/timer.hpp"
13
15
16// ============================================================================
17// FVFCTBuffers — pre-allocated working arrays for one FCT step
18// ============================================================================
19
20/// @brief All working storage for a single FCT timestep, allocated once and reused every step.
21///
22/// Avoids heap allocation in the time loop. Pass a single `FVFCTBuffers` instance to all
23/// stage functions (`fct_predictor`, `fct_limiter`, `fct_correction`) and to the convenience
24/// wrappers (`fct_explicit_step`, `upwind_explicit_step`, `fct_semiimplicit_step`).
25///
26/// @tparam ScalarType Floating-point type used for all fields.
27template < typename ScalarType >
29{
30 /// Low-order (upwind) predictor \f$T^L\f$; same cell layout as the transported scalar.
32 /// Pre-scaled antidiffusive fluxes \f$\tilde{f}_{ij} = (\Delta t / M_{ii})\,f_{ij}\f$,
33 /// sixth dimension indexes the 6 faces in neighbour order (0=x−1, 1=x+1, …, 5=r+1).
35 /// Zalesak correction factor \f$R_i^+\f$ — limits incoming positive antidiff flux.
37 /// Zalesak correction factor \f$R_i^-\f$ — limits incoming negative antidiff flux.
39
40 /// MPI ghost-layer buffers for \f$T\f$ (reused for `T_L` as well via the same slot).
42 /// MPI ghost-layer buffers for \f$R^+\f$ (needed so the correction kernel reads neighbours).
44 /// MPI ghost-layer buffers for \f$R^-\f$.
46
48 : T_L( "fct_T_L",
49 domain.subdomains().size(),
50 domain.domain_info().subdomain_num_nodes_per_side_laterally() + 1,
51 domain.domain_info().subdomain_num_nodes_per_side_laterally() + 1,
52 domain.domain_info().subdomain_num_nodes_radially() + 1 )
53 , antidiff(
54 "fct_antidiff",
55 domain.subdomains().size(),
56 domain.domain_info().subdomain_num_nodes_per_side_laterally() + 1,
57 domain.domain_info().subdomain_num_nodes_per_side_laterally() + 1,
58 domain.domain_info().subdomain_num_nodes_radially() + 1,
59 6 )
60 , R_plus(
61 "fct_R_plus",
62 domain.subdomains().size(),
63 domain.domain_info().subdomain_num_nodes_per_side_laterally() + 1,
64 domain.domain_info().subdomain_num_nodes_per_side_laterally() + 1,
65 domain.domain_info().subdomain_num_nodes_radially() + 1 )
66 , R_minus(
67 "fct_R_minus",
68 domain.subdomains().size(),
69 domain.domain_info().subdomain_num_nodes_per_side_laterally() + 1,
70 domain.domain_info().subdomain_num_nodes_per_side_laterally() + 1,
71 domain.domain_info().subdomain_num_nodes_radially() + 1 )
72 , ghost_T( domain )
73 , ghost_R_plus( domain )
74 , ghost_R_minus( domain )
75 {}
76};
77
78namespace fct_detail {
79
80// Alias into the shared geometry helper.
81template < typename ScalarT >
83
84} // namespace fct_detail
85
86// ============================================================================
87// Stable timestep computation
88// ============================================================================
89
90/// @brief Kokkos kernel that computes the local maximum stable explicit dt for each FV cell.
91///
92/// The low-order predictor \f$T^L_i\f$ is stable if and only if
93/// \f[
94/// \frac{\Delta t}{M_{ii}}\,\lambda_i \leq 1, \qquad
95/// \lambda_i = \sum_{j:\,\beta_{ij}<0} |\beta_{ij}|
96/// + \sum_j \kappa\,\frac{|\mathbf{S}_{f,j}|^2}
97/// {(\mathbf{x}_j-\mathbf{x}_i)\cdot\mathbf{S}_{f,j}}
98/// \f]
99/// (assumes `subtract_divergence = true`; the formula naturally coincides with the advective
100/// stability limit \f$\sum_{j:\,\beta>0}\beta_{ij}\f$ for exactly divergence-free fields.)
101///
102/// For each cell the kernel outputs \f$M_{ii}/\lambda_i\f$ (a time scale), so the global
103/// minimum is the largest dt that satisfies the stability criterion everywhere.
104///
105/// This accounts for:
106/// - Lateral face fluxes on irregular/small cells near pentagon vertices of the icosahedral
107/// grid — these are missed by the simpler \f$h_\text{min,radial}/u_\text{max}\f$ estimate.
108/// - Non-orthogonal diffusion stencils, where \f$|\mathbf{S}_f|^2/(\mathbf{dx}\cdot\mathbf{S}_f)\f$
109/// can be much larger than \f$1/h^2\f$.
110template < typename ScalarT >
112{
113 using ScalarType = ScalarT;
116
117 static constexpr int num_neighbors = GH::num_neighbors;
118
124
125 KOKKOS_INLINE_FUNCTION
126 void operator()( const int id, const int x, const int y, const int r, ScalarT& local_min ) const
127 {
128 constexpr int cell_offset_x[num_neighbors] = { -1, 1, 0, 0, 0, 0 };
129 constexpr int cell_offset_y[num_neighbors] = { 0, 0, -1, 1, 0, 0 };
130 constexpr int cell_offset_r[num_neighbors] = { 0, 0, 0, 0, -1, 1 };
131
132 ScalarT beta[num_neighbors];
133 ScalarT M_ii = ScalarT( 0 );
134 Vec3 S_f[num_neighbors];
135 GH::compute_geometry( grid_, radii_, cell_centers_, vel_grid_, id, x, y, r, beta, M_ii, S_f );
136
137 // Effective diagonal of the low-order predictor (subtract_divergence = true):
138 // lambda_i = -sum_{beta<0} beta_ij + sum_j kappa * |S_f|^2 / (dx . S_f)
139 // For divergence-free velocity: this equals sum_{beta>0} beta_ij.
140 ScalarT lambda = ScalarT( 0 );
141
142 for ( int n = 0; n < num_neighbors; ++n )
143 {
144 if ( beta[n] < ScalarT( 0 ) )
145 lambda -= beta[n]; // accumulate inflow (beta<0 => outflow from neighbour, inflow to i)
146
147 if ( diffusivity_ > ScalarT( 0 ) )
148 {
149 const int nx = x + GH::cell_offset_x( n );
150 const int ny = y + GH::cell_offset_y( n );
151 const int nr = r + GH::cell_offset_r( n );
152 const Vec3 dx{
153 cell_centers_( id, nx, ny, nr, 0 ) - cell_centers_( id, x, y, r, 0 ),
154 cell_centers_( id, nx, ny, nr, 1 ) - cell_centers_( id, x, y, r, 1 ),
155 cell_centers_( id, nx, ny, nr, 2 ) - cell_centers_( id, x, y, r, 2 ) };
156 const ScalarT denom = dx.dot( S_f[n] );
157 if ( denom > ScalarT( 0 ) )
158 lambda += diffusivity_ * S_f[n].dot( S_f[n] ) / denom;
159 }
160 }
161
162 const ScalarT dt_cell = ( lambda > ScalarT( 0 ) ) ? ( M_ii / lambda ) : ScalarT( 1e30 );
163 local_min = Kokkos::min( local_min, dt_cell );
164 }
165};
166
167/// @brief Compute the largest explicit time step that keeps the FCT low-order predictor stable.
168///
169/// Performs a Kokkos parallel reduction over all non-ghost FV cells followed by an
170/// MPI_Allreduce to obtain the global minimum across all MPI ranks.
171///
172/// The result is exact (derived from the actual face-normal velocity fluxes and cell volumes),
173/// unlike the approximate estimate \f$h_\text{min,radial} / u_\text{max}\f$ which ignores
174/// lateral cell sizes and diffusion stiffness on non-orthogonal cells.
175///
176/// @note The resulting time step size can still be too large to ensure an "accurate" time discretization.
177/// It can be a good idea to scale it down further in practice.
178///
179/// **Typical usage:**
180/// @code
181/// const ScalarType dt = dt_scaling * fv::hex::operators::compute_dt_stable(
182/// domain, u, cell_centers.grid_data(), coords_shell, coords_radii, diffusivity);
183/// @endcode
184///
185/// @param domain Distributed domain.
186/// @param vel Q1 nodal velocity (read-only; no ghost-layer update required).
187/// @param cell_centers Pre-computed cell centres with ghost layers (from `initialize_cell_centers`).
188/// @param grid Lateral node coordinates of the unit-sphere surface.
189/// @param radii Radial shell radii.
190/// @param diffusivity Physical diffusivity \f$\kappa\f$ (default 0 = pure advection).
191/// @returns The minimum over all cells of \f$M_{ii}/\lambda_i\f$.
192template < typename ScalarT >
194 const grid::shell::DistributedDomain& domain,
196 const grid::Grid4DDataVec< ScalarT, 3 >& cell_centers,
199 const ScalarT diffusivity = ScalarT( 0 ) )
200{
201 ScalarT local_min;
202
203 Kokkos::parallel_reduce(
204 "compute_dt_stable",
207 .grid_ = grid,
208 .radii_ = radii,
209 .cell_centers_ = cell_centers,
210 .vel_grid_ = vel.grid_data(),
211 .diffusivity_ = diffusivity,
212 },
213 Kokkos::Min< ScalarT >( local_min ) );
214
215 Kokkos::fence();
216
217 ScalarT global_min = local_min;
218 MPI_Allreduce( &local_min, &global_min, 1, mpi::mpi_datatype< ScalarT >(), MPI_MIN, MPI_COMM_WORLD );
219
220 return global_min;
221}
222
223// ============================================================================
224// Stage 1: Predictor — low-order upwind + antidiffusive fluxes
225// ============================================================================
226
227/// @brief Kokkos kernel for the explicit low-order predictor and antidiffusive flux assembly.
228///
229/// In a single pass over each cell \f$i\f$ this kernel:
230///
231/// 1. Calls `GeometryHelper::compute_geometry` to obtain the face-normal velocity fluxes
232/// \f$\beta_{ij}\f$, the cell volume \f$M_{ii}\f$, and the area-weighted face normals
233/// \f$\mathbf{S}_f^{(j)}\f$ for all 6 neighbours.
234///
235/// 2. Computes the **low-order predictor** (first-order upwind + two-point diffusion):
236/// \f[
237/// T_i^L = T_i^n
238/// - \frac{\Delta t}{M_{ii}}
239/// \left[
240/// \beta_{ii}^+\,T_i^n
241/// + \sum_j \beta_{ij}^-\,T_j^n
242/// + \sum_j \kappa\,
243/// \frac{|\mathbf{S}_f^{(j)}|^2}
244/// {(\mathbf{x}_j-\mathbf{x}_i)\cdot\mathbf{S}_f^{(j)}}
245/// (T_i^n - T_j^n)
246/// \right],
247/// \f]
248/// where \f$\beta^+ = \max(\beta,0)\f$ and \f$\beta^- = \min(\beta,0)\f$.
249///
250/// 3. Stores the **pre-scaled antidiffusive flux** for face \f$j\f$:
251/// \f[
252/// \tilde{f}_{ij} = \frac{\Delta t}{M_{ii}}\,\frac{|\beta_{ij}|}{2}\,(T_i^n - T_j^n).
253/// \f]
254/// Physical diffusion is **not** included in \f$\tilde{f}_{ij}\f$ — only purely advective
255/// antidiffusion enters the Zalesak limiter.
256///
257/// @note Ghost layers of \f$T^n\f$ (and of `cell_centers_`) must be filled before launch.
258/// @note The kernel is Kokkos-portable (CUDA/HIP/OpenMP/Serial).
259///
260/// @tparam ScalarT Floating-point scalar type.
261template < typename ScalarT >
263{
264 using ScalarType = ScalarT;
267
268 static constexpr int num_neighbors = GH::num_neighbors;
269
274
276 T_old_; ///< \f$T^n\f$: scalar at time level \f$n\f$ (ghost layers must be filled).
277 grid::Grid4DDataScalar< ScalarT > T_L_; ///< \f$T^L\f$: low-order predictor output.
279 antidiff_; ///< \f$\tilde{f}_{ij}\f$: pre-scaled antidiff flux per face, shape \f$[\ldots, 6]\f$.
280
281 ScalarT dt_; ///< Time step \f$\Delta t\f$.
282 ScalarT diffusivity_; ///< Physical diffusivity \f$\kappa \geq 0\f$; set to 0 for pure advection.
283
284 /// @name Optional volumetric source term \f$f\f$ [T/time]
285 ///@{
286 /// Source values per cell. A null (default-constructed) view means no source.
287 /// The explicit predictor adds \f$\Delta t \cdot f_i\f$ to \f$T_i^L\f$ after the
288 /// upwind/diffusion update. Physical units: same as \f$T\f$ per unit time.
290 bool has_source_ = false;
291 ///@}
292
293 /// @brief Whether to subtract the discrete divergence error \f$T_i\,\nabla\cdot\mathbf{u}\f$.
294 ///
295 /// When `true` (default), the correction \f$-T_i\,(\sum_j \beta_{ij})/M_{ii}\f$ is applied,
296 /// converting the conservative form \f$\nabla\cdot(\mathbf{u}T)\f$ into the advective form
297 /// \f$\mathbf{u}\cdot\nabla T\f$. This is the physically correct equation for temperature.
298 /// When \f$\nabla\cdot\mathbf{u} = 0\f$ the correction is exactly zero and costs nothing.
300
301 KOKKOS_INLINE_FUNCTION
302 void operator()( const int id, const int x, const int y, const int r ) const
303 {
304 constexpr int cell_offset_x[num_neighbors] = { -1, 1, 0, 0, 0, 0 };
305 constexpr int cell_offset_y[num_neighbors] = { 0, 0, -1, 1, 0, 0 };
306 constexpr int cell_offset_r[num_neighbors] = { 0, 0, 0, 0, -1, 1 };
307
308 ScalarT beta[num_neighbors];
309 ScalarT M_ii;
310 Vec3 S_f[num_neighbors];
311 GH::compute_geometry( grid_, radii_, cell_centers_, vel_grid_, id, x, y, r, beta, M_ii, S_f );
312
313 const ScalarT T_i = T_old_( id, x, y, r );
314
315 // Low-order predictor: first-order upwind advection + explicit diffusion.
316 //
317 // T_L_i = T_i - (dt/M_ii) * [A_upwind*T + D*T]_i
318 //
319 // Advection (upwind): contributes beta^+*T_i (diagonal) and beta^-*T_j (off-diagonal).
320 // Diffusion (two-point flux): κ*(S_f·S_f / dx·S_f)*(T_i - T_j), not FCT-limited.
321 //
322 // Antidiffusive fluxes remain purely advective — physical diffusion is not FCT-corrected.
323
324 ScalarT lo_diag = ScalarT( 0 );
325 ScalarT lo_update = ScalarT( 0 );
326 ScalarT beta_sum = ScalarT( 0 ); // Σ_j β_ij = discrete divergence * M_ii
327
328 for ( int n = 0; n < num_neighbors; ++n )
329 {
330 const int nx = x + GH::cell_offset_x( n );
331 const int ny = y + GH::cell_offset_y( n );
332 const int nr = r + GH::cell_offset_r( n );
333 const ScalarT T_j = T_old_( id, nx, ny, nr );
334
335 // Upwind advection.
336 if ( beta[n] >= ScalarT( 0 ) )
337 lo_diag += beta[n];
338 else
339 lo_update += beta[n] * T_j;
340
341 beta_sum += beta[n]; // accumulate for optional divergence correction
342
343 // Explicit diffusion (two-point flux).
344 const Vec3 neighbor_center{
345 cell_centers_( id, nx, ny, nr, 0 ),
346 cell_centers_( id, nx, ny, nr, 1 ),
347 cell_centers_( id, nx, ny, nr, 2 ) };
348 const Vec3 cell_center{
349 cell_centers_( id, x, y, r, 0 ), cell_centers_( id, x, y, r, 1 ), cell_centers_( id, x, y, r, 2 ) };
350 const Vec3 dx = neighbor_center - cell_center;
351 const ScalarT denom = dx.dot( S_f[n] );
352 const ScalarT diff_coeff = diffusivity_ * ( S_f[n].dot( S_f[n] ) / denom );
353 lo_diag += diff_coeff; // diagonal: κ*(S·S/dx·S)*T_i
354 lo_update -= diff_coeff * T_j; // off-diagonal: -κ*(S·S/dx·S)*T_j (note: lo_update is subtracted below)
355
356 // Antidiffusive flux: purely advective, not affected by physical diffusion.
357 const ScalarT abs_beta = Kokkos::abs( beta[n] );
358 antidiff_( id, x, y, r, n ) = ( dt_ / M_ii ) * ( abs_beta / ScalarT( 2 ) ) * ( T_i - T_j );
359 }
360
361 // Divergence correction: subtract T_i * (Σ_j β_ij) / M_ii * dt from T_L.
362 // This converts the conservative form ∇·(uT) to the advective form u·∇T.
363 // Equivalent to reducing lo_diag by beta_sum (the inflow part cancels with lo_update).
365 lo_diag -= beta_sum;
366
367 ScalarT T_L_val = T_i - ( dt_ / M_ii ) * ( lo_diag * T_i + lo_update );
368
369 // Volumetric source term: T^L += dt * f_i (f_i in [T/time]).
370 if ( has_source_ )
371 {
372 T_L_val += dt_ * source_( id, x, y, r );
373 }
374
375 T_L_( id, x, y, r ) = T_L_val;
376 }
377};
378
379/// @brief Stage 1: low-order predictor + antidiffusive flux computation.
380///
381/// Ghost layers of `T_old` are exchanged via MPI before the kernel runs.
382/// On exit:
383/// - `bufs.T_L` holds \f$T^L\f$ (first-order upwind + explicit diffusion at \f$t+\Delta t\f$).
384/// - `bufs.antidiff` holds \f$\tilde{f}_{ij} = (\Delta t / M_{ii})\,f_{ij}\f$ for every face.
385///
386/// @param domain Distributed domain (used for MPI ghost-layer exchange).
387/// @param T_old Transported scalar \f$T^n\f$ at time level \f$n\f$.
388/// @param vel Q1 velocity field \f$\mathbf{u}\f$ (nodal, read-only).
389/// @param cell_centers Pre-computed cell centres (with ghost layers filled via `initialize_cell_centers`).
390/// @param grid Lateral node coordinates of the unit-sphere surface.
391/// @param radii Radial shell radii.
392/// @param dt Time step \f$\Delta t\f$.
393/// @param bufs Pre-allocated FCT scratch arrays (updated in-place).
394/// @param diffusivity Physical diffusivity \f$\kappa\f$ (default 0 = pure advection).
395/// @param source Volumetric source term \f$f\f$ [T/time] per cell. A
396/// default-constructed (null) view means no source. Added as
397/// \f$\Delta t \cdot f_i\f$ to the low-order predictor.
398/// @param subtract_divergence When `true` (default), subtract \f$T_i\,(\sum_j \beta_{ij})/M_{ii}\f$
399/// from the predictor, converting \f$\nabla\cdot(\mathbf{u}T)\f$ to
400/// \f$\mathbf{u}\cdot\nabla T\f$. This is always correct for
401/// temperature: when \f$\nabla\cdot\mathbf{u}=0\f$ the correction
402/// vanishes; when \f$\nabla\cdot\mathbf{u}\neq 0\f$ it removes an
403/// unphysical source term.
404template < typename ScalarT >
406 const grid::shell::DistributedDomain& domain,
409 const grid::Grid4DDataVec< ScalarT, 3 >& cell_centers,
412 const ScalarT dt,
414 const ScalarT diffusivity = ScalarT( 0 ),
415 const grid::Grid4DDataScalar< ScalarT >& source = {},
416 const bool subtract_divergence = true )
417{
418 util::Timer timer_predictor( "fct_predictor" );
419
420 {
421 util::Timer timer_comm( "fct_predictor_comm" );
423 }
424
425 FCTPredictorKernel< ScalarT > kernel{
426 .grid_ = grid,
427 .radii_ = radii,
428 .cell_centers_ = cell_centers,
429 .vel_grid_ = vel.grid_data(),
430 .T_old_ = T_old.grid_data(),
431 .T_L_ = bufs.T_L,
432 .antidiff_ = bufs.antidiff,
433 .dt_ = dt,
434 .diffusivity_ = diffusivity,
435 .source_ = source,
436 .has_source_ = ( source.data() != nullptr ),
437 .subtract_divergence_ = subtract_divergence,
438 };
439
440 {
441 util::Timer timer_kernel( "fct_predictor_kernel" );
442 Kokkos::parallel_for(
444 Kokkos::fence();
445 }
446}
447
448// ============================================================================
449// Stage 2: Limiter — compute Zalesak R+ / R- correction factors
450// ============================================================================
451
452/// @brief Kokkos kernel that computes the Zalesak nodal correction factors \f$R_i^+\f$ and
453/// \f$R_i^-\f$ from the low-order predictor \f$T^L\f$ and the antidiffusive fluxes.
454///
455/// Per cell \f$i\f$ (summing over all 6 neighbours \f$j \in \mathcal{N}(i)\f$):
456///
457/// **Positive/negative flux sums:**
458/// \f[
459/// P_i^+ = \sum_{j:\,\tilde{f}_{ij} > 0} \tilde{f}_{ij}, \qquad
460/// P_i^- = \sum_{j:\,\tilde{f}_{ij} < 0} \tilde{f}_{ij}.
461/// \f]
462///
463/// **Local extrema of the low-order solution:**
464/// \f[
465/// T_i^{\max} = \max(T_i^L,\;T_j^L\;\forall j \in \mathcal{N}(i)), \qquad
466/// T_i^{\min} = \min(T_i^L,\;T_j^L\;\forall j \in \mathcal{N}(i)).
467/// \f]
468///
469/// **Room to grow/shrink:**
470/// \f[
471/// Q_i^+ = T_i^{\max} - T_i^L \geq 0, \qquad
472/// Q_i^- = T_i^{\min} - T_i^L \leq 0.
473/// \f]
474///
475/// **Correction factors** (Zalesak 1979, eq. 13–14):
476/// \f[
477/// R_i^+ = \begin{cases} \min\!\left(1,\;\dfrac{Q_i^+}{P_i^+}\right) & P_i^+ > 0 \\ 1 & \text{otherwise} \end{cases},
478/// \qquad
479/// R_i^- = \begin{cases} \min\!\left(1,\;\dfrac{Q_i^-}{P_i^-}\right) & P_i^- < 0 \\ 1 & \text{otherwise} \end{cases}.
480/// \f]
481///
482/// @note Ghost layers of \f$T^L\f$ must be filled before launch so that
483/// \f$T_j^L\f$ for boundary-adjacent cells is available.
484/// @tparam ScalarT Floating-point scalar type.
485template < typename ScalarT >
487{
489 static constexpr int num_neighbors = GH::num_neighbors;
490
491 grid::Grid4DDataScalar< ScalarT > T_L_; ///< \f$T^L\f$ with ghost layers filled.
492 grid::Grid5DDataScalar< ScalarT > antidiff_; ///< \f$\tilde{f}_{ij}\f$: pre-scaled antidiff fluxes.
493 grid::Grid4DDataScalar< ScalarT > R_plus_; ///< Output: \f$R_i^+\f$ correction factor.
494 grid::Grid4DDataScalar< ScalarT > R_minus_; ///< Output: \f$R_i^-\f$ correction factor.
495
496 KOKKOS_INLINE_FUNCTION
497 void operator()( const int id, const int x, const int y, const int r ) const
498 {
499 constexpr int cell_offset_x[num_neighbors] = { -1, 1, 0, 0, 0, 0 };
500 constexpr int cell_offset_y[num_neighbors] = { 0, 0, -1, 1, 0, 0 };
501 constexpr int cell_offset_r[num_neighbors] = { 0, 0, 0, 0, -1, 1 };
502
503 const ScalarT T_L_i = T_L_( id, x, y, r );
504
505 ScalarT P_plus = ScalarT( 0 );
506 ScalarT P_minus = ScalarT( 0 );
507 ScalarT T_max = T_L_i;
508 ScalarT T_min = T_L_i;
509
510 for ( int n = 0; n < num_neighbors; ++n )
511 {
512 const ScalarT f_ij = antidiff_( id, x, y, r, n );
513 if ( f_ij > ScalarT( 0 ) ) P_plus += f_ij;
514 else P_minus += f_ij;
515
516 const ScalarT T_L_j = T_L_(
517 id,
518 x + GH::cell_offset_x( n ),
519 y + GH::cell_offset_y( n ),
520 r + GH::cell_offset_r( n ) );
521 T_max = Kokkos::max( T_max, T_L_j );
522 T_min = Kokkos::min( T_min, T_L_j );
523 }
524
525 const ScalarT Q_plus = T_max - T_L_i; // ≥ 0
526 const ScalarT Q_minus = T_min - T_L_i; // ≤ 0
527
528 R_plus_( id, x, y, r ) =
529 ( P_plus > ScalarT( 0 ) ) ? Kokkos::min( ScalarT( 1 ), Q_plus / P_plus ) : ScalarT( 1 );
530 R_minus_( id, x, y, r ) =
531 ( P_minus < ScalarT( 0 ) ) ? Kokkos::min( ScalarT( 1 ), Q_minus / P_minus ) : ScalarT( 1 );
532 }
533};
534
535/// @brief Stage 2: compute Zalesak \f$R^+\f$/\f$R^-\f$ correction factors.
536///
537/// Ghost layers of `bufs.T_L` are exchanged via MPI so that the neighbourhood
538/// min/max stencil is correct for subdomain-boundary cells. Then
539/// `FCTLimiterKernel` is launched, and finally ghost layers of `bufs.R_plus`
540/// and `bufs.R_minus` are exchanged so that Stage 3 can read neighbour factors.
541///
542/// This function can be called **multiple times** in a non-linear iteration loop
543/// with a fixed `bufs.antidiff` computed once from \f$T^n\f$ but an updated
544/// `bufs.T_L` iterate.
545///
546/// @param domain Distributed domain (used for ghost-layer exchange).
547/// @param bufs FCT scratch arrays; reads `T_L` and `antidiff`, writes `R_plus` and `R_minus`.
548template < typename ScalarT >
550{
551 util::Timer timer_limiter( "fct_limiter" );
552
553 {
554 util::Timer timer_comm( "fct_limiter_comm_tl" );
556 }
557
559 .T_L_ = bufs.T_L,
560 .antidiff_ = bufs.antidiff,
561 .R_plus_ = bufs.R_plus,
562 .R_minus_ = bufs.R_minus,
563 };
564
565 {
566 util::Timer timer_kernel( "fct_limiter_kernel" );
567 Kokkos::parallel_for(
569 Kokkos::fence();
570 }
571
572 // R+/R- ghost layers must be filled before the correction kernel reads neighbours.
573 {
574 util::Timer timer_comm_r( "fct_limiter_comm_r" );
577 }
578}
579
580// ============================================================================
581// Stage 3: Correction — apply limited antidiffusive fluxes to T_L
582// ============================================================================
583
584/// @brief Kokkos kernel that applies the Zalesak-limited antidiffusive correction to \f$T^L\f$.
585///
586/// For each cell \f$i\f$ and face \f$j\f$, the symmetric limited flux factor is
587/// \f[
588/// \alpha_{ij} =
589/// \begin{cases}
590/// \min(R_i^+,\; R_j^-) & \tilde{f}_{ij} > 0 \quad (\text{flux increases } T_i), \\
591/// \min(R_i^-,\; R_j^+) & \tilde{f}_{ij} < 0 \quad (\text{flux decreases } T_i).
592/// \end{cases}
593/// \f]
594/// Note that \f$\alpha_{ji} = \alpha_{ij}\f$ by construction (symmetry), which ensures
595/// conservation: what is added to cell \f$i\f$ from face \f$j\f$ is subtracted from
596/// cell \f$j\f$ on the same face.
597///
598/// The corrected solution is
599/// \f[
600/// T_i^{n+1} = T_i^L + \sum_j \alpha_{ij}\,\tilde{f}_{ij}.
601/// \f]
602///
603/// @note Ghost layers of \f$R^+\f$ and \f$R^-\f$ must be filled (done by `fct_limiter`).
604/// @tparam ScalarT Floating-point scalar type.
605template < typename ScalarT >
607{
609 static constexpr int num_neighbors = GH::num_neighbors;
610
611 grid::Grid4DDataScalar< ScalarT > T_L_; ///< \f$T^L\f$: low-order predictor.
612 grid::Grid5DDataScalar< ScalarT > antidiff_; ///< \f$\tilde{f}_{ij}\f$: pre-scaled antidiff fluxes.
613 grid::Grid4DDataScalar< ScalarT > R_plus_; ///< \f$R^+\f$ correction factor (ghost layers filled).
614 grid::Grid4DDataScalar< ScalarT > R_minus_; ///< \f$R^-\f$ correction factor (ghost layers filled).
615 grid::Grid4DDataScalar< ScalarT > T_new_; ///< Output: \f$T^{n+1}\f$.
616
617 KOKKOS_INLINE_FUNCTION
618 void operator()( const int id, const int x, const int y, const int r ) const
619 {
620 constexpr int cell_offset_x[num_neighbors] = { -1, 1, 0, 0, 0, 0 };
621 constexpr int cell_offset_y[num_neighbors] = { 0, 0, -1, 1, 0, 0 };
622 constexpr int cell_offset_r[num_neighbors] = { 0, 0, 0, 0, -1, 1 };
623
624 ScalarT correction = ScalarT( 0 );
625
626 const ScalarT R_plus_i = R_plus_( id, x, y, r );
627 const ScalarT R_minus_i = R_minus_( id, x, y, r );
628
629 for ( int n = 0; n < num_neighbors; ++n )
630 {
631 const int jx = x + GH::cell_offset_x( n );
632 const int jy = y + GH::cell_offset_y( n );
633 const int jr = r + GH::cell_offset_r( n );
634
635 const ScalarT f_ij = antidiff_( id, x, y, r, n );
636 const ScalarT R_plus_j = R_plus_( id, jx, jy, jr );
637 const ScalarT R_minus_j = R_minus_( id, jx, jy, jr );
638
639 ScalarT alpha;
640 if ( f_ij > ScalarT( 0 ) )
641 alpha = Kokkos::min( R_plus_i, R_minus_j );
642 else
643 alpha = Kokkos::min( R_minus_i, R_plus_j );
644
645 correction += alpha * f_ij;
646 }
647
648 T_new_( id, x, y, r ) = T_L_( id, x, y, r ) + correction;
649 }
650};
651
652/// @brief Stage 3: apply the Zalesak-limited antidiffusive correction.
653///
654/// Reads `bufs.T_L`, `bufs.antidiff`, `bufs.R_plus`, `bufs.R_minus` (all read-only).
655/// Writes \f$T^{n+1} = T^L + \sum_j \alpha_{ij}\,\tilde{f}_{ij}\f$ into `T_new`.
656///
657/// @pre `fct_limiter` must have been called first so that ghost layers of \f$R^\pm\f$ are current.
658/// @param domain Distributed domain.
659/// @param T_new Output: high-order corrected scalar at \f$t_{n+1}\f$.
660/// @param bufs FCT scratch arrays (reads `T_L`, `antidiff`, `R_plus`, `R_minus`).
661template < typename ScalarT >
663 const grid::shell::DistributedDomain& domain,
666{
667 util::Timer timer_correction( "fct_correction" );
668
670 .T_L_ = bufs.T_L,
671 .antidiff_ = bufs.antidiff,
672 .R_plus_ = bufs.R_plus,
673 .R_minus_ = bufs.R_minus,
674 .T_new_ = T_new.grid_data(),
675 };
676
677 {
678 util::Timer timer_kernel( "fct_correction_kernel" );
679 Kokkos::parallel_for(
681 Kokkos::fence();
682 }
683}
684
685// ============================================================================
686// Convenience wrapper: single explicit FCT step
687// ============================================================================
688
689/// @brief One complete explicit FCT advection–diffusion timestep.
690///
691/// Executes the three stages in sequence:
692/// 1. `fct_predictor` — low-order upwind + diffusion predictor \f$T^L\f$ and antidiff fluxes.
693/// 2. `fct_limiter` — Zalesak \f$R^\pm\f$ factors.
694/// 3. `fct_correction` — limited antidiffusive correction \f$T^{n+1} = T^L + \sum_j \alpha_{ij}\,\tilde{f}_{ij}\f$.
695///
696/// The scheme is **LED** (local extremum diminishing) and **conservation-consistent**:
697/// the limited fluxes are antisymmetric, so cell integrals are preserved up to boundary terms.
698///
699/// For a non-linear iteration (e.g. defect-correction), call the three stages individually
700/// and repeat stages 2–3 with an updated \f$T^L\f$.
701///
702/// **Stability:** requires \f$\mathrm{CFL} = \Delta t\,\max_i(\sum_j \beta_{ij}^+)/M_{ii} < 1\f$.
703///
704/// @param domain Distributed domain.
705/// @param T Transported scalar — \f$T^n\f$ on entry, \f$T^{n+1}\f$ on exit.
706/// @param vel Q1 nodal velocity field \f$\mathbf{u}\f$.
707/// @param cell_centers Pre-computed cell centres (ghost layers populated via `initialize_cell_centers`).
708/// @param grid Lateral node coordinates of the unit-sphere surface.
709/// @param radii Radial shell radii.
710/// @param dt Time step \f$\Delta t\f$.
711/// @param bufs Pre-allocated FCT scratch arrays.
712/// @param diffusivity Physical diffusivity \f$\kappa \geq 0\f$ (default 0 = pure advection).
713/// @param source Volumetric source term \f$f\f$ [T/time]; null view = no source.
714/// @param subtract_divergence Subtract discrete divergence error (default `true`); see `fct_predictor`.
715/// @param boundary_mask Node-based boundary flag array (Q1 layout). When provided (non-null),
716/// Dirichlet BCs are enforced on \f$T^L\f$ **between the predictor and
717/// limiter** so the Zalesak \f$R^\pm\f$ factors see the correct boundary
718/// values. Default: null (no enforcement).
719/// @param bcs Prescribed boundary values. Ignored when `boundary_mask` is null.
720template < typename ScalarT >
722 const grid::shell::DistributedDomain& domain,
725 const grid::Grid4DDataVec< ScalarT, 3 >& cell_centers,
728 const ScalarT dt,
730 const ScalarT diffusivity = ScalarT( 0 ),
731 const grid::Grid4DDataScalar< ScalarT >& source = {},
732 const bool subtract_divergence = true,
734 const DirichletBCs< ScalarT >& bcs = {} )
735{
736 util::Timer timer_fct( "fct_explicit_step" );
737 fct_predictor( domain, T, vel, cell_centers, grid, radii, dt, bufs, diffusivity, source, subtract_divergence );
738
739 // Enforce Dirichlet BCs on T_L before the limiter so that R+/R- are computed
740 // relative to the correct boundary values. Without this, the predictor can
741 // move boundary cells away from the prescribed value (due to discrete divergence
742 // error in the velocity), and the antidiffusive correction will then "confirm"
743 // that wrong value — leading to oscillations near the boundary.
744 if ( boundary_mask.extent( 0 ) > 0 )
745 apply_dirichlet_bcs( bufs.T_L, boundary_mask, bcs, domain );
746
747 fct_limiter( domain, bufs );
748 fct_correction( domain, T, bufs );
749}
750
751// ============================================================================
752// Convenience wrapper: plain explicit first-order upwind (no FCT correction)
753// ============================================================================
754
755/// @brief One explicit first-order upwind advection–diffusion timestep (no FCT correction).
756///
757/// Equivalent to calling `fct_predictor` and then copying \f$T^L \to T\f$.
758/// The result is the maximally diffusive (but stable) low-order solution. Use this as a
759/// baseline or whenever sharp-gradient preservation is not required.
760///
761/// Physical diffusion (two-point flux) is included when `diffusivity > 0`.
762///
763/// **Stability:** requires \f$\mathrm{CFL} = \Delta t\,\max_i(\sum_j \beta_{ij}^+)/M_{ii} < 1\f$.
764///
765/// @param domain Distributed domain.
766/// @param T Transported scalar — \f$T^n\f$ on entry, \f$T^{n+1}\f$ on exit.
767/// @param vel Q1 nodal velocity field \f$\mathbf{u}\f$.
768/// @param cell_centers Pre-computed cell centres (ghost layers populated via `initialize_cell_centers`).
769/// @param grid Lateral node coordinates of the unit-sphere surface.
770/// @param radii Radial shell radii.
771/// @param dt Time step \f$\Delta t\f$.
772/// @param bufs Pre-allocated FCT scratch arrays (uses `T_L` and `ghost_T`).
773/// @param diffusivity Physical diffusivity \f$\kappa \geq 0\f$ (default 0 = pure advection).
774/// @param source Optional volumetric source term \f$f\f$ [T/time] (default: none).
775/// @param subtract_divergence Subtract discrete divergence error (default `true`); see `fct_predictor`.
776template < typename ScalarT >
778 const grid::shell::DistributedDomain& domain,
781 const grid::Grid4DDataVec< ScalarT, 3 >& cell_centers,
784 const ScalarT dt,
786 const ScalarT diffusivity = ScalarT( 0 ),
787 const grid::Grid4DDataScalar< ScalarT >& source = {},
788 const bool subtract_divergence = true )
789{
790 util::Timer timer_upwind( "upwind_explicit_step" );
791 fct_predictor( domain, T, vel, cell_centers, grid, radii, dt, bufs, diffusivity, source, subtract_divergence );
792 Kokkos::deep_copy( T.grid_data(), bufs.T_L );
793}
794
795// ============================================================================
796// Stage 1 (semi-implicit variant): antidiffusive fluxes only
797// ============================================================================
798
799/// @brief Kokkos kernel that computes only the pre-scaled antidiffusive fluxes from \f$T^n\f$.
800///
801/// In the semi-implicit FCT scheme the low-order predictor \f$T^L\f$ is provided by an
802/// external implicit solve (see `fct_semiimplicit_step`). This kernel therefore skips the
803/// upwind update and only stores the antidiffusive fluxes needed by the Zalesak limiter:
804/// \f[
805/// \tilde{f}_{ij} = \frac{\Delta t}{M_{ii}}\,\frac{|\beta_{ij}|}{2}\,(T_i^n - T_j^n).
806/// \f]
807/// The formula is identical to the antidiffusive part of `FCTPredictorKernel`.
808///
809/// @note Ghost layers of \f$T^n\f$ must be filled before launch.
810/// @tparam ScalarT Floating-point scalar type.
811template < typename ScalarT >
813{
814 using ScalarType = ScalarT;
816
817 static constexpr int num_neighbors = GH::num_neighbors;
818
823 grid::Grid4DDataScalar< ScalarT > T_old_; ///< \f$T^n\f$ with ghost layers filled.
824 grid::Grid5DDataScalar< ScalarT > antidiff_; ///< Output: \f$\tilde{f}_{ij}\f$, shape \f$[\ldots, 6]\f$.
825 ScalarT dt_; ///< Time step \f$\Delta t\f$.
826
827 KOKKOS_INLINE_FUNCTION
828 void operator()( const int id, const int x, const int y, const int r ) const
829 {
830 ScalarT beta[num_neighbors];
831 ScalarT M_ii;
832 typename GH::Vec3 S_f[num_neighbors]; // not used here; required by shared compute_geometry
833 GH::compute_geometry( grid_, radii_, cell_centers_, vel_grid_, id, x, y, r, beta, M_ii, S_f );
834
835 const ScalarT T_i = T_old_( id, x, y, r );
836 for ( int n = 0; n < num_neighbors; ++n )
837 {
838 const ScalarT T_j = T_old_(
839 id,
840 x + GH::cell_offset_x( n ),
841 y + GH::cell_offset_y( n ),
842 r + GH::cell_offset_r( n ) );
843 const ScalarT abs_beta = Kokkos::abs( beta[n] );
844 antidiff_( id, x, y, r, n ) = ( dt_ / M_ii ) * ( abs_beta / ScalarT( 2 ) ) * ( T_i - T_j );
845 }
846 }
847};
848
849/// @brief Computes pre-scaled antidiffusive fluxes \f$\tilde{f}_{ij}\f$ from \f$T^n\f$
850/// (semi-implicit FCT stage 1).
851///
852/// Ghost layers of `T_old` are exchanged via MPI before the kernel runs.
853/// On exit `bufs.antidiff` holds \f$\tilde{f}_{ij} = (\Delta t / M_{ii})\,f_{ij}\f$ for
854/// every real cell and all 6 faces.
855///
856/// @param domain Distributed domain (used for MPI ghost-layer exchange).
857/// @param T_old Transported scalar \f$T^n\f$ at time level \f$n\f$.
858/// @param vel Q1 nodal velocity field \f$\mathbf{u}\f$.
859/// @param cell_centers Pre-computed cell centres (ghost layers populated via `initialize_cell_centers`).
860/// @param grid Lateral node coordinates of the unit-sphere surface.
861/// @param radii Radial shell radii.
862/// @param dt Time step \f$\Delta t\f$.
863/// @param bufs FCT scratch arrays; only `antidiff` and `ghost_T` are written.
864template < typename ScalarT >
866 const grid::shell::DistributedDomain& domain,
869 const grid::Grid4DDataVec< ScalarT, 3 >& cell_centers,
872 const ScalarT dt,
874{
876
878 .grid_ = grid,
879 .radii_ = radii,
880 .cell_centers_ = cell_centers,
881 .vel_grid_ = vel.grid_data(),
882 .T_old_ = T_old.grid_data(),
883 .antidiff_ = bufs.antidiff,
884 .dt_ = dt,
885 };
886
887 Kokkos::parallel_for(
889 Kokkos::fence();
890}
891
892// ============================================================================
893// Semi-implicit FCT step
894// ============================================================================
895
896/// @brief One semi-implicit FCT advection–diffusion timestep.
897///
898/// The semi-implicit scheme decouples the implicit solve from the flux-correction step,
899/// removing the CFL restriction while retaining the LED property. The caller is
900/// responsible for providing the implicit low-order predictor \f$T^L\f$, obtained by
901/// solving the backward-Euler system
902/// \f[
903/// \bigl(M + \Delta t\,A_{\mathrm{upw}} + \Delta t\,D\bigr)\,T^L = M\,T^n,
904/// \f]
905/// where \f$A_{\mathrm{upw}}\f$ is the upwind advection matrix and \f$D\f$ is the
906/// two-point diffusion matrix. See `UnsteadyAdvectionDiffusion` in
907/// `advection_diffusion.hpp` for the concrete operator and FGMRES for the solver.
908///
909/// Steps executed internally:
910/// 1. `fct_antidiff`: ghost-update \f$T^n\f$, compute \f$\tilde{f}_{ij}\f$ from \f$T^n\f$.
911/// 2. Copy the externally provided \f$T^L\f$ into `bufs.T_L`.
912/// 3. `fct_limiter`: ghost-update \f$T^L\f$, compute \f$R^\pm\f$, ghost-update \f$R^\pm\f$.
913/// 4. `fct_correction`: \f$T^{n+1} = T^L + \sum_j \alpha_{ij}\,\tilde{f}_{ij}\f$ → written into \f$T\f$.
914///
915/// **Stability:** unconditionally stable for the low-order predictor; the Zalesak correction
916/// cannot introduce new extrema beyond those already present in \f$T^L\f$.
917///
918/// @param domain Distributed domain.
919/// @param T Transported scalar — \f$T^n\f$ on entry, \f$T^{n+1}\f$ on exit.
920/// @param T_L Implicit low-order predictor satisfying the backward-Euler system above.
921/// @param vel Q1 nodal velocity field \f$\mathbf{u}\f$ (needed only for antidiff geometry).
922/// @param cell_centers Pre-computed cell centres (ghost layers populated via `initialize_cell_centers`).
923/// @param grid Lateral node coordinates of the unit-sphere surface.
924/// @param radii Radial shell radii.
925/// @param dt Time step \f$\Delta t\f$.
926/// @param bufs Pre-allocated FCT scratch arrays.
927template < typename ScalarT >
929 const grid::shell::DistributedDomain& domain,
933 const grid::Grid4DDataVec< ScalarT, 3 >& cell_centers,
936 const ScalarT dt,
938{
939 // Stage 1: antidiffusive fluxes from T^n.
940 fct_antidiff( domain, T, vel, cell_centers, grid, radii, dt, bufs );
941
942 // Stage 2: install the implicit predictor as the low-order solution.
943 Kokkos::deep_copy( bufs.T_L, T_L.grid_data() );
944
945 // Stage 3: Zalesak limiter — ghost T_L, compute R+/R-, ghost R+/R-.
946 fct_limiter( domain, bufs );
947
948 // Stage 4: apply limited correction. T^{n+1} is written into T.
949 fct_correction( domain, T, bufs );
950}
951
952} // namespace terra::fv::hex::operators
Pre-allocated send/recv buffers and sorted face-pair lists for FV ghost layer comm.
Definition fv_communication.hpp:184
Parallel data structure organizing the thick spherical shell metadata for distributed (MPI parallel) ...
Definition spherical_shell.hpp:2518
Finite volume vector on distributed shell grid with one DoF per hex (merging 2 wedges) and ghost-laye...
Definition vector_fv.hpp:23
const grid::Grid4DDataScalar< ScalarType > & grid_data() const
Get const reference to grid data.
Definition vector_fv.hpp:145
const grid::Grid4DDataVec< ScalarType, VecDim > & grid_data() const
Get const reference to grid data.
Definition vector_q1.hpp:288
Timer supporting RAII scope or manual stop.
Definition timer.hpp:342
Ghost layer communication for scalar finite volume (FV) fields on the shell grid.
Shared FV hex cell geometry: face-normal surface integrals, velocity fluxes, cell volume.
void update_fv_ghost_layers(const grid::shell::DistributedDomain &domain, const grid::Grid4DDataScalar< ScalarType > &data, FVGhostLayerBuffers< ScalarType > &bufs)
Fills all ghost layers of a scalar FV field from neighbouring subdomains.
Definition fv_communication.hpp:316
Definition advection_diffusion.hpp:9
void fct_correction(const grid::shell::DistributedDomain &domain, linalg::VectorFVScalar< ScalarT > &T_new, FVFCTBuffers< ScalarT > &bufs)
Stage 3: apply the Zalesak-limited antidiffusive correction.
Definition fct_advection_diffusion.hpp:662
ScalarT compute_dt_stable(const grid::shell::DistributedDomain &domain, const linalg::VectorQ1Vec< ScalarT, 3 > &vel, const grid::Grid4DDataVec< ScalarT, 3 > &cell_centers, const grid::Grid3DDataVec< ScalarT, 3 > &grid, const grid::Grid2DDataScalar< ScalarT > &radii, const ScalarT diffusivity=ScalarT(0))
Compute the largest explicit time step that keeps the FCT low-order predictor stable.
Definition fct_advection_diffusion.hpp:193
void fct_semiimplicit_step(const grid::shell::DistributedDomain &domain, linalg::VectorFVScalar< ScalarT > &T, const linalg::VectorFVScalar< ScalarT > &T_L, const linalg::VectorQ1Vec< ScalarT, 3 > &vel, const grid::Grid4DDataVec< ScalarT, 3 > &cell_centers, const grid::Grid3DDataVec< ScalarT, 3 > &grid, const grid::Grid2DDataScalar< ScalarT > &radii, const ScalarT dt, FVFCTBuffers< ScalarT > &bufs)
One semi-implicit FCT advection–diffusion timestep.
Definition fct_advection_diffusion.hpp:928
void fct_predictor(const grid::shell::DistributedDomain &domain, const linalg::VectorFVScalar< ScalarT > &T_old, const linalg::VectorQ1Vec< ScalarT, 3 > &vel, const grid::Grid4DDataVec< ScalarT, 3 > &cell_centers, const grid::Grid3DDataVec< ScalarT, 3 > &grid, const grid::Grid2DDataScalar< ScalarT > &radii, const ScalarT dt, FVFCTBuffers< ScalarT > &bufs, const ScalarT diffusivity=ScalarT(0), const grid::Grid4DDataScalar< ScalarT > &source={}, const bool subtract_divergence=true)
Stage 1: low-order predictor + antidiffusive flux computation.
Definition fct_advection_diffusion.hpp:405
void fct_limiter(const grid::shell::DistributedDomain &domain, FVFCTBuffers< ScalarT > &bufs)
Stage 2: compute Zalesak / correction factors.
Definition fct_advection_diffusion.hpp:549
void fct_antidiff(const grid::shell::DistributedDomain &domain, const linalg::VectorFVScalar< ScalarT > &T_old, const linalg::VectorQ1Vec< ScalarT, 3 > &vel, const grid::Grid4DDataVec< ScalarT, 3 > &cell_centers, const grid::Grid3DDataVec< ScalarT, 3 > &grid, const grid::Grid2DDataScalar< ScalarT > &radii, const ScalarT dt, FVFCTBuffers< ScalarT > &bufs)
Computes pre-scaled antidiffusive fluxes from (semi-implicit FCT stage 1).
Definition fct_advection_diffusion.hpp:865
void upwind_explicit_step(const grid::shell::DistributedDomain &domain, linalg::VectorFVScalar< ScalarT > &T, const linalg::VectorQ1Vec< ScalarT, 3 > &vel, const grid::Grid4DDataVec< ScalarT, 3 > &cell_centers, const grid::Grid3DDataVec< ScalarT, 3 > &grid, const grid::Grid2DDataScalar< ScalarT > &radii, const ScalarT dt, FVFCTBuffers< ScalarT > &bufs, const ScalarT diffusivity=ScalarT(0), const grid::Grid4DDataScalar< ScalarT > &source={}, const bool subtract_divergence=true)
One explicit first-order upwind advection–diffusion timestep (no FCT correction).
Definition fct_advection_diffusion.hpp:777
void fct_explicit_step(const grid::shell::DistributedDomain &domain, linalg::VectorFVScalar< ScalarT > &T, const linalg::VectorQ1Vec< ScalarT, 3 > &vel, const grid::Grid4DDataVec< ScalarT, 3 > &cell_centers, const grid::Grid3DDataVec< ScalarT, 3 > &grid, const grid::Grid2DDataScalar< ScalarT > &radii, const ScalarT dt, FVFCTBuffers< ScalarT > &bufs, const ScalarT diffusivity=ScalarT(0), const grid::Grid4DDataScalar< ScalarT > &source={}, const bool subtract_divergence=true, const grid::Grid4DDataScalar< grid::shell::ShellBoundaryFlag > &boundary_mask={}, const DirichletBCs< ScalarT > &bcs={})
One complete explicit FCT advection–diffusion timestep.
Definition fct_advection_diffusion.hpp:721
void apply_dirichlet_bcs(grid::Grid4DDataScalar< ScalarT > data, const grid::Grid4DDataScalar< grid::shell::ShellBoundaryFlag > &boundary_mask, const DirichletBCs< ScalarT > &bcs, const grid::shell::DistributedDomain &domain)
Strongly enforce Dirichlet boundary conditions on the radial shell boundaries.
Definition helpers.hpp:66
Kokkos::MDRangePolicy< Kokkos::Rank< 4 > > local_domain_md_range_policy_cells_fv_skip_ghost_layers(const DistributedDomain &distributed_domain)
Definition spherical_shell.hpp:2763
Kokkos::View< ScalarType *****, Layout > Grid5DDataScalar
Definition grid_types.hpp:30
Kokkos::View< ScalarType ***[VecDim], Layout > Grid3DDataVec
Definition grid_types.hpp:42
Kokkos::View< ScalarType ****, Layout > Grid4DDataScalar
Definition grid_types.hpp:27
Kokkos::View< ScalarType **, Layout > Grid2DDataScalar
Definition grid_types.hpp:21
T dot(const Vec &other) const
Definition vec.hpp:39
Kokkos kernel that computes the local maximum stable explicit dt for each FV cell.
Definition fct_advection_diffusion.hpp:112
static constexpr int num_neighbors
Definition fct_advection_diffusion.hpp:117
grid::Grid3DDataVec< ScalarT, 3 > grid_
Definition fct_advection_diffusion.hpp:119
grid::Grid4DDataVec< ScalarT, 3 > vel_grid_
Definition fct_advection_diffusion.hpp:122
grid::Grid2DDataScalar< ScalarT > radii_
Definition fct_advection_diffusion.hpp:120
grid::Grid4DDataVec< ScalarT, 3 > cell_centers_
Definition fct_advection_diffusion.hpp:121
ScalarT diffusivity_
Definition fct_advection_diffusion.hpp:123
ScalarT ScalarType
Definition fct_advection_diffusion.hpp:113
void operator()(const int id, const int x, const int y, const int r, ScalarT &local_min) const
Definition fct_advection_diffusion.hpp:126
Kokkos kernel that computes only the pre-scaled antidiffusive fluxes from .
Definition fct_advection_diffusion.hpp:813
grid::Grid4DDataScalar< ScalarT > T_old_
with ghost layers filled.
Definition fct_advection_diffusion.hpp:823
grid::Grid3DDataVec< ScalarT, 3 > grid_
Definition fct_advection_diffusion.hpp:819
ScalarT dt_
Time step .
Definition fct_advection_diffusion.hpp:825
grid::Grid5DDataScalar< ScalarT > antidiff_
Output: , shape .
Definition fct_advection_diffusion.hpp:824
void operator()(const int id, const int x, const int y, const int r) const
Definition fct_advection_diffusion.hpp:828
static constexpr int num_neighbors
Definition fct_advection_diffusion.hpp:817
grid::Grid4DDataVec< ScalarT, 3 > cell_centers_
Definition fct_advection_diffusion.hpp:821
grid::Grid2DDataScalar< ScalarT > radii_
Definition fct_advection_diffusion.hpp:820
ScalarT ScalarType
Definition fct_advection_diffusion.hpp:814
grid::Grid4DDataVec< ScalarT, 3 > vel_grid_
Definition fct_advection_diffusion.hpp:822
Kokkos kernel that applies the Zalesak-limited antidiffusive correction to .
Definition fct_advection_diffusion.hpp:607
grid::Grid4DDataScalar< ScalarT > R_minus_
correction factor (ghost layers filled).
Definition fct_advection_diffusion.hpp:614
grid::Grid5DDataScalar< ScalarT > antidiff_
: pre-scaled antidiff fluxes.
Definition fct_advection_diffusion.hpp:612
static constexpr int num_neighbors
Definition fct_advection_diffusion.hpp:609
grid::Grid4DDataScalar< ScalarT > R_plus_
correction factor (ghost layers filled).
Definition fct_advection_diffusion.hpp:613
grid::Grid4DDataScalar< ScalarT > T_L_
: low-order predictor.
Definition fct_advection_diffusion.hpp:611
grid::Grid4DDataScalar< ScalarT > T_new_
Output: .
Definition fct_advection_diffusion.hpp:615
void operator()(const int id, const int x, const int y, const int r) const
Definition fct_advection_diffusion.hpp:618
Kokkos kernel that computes the Zalesak nodal correction factors and from the low-order predictor ...
Definition fct_advection_diffusion.hpp:487
grid::Grid4DDataScalar< ScalarT > R_minus_
Output: correction factor.
Definition fct_advection_diffusion.hpp:494
grid::Grid4DDataScalar< ScalarT > T_L_
with ghost layers filled.
Definition fct_advection_diffusion.hpp:491
grid::Grid4DDataScalar< ScalarT > R_plus_
Output: correction factor.
Definition fct_advection_diffusion.hpp:493
static constexpr int num_neighbors
Definition fct_advection_diffusion.hpp:489
void operator()(const int id, const int x, const int y, const int r) const
Definition fct_advection_diffusion.hpp:497
grid::Grid5DDataScalar< ScalarT > antidiff_
: pre-scaled antidiff fluxes.
Definition fct_advection_diffusion.hpp:492
Kokkos kernel for the explicit low-order predictor and antidiffusive flux assembly.
Definition fct_advection_diffusion.hpp:263
static constexpr int num_neighbors
Definition fct_advection_diffusion.hpp:268
bool has_source_
Definition fct_advection_diffusion.hpp:290
ScalarT dt_
Time step .
Definition fct_advection_diffusion.hpp:281
bool subtract_divergence_
Whether to subtract the discrete divergence error .
Definition fct_advection_diffusion.hpp:299
grid::Grid4DDataScalar< ScalarT > T_old_
: scalar at time level (ghost layers must be filled).
Definition fct_advection_diffusion.hpp:276
void operator()(const int id, const int x, const int y, const int r) const
Definition fct_advection_diffusion.hpp:302
grid::Grid4DDataScalar< ScalarT > source_
Definition fct_advection_diffusion.hpp:289
ScalarT ScalarType
Definition fct_advection_diffusion.hpp:264
grid::Grid3DDataVec< ScalarT, 3 > grid_
Definition fct_advection_diffusion.hpp:270
grid::Grid4DDataScalar< ScalarT > T_L_
: low-order predictor output.
Definition fct_advection_diffusion.hpp:277
grid::Grid2DDataScalar< ScalarT > radii_
Definition fct_advection_diffusion.hpp:271
grid::Grid5DDataScalar< ScalarT > antidiff_
: pre-scaled antidiff flux per face, shape .
Definition fct_advection_diffusion.hpp:279
grid::Grid4DDataVec< ScalarT, 3 > vel_grid_
Definition fct_advection_diffusion.hpp:273
grid::Grid4DDataVec< ScalarT, 3 > cell_centers_
Definition fct_advection_diffusion.hpp:272
ScalarT diffusivity_
Physical diffusivity ; set to 0 for pure advection.
Definition fct_advection_diffusion.hpp:282
All working storage for a single FCT timestep, allocated once and reused every step.
Definition fct_advection_diffusion.hpp:29
grid::Grid4DDataScalar< ScalarType > T_L
Low-order (upwind) predictor ; same cell layout as the transported scalar.
Definition fct_advection_diffusion.hpp:31
grid::Grid4DDataScalar< ScalarType > R_minus
Zalesak correction factor — limits incoming negative antidiff flux.
Definition fct_advection_diffusion.hpp:38
communication::shell::FVGhostLayerBuffers< ScalarType > ghost_T
MPI ghost-layer buffers for (reused for T_L as well via the same slot).
Definition fct_advection_diffusion.hpp:41
grid::Grid4DDataScalar< ScalarType > R_plus
Zalesak correction factor — limits incoming positive antidiff flux.
Definition fct_advection_diffusion.hpp:36
communication::shell::FVGhostLayerBuffers< ScalarType > ghost_R_plus
MPI ghost-layer buffers for (needed so the correction kernel reads neighbours).
Definition fct_advection_diffusion.hpp:43
grid::Grid5DDataScalar< ScalarType > antidiff
Definition fct_advection_diffusion.hpp:34
communication::shell::FVGhostLayerBuffers< ScalarType > ghost_R_minus
MPI ghost-layer buffers for .
Definition fct_advection_diffusion.hpp:45
FVFCTBuffers(const grid::shell::DistributedDomain &domain)
Definition fct_advection_diffusion.hpp:47
Stateless geometry helper for a single FV hex cell.
Definition geometry_helper.hpp:32
static constexpr int cell_offset_y(int n)
Definition geometry_helper.hpp:42
static constexpr int cell_offset_r(int n)
Definition geometry_helper.hpp:47
static constexpr int cell_offset_x(int n)
Definition geometry_helper.hpp:37
static constexpr int num_neighbors
Definition geometry_helper.hpp:35
static void compute_geometry(const grid::Grid3DDataVec< ScalarT, 3 > &grid, const grid::Grid2DDataScalar< ScalarT > &radii, const grid::Grid4DDataVec< ScalarT, 3 > &cell_centers, const grid::Grid4DDataVec< ScalarT, 3 > &vel_grid, const int local_subdomain_id, const int x_cell, const int y_cell, const int r_cell, ScalarT(&beta)[num_neighbors], ScalarT &M_ii, Vec3(&S_f)[num_neighbors])
Compute beta[6], M_ii, and S_f[6] for the cell at (x_cell, y_cell, r_cell).
Definition geometry_helper.hpp:191