xref: /honee/qfunctions/bc_outflow.h (revision 475f0cac5d40259768f4556cf888e8f2448554cb)
1224fc8c8SJames Wright // SPDX-FileCopyrightText: Copyright (c) 2017-2025, HONEE contributors.
2224fc8c8SJames Wright // SPDX-License-Identifier: Apache-2.0 OR BSD-2-Clause
3224fc8c8SJames Wright 
4224fc8c8SJames Wright /// @file
5224fc8c8SJames Wright /// QFunctions for the `bc_freestream` and `bc_outflow` boundary conditions
6224fc8c8SJames Wright #include "bc_freestream_type.h"
7224fc8c8SJames Wright #include "newtonian_state.h"
8224fc8c8SJames Wright #include "newtonian_types.h"
9224fc8c8SJames Wright #include "riemann_solver.h"
10224fc8c8SJames Wright 
11224fc8c8SJames Wright // Note the identity
12224fc8c8SJames Wright //
13224fc8c8SJames Wright // softplus(x) - x = log(1 + exp(x)) - x
14224fc8c8SJames Wright //                 = log(1 + exp(x)) + log(exp(-x))
15224fc8c8SJames Wright //                 = log((1 + exp(x)) * exp(-x))
16224fc8c8SJames Wright //                 = log(exp(-x) + 1)
17224fc8c8SJames Wright //                 = softplus(-x)
Softplus(CeedScalar x,CeedScalar width)18224fc8c8SJames Wright CEED_QFUNCTION_HELPER CeedScalar Softplus(CeedScalar x, CeedScalar width) {
19224fc8c8SJames Wright   if (x > 40 * width) return x;
20224fc8c8SJames Wright   return width * log1p(exp(x / width));
21224fc8c8SJames Wright }
22224fc8c8SJames Wright 
Softplus_fwd(CeedScalar x,CeedScalar dx,CeedScalar width)23224fc8c8SJames Wright CEED_QFUNCTION_HELPER CeedScalar Softplus_fwd(CeedScalar x, CeedScalar dx, CeedScalar width) {
24224fc8c8SJames Wright   if (x > 40 * width) return 1;
25224fc8c8SJames Wright   const CeedScalar t = exp(x / width);
26224fc8c8SJames Wright   return t / (1 + t);
27224fc8c8SJames Wright }
28224fc8c8SJames Wright 
29224fc8c8SJames Wright // Viscous Outflow boundary condition, setting a constant exterior pressure and
30224fc8c8SJames Wright // temperature as input for a Riemann solve. This condition is stable even in
31224fc8c8SJames Wright // recirculating flow so long as the exterior temperature is sensible.
32224fc8c8SJames Wright //
33224fc8c8SJames Wright // The velocity in the exterior state has optional softplus regularization to
34224fc8c8SJames Wright // keep it outflow. These parameters have been finnicky in practice and provide
35224fc8c8SJames Wright // little or no benefit in the tests we've run thus far, thus we recommend
36224fc8c8SJames Wright // skipping this feature and just allowing recirculation.
RiemannOutflow(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out,StateVariable state_var)37224fc8c8SJames Wright CEED_QFUNCTION_HELPER int RiemannOutflow(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out, StateVariable state_var) {
38224fc8c8SJames Wright   const OutflowContext outflow     = (OutflowContext)ctx;
39224fc8c8SJames Wright   const CeedScalar(*q)[CEED_Q_VLA] = (const CeedScalar(*)[CEED_Q_VLA])in[0];
40224fc8c8SJames Wright   const CeedScalar(*Grad_q)        = in[1];
41224fc8c8SJames Wright   const CeedScalar(*q_data_sur)    = in[2];
42224fc8c8SJames Wright   CeedScalar(*v)[CEED_Q_VLA]       = (CeedScalar(*)[CEED_Q_VLA])out[0];
43*cde3d787SJames Wright   CeedScalar(*jac_data_sur)        = outflow->newt_ctx.is_implicit ? out[1] : NULL;
44224fc8c8SJames Wright 
45*cde3d787SJames Wright   const NewtonianIGProperties gas         = outflow->newt_ctx.gas;
46*cde3d787SJames Wright   const bool                  is_implicit = outflow->newt_ctx.is_implicit;
47224fc8c8SJames Wright 
48224fc8c8SJames Wright   CeedPragmaSIMD for (CeedInt i = 0; i < Q; i++) {
49224fc8c8SJames Wright     CeedScalar wdetJb, dXdx[2][3], normal[3];
50224fc8c8SJames Wright     QdataBoundaryUnpack_3D(Q, i, q_data_sur, &wdetJb, dXdx, normal);
51224fc8c8SJames Wright     wdetJb *= is_implicit ? -1. : 1.;
52224fc8c8SJames Wright     const CeedScalar qi[5] = {q[0][i], q[1][i], q[2][i], q[3][i], q[4][i]};
53224fc8c8SJames Wright     const State      s_int = StateFromQ(gas, qi, state_var);
54224fc8c8SJames Wright 
55224fc8c8SJames Wright     StatePrimitive y_ext      = s_int.Y;
56224fc8c8SJames Wright     y_ext.pressure            = outflow->pressure;
57224fc8c8SJames Wright     y_ext.temperature         = outflow->temperature;
58224fc8c8SJames Wright     const CeedScalar u_normal = Dot3(y_ext.velocity, normal);
59224fc8c8SJames Wright     const CeedScalar proj     = (1 - outflow->recirc) * Softplus(-u_normal, outflow->softplus_velocity);
60224fc8c8SJames Wright     for (CeedInt j = 0; j < 3; j++) {
61224fc8c8SJames Wright       y_ext.velocity[j] += normal[j] * proj;  // (I - n n^T) projects into the plane tangent to the normal
62224fc8c8SJames Wright     }
63224fc8c8SJames Wright     State s_ext = StateFromPrimitive(gas, y_ext);
64224fc8c8SJames Wright 
65224fc8c8SJames Wright     State grad_s[3];
66224fc8c8SJames Wright     StatePhysicalGradientFromReference_Boundary(Q, i, gas, s_int, state_var, Grad_q, dXdx, grad_s);
67224fc8c8SJames Wright 
68224fc8c8SJames Wright     CeedScalar strain_rate[6], kmstress[6], stress[3][3], Fe[3];
69224fc8c8SJames Wright     KMStrainRate_State(grad_s, strain_rate);
70224fc8c8SJames Wright     NewtonianStress(gas, strain_rate, kmstress);
71224fc8c8SJames Wright     KMUnpack(kmstress, stress);
72224fc8c8SJames Wright     ViscousEnergyFlux(gas, s_int.Y, grad_s, stress, Fe);
73224fc8c8SJames Wright 
74224fc8c8SJames Wright     StateConservative F_inviscid_normal = RiemannFlux_HLLC(gas, s_int, s_ext, normal);
75224fc8c8SJames Wright 
76224fc8c8SJames Wright     CeedScalar Flux[5];
77224fc8c8SJames Wright     FluxTotal_RiemannBoundary(F_inviscid_normal, stress, Fe, normal, Flux);
78224fc8c8SJames Wright 
79224fc8c8SJames Wright     for (CeedInt j = 0; j < 5; j++) v[j][i] = -wdetJb * Flux[j];
80224fc8c8SJames Wright 
81224fc8c8SJames Wright     // Save values for Jacobian
82224fc8c8SJames Wright     if (is_implicit) {
83224fc8c8SJames Wright       StoredValuesPack(Q, i, 0, 5, qi, jac_data_sur);
84224fc8c8SJames Wright       StoredValuesPack(Q, i, 5, 6, kmstress, jac_data_sur);
85224fc8c8SJames Wright     }
86224fc8c8SJames Wright   }
87224fc8c8SJames Wright   return 0;
88224fc8c8SJames Wright }
89224fc8c8SJames Wright 
RiemannOutflow_Conserv(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)90224fc8c8SJames Wright CEED_QFUNCTION(RiemannOutflow_Conserv)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
91224fc8c8SJames Wright   return RiemannOutflow(ctx, Q, in, out, STATEVAR_CONSERVATIVE);
92224fc8c8SJames Wright }
93224fc8c8SJames Wright 
RiemannOutflow_Prim(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)94224fc8c8SJames Wright CEED_QFUNCTION(RiemannOutflow_Prim)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
95224fc8c8SJames Wright   return RiemannOutflow(ctx, Q, in, out, STATEVAR_PRIMITIVE);
96224fc8c8SJames Wright }
97224fc8c8SJames Wright 
RiemannOutflow_Entropy(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)98224fc8c8SJames Wright CEED_QFUNCTION(RiemannOutflow_Entropy)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
99224fc8c8SJames Wright   return RiemannOutflow(ctx, Q, in, out, STATEVAR_ENTROPY);
100224fc8c8SJames Wright }
101224fc8c8SJames Wright 
102224fc8c8SJames Wright // *****************************************************************************
103224fc8c8SJames Wright // Jacobian for Riemann pressure/temperature outflow boundary condition
104224fc8c8SJames Wright // *****************************************************************************
RiemannOutflow_Jacobian(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out,StateVariable state_var)105224fc8c8SJames Wright CEED_QFUNCTION_HELPER int RiemannOutflow_Jacobian(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out,
106224fc8c8SJames Wright                                                   StateVariable state_var) {
107224fc8c8SJames Wright   const CeedScalar(*dq)[CEED_Q_VLA] = (const CeedScalar(*)[CEED_Q_VLA])in[0];
108224fc8c8SJames Wright   const CeedScalar(*Grad_dq)        = in[1];
109224fc8c8SJames Wright   const CeedScalar(*q_data_sur)     = in[2];
110224fc8c8SJames Wright   const CeedScalar(*jac_data_sur)   = in[4];
111224fc8c8SJames Wright   CeedScalar(*v)[CEED_Q_VLA]        = (CeedScalar(*)[CEED_Q_VLA])out[0];
112224fc8c8SJames Wright 
113224fc8c8SJames Wright   const OutflowContext        outflow     = (OutflowContext)ctx;
114*cde3d787SJames Wright   const NewtonianIGProperties gas         = outflow->newt_ctx.gas;
115*cde3d787SJames Wright   const bool                  is_implicit = outflow->newt_ctx.is_implicit;
116224fc8c8SJames Wright 
117224fc8c8SJames Wright   CeedPragmaSIMD for (CeedInt i = 0; i < Q; i++) {
118224fc8c8SJames Wright     CeedScalar wdetJb, dXdx[2][3], normal[3];
119224fc8c8SJames Wright     QdataBoundaryUnpack_3D(Q, i, q_data_sur, &wdetJb, dXdx, normal);
120224fc8c8SJames Wright     wdetJb *= is_implicit ? -1. : 1.;
121224fc8c8SJames Wright 
122224fc8c8SJames Wright     CeedScalar qi[5], kmstress[6], dqi[5];
123224fc8c8SJames Wright     StoredValuesUnpack(Q, i, 0, 5, jac_data_sur, qi);
124224fc8c8SJames Wright     StoredValuesUnpack(Q, i, 5, 6, jac_data_sur, kmstress);
125224fc8c8SJames Wright     for (int j = 0; j < 5; j++) dqi[j] = dq[j][i];
126224fc8c8SJames Wright 
127224fc8c8SJames Wright     State          s_int  = StateFromQ(gas, qi, state_var);
128224fc8c8SJames Wright     const State    ds_int = StateFromQ_fwd(gas, s_int, dqi, state_var);
129224fc8c8SJames Wright     StatePrimitive y_ext = s_int.Y, dy_ext = ds_int.Y;
130224fc8c8SJames Wright     y_ext.pressure             = outflow->pressure;
131224fc8c8SJames Wright     y_ext.temperature          = outflow->temperature;
132224fc8c8SJames Wright     dy_ext.pressure            = 0;
133224fc8c8SJames Wright     dy_ext.temperature         = 0;
134224fc8c8SJames Wright     const CeedScalar u_normal  = Dot3(s_int.Y.velocity, normal);
135224fc8c8SJames Wright     const CeedScalar du_normal = Dot3(ds_int.Y.velocity, normal);
136224fc8c8SJames Wright     const CeedScalar proj      = (1 - outflow->recirc) * Softplus(-u_normal, outflow->softplus_velocity);
137224fc8c8SJames Wright     const CeedScalar dproj     = (1 - outflow->recirc) * Softplus_fwd(-u_normal, -du_normal, outflow->softplus_velocity);
138224fc8c8SJames Wright     for (CeedInt j = 0; j < 3; j++) {
139224fc8c8SJames Wright       y_ext.velocity[j] += normal[j] * proj;
140224fc8c8SJames Wright       dy_ext.velocity[j] += normal[j] * dproj;
141224fc8c8SJames Wright     }
142224fc8c8SJames Wright 
143224fc8c8SJames Wright     State s_ext  = StateFromPrimitive(gas, y_ext);
144224fc8c8SJames Wright     State ds_ext = StateFromPrimitive_fwd(gas, s_ext, dy_ext);
145224fc8c8SJames Wright 
146224fc8c8SJames Wright     State grad_ds[3];
147224fc8c8SJames Wright     StatePhysicalGradientFromReference_Boundary(Q, i, gas, s_int, state_var, Grad_dq, dXdx, grad_ds);
148224fc8c8SJames Wright 
149224fc8c8SJames Wright     CeedScalar dstrain_rate[6], dkmstress[6], stress[3][3], dstress[3][3], dFe[3];
150224fc8c8SJames Wright     KMStrainRate_State(grad_ds, dstrain_rate);
151224fc8c8SJames Wright     NewtonianStress(gas, dstrain_rate, dkmstress);
152224fc8c8SJames Wright     KMUnpack(dkmstress, dstress);
153224fc8c8SJames Wright     KMUnpack(kmstress, stress);
154224fc8c8SJames Wright     ViscousEnergyFlux_fwd(gas, s_int.Y, ds_int.Y, grad_ds, stress, dstress, dFe);
155224fc8c8SJames Wright 
156224fc8c8SJames Wright     StateConservative dF_inviscid_normal = RiemannFlux_HLLC_fwd(gas, s_int, ds_int, s_ext, ds_ext, normal);
157224fc8c8SJames Wright 
158224fc8c8SJames Wright     CeedScalar dFlux[5];
159224fc8c8SJames Wright     FluxTotal_RiemannBoundary(dF_inviscid_normal, dstress, dFe, normal, dFlux);
160224fc8c8SJames Wright 
161224fc8c8SJames Wright     for (int j = 0; j < 5; j++) v[j][i] = -wdetJb * dFlux[j];
162224fc8c8SJames Wright   }
163224fc8c8SJames Wright   return 0;
164224fc8c8SJames Wright }
165224fc8c8SJames Wright 
RiemannOutflow_Jacobian_Conserv(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)166224fc8c8SJames Wright CEED_QFUNCTION(RiemannOutflow_Jacobian_Conserv)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
167224fc8c8SJames Wright   return RiemannOutflow_Jacobian(ctx, Q, in, out, STATEVAR_CONSERVATIVE);
168224fc8c8SJames Wright }
169224fc8c8SJames Wright 
RiemannOutflow_Jacobian_Prim(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)170224fc8c8SJames Wright CEED_QFUNCTION(RiemannOutflow_Jacobian_Prim)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
171224fc8c8SJames Wright   return RiemannOutflow_Jacobian(ctx, Q, in, out, STATEVAR_PRIMITIVE);
172224fc8c8SJames Wright }
173224fc8c8SJames Wright 
RiemannOutflow_Jacobian_Entropy(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)174224fc8c8SJames Wright CEED_QFUNCTION(RiemannOutflow_Jacobian_Entropy)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
175224fc8c8SJames Wright   return RiemannOutflow_Jacobian(ctx, Q, in, out, STATEVAR_ENTROPY);
176224fc8c8SJames Wright }
177224fc8c8SJames Wright 
178224fc8c8SJames Wright // *****************************************************************************
179224fc8c8SJames Wright // Outflow boundary condition, weakly setting a constant pressure. This is the
180224fc8c8SJames Wright // classic outflow condition used by PHASTA-C and retained largely for
181224fc8c8SJames Wright // comparison. In our experiments, it is never better than RiemannOutflow, and
182224fc8c8SJames Wright // will crash if outflow ever becomes an inflow, as occurs with strong
183224fc8c8SJames Wright // acoustics, vortices, etc.
184224fc8c8SJames Wright // *****************************************************************************
PressureOutflow(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out,StateVariable state_var)185224fc8c8SJames Wright CEED_QFUNCTION_HELPER int PressureOutflow(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out, StateVariable state_var) {
186224fc8c8SJames Wright   const OutflowContext outflow     = (OutflowContext)ctx;
187224fc8c8SJames Wright   const CeedScalar(*q)[CEED_Q_VLA] = (const CeedScalar(*)[CEED_Q_VLA])in[0];
188224fc8c8SJames Wright   const CeedScalar(*Grad_q)        = in[1];
189224fc8c8SJames Wright   const CeedScalar(*q_data_sur)    = in[2];
190224fc8c8SJames Wright   CeedScalar(*v)[CEED_Q_VLA]       = (CeedScalar(*)[CEED_Q_VLA])out[0];
191*cde3d787SJames Wright   CeedScalar(*jac_data_sur)        = outflow->newt_ctx.is_implicit ? out[1] : NULL;
192224fc8c8SJames Wright 
193*cde3d787SJames Wright   const NewtonianIGProperties gas         = outflow->newt_ctx.gas;
194*cde3d787SJames Wright   const bool                  is_implicit = outflow->newt_ctx.is_implicit;
195224fc8c8SJames Wright 
196224fc8c8SJames Wright   CeedPragmaSIMD for (CeedInt i = 0; i < Q; i++) {
197224fc8c8SJames Wright     const CeedScalar qi[5] = {q[0][i], q[1][i], q[2][i], q[3][i], q[4][i]};
198224fc8c8SJames Wright     State            s     = StateFromQ(gas, qi, state_var);
199224fc8c8SJames Wright     s.Y.pressure           = outflow->pressure;
200224fc8c8SJames Wright 
201224fc8c8SJames Wright     CeedScalar wdetJb, dXdx[2][3], normal[3];
202224fc8c8SJames Wright     QdataBoundaryUnpack_3D(Q, i, q_data_sur, &wdetJb, dXdx, normal);
203224fc8c8SJames Wright     wdetJb *= is_implicit ? -1. : 1.;
204224fc8c8SJames Wright 
205224fc8c8SJames Wright     State grad_s[3];
206224fc8c8SJames Wright     StatePhysicalGradientFromReference_Boundary(Q, i, gas, s, state_var, Grad_q, dXdx, grad_s);
207224fc8c8SJames Wright 
208224fc8c8SJames Wright     CeedScalar strain_rate[6], kmstress[6], stress[3][3], Fe[3];
209224fc8c8SJames Wright     KMStrainRate_State(grad_s, strain_rate);
210224fc8c8SJames Wright     NewtonianStress(gas, strain_rate, kmstress);
211224fc8c8SJames Wright     KMUnpack(kmstress, stress);
212224fc8c8SJames Wright     ViscousEnergyFlux(gas, s.Y, grad_s, stress, Fe);
213224fc8c8SJames Wright 
214224fc8c8SJames Wright     StateConservative F_inviscid[3];
215224fc8c8SJames Wright     FluxInviscid(gas, s, F_inviscid);
216224fc8c8SJames Wright 
217224fc8c8SJames Wright     CeedScalar Flux[5];
218224fc8c8SJames Wright     FluxTotal_Boundary(F_inviscid, stress, Fe, normal, Flux);
219224fc8c8SJames Wright 
220224fc8c8SJames Wright     for (CeedInt j = 0; j < 5; j++) v[j][i] = -wdetJb * Flux[j];
221224fc8c8SJames Wright 
222224fc8c8SJames Wright     // Save values for Jacobian
223224fc8c8SJames Wright     if (is_implicit) {
224224fc8c8SJames Wright       StoredValuesPack(Q, i, 0, 5, qi, jac_data_sur);
225224fc8c8SJames Wright       StoredValuesPack(Q, i, 5, 6, kmstress, jac_data_sur);
226224fc8c8SJames Wright     }
227224fc8c8SJames Wright   }
228224fc8c8SJames Wright   return 0;
229224fc8c8SJames Wright }
230224fc8c8SJames Wright 
PressureOutflow_Conserv(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)231224fc8c8SJames Wright CEED_QFUNCTION(PressureOutflow_Conserv)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
232224fc8c8SJames Wright   return PressureOutflow(ctx, Q, in, out, STATEVAR_CONSERVATIVE);
233224fc8c8SJames Wright }
234224fc8c8SJames Wright 
PressureOutflow_Prim(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)235224fc8c8SJames Wright CEED_QFUNCTION(PressureOutflow_Prim)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
236224fc8c8SJames Wright   return PressureOutflow(ctx, Q, in, out, STATEVAR_PRIMITIVE);
237224fc8c8SJames Wright }
238224fc8c8SJames Wright 
PressureOutflow_Entropy(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)239224fc8c8SJames Wright CEED_QFUNCTION(PressureOutflow_Entropy)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
240224fc8c8SJames Wright   return PressureOutflow(ctx, Q, in, out, STATEVAR_ENTROPY);
241224fc8c8SJames Wright }
242224fc8c8SJames Wright 
243224fc8c8SJames Wright // *****************************************************************************
244224fc8c8SJames Wright // Jacobian for weak-pressure outflow boundary condition
245224fc8c8SJames Wright // *****************************************************************************
PressureOutflow_Jacobian(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out,StateVariable state_var)246224fc8c8SJames Wright CEED_QFUNCTION_HELPER int PressureOutflow_Jacobian(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out,
247224fc8c8SJames Wright                                                    StateVariable state_var) {
248224fc8c8SJames Wright   const CeedScalar(*dq)[CEED_Q_VLA] = (const CeedScalar(*)[CEED_Q_VLA])in[0];
249224fc8c8SJames Wright   const CeedScalar(*Grad_dq)        = in[1];
250224fc8c8SJames Wright   const CeedScalar(*q_data_sur)     = in[2];
251224fc8c8SJames Wright   const CeedScalar(*jac_data_sur)   = in[4];
252224fc8c8SJames Wright   CeedScalar(*v)[CEED_Q_VLA]        = (CeedScalar(*)[CEED_Q_VLA])out[0];
253224fc8c8SJames Wright 
254224fc8c8SJames Wright   const OutflowContext        outflow     = (OutflowContext)ctx;
255*cde3d787SJames Wright   const NewtonianIGProperties gas         = outflow->newt_ctx.gas;
256*cde3d787SJames Wright   const bool                  is_implicit = outflow->newt_ctx.is_implicit;
257224fc8c8SJames Wright 
258224fc8c8SJames Wright   CeedPragmaSIMD for (CeedInt i = 0; i < Q; i++) {
259224fc8c8SJames Wright     CeedScalar wdetJb, dXdx[2][3], normal[3];
260224fc8c8SJames Wright     QdataBoundaryUnpack_3D(Q, i, q_data_sur, &wdetJb, dXdx, normal);
261224fc8c8SJames Wright     wdetJb *= is_implicit ? -1. : 1.;
262224fc8c8SJames Wright 
263224fc8c8SJames Wright     CeedScalar qi[5], kmstress[6], dqi[5];
264224fc8c8SJames Wright     StoredValuesUnpack(Q, i, 0, 5, jac_data_sur, qi);
265224fc8c8SJames Wright     StoredValuesUnpack(Q, i, 5, 6, jac_data_sur, kmstress);
266224fc8c8SJames Wright     for (int j = 0; j < 5; j++) dqi[j] = dq[j][i];
267224fc8c8SJames Wright 
268224fc8c8SJames Wright     State s       = StateFromQ(gas, qi, state_var);
269224fc8c8SJames Wright     State ds      = StateFromQ_fwd(gas, s, dqi, state_var);
270224fc8c8SJames Wright     s.Y.pressure  = outflow->pressure;
271224fc8c8SJames Wright     ds.Y.pressure = 0.;
272224fc8c8SJames Wright 
273224fc8c8SJames Wright     State grad_ds[3];
274224fc8c8SJames Wright     StatePhysicalGradientFromReference_Boundary(Q, i, gas, s, state_var, Grad_dq, dXdx, grad_ds);
275224fc8c8SJames Wright 
276224fc8c8SJames Wright     CeedScalar dstrain_rate[6], dkmstress[6], stress[3][3], dstress[3][3], dFe[3];
277224fc8c8SJames Wright     KMStrainRate_State(grad_ds, dstrain_rate);
278224fc8c8SJames Wright     NewtonianStress(gas, dstrain_rate, dkmstress);
279224fc8c8SJames Wright     KMUnpack(dkmstress, dstress);
280224fc8c8SJames Wright     KMUnpack(kmstress, stress);
281224fc8c8SJames Wright     ViscousEnergyFlux_fwd(gas, s.Y, ds.Y, grad_ds, stress, dstress, dFe);
282224fc8c8SJames Wright 
283224fc8c8SJames Wright     StateConservative dF_inviscid[3];
284224fc8c8SJames Wright     FluxInviscid_fwd(gas, s, ds, dF_inviscid);
285224fc8c8SJames Wright 
286224fc8c8SJames Wright     CeedScalar dFlux[5];
287224fc8c8SJames Wright     FluxTotal_Boundary(dF_inviscid, dstress, dFe, normal, dFlux);
288224fc8c8SJames Wright 
289224fc8c8SJames Wright     for (int j = 0; j < 5; j++) v[j][i] = -wdetJb * dFlux[j];
290224fc8c8SJames Wright   }
291224fc8c8SJames Wright   return 0;
292224fc8c8SJames Wright }
293224fc8c8SJames Wright 
PressureOutflow_Jacobian_Conserv(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)294224fc8c8SJames Wright CEED_QFUNCTION(PressureOutflow_Jacobian_Conserv)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
295224fc8c8SJames Wright   return PressureOutflow_Jacobian(ctx, Q, in, out, STATEVAR_CONSERVATIVE);
296224fc8c8SJames Wright }
297224fc8c8SJames Wright 
PressureOutflow_Jacobian_Prim(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)298224fc8c8SJames Wright CEED_QFUNCTION(PressureOutflow_Jacobian_Prim)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
299224fc8c8SJames Wright   return PressureOutflow_Jacobian(ctx, Q, in, out, STATEVAR_PRIMITIVE);
300224fc8c8SJames Wright }
301224fc8c8SJames Wright 
PressureOutflow_Jacobian_Entropy(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)302224fc8c8SJames Wright CEED_QFUNCTION(PressureOutflow_Jacobian_Entropy)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
303224fc8c8SJames Wright   return PressureOutflow_Jacobian(ctx, Q, in, out, STATEVAR_ENTROPY);
304224fc8c8SJames Wright }
305