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