xref: /libCEED/examples/fluids/qfunctions/eulervortex.h (revision 91216f769229b3793e221c647f948ce6d0249ef6)
1 // Copyright (c) 2017-2025, Lawrence Livermore National Security, LLC and other CEED contributors.
2 // All Rights Reserved. See the top-level LICENSE and NOTICE files for details.
3 //
4 // SPDX-License-Identifier: BSD-2-Clause
5 //
6 // This file is part of CEED:  http://github.com/ceed
7 
8 /// @file
9 /// Euler traveling vortex initial condition and operator for Navier-Stokes
10 /// example using PETSc
11 
12 // Model from:
13 //   On the Order of Accuracy and Numerical Performance of Two Classes of Finite Volume WENO Schemes, Zhang, Zhang, and Shu (2011).
14 #include <ceed/types.h>
15 #ifndef CEED_RUNNING_JIT_PASS
16 #include <math.h>
17 #include <stdbool.h>
18 #endif
19 
20 #include "utils.h"
21 
22 typedef struct EulerContext_ *EulerContext;
23 struct EulerContext_ {
24   CeedScalar center[3];
25   CeedScalar curr_time;
26   CeedScalar vortex_strength;
27   CeedScalar c_tau;
28   CeedScalar mean_velocity[3];
29   bool       implicit;
30   int        euler_test;
31   int        stabilization;  // See StabilizationType: 0=none, 1=SU, 2=SUPG
32 };
33 
34 // *****************************************************************************
35 // This function sets the initial conditions
36 //
37 //   Temperature:
38 //     T   = 1 - (gamma - 1) vortex_strength**2 exp(1 - r**2) / (8 gamma pi**2)
39 //   Density:
40 //     rho = (T/S_vortex)^(1 / (gamma - 1))
41 //   Pressure:
42 //     P   = rho * T
43 //   Velocity:
44 //     ui  = 1 + vortex_strength exp((1 - r**2)/2.) [yc - y, x - xc] / (2 pi)
45 //     r   = sqrt( (x - xc)**2 + (y - yc)**2 )
46 //   Velocity/Momentum Density:
47 //     Ui  = rho ui
48 //   Total Energy:
49 //     E   = P / (gamma - 1) + rho (u u)/2
50 //
51 // Constants:
52 //   cv              ,  Specific heat, constant volume
53 //   cp              ,  Specific heat, constant pressure
54 //   vortex_strength ,  Strength of vortex
55 //   center          ,  Location of bubble center
56 //   gamma  = cp / cv,  Specific heat ratio
57 //
58 // *****************************************************************************
59 
60 // *****************************************************************************
61 // This helper function provides support for the exact, time-dependent solution (currently not implemented) and IC formulation for Euler traveling
62 // vortex
63 // *****************************************************************************
64 CEED_QFUNCTION_HELPER int Exact_Euler(CeedInt dim, CeedScalar time, const CeedScalar X[], CeedInt Nf, CeedScalar q[], void *ctx) {
65   // Context
66   const EulerContext context         = (EulerContext)ctx;
67   const CeedScalar   vortex_strength = context->vortex_strength;
68   const CeedScalar  *center          = context->center;  // Center of the domain
69   const CeedScalar  *mean_velocity   = context->mean_velocity;
70 
71   // Setup
72   const CeedScalar gamma = 1.4;
73   const CeedScalar cv    = 2.5;
74   const CeedScalar R     = 1.;
75   const CeedScalar x = X[0], y = X[1];  // Coordinates
76   // Vortex center
77   const CeedScalar xc = center[0] + mean_velocity[0] * time;
78   const CeedScalar yc = center[1] + mean_velocity[1] * time;
79 
80   const CeedScalar x0       = x - xc;
81   const CeedScalar y0       = y - yc;
82   const CeedScalar r        = sqrt(x0 * x0 + y0 * y0);
83   const CeedScalar C        = vortex_strength * exp((1. - r * r) / 2.) / (2. * M_PI);
84   const CeedScalar delta_T  = -(gamma - 1.) * vortex_strength * vortex_strength * exp(1 - r * r) / (8. * gamma * M_PI * M_PI);
85   const CeedScalar S_vortex = 1;  // no perturbation in the entropy P / rho^gamma
86   const CeedScalar S_bubble = (gamma - 1.) * vortex_strength * vortex_strength / (8. * gamma * M_PI * M_PI);
87   CeedScalar       rho, P, T, E, u[3] = {0.};
88 
89   // Initial Conditions
90   switch (context->euler_test) {
91     case 0:  // Traveling vortex
92       T = 1 + delta_T;
93       // P = rho * T
94       // P = S * rho^gamma
95       // Solve for rho, then substitute for P
96       rho  = pow(T / S_vortex, 1 / (gamma - 1.));
97       P    = rho * T;
98       u[0] = mean_velocity[0] - C * y0;
99       u[1] = mean_velocity[1] + C * x0;
100 
101       // Assign exact solution
102       q[0] = rho;
103       q[1] = rho * u[0];
104       q[2] = rho * u[1];
105       q[3] = rho * u[2];
106       q[4] = P / (gamma - 1.) + rho * (u[0] * u[0] + u[1] * u[1]) / 2.;
107       break;
108     case 1:  // Constant zero velocity, density constant, total energy constant
109       rho = 1.;
110       E   = 2.;
111 
112       // Assign exact solution
113       q[0] = rho;
114       q[1] = rho * u[0];
115       q[2] = rho * u[1];
116       q[3] = rho * u[2];
117       q[4] = E;
118       break;
119     case 2:  // Constant nonzero velocity, density constant, total energy constant
120       rho  = 1.;
121       E    = 2.;
122       u[0] = mean_velocity[0];
123       u[1] = mean_velocity[1];
124 
125       // Assign exact solution
126       q[0] = rho;
127       q[1] = rho * u[0];
128       q[2] = rho * u[1];
129       q[3] = rho * u[2];
130       q[4] = E;
131       break;
132     case 3:  // Velocity zero, pressure constant (so density and internal energy will be non-constant), but the velocity should stay zero and the
133              // bubble won't diffuse
134       // (for Euler, where there is no thermal conductivity)
135       P   = 1.;
136       T   = 1. - S_bubble * exp(1. - r * r);
137       rho = P / (R * T);
138 
139       // Assign exact solution
140       q[0] = rho;
141       q[1] = rho * u[0];
142       q[2] = rho * u[1];
143       q[3] = rho * u[2];
144       q[4] = rho * (cv * T + (u[0] * u[0] + u[1] * u[1]) / 2.);
145       break;
146     case 4:  // Constant nonzero velocity, pressure constant (so density and internal energy will be non-constant),
147       // It should be transported across the domain, but velocity stays constant
148       P    = 1.;
149       T    = 1. - S_bubble * exp(1. - r * r);
150       rho  = P / (R * T);
151       u[0] = mean_velocity[0];
152       u[1] = mean_velocity[1];
153 
154       // Assign exact solution
155       q[0] = rho;
156       q[1] = rho * u[0];
157       q[2] = rho * u[1];
158       q[3] = rho * u[2];
159       q[4] = rho * (cv * T + (u[0] * u[0] + u[1] * u[1]) / 2.);
160       break;
161     case 5:  // non-smooth thermal bubble - cylinder
162       P    = 1.;
163       T    = 1. - (r < 1. ? S_bubble : 0.);
164       rho  = P / (R * T);
165       u[0] = mean_velocity[0];
166       u[1] = mean_velocity[1];
167 
168       // Assign exact solution
169       q[0] = rho;
170       q[1] = rho * u[0];
171       q[2] = rho * u[1];
172       q[3] = rho * u[2];
173       q[4] = rho * (cv * T + (u[0] * u[0] + u[1] * u[1]) / 2.);
174       break;
175   }
176   return 0;
177 }
178 
179 // *****************************************************************************
180 // Helper function for computing flux Jacobian
181 // *****************************************************************************
182 CEED_QFUNCTION_HELPER void ConvectiveFluxJacobian_Euler(CeedScalar dF[3][5][5], const CeedScalar rho, const CeedScalar u[3], const CeedScalar E,
183                                                         const CeedScalar gamma) {
184   CeedScalar u_sq = u[0] * u[0] + u[1] * u[1] + u[2] * u[2];  // Velocity square
185   for (CeedInt i = 0; i < 3; i++) {                           // Jacobian matrices for 3 directions
186     for (CeedInt j = 0; j < 3; j++) {                         // Rows of each Jacobian matrix
187       dF[i][j + 1][0] = ((i == j) ? ((gamma - 1.) * (u_sq / 2.)) : 0.) - u[i] * u[j];
188       for (CeedInt k = 0; k < 3; k++) {  // Columns of each Jacobian matrix
189         dF[i][0][k + 1]     = ((i == k) ? 1. : 0.);
190         dF[i][j + 1][k + 1] = ((j == k) ? u[i] : 0.) + ((i == k) ? u[j] : 0.) - ((i == j) ? u[k] : 0.) * (gamma - 1.);
191         dF[i][4][k + 1]     = ((i == k) ? (E * gamma / rho - (gamma - 1.) * u_sq / 2.) : 0.) - (gamma - 1.) * u[i] * u[k];
192       }
193       dF[i][j + 1][4] = ((i == j) ? (gamma - 1.) : 0.);
194     }
195     dF[i][4][0] = u[i] * ((gamma - 1.) * u_sq - E * gamma / rho);
196     dF[i][4][4] = u[i] * gamma;
197   }
198 }
199 
200 // *****************************************************************************
201 // Helper function for computing Tau elements (stabilization constant)
202 //   Model from:
203 //     Stabilized Methods for Compressible Flows, Hughes et al 2010
204 //
205 //   Spatial criterion #2 - Tau is a 3x3 diagonal matrix
206 //   Tau[i] = c_tau h[i] Xi(Pe) / rho(A[i]) (no sum)
207 //
208 // Where
209 //   c_tau     = stabilization constant (0.5 is reported as "optimal")
210 //   h[i]      = 2 length(dxdX[i])
211 //   Pe        = Peclet number ( Pe = sqrt(u u) / dot(dXdx,u) diffusivity )
212 //   Xi(Pe)    = coth Pe - 1. / Pe (1. at large local Peclet number )
213 //   rho(A[i]) = spectral radius of the convective flux Jacobian i, wave speed in direction i
214 // *****************************************************************************
215 CEED_QFUNCTION_HELPER void Tau_spatial(CeedScalar Tau_x[3], const CeedScalar dXdx[3][3], const CeedScalar u[3], const CeedScalar sound_speed,
216                                        const CeedScalar c_tau) {
217   for (CeedInt i = 0; i < 3; i++) {
218     // length of element in direction i
219     CeedScalar h = 2 / sqrt(dXdx[0][i] * dXdx[0][i] + dXdx[1][i] * dXdx[1][i] + dXdx[2][i] * dXdx[2][i]);
220     // fastest wave in direction i
221     CeedScalar fastest_wave = fabs(u[i]) + sound_speed;
222     Tau_x[i]                = c_tau * h / fastest_wave;
223   }
224 }
225 
226 // *****************************************************************************
227 // This QFunction sets the initial conditions for Euler traveling vortex
228 // *****************************************************************************
229 CEED_QFUNCTION(ICsEuler)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
230   const CeedScalar(*X)[CEED_Q_VLA] = (const CeedScalar(*)[CEED_Q_VLA])in[0];
231   CeedScalar(*q0)[CEED_Q_VLA]      = (CeedScalar(*)[CEED_Q_VLA])out[0];
232 
233   const EulerContext context = (EulerContext)ctx;
234 
235   CeedPragmaSIMD for (CeedInt i = 0; i < Q; i++) {
236     const CeedScalar x[]  = {X[0][i], X[1][i], X[2][i]};
237     CeedScalar       q[5] = {0.};
238 
239     Exact_Euler(3, context->curr_time, x, 5, q, ctx);
240     for (CeedInt j = 0; j < 5; j++) q0[j][i] = q[j];
241   }
242   return 0;
243 }
244 
245 // *****************************************************************************
246 // This QFunction implements the following formulation of Euler equations with explicit time stepping method
247 //
248 // This is 3D Euler for compressible gas dynamics in conservation form with state variables of density, momentum density, and total energy density.
249 //
250 // State Variables: q = ( rho, U1, U2, U3, E )
251 //   rho - Mass Density
252 //   Ui  - Momentum Density,      Ui = rho ui
253 //   E   - Total Energy Density,  E  = P / (gamma - 1) + rho (u u)/2
254 //
255 // Euler Equations:
256 //   drho/dt + div( U )                   = 0
257 //   dU/dt   + div( rho (u x u) + P I3 )  = 0
258 //   dE/dt   + div( (E + P) u )           = 0
259 //
260 // Equation of State:
261 //   P = (gamma - 1) (E - rho (u u) / 2)
262 //
263 // Constants:
264 //   cv              ,  Specific heat, constant volume
265 //   cp              ,  Specific heat, constant pressure
266 //   g               ,  Gravity
267 //   gamma  = cp / cv,  Specific heat ratio
268 // *****************************************************************************
269 CEED_QFUNCTION(Euler)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
270   const CeedScalar(*q)[CEED_Q_VLA]     = (const CeedScalar(*)[CEED_Q_VLA])in[0];
271   const CeedScalar(*dq)[5][CEED_Q_VLA] = (const CeedScalar(*)[5][CEED_Q_VLA])in[1];
272   const CeedScalar(*q_data)            = in[2];
273   CeedScalar(*v)[CEED_Q_VLA]           = (CeedScalar(*)[CEED_Q_VLA])out[0];
274   CeedScalar(*dv)[5][CEED_Q_VLA]       = (CeedScalar(*)[5][CEED_Q_VLA])out[1];
275 
276   EulerContext     context = (EulerContext)ctx;
277   const CeedScalar c_tau   = context->c_tau;
278   const CeedScalar gamma   = 1.4;
279 
280   CeedPragmaSIMD for (CeedInt i = 0; i < Q; i++) {
281     // Setup
282     // -- Interp in
283     const CeedScalar rho      = q[0][i];
284     const CeedScalar u[3]     = {q[1][i] / rho, q[2][i] / rho, q[3][i] / rho};
285     const CeedScalar E        = q[4][i];
286     const CeedScalar drho[3]  = {dq[0][0][i], dq[1][0][i], dq[2][0][i]};
287     const CeedScalar dU[3][3] = {
288         {dq[0][1][i], dq[1][1][i], dq[2][1][i]},
289         {dq[0][2][i], dq[1][2][i], dq[2][2][i]},
290         {dq[0][3][i], dq[1][3][i], dq[2][3][i]}
291     };
292     const CeedScalar dE[3] = {dq[0][4][i], dq[1][4][i], dq[2][4][i]};
293     CeedScalar       wdetJ, dXdx[3][3];
294     QdataUnpack_3D(Q, i, q_data, &wdetJ, dXdx);
295     // dU/dx
296     CeedScalar drhodx[3]       = {0.};
297     CeedScalar dEdx[3]         = {0.};
298     CeedScalar dUdx[3][3]      = {{0.}};
299     CeedScalar dXdxdXdxT[3][3] = {{0.}};
300     for (CeedInt j = 0; j < 3; j++) {
301       for (CeedInt k = 0; k < 3; k++) {
302         drhodx[j] += drho[k] * dXdx[k][j];
303         dEdx[j] += dE[k] * dXdx[k][j];
304         for (CeedInt l = 0; l < 3; l++) {
305           dUdx[j][k] += dU[j][l] * dXdx[l][k];
306           dXdxdXdxT[j][k] += dXdx[j][l] * dXdx[k][l];  // dXdx_j,k * dXdx_k,j
307         }
308       }
309     }
310     // Pressure
311     const CeedScalar E_kinetic = 0.5 * rho * (u[0] * u[0] + u[1] * u[1] + u[2] * u[2]), E_internal = E - E_kinetic,
312                      P = E_internal * (gamma - 1.);  // P = pressure
313 
314     // The Physics
315     // Zero v and dv so all future terms can safely sum into it
316     for (CeedInt j = 0; j < 5; j++) {
317       v[j][i] = 0.;
318       for (CeedInt k = 0; k < 3; k++) dv[k][j][i] = 0.;
319     }
320 
321     // -- Density
322     // ---- u rho
323     for (CeedInt j = 0; j < 3; j++) dv[j][0][i] += wdetJ * (rho * u[0] * dXdx[j][0] + rho * u[1] * dXdx[j][1] + rho * u[2] * dXdx[j][2]);
324     // -- Momentum
325     // ---- rho (u x u) + P I3
326     for (CeedInt j = 0; j < 3; j++) {
327       for (CeedInt k = 0; k < 3; k++) {
328         dv[k][j + 1][i] += wdetJ * ((rho * u[j] * u[0] + (j == 0 ? P : 0.)) * dXdx[k][0] + (rho * u[j] * u[1] + (j == 1 ? P : 0.)) * dXdx[k][1] +
329                                     (rho * u[j] * u[2] + (j == 2 ? P : 0.)) * dXdx[k][2]);
330       }
331     }
332     // -- Total Energy Density
333     // ---- (E + P) u
334     for (CeedInt j = 0; j < 3; j++) dv[j][4][i] += wdetJ * (E + P) * (u[0] * dXdx[j][0] + u[1] * dXdx[j][1] + u[2] * dXdx[j][2]);
335 
336     // --Stabilization terms
337     // ---- jacob_F_conv[3][5][5] = dF(convective)/dq at each direction
338     CeedScalar jacob_F_conv[3][5][5] = {{{0.}}};
339     ConvectiveFluxJacobian_Euler(jacob_F_conv, rho, u, E, gamma);
340 
341     // ---- dqdx collects drhodx, dUdx and dEdx in one vector
342     CeedScalar dqdx[5][3];
343     for (CeedInt j = 0; j < 3; j++) {
344       dqdx[0][j] = drhodx[j];
345       dqdx[4][j] = dEdx[j];
346       for (CeedInt k = 0; k < 3; k++) dqdx[k + 1][j] = dUdx[k][j];
347     }
348 
349     // ---- strong_conv = dF/dq * dq/dx    (Strong convection)
350     CeedScalar strong_conv[5] = {0.};
351     for (CeedInt j = 0; j < 3; j++) {
352       for (CeedInt k = 0; k < 5; k++) {
353         for (CeedInt l = 0; l < 5; l++) strong_conv[k] += jacob_F_conv[j][k][l] * dqdx[l][j];
354       }
355     }
356 
357     // Stabilization
358     // -- Tau elements
359     const CeedScalar sound_speed = sqrt(gamma * P / rho);
360     CeedScalar       Tau_x[3]    = {0.};
361     Tau_spatial(Tau_x, dXdx, u, sound_speed, c_tau);
362 
363     // -- Stabilization method: none or SU
364     CeedScalar stab[5][3] = {{0.}};
365     switch (context->stabilization) {
366       case 0:  // Galerkin
367         break;
368       case 1:  // SU
369         for (CeedInt j = 0; j < 3; j++) {
370           for (CeedInt k = 0; k < 5; k++) {
371             for (CeedInt l = 0; l < 5; l++) stab[k][j] += jacob_F_conv[j][k][l] * Tau_x[j] * strong_conv[l];
372           }
373         }
374 
375         for (CeedInt j = 0; j < 5; j++) {
376           for (CeedInt k = 0; k < 3; k++) dv[k][j][i] -= wdetJ * (stab[j][0] * dXdx[k][0] + stab[j][1] * dXdx[k][1] + stab[j][2] * dXdx[k][2]);
377         }
378         break;
379       case 2:  // SUPG is not implemented for explicit scheme
380         break;
381     }
382   }
383   return 0;
384 }
385 
386 // *****************************************************************************
387 // This QFunction implements the Euler equations with (mentioned above) with implicit time stepping method
388 // *****************************************************************************
389 CEED_QFUNCTION(IFunction_Euler)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
390   const CeedScalar(*q)[CEED_Q_VLA]     = (const CeedScalar(*)[CEED_Q_VLA])in[0];
391   const CeedScalar(*dq)[5][CEED_Q_VLA] = (const CeedScalar(*)[5][CEED_Q_VLA])in[1];
392   const CeedScalar(*q_dot)[CEED_Q_VLA] = (const CeedScalar(*)[CEED_Q_VLA])in[2];
393   const CeedScalar(*q_data)            = in[3];
394   CeedScalar(*v)[CEED_Q_VLA]           = (CeedScalar(*)[CEED_Q_VLA])out[0];
395   CeedScalar(*dv)[5][CEED_Q_VLA]       = (CeedScalar(*)[5][CEED_Q_VLA])out[1];
396   CeedScalar *jac_data                 = out[2];
397 
398   EulerContext     context   = (EulerContext)ctx;
399   const CeedScalar c_tau     = context->c_tau;
400   const CeedScalar gamma     = 1.4;
401   const CeedScalar zeros[14] = {0.};
402 
403   CeedPragmaSIMD for (CeedInt i = 0; i < Q; i++) {
404     // Setup
405     // -- Interp in
406     const CeedScalar rho      = q[0][i];
407     const CeedScalar u[3]     = {q[1][i] / rho, q[2][i] / rho, q[3][i] / rho};
408     const CeedScalar E        = q[4][i];
409     const CeedScalar drho[3]  = {dq[0][0][i], dq[1][0][i], dq[2][0][i]};
410     const CeedScalar dU[3][3] = {
411         {dq[0][1][i], dq[1][1][i], dq[2][1][i]},
412         {dq[0][2][i], dq[1][2][i], dq[2][2][i]},
413         {dq[0][3][i], dq[1][3][i], dq[2][3][i]}
414     };
415     const CeedScalar dE[3] = {dq[0][4][i], dq[1][4][i], dq[2][4][i]};
416     CeedScalar       wdetJ, dXdx[3][3];
417     QdataUnpack_3D(Q, i, q_data, &wdetJ, dXdx);
418     // dU/dx
419     CeedScalar drhodx[3]       = {0.};
420     CeedScalar dEdx[3]         = {0.};
421     CeedScalar dUdx[3][3]      = {{0.}};
422     CeedScalar dXdxdXdxT[3][3] = {{0.}};
423     for (CeedInt j = 0; j < 3; j++) {
424       for (CeedInt k = 0; k < 3; k++) {
425         drhodx[j] += drho[k] * dXdx[k][j];
426         dEdx[j] += dE[k] * dXdx[k][j];
427         for (CeedInt l = 0; l < 3; l++) {
428           dUdx[j][k] += dU[j][l] * dXdx[l][k];
429           dXdxdXdxT[j][k] += dXdx[j][l] * dXdx[k][l];  // dXdx_j,k * dXdx_k,j
430         }
431       }
432     }
433     const CeedScalar E_kinetic = 0.5 * rho * (u[0] * u[0] + u[1] * u[1] + u[2] * u[2]), E_internal = E - E_kinetic,
434                      P = E_internal * (gamma - 1.);  // P = pressure
435 
436     // The Physics
437     // Zero v and dv so all future terms can safely sum into it
438     for (CeedInt j = 0; j < 5; j++) {
439       v[j][i] = 0.;
440       for (CeedInt k = 0; k < 3; k++) dv[k][j][i] = 0.;
441     }
442     //-----mass matrix
443     for (CeedInt j = 0; j < 5; j++) v[j][i] += wdetJ * q_dot[j][i];
444 
445     // -- Density
446     // ---- u rho
447     for (CeedInt j = 0; j < 3; j++) dv[j][0][i] -= wdetJ * (rho * u[0] * dXdx[j][0] + rho * u[1] * dXdx[j][1] + rho * u[2] * dXdx[j][2]);
448     // -- Momentum
449     // ---- rho (u x u) + P I3
450     for (CeedInt j = 0; j < 3; j++) {
451       for (CeedInt k = 0; k < 3; k++) {
452         dv[k][j + 1][i] -= wdetJ * ((rho * u[j] * u[0] + (j == 0 ? P : 0.)) * dXdx[k][0] + (rho * u[j] * u[1] + (j == 1 ? P : 0.)) * dXdx[k][1] +
453                                     (rho * u[j] * u[2] + (j == 2 ? P : 0.)) * dXdx[k][2]);
454       }
455     }
456     // -- Total Energy Density
457     // ---- (E + P) u
458     for (CeedInt j = 0; j < 3; j++) dv[j][4][i] -= wdetJ * (E + P) * (u[0] * dXdx[j][0] + u[1] * dXdx[j][1] + u[2] * dXdx[j][2]);
459 
460     // -- Stabilization terms
461     // ---- jacob_F_conv[3][5][5] = dF(convective)/dq at each direction
462     CeedScalar jacob_F_conv[3][5][5] = {{{0.}}};
463     ConvectiveFluxJacobian_Euler(jacob_F_conv, rho, u, E, gamma);
464 
465     // ---- dqdx collects drhodx, dUdx and dEdx in one vector
466     CeedScalar dqdx[5][3];
467     for (CeedInt j = 0; j < 3; j++) {
468       dqdx[0][j] = drhodx[j];
469       dqdx[4][j] = dEdx[j];
470       for (CeedInt k = 0; k < 3; k++) dqdx[k + 1][j] = dUdx[k][j];
471     }
472 
473     // ---- strong_conv = dF/dq * dq/dx    (Strong convection)
474     CeedScalar strong_conv[5] = {0.};
475     for (CeedInt j = 0; j < 3; j++) {
476       for (CeedInt k = 0; k < 5; k++) {
477         for (CeedInt l = 0; l < 5; l++) strong_conv[k] += jacob_F_conv[j][k][l] * dqdx[l][j];
478       }
479     }
480 
481     // ---- Strong residual
482     CeedScalar strong_res[5];
483     for (CeedInt j = 0; j < 5; j++) strong_res[j] = q_dot[j][i] + strong_conv[j];
484 
485     // Stabilization
486     // -- Tau elements
487     const CeedScalar sound_speed = sqrt(gamma * P / rho);
488     CeedScalar       Tau_x[3]    = {0.};
489     Tau_spatial(Tau_x, dXdx, u, sound_speed, c_tau);
490 
491     // -- Stabilization method: none, SU, or SUPG
492     CeedScalar stab[5][3] = {{0.}};
493     switch (context->stabilization) {
494       case 0:  // Galerkin
495         break;
496       case 1:  // SU
497         for (CeedInt j = 0; j < 3; j++) {
498           for (CeedInt k = 0; k < 5; k++) {
499             for (CeedInt l = 0; l < 5; l++) stab[k][j] += jacob_F_conv[j][k][l] * Tau_x[j] * strong_conv[l];
500           }
501         }
502 
503         for (CeedInt j = 0; j < 5; j++) {
504           for (CeedInt k = 0; k < 3; k++) dv[k][j][i] += wdetJ * (stab[j][0] * dXdx[k][0] + stab[j][1] * dXdx[k][1] + stab[j][2] * dXdx[k][2]);
505         }
506         break;
507       case 2:  // SUPG
508         for (CeedInt j = 0; j < 3; j++) {
509           for (CeedInt k = 0; k < 5; k++) {
510             for (CeedInt l = 0; l < 5; l++) stab[k][j] = jacob_F_conv[j][k][l] * Tau_x[j] * strong_res[l];
511           }
512         }
513 
514         for (CeedInt j = 0; j < 5; j++) {
515           for (CeedInt k = 0; k < 3; k++) dv[k][j][i] += wdetJ * (stab[j][0] * dXdx[k][0] + stab[j][1] * dXdx[k][1] + stab[j][2] * dXdx[k][2]);
516         }
517         break;
518     }
519     StoredValuesPack(Q, i, 0, 14, zeros, jac_data);
520   }
521   return 0;
522 }
523 // *****************************************************************************
524 // This QFunction sets the inflow boundary conditions for the traveling vortex problem.
525 //
526 //  Prescribed T_inlet and P_inlet are converted to conservative variables and applied weakly.
527 // *****************************************************************************
528 CEED_QFUNCTION(TravelingVortex_Inflow)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
529   const CeedScalar(*q_data_sur) = in[2];
530   CeedScalar(*v)[CEED_Q_VLA]    = (CeedScalar(*)[CEED_Q_VLA])out[0];
531 
532   EulerContext     context       = (EulerContext)ctx;
533   const int        euler_test    = context->euler_test;
534   const bool       is_implicit   = context->implicit;
535   CeedScalar      *mean_velocity = context->mean_velocity;
536   const CeedScalar cv            = 2.5;
537   const CeedScalar R             = 1.;
538   CeedScalar       T_inlet;
539   CeedScalar       P_inlet;
540 
541   // For test cases 1 and 3 the background velocity is zero
542   if (euler_test == 1 || euler_test == 3) {
543     for (CeedInt i = 0; i < 3; i++) mean_velocity[i] = 0.;
544   }
545 
546   // For test cases 1 and 2, T_inlet = T_inlet = 0.4
547   if (euler_test == 1 || euler_test == 2) T_inlet = P_inlet = .4;
548   else T_inlet = P_inlet = 1.;
549 
550   CeedPragmaSIMD for (CeedInt i = 0; i < Q; i++) {
551     CeedScalar wdetJb, norm[3];
552     QdataBoundaryUnpack_3D(Q, i, q_data_sur, &wdetJb, NULL, norm);
553     wdetJb *= is_implicit ? -1. : 1.;
554 
555     // face_normal = Normal vector of the face
556     const CeedScalar face_normal = norm[0] * mean_velocity[0] + norm[1] * mean_velocity[1] + norm[2] * mean_velocity[2];
557     // The Physics
558     // Zero v so all future terms can safely sum into it
559     for (CeedInt j = 0; j < 5; j++) v[j][i] = 0.;
560 
561     // Implementing in/outflow BCs
562     if (face_normal > 0) {
563     } else {  // inflow
564       const CeedScalar rho_inlet       = P_inlet / (R * T_inlet);
565       const CeedScalar E_kinetic_inlet = (mean_velocity[0] * mean_velocity[0] + mean_velocity[1] * mean_velocity[1]) / 2.;
566       // incoming total energy
567       const CeedScalar E_inlet = rho_inlet * (cv * T_inlet + E_kinetic_inlet);
568 
569       // The Physics
570       // -- Density
571       v[0][i] -= wdetJb * rho_inlet * face_normal;
572 
573       // -- Momentum
574       for (CeedInt j = 0; j < 3; j++) v[j + 1][i] -= wdetJb * (rho_inlet * face_normal * mean_velocity[j] + norm[j] * P_inlet);
575 
576       // -- Total Energy Density
577       v[4][i] -= wdetJb * face_normal * (E_inlet + P_inlet);
578     }
579   }
580   return 0;
581 }
582 
583 // *****************************************************************************
584 // This QFunction sets the outflow boundary conditions for the Euler solver.
585 //
586 //  Outflow BCs:
587 //    The validity of the weak form of the governing equations is extended to the outflow.
588 // *****************************************************************************
589 CEED_QFUNCTION(Euler_Outflow)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
590   const CeedScalar(*q)[CEED_Q_VLA] = (const CeedScalar(*)[CEED_Q_VLA])in[0];
591   const CeedScalar(*q_data_sur)    = in[2];
592   CeedScalar(*v)[CEED_Q_VLA]       = (CeedScalar(*)[CEED_Q_VLA])out[0];
593 
594   EulerContext context       = (EulerContext)ctx;
595   const bool   is_implicit   = context->implicit;
596   CeedScalar  *mean_velocity = context->mean_velocity;
597 
598   const CeedScalar gamma = 1.4;
599 
600   CeedPragmaSIMD for (CeedInt i = 0; i < Q; i++) {
601     // Setup
602     // -- Interp in
603     const CeedScalar rho  = q[0][i];
604     const CeedScalar u[3] = {q[1][i] / rho, q[2][i] / rho, q[3][i] / rho};
605     const CeedScalar E    = q[4][i];
606 
607     CeedScalar wdetJb, norm[3];
608     QdataBoundaryUnpack_3D(Q, i, q_data_sur, &wdetJb, NULL, norm);
609     wdetJb *= is_implicit ? -1. : 1.;
610 
611     // face_normal = Normal vector of the face
612     const CeedScalar face_normal = norm[0] * mean_velocity[0] + norm[1] * mean_velocity[1] + norm[2] * mean_velocity[2];
613     // The Physics
614     // Zero v so all future terms can safely sum into it
615     for (CeedInt j = 0; j < 5; j++) v[j][i] = 0;
616 
617     // Implementing in/outflow BCs
618     if (face_normal > 0) {  // outflow
619       const CeedScalar E_kinetic = (u[0] * u[0] + u[1] * u[1]) / 2.;
620       const CeedScalar P         = (E - E_kinetic * rho) * (gamma - 1.);              // pressure
621       const CeedScalar u_normal  = norm[0] * u[0] + norm[1] * u[1] + norm[2] * u[2];  // Normal velocity
622       // The Physics
623       // -- Density
624       v[0][i] -= wdetJb * rho * u_normal;
625 
626       // -- Momentum
627       for (CeedInt j = 0; j < 3; j++) v[j + 1][i] -= wdetJb * (rho * u_normal * u[j] + norm[j] * P);
628 
629       // -- Total Energy Density
630       v[4][i] -= wdetJb * u_normal * (E + P);
631     }
632   }
633   return 0;
634 }
635