Loading...
Searching...
No Matches
unsteady_advection_diffusion_supg.hpp
Go to the documentation of this file.
1
2#pragma once
3
4#include "../../quadrature/quadrature.hpp"
7#include "dense/vec.hpp"
11#include "linalg/operator.hpp"
12#include "linalg/vector.hpp"
13#include "linalg/vector_q1.hpp"
14#include "util/timer.hpp"
15
17
18template < typename ScalarT >
19KOKKOS_INLINE_FUNCTION ScalarT
20 supg_tau( const ScalarT vel_norm, const ScalarT kappa, const ScalarT h, const ScalarT Pe_tol = 1e-8 )
21{
22 const ScalarT kappa_min = ScalarT( 1e-8 );
23 const ScalarT kappa_virtual = Kokkos::max( kappa, kappa_min );
24
25 // Guarding against zero velocity.
26 const ScalarT eps_vel = ScalarT( 1e-12 );
27
28 if ( vel_norm <= eps_vel )
29 {
30 return 0.0;
31 }
32
33 // For small Peclet numbers (diffusion-dominated flow) set tau to 0 (no stabilization).
34 const ScalarT Pe = vel_norm * h / ( 2.0 * kappa_virtual );
35
36 if ( Pe <= Pe_tol )
37 {
38 return 0.0;
39 }
40
41 // Clamp tau to avoid huge values
42 const ScalarT tau_max = ScalarT( 10.0 ) * h / Kokkos::max( vel_norm, eps_vel );
43
44 // Finally, compute tau.
45 // Note: coth(Pe) = 1/tanh(Pe)
46 const ScalarT coth_term = ScalarT( 1.0 ) / Kokkos::tanh( Pe ) - ScalarT( 1.0 ) / Pe;
47 const ScalarT tau = ( h / ( 2.0 * vel_norm ) ) * coth_term;
48
49 return ( tau > tau_max ) ? tau_max : tau;
50 // return tau;
51}
52
53/// \brief Linear operator for a method-of-lines discretization of the unsteady advection-diffusion equation with SUPG
54/// (streamline upwind Petrov-Galerkin) stabilization.
55///
56/// # Continuous problem
57///
58/// The unsteady advection-diffusion equation is given by
59/// \f[
60/// \frac{\partial}{\partial t}T + \mathbf{u} \cdot \nabla T - \nabla \cdot (\kappa \nabla T) = f
61/// \f]
62/// where \f$ T \f$ is the scalar temperature solution, \f$ \mathbf{u} \f$ a given velocity field,
63/// \f$ f \f$ a given source term, and \f$ \kappa \f$ a given diffusivity function.
64///
65/// # Space discretization
66///
67/// \note We assume here that we have \f$ \kappa|_E = \mathrm{const} \f$ on each element \f$E\f$ which simplifies the
68/// implementation of the SUPG stabilization for linear finite elements as certain terms drop. Currently, \f$\kappa =
69/// \mathrm{const}\f$ globally, but once that is changed, we will need to average on every element it in the kernel or
70/// pass it in as an elementwise constant (FE) function.
71///
72/// We first discretize in space, then in time (method of lines). After discretization in space, we get the system of
73/// ODEs in time
74/// \f[
75/// M \frac{d}{dt}T + (C + K + G)T = F + F_\mathrm{SUPG}
76/// \f]
77/// where
78///
79/// Term | Description | Bilinear form
80/// -------------|--------------|--------------
81/// \f$M_{ij}\f$ | mass | \f$ \int \phi_i \phi_j \f$
82/// \f$C_{ij}\f$ | advection | \f$ \int \phi_i (\mathbf{u} \cdot \nabla \phi_j) \f$
83/// \f$K_{ij}\f$ | diffusion | \f$ \int \nabla \phi_i \cdot (\kappa \nabla \phi_j) \f$
84/// \f$G_{ij}\f$ | SUPG adv-adv | \f$ \sum_E \int_E \tau_E (\mathbf{u} \cdot \nabla \phi_i) (\mathbf{u} \cdot \nabla \phi_j) \f$
85/// \f$F_i\f$ | forcing | \f$ \int \phi_i f \f$
86/// \f$(F_\mathrm{SUPG})_{i}\f$ | SUPG forcing | \f$ \sum_E \int_E \tau_E (\mathbf{u} \cdot \nabla \phi_i) f \f$.
87///
88/// \note After brief testing, it seems that in general the term \f$F_\mathrm{SUPG}\f$ does not always improve the
89/// computed solutions. It even appears to slightly increase the error sometimes. So setting
90/// \f$F_\mathrm{SUPG} = 0\f$ can really be just fine in certain settings.
91///
92/// # Time discretization
93///
94/// For the time discretization, we employ implicit BDF schemes. A general formula is
95///
96/// \f[
97/// (\alpha_0 M + \Delta t A) T^{n+1} = - \sum_{j=1}^k \alpha_j M T^{n+1-j} + \Delta t R^{n+1}
98/// \f]
99///
100/// where \f$A = (C + K + G)\f$ and \f$R = F + F_{\mathrm{SUPG}}\f$.
101///
102/// We recover the common BDF1 (backward or implicit Euler) and BDF2 schemes by choosing:
103///
104/// Scheme | \f$k\f$ | \f$\alpha\f$ | full equation
105/// --------------------------------|---------|--------------------------------------|--------------
106/// BDF1 (backward/implicit Euler) | 1 | \f$[1, -1]\f$ | \f$(M + \Delta t A) T^{n+1} = M T^{n} + \Delta t R^{n+1}\f$
107/// BDF2 | 2 | \f$[\frac{3}{2}, -2, \frac{1}{2}]\f$ | \f$(\frac{3}{2} M + \Delta t A) T^{n+1} = 2 M T^{n} - \frac{1}{2} M T^{n-1} + \Delta t R^{n+1}\f$
108///
109/// The RHS term must be built with appropriate other classes/function. This class is only concerned with the
110/// matrix-free evaluation of the LHS system matrix
111/// \f[
112/// \alpha_0 M + \Delta t A.
113/// \f]
114///
115/// The parameters \f$\alpha_0\f$ and \f$\Delta t\f$ are set through the constructor via `mass_scaling` and `dt`
116/// respectively.
117///
118/// # SUPG stabilization
119///
120/// Several choices for the stabilization parameter \f$ \tau_E \f$ are possible.
121/// As it is commonly selected in the literature (see, e.g., John and Knobloch (2006)), we set on element \f$E\f$
122/// \f[
123/// \tau_E = \frac{h_E}{2 \|\mathbf{u}\|} \left(\coth(\mathrm{Pe_E}) - \frac{1}{\mathrm{Pe_E}}\right)
124/// \f]
125/// where
126/// \f[
127/// \mathrm{Pe_E} = \frac{\|\mathbf{u}\|h_E}{2 \kappa}
128/// \f]
129/// with some precautionary measures to avoid edge cases that would result in division by zero etc.
130///
131template < typename ScalarT, int VelocityVecDim = 3 >
133{
134 public:
137 using ScalarType = ScalarT;
138
139 private:
141
145
147
148 ScalarT diffusivity_;
149 ScalarT dt_;
150
151 bool treat_boundary_;
152 bool diagonal_;
153 ScalarT mass_scaling_;
154 bool lumped_mass_;
155
156 linalg::OperatorApplyMode operator_apply_mode_;
157 linalg::OperatorCommunicationMode operator_communication_mode_;
158
161
165
166 public:
168 const grid::shell::DistributedDomain& domain,
173 const ScalarT diffusivity,
174 const ScalarT dt,
175 bool treat_boundary,
176 bool diagonal = false,
177 ScalarT mass_scaling = 1.0,
178 bool lumped_mass = false,
180 linalg::OperatorCommunicationMode operator_communication_mode =
182 : domain_( domain )
183 , grid_( grid )
184 , radii_( radii )
185 , boundary_mask_( boundary_mask )
186 , velocity_( velocity )
187 , diffusivity_( diffusivity )
188 , dt_( dt )
189 , treat_boundary_( treat_boundary )
190 , diagonal_( diagonal )
191 , mass_scaling_( mass_scaling )
192 , lumped_mass_( lumped_mass )
193 , operator_apply_mode_( operator_apply_mode )
194 , operator_communication_mode_( operator_communication_mode )
195 , recv_buffers_( domain )
196 , comm_plan_( domain )
197 {}
198
199 ScalarT& dt() { return dt_; }
200 const ScalarT& dt() const { return dt_; }
201
202 void apply_impl( const SrcVectorType& src, DstVectorType& dst )
203 {
204 util::Timer timer_apply( "ad_supg_apply" );
205
206 if ( operator_apply_mode_ == linalg::OperatorApplyMode::Replace )
207 {
208 assign( dst, 0 );
209 }
210
211 src_ = src.grid_data();
212 dst_ = dst.grid_data();
213 vel_grid_ = velocity_.grid_data();
214
215 util::Timer timer_kernel( "ad_supg_kernel" );
216 Kokkos::parallel_for( "matvec", grid::shell::local_domain_md_range_policy_cells( domain_ ), *this );
217 Kokkos::fence();
218 timer_kernel.stop();
219
220 if ( operator_communication_mode_ == linalg::OperatorCommunicationMode::CommunicateAdditively )
221 {
222 util::Timer timer_comm( "ad_supg_comm" );
223 terra::communication::shell::send_recv_with_plan( comm_plan_, dst_, recv_buffers_ );
224 }
225 }
226
227 KOKKOS_INLINE_FUNCTION void
228 operator()( const int local_subdomain_id, const int x_cell, const int y_cell, const int r_cell ) const
229 {
230 // Gather surface points for each wedge.
232 wedge_surface_physical_coords( wedge_phy_surf, grid_, local_subdomain_id, x_cell, y_cell );
233
234 // Gather wedge radii.
235 const ScalarT r_1 = radii_( local_subdomain_id, r_cell );
236 const ScalarT r_2 = radii_( local_subdomain_id, r_cell + 1 );
237
238 // Quadrature points.
239 constexpr auto num_quad_points = quadrature::quad_felippa_3x2_num_quad_points;
240
241 dense::Vec< ScalarT, 3 > quad_points[num_quad_points];
242 ScalarT quad_weights[num_quad_points];
243
246
247 // Interpolating velocity into quad points.
248
250 dense::Vec< ScalarT, 6 > vel_coeffs[VelocityVecDim][num_wedges_per_hex_cell];
251
252 for ( int d = 0; d < VelocityVecDim; d++ )
253 {
255 vel_coeffs[d], local_subdomain_id, x_cell, y_cell, r_cell, d, vel_grid_ );
256 }
257
258 for ( int wedge = 0; wedge < num_wedges_per_hex_cell; wedge++ )
259 {
260 for ( int q = 0; q < num_quad_points; q++ )
261 {
262 for ( int i = 0; i < num_nodes_per_wedge; i++ )
263 {
264 const auto shape_i = shape( i, quad_points[q] );
265 for ( int d = 0; d < VelocityVecDim; d++ )
266 {
267 vel_interp[wedge][q]( d ) += vel_coeffs[d][wedge]( i ) * shape_i;
268 }
269 }
270 }
271 }
272
273 // Let's compute the streamline diffusivity.
274
275 ScalarT streamline_diffusivity[num_wedges_per_hex_cell];
276
277 // Far from accurate but for now assume h = r.
278 const auto h = r_2 - r_1;
279
280 for ( int wedge = 0; wedge < num_wedges_per_hex_cell; wedge++ )
281 {
282 ScalarT tau_accum = 0.0;
283 ScalarT waccum = 0.0;
284
285 for ( int q = 0; q < num_quad_points; ++q )
286 {
287 // get velocity at this quad point
288 const auto& uq = vel_interp[wedge][q];
289 const ScalarT vel_norm_q = uq.norm();
290
291 const ScalarT tau_q = supg_tau< ScalarT >( vel_norm_q, diffusivity_, h, 1e-08 );
292
293 // quadrature weight for this point (if you have weights)
294 const ScalarT wq = quad_weights[q]; // if not available, use 1.0
295 tau_accum += tau_q * wq;
296 waccum += wq;
297 }
298
299 // final cell/wedge tau: volume-weighted average
300 ScalarT tau_cell = ( waccum > 0.0 ) ? ( tau_accum / waccum ) : 0.0;
301 streamline_diffusivity[wedge] = tau_cell;
302 }
303
304 // Compute the local element matrix.
307
308 for ( int q = 0; q < num_quad_points; q++ )
309 {
310 const auto w = quad_weights[q];
311
312 for ( int wedge = 0; wedge < num_wedges_per_hex_cell; wedge++ )
313 {
314 const auto J = jac( wedge_phy_surf[wedge], r_1, r_2, quad_points[q] );
315 const auto det = Kokkos::abs( J.det() );
316 const auto J_inv_transposed = J.inv().transposed();
317
318 const auto vel = vel_interp[wedge][q];
319
320 for ( int i = 0; i < num_nodes_per_wedge; i++ )
321 {
322 const auto shape_i = shape( i, quad_points[q] );
323 const auto grad_i = J_inv_transposed * grad_shape( i, quad_points[q] );
324
325 for ( int j = 0; j < num_nodes_per_wedge; j++ )
326 {
327 const auto shape_j = shape( j, quad_points[q] );
328 const auto grad_j = J_inv_transposed * grad_shape( j, quad_points[q] );
329
330 const auto mass = shape_i * shape_j;
331 const auto diffusion = diffusivity_ * ( grad_i ).dot( grad_j );
332 const auto advection = ( vel.dot( grad_j ) ) * shape_i;
333 const auto streamline_diffusion =
334 streamline_diffusivity[wedge] * ( vel.dot( grad_j ) ) * ( vel.dot( grad_i ) );
335
336 M[wedge]( i, j ) += w * mass_scaling_ * mass * det;
337 A[wedge]( i, j ) += w * dt_ * ( diffusion + advection + streamline_diffusion ) * det;
338 }
339 }
340 }
341 }
342
343 if ( lumped_mass_ )
344 {
346 ones.fill( 1.0 );
349 }
350
351 if ( treat_boundary_ )
352 {
353 const int at_cmb_boundary = util::has_flag(
354 boundary_mask_( local_subdomain_id, x_cell, y_cell, r_cell ), grid::shell::ShellBoundaryFlag::CMB );
355 const int at_surface_boundary = util::has_flag(
356 boundary_mask_( local_subdomain_id, x_cell, y_cell, r_cell + 1 ),
358
359 for ( int wedge = 0; wedge < num_wedges_per_hex_cell; wedge++ )
360 {
361 dense::Mat< ScalarT, 6, 6 > boundary_mask;
362 boundary_mask.fill( 1.0 );
363 if ( at_cmb_boundary )
364 {
365 // Inner boundary (CMB).
366 for ( int i = 0; i < 6; i++ )
367 {
368 for ( int j = 0; j < 6; j++ )
369 {
370 if ( i != j && ( i < 3 || j < 3 ) )
371 {
372 boundary_mask( i, j ) = 0.0;
373 }
374 }
375 }
376 }
377
378 if ( at_surface_boundary )
379 {
380 // Outer boundary (surface).
381 for ( int i = 0; i < 6; i++ )
382 {
383 for ( int j = 0; j < 6; j++ )
384 {
385 if ( i != j && ( i >= 3 || j >= 3 ) )
386 {
387 boundary_mask( i, j ) = 0.0;
388 }
389 }
390 }
391 }
392
393 M[wedge].hadamard_product( boundary_mask );
394 A[wedge].hadamard_product( boundary_mask );
395 }
396 }
397
398 if ( diagonal_ )
399 {
400 M[0] = M[0].diagonal();
401 M[1] = M[1].diagonal();
402 A[0] = A[0].diagonal();
403 A[1] = A[1].diagonal();
404 }
405
407 extract_local_wedge_scalar_coefficients( src, local_subdomain_id, x_cell, y_cell, r_cell, src_ );
408
410
411 dst[0] = ( M[0] + A[0] ) * src[0];
412 dst[1] = ( M[1] + A[1] ) * src[1];
413
414 atomically_add_local_wedge_scalar_coefficients( dst_, local_subdomain_id, x_cell, y_cell, r_cell, dst );
415 }
416};
417
419
420} // namespace terra::fe::wedge::operators::shell
Definition communication_plan.hpp:33
Linear operator for a method-of-lines discretization of the unsteady advection-diffusion equation wit...
Definition unsteady_advection_diffusion_supg.hpp:133
UnsteadyAdvectionDiffusionSUPG(const grid::shell::DistributedDomain &domain, const grid::Grid3DDataVec< ScalarT, 3 > &grid, const grid::Grid2DDataScalar< ScalarT > &radii, const grid::Grid4DDataScalar< grid::shell::ShellBoundaryFlag > &boundary_mask, const linalg::VectorQ1Vec< ScalarT, VelocityVecDim > &velocity, const ScalarT diffusivity, const ScalarT dt, bool treat_boundary, bool diagonal=false, ScalarT mass_scaling=1.0, bool lumped_mass=false, linalg::OperatorApplyMode operator_apply_mode=linalg::OperatorApplyMode::Replace, linalg::OperatorCommunicationMode operator_communication_mode=linalg::OperatorCommunicationMode::CommunicateAdditively)
Definition unsteady_advection_diffusion_supg.hpp:167
void apply_impl(const SrcVectorType &src, DstVectorType &dst)
Definition unsteady_advection_diffusion_supg.hpp:202
void operator()(const int local_subdomain_id, const int x_cell, const int y_cell, const int r_cell) const
Definition unsteady_advection_diffusion_supg.hpp:228
const ScalarT & dt() const
Definition unsteady_advection_diffusion_supg.hpp:200
ScalarT ScalarType
Definition unsteady_advection_diffusion_supg.hpp:137
ScalarT & dt()
Definition unsteady_advection_diffusion_supg.hpp:199
Parallel data structure organizing the thick spherical shell metadata for distributed (MPI parallel) ...
Definition spherical_shell.hpp:2518
Q1 scalar finite element vector on a distributed shell grid.
Definition vector_q1.hpp:21
const grid::Grid4DDataScalar< ScalarType > & grid_data() const
Get const reference to grid data.
Definition vector_q1.hpp:139
Static assertion: VectorQ1Scalar satisfies VectorLike concept.
Definition vector_q1.hpp:168
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
void stop()
Stop the timer and record elapsed time.
Definition timer.hpp:364
Concept for types that behave like linear operators.
Definition operator.hpp:57
void send_recv_with_plan(const ShellBoundaryCommPlan< GridDataType > &plan, const GridDataType &data, SubdomainNeighborhoodSendRecvBuffer< typename GridDataType::value_type, grid::grid_data_vec_dim< GridDataType >() > &recv_buffers, CommunicationReduction reduction=CommunicationReduction::SUM)
Definition communication_plan.hpp:652
Definition boundary_mass.hpp:14
ScalarT supg_tau(const ScalarT vel_norm, const ScalarT kappa, const ScalarT h, const ScalarT Pe_tol=1e-8)
Definition unsteady_advection_diffusion_supg.hpp:20
constexpr void quad_felippa_3x2_quad_weights(T(&quad_weights)[quad_felippa_3x2_num_quad_points])
Definition wedge/quadrature/quadrature.hpp:93
constexpr int quad_felippa_3x2_num_quad_points
Definition wedge/quadrature/quadrature.hpp:66
constexpr void quad_felippa_3x2_quad_points(dense::Vec< T, 3 >(&quad_points)[quad_felippa_3x2_num_quad_points])
Definition wedge/quadrature/quadrature.hpp:70
constexpr int num_nodes_per_wedge_surface
Definition kernel_helpers.hpp:6
void atomically_add_local_wedge_scalar_coefficients(const grid::Grid4DDataScalar< T > &global_coefficients, const int local_subdomain_id, const int x_cell, const int y_cell, const int r_cell, const dense::Vec< T, 6 >(&local_coefficients)[2])
Performs an atomic add of the two local wedge coefficient vectors of a hex cell into the global coeff...
Definition kernel_helpers.hpp:407
void wedge_surface_physical_coords(dense::Vec< T, 3 >(&wedge_surf_phy_coords)[num_wedges_per_hex_cell][num_nodes_per_wedge_surface], const grid::Grid3DDataVec< T, 3 > &lateral_grid, const int local_subdomain_id, const int x_cell, const int y_cell)
Extracts the (unit sphere) surface vertex coords of the two wedges of a hex cell.
Definition kernel_helpers.hpp:26
constexpr int num_wedges_per_hex_cell
Definition kernel_helpers.hpp:5
void extract_local_wedge_scalar_coefficients(dense::Vec< T, 6 >(&local_coefficients)[2], const int local_subdomain_id, const int x_cell, const int y_cell, const int r_cell, const grid::Grid4DDataScalar< T > &global_coefficients)
Extracts the local vector coefficients for the two wedges of a hex cell from the global coefficient v...
Definition kernel_helpers.hpp:306
void extract_local_wedge_vector_coefficients(dense::Vec< T, 6 >(&local_coefficients)[2], const int local_subdomain_id, const int x_cell, const int y_cell, const int r_cell, const int d, const grid::Grid4DDataVec< T, VecDim > &global_coefficients)
Extracts the local vector coefficients for the two wedges of a hex cell from the global coefficient v...
Definition kernel_helpers.hpp:356
constexpr int num_nodes_per_wedge
Definition kernel_helpers.hpp:7
constexpr dense::Vec< T, 3 > grad_shape(const int node_idx, const T xi, const T eta, const T zeta)
Gradient of the full shape function:
Definition integrands.hpp:228
constexpr T shape(const int node_idx, const T xi, const T eta, const T zeta)
(Tensor-product) Shape function.
Definition integrands.hpp:146
constexpr dense::Mat< T, 3, 3 > jac(const dense::Vec< T, 3 > &p1_phy, const dense::Vec< T, 3 > &p2_phy, const dense::Vec< T, 3 > &p3_phy, const T r_1, const T r_2, const T xi, const T eta, const T zeta)
Definition integrands.hpp:657
Kokkos::MDRangePolicy< Kokkos::Rank< 4 > > local_domain_md_range_policy_cells(const DistributedDomain &distributed_domain)
Definition spherical_shell.hpp:2739
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
OperatorApplyMode
Modes for applying an operator to a vector.
Definition operator.hpp:30
@ Replace
Overwrite the destination vector.
OperatorCommunicationMode
Modes for communication during operator application.
Definition operator.hpp:40
@ CommunicateAdditively
Communicate and add results.
constexpr bool has_flag(E mask_value, E flag) noexcept
Checks if a bitmask value contains a specific flag.
Definition bit_masking.hpp:43
Definition mat.hpp:10
void fill(const T value)
Definition mat.hpp:201
constexpr Mat diagonal() const
Definition mat.hpp:377
static constexpr Mat diagonal_from_vec(const Vec< T, Rows > &diagonal)
Definition mat.hpp:79
Mat & hadamard_product(const Mat &mat)
Definition mat.hpp:213
T norm() const
Definition vec.hpp:76
void fill(const T value)
Definition vec.hpp:30
SoA (Structure-of-Arrays) 4D vector grid data.
Definition grid_types.hpp:51