xref: /honee/qfunctions/bc_outflow.h (revision a5677b81b23156371f678817da1e299792f51b83)
1 // SPDX-FileCopyrightText: Copyright (c) 2017-2025, HONEE contributors.
2 // SPDX-License-Identifier: Apache-2.0 OR BSD-2-Clause
3 
4 /// @file
5 /// QFunctions for the `bc_freestream` and `bc_outflow` boundary conditions
6 #include "bc_freestream_type.h"
7 #include "newtonian_state.h"
8 #include "newtonian_types.h"
9 #include "riemann_solver.h"
10 
11 // Note the identity
12 //
13 // softplus(x) - x = log(1 + exp(x)) - x
14 //                 = log(1 + exp(x)) + log(exp(-x))
15 //                 = log((1 + exp(x)) * exp(-x))
16 //                 = log(exp(-x) + 1)
17 //                 = softplus(-x)
18 CEED_QFUNCTION_HELPER CeedScalar Softplus(CeedScalar x, CeedScalar width) {
19   if (x > 40 * width) return x;
20   return width * log1p(exp(x / width));
21 }
22 
23 CEED_QFUNCTION_HELPER CeedScalar Softplus_fwd(CeedScalar x, CeedScalar dx, CeedScalar width) {
24   if (x > 40 * width) return 1;
25   const CeedScalar t = exp(x / width);
26   return t / (1 + t);
27 }
28 
29 // Viscous Outflow boundary condition, setting a constant exterior pressure and
30 // temperature as input for a Riemann solve. This condition is stable even in
31 // recirculating flow so long as the exterior temperature is sensible.
32 //
33 // The velocity in the exterior state has optional softplus regularization to
34 // keep it outflow. These parameters have been finnicky in practice and provide
35 // little or no benefit in the tests we've run thus far, thus we recommend
36 // skipping this feature and just allowing recirculation.
37 CEED_QFUNCTION_HELPER int RiemannOutflow(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out, StateVariable state_var) {
38   const OutflowContext outflow     = (OutflowContext)ctx;
39   const CeedScalar(*q)[CEED_Q_VLA] = (const CeedScalar(*)[CEED_Q_VLA])in[0];
40   const CeedScalar(*Grad_q)        = in[1];
41   const CeedScalar(*q_data_sur)    = in[2];
42   CeedScalar(*v)[CEED_Q_VLA]       = (CeedScalar(*)[CEED_Q_VLA])out[0];
43   CeedScalar(*jac_data_sur)        = outflow->gas.is_implicit ? out[1] : NULL;
44 
45   const NewtonianIdealGasContext gas         = &outflow->gas;
46   const bool                     is_implicit = gas->is_implicit;
47 
48   CeedPragmaSIMD for (CeedInt i = 0; i < Q; i++) {
49     CeedScalar wdetJb, dXdx[2][3], normal[3];
50     QdataBoundaryUnpack_3D(Q, i, q_data_sur, &wdetJb, dXdx, normal);
51     wdetJb *= is_implicit ? -1. : 1.;
52     const CeedScalar qi[5] = {q[0][i], q[1][i], q[2][i], q[3][i], q[4][i]};
53     const State      s_int = StateFromQ(gas, qi, state_var);
54 
55     StatePrimitive y_ext      = s_int.Y;
56     y_ext.pressure            = outflow->pressure;
57     y_ext.temperature         = outflow->temperature;
58     const CeedScalar u_normal = Dot3(y_ext.velocity, normal);
59     const CeedScalar proj     = (1 - outflow->recirc) * Softplus(-u_normal, outflow->softplus_velocity);
60     for (CeedInt j = 0; j < 3; j++) {
61       y_ext.velocity[j] += normal[j] * proj;  // (I - n n^T) projects into the plane tangent to the normal
62     }
63     State s_ext = StateFromPrimitive(gas, y_ext);
64 
65     State grad_s[3];
66     StatePhysicalGradientFromReference_Boundary(Q, i, gas, s_int, state_var, Grad_q, dXdx, grad_s);
67 
68     CeedScalar strain_rate[6], kmstress[6], stress[3][3], Fe[3];
69     KMStrainRate_State(grad_s, strain_rate);
70     NewtonianStress(gas, strain_rate, kmstress);
71     KMUnpack(kmstress, stress);
72     ViscousEnergyFlux(gas, s_int.Y, grad_s, stress, Fe);
73 
74     StateConservative F_inviscid_normal = RiemannFlux_HLLC(gas, s_int, s_ext, normal);
75 
76     CeedScalar Flux[5];
77     FluxTotal_RiemannBoundary(F_inviscid_normal, stress, Fe, normal, Flux);
78 
79     for (CeedInt j = 0; j < 5; j++) v[j][i] = -wdetJb * Flux[j];
80 
81     // Save values for Jacobian
82     if (is_implicit) {
83       StoredValuesPack(Q, i, 0, 5, qi, jac_data_sur);
84       StoredValuesPack(Q, i, 5, 6, kmstress, jac_data_sur);
85     }
86   }
87   return 0;
88 }
89 
90 CEED_QFUNCTION(RiemannOutflow_Conserv)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
91   return RiemannOutflow(ctx, Q, in, out, STATEVAR_CONSERVATIVE);
92 }
93 
94 CEED_QFUNCTION(RiemannOutflow_Prim)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
95   return RiemannOutflow(ctx, Q, in, out, STATEVAR_PRIMITIVE);
96 }
97 
98 CEED_QFUNCTION(RiemannOutflow_Entropy)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
99   return RiemannOutflow(ctx, Q, in, out, STATEVAR_ENTROPY);
100 }
101 
102 // *****************************************************************************
103 // Jacobian for Riemann pressure/temperature outflow boundary condition
104 // *****************************************************************************
105 CEED_QFUNCTION_HELPER int RiemannOutflow_Jacobian(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out,
106                                                   StateVariable state_var) {
107   const CeedScalar(*dq)[CEED_Q_VLA] = (const CeedScalar(*)[CEED_Q_VLA])in[0];
108   const CeedScalar(*Grad_dq)        = in[1];
109   const CeedScalar(*q_data_sur)     = in[2];
110   const CeedScalar(*jac_data_sur)   = in[4];
111   CeedScalar(*v)[CEED_Q_VLA]        = (CeedScalar(*)[CEED_Q_VLA])out[0];
112 
113   const OutflowContext           outflow     = (OutflowContext)ctx;
114   const NewtonianIdealGasContext gas         = &outflow->gas;
115   const bool                     is_implicit = gas->is_implicit;
116 
117   CeedPragmaSIMD for (CeedInt i = 0; i < Q; i++) {
118     CeedScalar wdetJb, dXdx[2][3], normal[3];
119     QdataBoundaryUnpack_3D(Q, i, q_data_sur, &wdetJb, dXdx, normal);
120     wdetJb *= is_implicit ? -1. : 1.;
121 
122     CeedScalar qi[5], kmstress[6], dqi[5];
123     StoredValuesUnpack(Q, i, 0, 5, jac_data_sur, qi);
124     StoredValuesUnpack(Q, i, 5, 6, jac_data_sur, kmstress);
125     for (int j = 0; j < 5; j++) dqi[j] = dq[j][i];
126 
127     State          s_int  = StateFromQ(gas, qi, state_var);
128     const State    ds_int = StateFromQ_fwd(gas, s_int, dqi, state_var);
129     StatePrimitive y_ext = s_int.Y, dy_ext = ds_int.Y;
130     y_ext.pressure             = outflow->pressure;
131     y_ext.temperature          = outflow->temperature;
132     dy_ext.pressure            = 0;
133     dy_ext.temperature         = 0;
134     const CeedScalar u_normal  = Dot3(s_int.Y.velocity, normal);
135     const CeedScalar du_normal = Dot3(ds_int.Y.velocity, normal);
136     const CeedScalar proj      = (1 - outflow->recirc) * Softplus(-u_normal, outflow->softplus_velocity);
137     const CeedScalar dproj     = (1 - outflow->recirc) * Softplus_fwd(-u_normal, -du_normal, outflow->softplus_velocity);
138     for (CeedInt j = 0; j < 3; j++) {
139       y_ext.velocity[j] += normal[j] * proj;
140       dy_ext.velocity[j] += normal[j] * dproj;
141     }
142 
143     State s_ext  = StateFromPrimitive(gas, y_ext);
144     State ds_ext = StateFromPrimitive_fwd(gas, s_ext, dy_ext);
145 
146     State grad_ds[3];
147     StatePhysicalGradientFromReference_Boundary(Q, i, gas, s_int, state_var, Grad_dq, dXdx, grad_ds);
148 
149     CeedScalar dstrain_rate[6], dkmstress[6], stress[3][3], dstress[3][3], dFe[3];
150     KMStrainRate_State(grad_ds, dstrain_rate);
151     NewtonianStress(gas, dstrain_rate, dkmstress);
152     KMUnpack(dkmstress, dstress);
153     KMUnpack(kmstress, stress);
154     ViscousEnergyFlux_fwd(gas, s_int.Y, ds_int.Y, grad_ds, stress, dstress, dFe);
155 
156     StateConservative dF_inviscid_normal = RiemannFlux_HLLC_fwd(gas, s_int, ds_int, s_ext, ds_ext, normal);
157 
158     CeedScalar dFlux[5];
159     FluxTotal_RiemannBoundary(dF_inviscid_normal, dstress, dFe, normal, dFlux);
160 
161     for (int j = 0; j < 5; j++) v[j][i] = -wdetJb * dFlux[j];
162   }
163   return 0;
164 }
165 
166 CEED_QFUNCTION(RiemannOutflow_Jacobian_Conserv)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
167   return RiemannOutflow_Jacobian(ctx, Q, in, out, STATEVAR_CONSERVATIVE);
168 }
169 
170 CEED_QFUNCTION(RiemannOutflow_Jacobian_Prim)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
171   return RiemannOutflow_Jacobian(ctx, Q, in, out, STATEVAR_PRIMITIVE);
172 }
173 
174 CEED_QFUNCTION(RiemannOutflow_Jacobian_Entropy)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
175   return RiemannOutflow_Jacobian(ctx, Q, in, out, STATEVAR_ENTROPY);
176 }
177 
178 // *****************************************************************************
179 // Outflow boundary condition, weakly setting a constant pressure. This is the
180 // classic outflow condition used by PHASTA-C and retained largely for
181 // comparison. In our experiments, it is never better than RiemannOutflow, and
182 // will crash if outflow ever becomes an inflow, as occurs with strong
183 // acoustics, vortices, etc.
184 // *****************************************************************************
185 CEED_QFUNCTION_HELPER int PressureOutflow(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out, StateVariable state_var) {
186   const OutflowContext outflow     = (OutflowContext)ctx;
187   const CeedScalar(*q)[CEED_Q_VLA] = (const CeedScalar(*)[CEED_Q_VLA])in[0];
188   const CeedScalar(*Grad_q)        = in[1];
189   const CeedScalar(*q_data_sur)    = in[2];
190   CeedScalar(*v)[CEED_Q_VLA]       = (CeedScalar(*)[CEED_Q_VLA])out[0];
191   CeedScalar(*jac_data_sur)        = outflow->gas.is_implicit ? out[1] : NULL;
192 
193   const NewtonianIdealGasContext gas         = &outflow->gas;
194   const bool                     is_implicit = gas->is_implicit;
195 
196   CeedPragmaSIMD for (CeedInt i = 0; i < Q; i++) {
197     const CeedScalar qi[5] = {q[0][i], q[1][i], q[2][i], q[3][i], q[4][i]};
198     State            s     = StateFromQ(gas, qi, state_var);
199     s.Y.pressure           = outflow->pressure;
200 
201     CeedScalar wdetJb, dXdx[2][3], normal[3];
202     QdataBoundaryUnpack_3D(Q, i, q_data_sur, &wdetJb, dXdx, normal);
203     wdetJb *= is_implicit ? -1. : 1.;
204 
205     State grad_s[3];
206     StatePhysicalGradientFromReference_Boundary(Q, i, gas, s, state_var, Grad_q, dXdx, grad_s);
207 
208     CeedScalar strain_rate[6], kmstress[6], stress[3][3], Fe[3];
209     KMStrainRate_State(grad_s, strain_rate);
210     NewtonianStress(gas, strain_rate, kmstress);
211     KMUnpack(kmstress, stress);
212     ViscousEnergyFlux(gas, s.Y, grad_s, stress, Fe);
213 
214     StateConservative F_inviscid[3];
215     FluxInviscid(gas, s, F_inviscid);
216 
217     CeedScalar Flux[5];
218     FluxTotal_Boundary(F_inviscid, stress, Fe, normal, Flux);
219 
220     for (CeedInt j = 0; j < 5; j++) v[j][i] = -wdetJb * Flux[j];
221 
222     // Save values for Jacobian
223     if (is_implicit) {
224       StoredValuesPack(Q, i, 0, 5, qi, jac_data_sur);
225       StoredValuesPack(Q, i, 5, 6, kmstress, jac_data_sur);
226     }
227   }
228   return 0;
229 }
230 
231 CEED_QFUNCTION(PressureOutflow_Conserv)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
232   return PressureOutflow(ctx, Q, in, out, STATEVAR_CONSERVATIVE);
233 }
234 
235 CEED_QFUNCTION(PressureOutflow_Prim)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
236   return PressureOutflow(ctx, Q, in, out, STATEVAR_PRIMITIVE);
237 }
238 
239 CEED_QFUNCTION(PressureOutflow_Entropy)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
240   return PressureOutflow(ctx, Q, in, out, STATEVAR_ENTROPY);
241 }
242 
243 // *****************************************************************************
244 // Jacobian for weak-pressure outflow boundary condition
245 // *****************************************************************************
246 CEED_QFUNCTION_HELPER int PressureOutflow_Jacobian(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out,
247                                                    StateVariable state_var) {
248   const CeedScalar(*dq)[CEED_Q_VLA] = (const CeedScalar(*)[CEED_Q_VLA])in[0];
249   const CeedScalar(*Grad_dq)        = in[1];
250   const CeedScalar(*q_data_sur)     = in[2];
251   const CeedScalar(*jac_data_sur)   = in[4];
252   CeedScalar(*v)[CEED_Q_VLA]        = (CeedScalar(*)[CEED_Q_VLA])out[0];
253 
254   const OutflowContext           outflow     = (OutflowContext)ctx;
255   const NewtonianIdealGasContext gas         = &outflow->gas;
256   const bool                     is_implicit = gas->is_implicit;
257 
258   CeedPragmaSIMD for (CeedInt i = 0; i < Q; i++) {
259     CeedScalar wdetJb, dXdx[2][3], normal[3];
260     QdataBoundaryUnpack_3D(Q, i, q_data_sur, &wdetJb, dXdx, normal);
261     wdetJb *= is_implicit ? -1. : 1.;
262 
263     CeedScalar qi[5], kmstress[6], dqi[5];
264     StoredValuesUnpack(Q, i, 0, 5, jac_data_sur, qi);
265     StoredValuesUnpack(Q, i, 5, 6, jac_data_sur, kmstress);
266     for (int j = 0; j < 5; j++) dqi[j] = dq[j][i];
267 
268     State s       = StateFromQ(gas, qi, state_var);
269     State ds      = StateFromQ_fwd(gas, s, dqi, state_var);
270     s.Y.pressure  = outflow->pressure;
271     ds.Y.pressure = 0.;
272 
273     State grad_ds[3];
274     StatePhysicalGradientFromReference_Boundary(Q, i, gas, s, state_var, Grad_dq, dXdx, grad_ds);
275 
276     CeedScalar dstrain_rate[6], dkmstress[6], stress[3][3], dstress[3][3], dFe[3];
277     KMStrainRate_State(grad_ds, dstrain_rate);
278     NewtonianStress(gas, dstrain_rate, dkmstress);
279     KMUnpack(dkmstress, dstress);
280     KMUnpack(kmstress, stress);
281     ViscousEnergyFlux_fwd(gas, s.Y, ds.Y, grad_ds, stress, dstress, dFe);
282 
283     StateConservative dF_inviscid[3];
284     FluxInviscid_fwd(gas, s, ds, dF_inviscid);
285 
286     CeedScalar dFlux[5];
287     FluxTotal_Boundary(dF_inviscid, dstress, dFe, normal, dFlux);
288 
289     for (int j = 0; j < 5; j++) v[j][i] = -wdetJb * dFlux[j];
290   }
291   return 0;
292 }
293 
294 CEED_QFUNCTION(PressureOutflow_Jacobian_Conserv)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
295   return PressureOutflow_Jacobian(ctx, Q, in, out, STATEVAR_CONSERVATIVE);
296 }
297 
298 CEED_QFUNCTION(PressureOutflow_Jacobian_Prim)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
299   return PressureOutflow_Jacobian(ctx, Q, in, out, STATEVAR_PRIMITIVE);
300 }
301 
302 CEED_QFUNCTION(PressureOutflow_Jacobian_Entropy)(void *ctx, CeedInt Q, const CeedScalar *const *in, CeedScalar *const *out) {
303   return PressureOutflow_Jacobian(ctx, Q, in, out, STATEVAR_ENTROPY);
304 }
305