Theseus
Compressible flow solver
Loading...
Searching...
No Matches
bc_kernels.hpp
Go to the documentation of this file.
1// Copyright (c) 2025-2026 Board of Trustees of the University of Illinois
2//
3// This file is part of Theseus.
4//
5// SPDX-License-Identifier: BSD-3-Clause
6#pragma once
7
9#include "Flow.hpp"
10#include "NavierStokesFlux.hpp"
11
12namespace Theseus {
13
14 namespace BC {
15
16 template<typename GasModelT>
17 MFEM_HOST_DEVICE inline void ReflectAxisState(
18 const GasModelT &gas, const mfem::real_t *interior,
19 mfem::real_t *exterior)
20 {
21 const int equations = gas.L.nequations();
22 for (int equation = 0; equation < equations; ++equation)
23 {
24 exterior[equation] = interior[equation];
25 }
26 const int radial_momentum =
28 exterior[radial_momentum] = -interior[radial_momentum];
29 }
30
31
32 template <typename DeviceCacheT>
33 MFEM_HOST_DEVICE inline
34 void ComputeBdrFaceGradFlux(const DeviceCacheT &dc,
35 const Theseus::BCDescriptor &bc,
36 const mfem::real_t *state1,
37 mfem::real_t *fluxN)
38 {
39 const auto &gas = dc.gas;
40 const int neq = dc.num_equations;
41 const int dim = dc.dim;
42
43 for (int q = 0; q < neq; ++q)
44 {
45 fluxN[q] = mfem::real_t(0);
46 }
47
48 switch (static_cast<Theseus::BCType>(bc.type))
49 {
51 {
52 const mfem::real_t *vector_data = dc.bc_vector_d;
53 const mfem::real_t *Vwall = vector_data + bc.data_index;
54 // unused - but be aware
55 // const mfem::real_t *wallHeat = vector_data + bc.data_index + dim;
56
57 // Note this must be entropy state
60
61 const mfem::real_t v = -gas.energy(S);
62
63 // Build the same provisional "boundary" state as legacy, then subtract state1.
64 F.set_mass(gas.L, gas.mass(S)); // Is this really correct? v+ != v- so.. hrm.
65 for(int idim = 0;idim < dim;idim++){
66 F.set_momentum(gas.L, idim, Vwall[idim] * v);
67 }
68 F.set_energy(gas.L, -v);
69 for (int q = 0; q < neq; ++q)
70 {
71 fluxN[q] -= state1[q];
72 }
73 return;
74 }
75
77 {
78 const mfem::real_t *bc_data = dc.bc_vector_d + bc.data_index;
79 const mfem::real_t *Vwall = bc_data;
80 const mfem::real_t Twall = bc_data[dim];
81
82 // state1 is entropy state
85
86 /*
87 * Need the entropy state / beta corresponding to Twall and Vwall.
88 *
89 * Legacy Prandtl does this (roughly):
90 * beta = isothermal_wall_beta(S, Twall, gas);
91 * F[mom] = Vwall * beta;
92 * F[energy] = -beta;
93 *
94 * So this method should return the same quantity that -gas.energy(S)
95 * returns for the adiabatic case, but evaluated at Twall. I am going
96 * to match legacy-like behavior here - but I'm skeptical of it.
97 *
98 * Note:
99 * Strictly speaking, shouldn't we form the (+) entropy state by prescribing
100 * Twall, and Vwall? In that case, I think at least the mass component is off
101 *
102 */
103
104 const mfem::real_t beta_like = Theseus::Flow::isothermal_wall_beta(S, Twall, gas);
105
106 F.set_mass(gas.L, gas.mass(S));
107 for (int idim = 0; idim < dim; ++idim)
108 {
109 F.set_momentum(gas.L, idim, Vwall[idim] * beta_like);
110 }
111 F.set_energy(gas.L, -beta_like);
112
113 for (int q = 0; q < neq; ++q)
114 {
115 fluxN[q] -= state1[q];
116 }
117
118 return;
119 }
120
122 {
123 mfem::real_t exterior[Theseus::MAXEQ];
124 ReflectAxisState(gas, state1, exterior);
125 for (int equation = 0; equation < neq; ++equation)
126 {
127 fluxN[equation] = exterior[equation] - state1[equation];
128 }
129 return;
130 }
131
132 default:
133 {
134 // Conservative placeholder for unsupported BCs.
135 for (int q = 0; q < neq; ++q)
136 {
137 fluxN[q] = mfem::real_t(0);
138 }
139 return;
140 }
141 }
142 }
143
144
145 template<typename GasModelT>
146 MFEM_HOST_DEVICE
147 void SlipWallInviscidFluxKernel(const GasModelT &gasModel, const mfem::real_t *state1,
148 const mfem::real_t *nor, mfem::real_t *fluxN)
149 {
150
151 mfem::real_t unit_nor[Theseus::MAXDIM];
152 mfem::real_t state2[Theseus::MAXEQ];
153 const int dim = gasModel.L.dim;
154 const int neq = gasModel.L.nequations();
155 for(int idim = 0;idim < dim;idim++)
156 unit_nor[idim] = nor[idim];
157 for(int ieq = 0;ieq < neq;ieq++){
158 state2[ieq] = state1[ieq];
159 fluxN[ieq] = 0.0;
160 }
161 Theseus::Kernels::Normalize(dim, unit_nor);
163 Theseus::Flow::RotateState(gasModel.L, unit_nor, S);
164 const mfem::real_t p_star = Theseus::Flow::slipwall_pstar(S, gasModel);
165 const int mom_eq = gasModel.L.eq_mom0;
166 for(int idim = 0;idim < dim;idim++)
167 fluxN[mom_eq+idim] = p_star * nor[idim];
168 }
169
170 template<typename GasModelT>
171 MFEM_HOST_DEVICE
172 void NoSlipAdiabWallFluxKernel(const GasModelT &gasModel, const mfem::real_t *state1,
173 const mfem::real_t *gradPrim_x, const mfem::real_t *gradPrim_y,
174 const mfem::real_t *gradPrim_z,
175 const mfem::real_t *nor, const mfem::real_t vWall[Theseus::MAXDIM],
176 const mfem::real_t qWall, bool axisymmetric,
177 mfem::real_t radius, mfem::real_t *fluxN)
178 {
179 mfem::real_t unit_nor[Theseus::MAXDIM];
180 mfem::real_t state2[Theseus::MAXEQ];
181 mfem::real_t visc_flux[Theseus::MAXEQ][Theseus::MAXDIM];
182 const int dim = gasModel.L.dim;
183 const int neq = gasModel.L.nequations();
184 mfem::real_t normag = 0.0;
185 for(int idim = 0;idim < dim;idim++){
186 unit_nor[idim] = nor[idim];
187 normag += nor[idim]*nor[idim];
188 }
189 normag = Theseus::Kernels::rsqrt(normag);
190
191 for(int ieq = 0;ieq < neq;ieq++){
192 state2[ieq] = state1[ieq];
193 fluxN[ieq] = 0.0;
194 }
195 // Inviscid part is just like slipwall
196 Theseus::Kernels::Normalize(dim, unit_nor);
198 Theseus::Flow::RotateState(gasModel.L, unit_nor, S);
199 const mfem::real_t p_star = Theseus::Flow::slipwall_pstar(S, gasModel);
200 const int mom_eq = gasModel.L.eq_mom0;
201 for(int idim = 0;idim < dim;idim++)
202 fluxN[mom_eq+idim] = p_star * nor[idim];
203
204 // Inviscid part is done, now for the viscous part
205 mfem::real_t qn = qWall * normag;
206 NavierStokesFlux::ComputeViscousFluxKernel(gasModel, state1, gradPrim_x, gradPrim_y,
207 gradPrim_z, visc_flux, axisymmetric, radius);
208 mfem::real_t vflux_n[Theseus::MAXEQ];
209 for(int j = 0;j < neq;j++){
210 vflux_n[j] = 0.0;
211 for(int idim = 0;idim < dim;idim++){
212 vflux_n[j] += nor[idim]*visc_flux[j][idim];
213 }
214 }
215 const int ener_eq = gasModel.L.eq_energy;
216 vflux_n[ener_eq] = qn;
217 for(int idim = 0;idim < dim;idim++){
218 vflux_n[ener_eq] += vWall[idim]*vflux_n[mom_eq+idim];
219 }
220 for(int j = 0; j < neq;j++){
221 fluxN[j] -= vflux_n[j];
222 }
223 }
224
225 template<typename GasModelT>
226 MFEM_HOST_DEVICE
227 void NoSlipIsothWallFluxKernel(const GasModelT &gasModel,
228 const mfem::real_t *state1,
229 const mfem::real_t *gradPrim_x,
230 const mfem::real_t *gradPrim_y,
231 const mfem::real_t *gradPrim_z,
232 const mfem::real_t *nor,
233 const mfem::real_t vWall[Theseus::MAXDIM],
234 const mfem::real_t tWall,
235 bool axisymmetric, mfem::real_t radius,
236 mfem::real_t *fluxN)
237 {
238 mfem::real_t unit_nor[Theseus::MAXDIM];
239 mfem::real_t state2[Theseus::MAXEQ];
240 mfem::real_t visc_flux[Theseus::MAXEQ][Theseus::MAXDIM];
241
242 const int dim = gasModel.L.dim;
243 const int neq = gasModel.L.nequations();
244 const int mom_eq = gasModel.L.eq_mom0;
245 const int ener_eq = gasModel.L.eq_energy;
246
247 for (int idim = 0; idim < dim; ++idim)
248 {
249 unit_nor[idim] = nor[idim];
250 }
251
252 for (int ieq = 0; ieq < neq; ++ieq)
253 {
254 state2[ieq] = state1[ieq];
255 fluxN[ieq] = 0.0;
256 }
257
258 // Inviscid part - same as slipwall
259 Theseus::Kernels::Normalize(dim, unit_nor);
260 Theseus::PointStateViewRW Srot{state2};
261 Theseus::Flow::RotateState(gasModel.L, unit_nor, Srot);
262
263 const mfem::real_t p_star = Theseus::Flow::slipwall_pstar(Srot, gasModel);
264 for (int idim = 0; idim < dim; ++idim)
265 {
266 fluxN[mom_eq + idim] = p_star * nor[idim];
267 }
268
269 // Viscous part
270 Theseus::NavierStokesFlux::ComputeViscousFluxKernel(gasModel, state1, gradPrim_x,
271 gradPrim_y, gradPrim_z, visc_flux,
272 axisymmetric, radius);
273
274 mfem::real_t vflux_n[Theseus::MAXEQ];
275 for (int eq = 0; eq < neq; ++eq)
276 {
277 vflux_n[eq] = 0.0;
278 for (int idim = 0; idim < dim; ++idim)
279 {
280 vflux_n[eq] += nor[idim] * visc_flux[eq][idim];
281 }
282 }
283
284 // Recover just the heat flux part so we can adjust
285 // the mechanical part
286 Theseus::PointStateView S{state1};
287 mfem::real_t conductive_n = vflux_n[ener_eq];
288 for (int idim = 0; idim < dim; ++idim)
289 {
290 const mfem::real_t u_trace = gasModel.velocity(S, idim);
291 conductive_n -= u_trace * vflux_n[mom_eq + idim];
292 }
293
294 // reinsert heat flux and adjust mechanical work for Vwall
295 vflux_n[ener_eq] = conductive_n;
296 for (int idim = 0; idim < dim; ++idim)
297 {
298 vflux_n[ener_eq] += vWall[idim] * vflux_n[mom_eq + idim];
299 }
300
301 // Same sign convention as adiabatic kernel:
302 // Ultimately we want F_visc - F_inv, note
303 // that this result is negated at the top level (sigh)
304 for (int eq = 0; eq < neq; ++eq)
305 {
306 fluxN[eq] -= vflux_n[eq];
307 }
308
309 }
310
311
312 template <typename DeviceCacheT>
313 MFEM_HOST_DEVICE
314 void ApplyBoundaryConditionInviscid(const DeviceCacheT &dc,
315 const Theseus::BCDescriptor &bc,
316 const mfem::real_t *state1,
317 const mfem::real_t *nor,
318 mfem::real_t *fluxN)
319 {
320 const auto &gas = dc.gas;
321 const mfem::real_t *scalar_data = dc.bc_scalar_d;
322 const mfem::real_t *vector_data = dc.bc_vector_d;
323 switch (static_cast<Theseus::BCType>(bc.type))
324 {
326 SlipWallInviscidFluxKernel(gas, state1, nor, fluxN);
327 return;
328
330 dc.iflux.ComputeFaceFlux(gas, state1, state1, nor, fluxN);
331 return;
332
334 {
335 const mfem::real_t *bc_state = vector_data + bc.data_index;
336 dc.iflux.ComputeFaceFlux(gas, state1, bc_state, nor, fluxN);
337 return;
338 }
339
341 {
342 const int neq = dc.num_equations;
343 Theseus::PointStateView S{state1};
344 mfem::real_t bc_state[Theseus::MAXEQ];
345 for(int ieq = 0;ieq < neq;ieq++){
346 bc_state[ieq] = state1[ieq];
347 }
348 Theseus::PointStateViewRW S2{bc_state};
349 const int dim = dc.dim;
350 mfem::real_t unorm[Theseus::MAXDIM];
351 mfem::real_t mom[Theseus::MAXDIM];
352 for(int idim = 0;idim < dim;idim++){
353 unorm[idim] = nor[idim];
354 mom[idim] = S.momentum(gas.L, idim);
355 }
356 Theseus::Kernels::Normalize(dim, unorm);
357 mfem::real_t nv = Theseus::Kernels::Dot(dim, mom, unorm);
358 for(int idim = 0;idim < dim;idim++){
359 mfem::real_t mm = -2.0*nv*unorm[idim] + mom[idim];
360 S2.set_momentum(gas.L, idim, mm);
361 }
362 dc.iflux.ComputeFaceFlux(gas, state1, bc_state, nor, fluxN);
363 return;
364 }
366 {
367 mfem::real_t bc_state[Theseus::MAXEQ];
368 ReflectAxisState(gas, state1, bc_state);
369 dc.iflux.ComputeFaceFlux(gas, state1, bc_state, nor, fluxN);
370 return;
371 }
372 default:
373 {
374 const int neq = dc.num_equations;
375 for (int eq = 0; eq < neq; ++eq) { fluxN[eq] = 0.0; }
376 return;
377 }
378 }
379 }
380
381 template <typename DeviceCacheT>
382 MFEM_HOST_DEVICE
383 void ApplyViscousBoundaryCondition(const DeviceCacheT &dc,
384 const Theseus::BCDescriptor &bc,
385 const mfem::real_t *state1,
386 const mfem::real_t *gradPrim_x,
387 const mfem::real_t *gradPrim_y,
388 const mfem::real_t *gradPrim_z,
389 const mfem::real_t *nor,
390 mfem::real_t radius,
391 mfem::real_t *fluxN)
392 {
393 const auto &gas = dc.gas;
394 const int dim = dc.dim;
395 const mfem::real_t *scalar_data = dc.bc_scalar_d;
396 const mfem::real_t *vector_data = dc.bc_vector_d;
397 switch (static_cast<Theseus::BCType>(bc.type))
398 {
403 ApplyBoundaryConditionInviscid(dc, bc, state1, nor, fluxN);
404 return;
405
407 {
408 const mfem::real_t *bc_vec_data = vector_data + bc.data_index;
409 mfem::real_t vWall[Theseus::MAXDIM];
410 for(int idim=0;idim < dim;idim++){
411 vWall[idim] = bc_vec_data[idim];
412 }
413 const mfem::real_t qWall = bc_vec_data[dim];
414 NoSlipAdiabWallFluxKernel(gas, state1, gradPrim_x, gradPrim_y,
415 gradPrim_z, nor, vWall, qWall,
416 dc.axisymmetric, radius, fluxN);
417 return;
418 }
420 {
421 const mfem::real_t *bc_vec_data = vector_data + bc.data_index;
422 mfem::real_t vWall[Theseus::MAXDIM];
423 for(int idim=0;idim < dim;idim++){
424 vWall[idim] = bc_vec_data[idim];
425 }
426 const mfem::real_t tWall = bc_vec_data[dim];
427 NoSlipIsothWallFluxKernel(gas, state1, gradPrim_x, gradPrim_y,
428 gradPrim_z, nor, vWall, tWall,
429 dc.axisymmetric, radius, fluxN);
430 return;
431 }
433 {
434 ApplyBoundaryConditionInviscid(dc, bc, state1, nor, fluxN);
435 mfem::real_t viscous_flux[Theseus::MAXEQ][Theseus::MAXDIM];
436 NavierStokesFlux::ComputeViscousFluxKernel(
437 gas, state1, gradPrim_x, gradPrim_y, gradPrim_z,
438 viscous_flux, dc.axisymmetric, radius);
439 for (int equation = 0; equation < dc.num_equations; ++equation)
440 {
441 for (int direction = 0; direction < dim; ++direction)
442 {
443 fluxN[equation] -=
444 nor[direction] * viscous_flux[equation][direction];
445 }
446 }
447 return;
448 }
449 default:
450 {
451 const int neq = dc.num_equations;
452 for (int eq = 0; eq < neq; ++eq) { fluxN[eq] = 0.0; }
453 return;
454 }
455 }
456 }
457 }
458}
MFEM_HOST_DEVICE void ComputeBdrFaceGradFlux(const DeviceCacheT &dc, const Theseus::BCDescriptor &bc, const mfem::real_t *state1, mfem::real_t *fluxN)
Definition bc_kernels.hpp:34
MFEM_HOST_DEVICE void NoSlipIsothWallFluxKernel(const GasModelT &gasModel, const mfem::real_t *state1, const mfem::real_t *gradPrim_x, const mfem::real_t *gradPrim_y, const mfem::real_t *gradPrim_z, const mfem::real_t *nor, const mfem::real_t vWall[Theseus::MAXDIM], const mfem::real_t tWall, bool axisymmetric, mfem::real_t radius, mfem::real_t *fluxN)
Definition bc_kernels.hpp:227
MFEM_HOST_DEVICE void ApplyViscousBoundaryCondition(const DeviceCacheT &dc, const Theseus::BCDescriptor &bc, const mfem::real_t *state1, const mfem::real_t *gradPrim_x, const mfem::real_t *gradPrim_y, const mfem::real_t *gradPrim_z, const mfem::real_t *nor, mfem::real_t radius, mfem::real_t *fluxN)
Definition bc_kernels.hpp:383
MFEM_HOST_DEVICE void SlipWallInviscidFluxKernel(const GasModelT &gasModel, const mfem::real_t *state1, const mfem::real_t *nor, mfem::real_t *fluxN)
Definition bc_kernels.hpp:147
MFEM_HOST_DEVICE void NoSlipAdiabWallFluxKernel(const GasModelT &gasModel, const mfem::real_t *state1, const mfem::real_t *gradPrim_x, const mfem::real_t *gradPrim_y, const mfem::real_t *gradPrim_z, const mfem::real_t *nor, const mfem::real_t vWall[Theseus::MAXDIM], const mfem::real_t qWall, bool axisymmetric, mfem::real_t radius, mfem::real_t *fluxN)
Definition bc_kernels.hpp:172
MFEM_HOST_DEVICE void ApplyBoundaryConditionInviscid(const DeviceCacheT &dc, const Theseus::BCDescriptor &bc, const mfem::real_t *state1, const mfem::real_t *nor, mfem::real_t *fluxN)
Definition bc_kernels.hpp:314
MFEM_HOST_DEVICE void ReflectAxisState(const GasModelT &gas, const mfem::real_t *interior, mfem::real_t *exterior)
Definition bc_kernels.hpp:17
MFEM_HOST_DEVICE mfem::real_t isothermal_wall_beta(const StateView &Se, mfem::real_t Tw, const GasModelT &gasModel)
Definition Flow.hpp:84
MFEM_HOST_DEVICE mfem::real_t slipwall_pstar(const StateView &S, const GasModelT &gasModel)
Definition Flow.hpp:57
MFEM_HOST_DEVICE void RotateState(const StateLayout layout, const mfem::real_t *nor, Theseus::PointStateViewRW &S)
Definition Flow.hpp:24
MFEM_HOST_DEVICE void Normalize(const int dim, mfem::real_t *vec)
Definition theseus_kernels.hpp:24
MFEM_HOST_DEVICE mfem::real_t rsqrt(mfem::real_t x)
Definition theseus_kernels.hpp:19
MFEM_HOST_DEVICE mfem::real_t Dot(const int dim, const mfem::real_t *vec1, const mfem::real_t *vec2)
Definition theseus_kernels.hpp:82
Definition AxisymmetricGeometry.hpp:15
constexpr const int MAXDIM
Definition theseus_kernels.hpp:14
constexpr const int MAXEQ
Definition theseus_kernels.hpp:13
BCType
Definition bc_cache_utilities.hpp:12
static constexpr int radial_coordinate
Definition AxisymmetricGeometry.hpp:20
Definition bc_cache_utilities.hpp:35
int type
Definition bc_cache_utilities.hpp:36
int data_index
Definition bc_cache_utilities.hpp:38
Definition GasState.hpp:218
MFEM_HOST_DEVICE mfem::real_t energy(const StateLayout &L) const
Definition GasState.hpp:295
MFEM_HOST_DEVICE mfem::real_t momentum(const StateLayout &L, int d) const
Definition GasState.hpp:245
Definition GasState.hpp:127
MFEM_HOST_DEVICE mfem::real_t velocity(const StateLayout &L, int d) const
Definition GasState.hpp:169