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)
Softplus(CeedScalar x,CeedScalar width)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
Softplus_fwd(CeedScalar x,CeedScalar dx,CeedScalar width)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.
RiemannOutflow(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out,StateVariable state_var)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->newt_ctx.is_implicit ? out[1] : NULL;
44
45 const NewtonianIGProperties gas = outflow->newt_ctx.gas;
46 const bool is_implicit = outflow->newt_ctx.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
RiemannOutflow_Conserv(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)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
RiemannOutflow_Prim(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)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
RiemannOutflow_Entropy(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)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 // *****************************************************************************
RiemannOutflow_Jacobian(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out,StateVariable state_var)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 NewtonianIGProperties gas = outflow->newt_ctx.gas;
115 const bool is_implicit = outflow->newt_ctx.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
RiemannOutflow_Jacobian_Conserv(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)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
RiemannOutflow_Jacobian_Prim(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)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
RiemannOutflow_Jacobian_Entropy(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)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 // *****************************************************************************
PressureOutflow(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out,StateVariable state_var)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->newt_ctx.is_implicit ? out[1] : NULL;
192
193 const NewtonianIGProperties gas = outflow->newt_ctx.gas;
194 const bool is_implicit = outflow->newt_ctx.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
PressureOutflow_Conserv(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)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
PressureOutflow_Prim(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)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
PressureOutflow_Entropy(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)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 // *****************************************************************************
PressureOutflow_Jacobian(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out,StateVariable state_var)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 NewtonianIGProperties gas = outflow->newt_ctx.gas;
256 const bool is_implicit = outflow->newt_ctx.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
PressureOutflow_Jacobian_Conserv(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)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
PressureOutflow_Jacobian_Prim(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)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
PressureOutflow_Jacobian_Entropy(void * ctx,CeedInt Q,const CeedScalar * const * in,CeedScalar * const * out)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