Theseus
Compressible flow solver
Loading...
Searching...
No Matches
EulerOperator_impl.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
7namespace Theseus
8{
9 // This is a Inf/NAN detector helper
10 int CBE(const mfem::Vector &v)
11 {
12 mfem::Vector one_bad(v.Size());
13 const mfem::real_t *vd = v.Read();
14 mfem::real_t *bd_w = one_bad.Write();
15
16 mfem::forall(v.Size(), [=] MFEM_HOST_DEVICE (int i) {
17 bd_w[i] = Theseus::Kernels::is_bad_value(vd[i]) ? 1.0 : 0.0;
18 });
19 const mfem::real_t *bd_r = one_bad.Read();
20 int nbad = 0;
21 for(int i = 0;i < v.Size();i++){
22 nbad += bd_r[i];
23 }
24 return nbad;
25 }
26
27 // Assemble the Cartesian and optional axisymmetric volume RHS for all elements.
28 template<typename PhysicsT>
29 void EulerOperator<PhysicsT>::MultEuler_Volume(const mfem::Vector &pu, mfem::Vector &pdudt) const
30 {
31 Theseus::ScopedTimer timer("MultEuler_Volume");
32
33 // This block is executed by the host
34 int nval_restr = operator_cache.restr_v->Height();
35 if(operator_cache.uVol.Size() != nval_restr){
36 operator_cache.uVol.SetSize(nval_restr);
37 operator_cache.uVol.UseDevice();
38 }
39 mfem::Vector &Ue(operator_cache.uVol);
40 if(!operator_cache.u_vol_restr_ready){
41 Theseus::ScopedTimer timer("VolRestrict");
42 operator_cache.restr_v->Mult(pu, Ue);
43 operator_cache.u_vol_restr_ready = true;
44 }
45 if(operator_cache.rhsVol.Size() != nval_restr){
46 operator_cache.rhsVol.SetSize(nval_restr);
47 operator_cache.rhsVol.UseDevice();
48 }
49 mfem::Vector &dUe(operator_cache.rhsVol);
50 // Zero the array on-device (*needed for now*)
51 {
52 Theseus::ScopedTimer zerotim("ZeroRHSStorage");
53 mfem::real_t *d = dUe.Write();
54 mfem::forall(dUe.Size(), [=] MFEM_HOST_DEVICE (int i) { d[i] = mfem::real_t(0); });
55 }
56
57 const mfem::real_t *Ue_d = Ue.Read();
58 mfem::real_t *dUe_d = dUe.Write();
59
60 // Copy the device cache so that it is not member data
61 auto dc = device_cache;
62
63 // Device cache parameters
64 const int dim = dc.dim;
65 const int ne = dc.num_elements;
66 const int ndof = dc.ndof_scalar_el;
67 const int neq = dc.num_equations;
68#ifdef SUBCELL_FV_BLENDING
69 const int Np_x = dc.Np_x;
70 const int Np_y = dc.Np_y;
71 const int Np_z = dc.Np_z;
72 const int npe_metric_xi = (Np_x + 1)*Np_y*Np_z;
73 const int npe_metric_eta = Np_x*(Np_y + 1)*Np_z;
74 const int npe_metric_zeta = Np_x * Np_y * (Np_z + 1);
75 const mfem::real_t *metric_xi_d = dc.subcell_metric_xi_d;
76 const mfem::real_t *metric_eta_d = (dim > 1 ? dc.subcell_metric_eta_d : nullptr);
77 const mfem::real_t *metric_zeta_d = (dim > 2 ? dc.subcell_metric_zeta_d : nullptr);
78
79 mfem::Vector &dUfv(operator_cache.dUfv);
80 if(dUfv.Size() == 0){
81 dUfv.SetSize(nval_restr);
82 dUfv.UseDevice();
83 operator_cache.dUfv_d = dUfv.Write();
84 }
85 mfem::real_t *dUfv_d = operator_cache.dUfv_d; // zeroing not needed, kernel does it
86
87 const mfem::real_t *alpha_d = operator_cache.alpha->Read();
88
89#endif
90 // Derived parameters
91 const int metric_stride = ndof * dim * dim;
92 const int jac_stride = ndof;
93 const int estride = ndof*neq;
94
95 // Device cache data/arrays
96 const int *elem_attr_d = dc.elem_attr_d;
97 const int *attr_marker_d = dc.attr_marker_d;
98 const mfem::real_t *elJac_d = dc.elJac_d;
99 const mfem::real_t *elMetric_d = dc.elMetric_d;
100 const mfem::real_t *elRadius_d = dc.elRadius_d;
101
102 // Inside the FORALL below, executed on device
103#ifdef POINT_PARALLEL_VOLUME
104 const int npoints = ne * ndof;
105 mfem::forall(npoints, [=] MFEM_HOST_DEVICE (int p)
106 {
107 const int e = p / ndof;
108 const int point = p % ndof;
109 const int attr = elem_attr_d[e];
110 if (attr_marker_d[attr-1] == 0) {
111 return;
112 }
113
114 const int element_offset = e * estride;
115 DGSEMIntegrator::AssembleVolumePointKernel(
116 dc, Ue_d + element_offset, elJac_d + e*jac_stride,
117 elMetric_d + e*metric_stride, point, dUe_d + element_offset);
118 });
119
120 mfem::forall(ne, [=] MFEM_HOST_DEVICE (int e)
121 {
122 const int attr = elem_attr_d[e];
123 if (attr_marker_d[attr-1] == 0) {
124 return;
125 }
126
127 const int element_offset = e * estride;
128 const mfem::real_t *u_el = Ue_d + element_offset;
129 mfem::real_t *du_el = dUe_d + element_offset;
130 const mfem::real_t *jac_el = elJac_d + e*jac_stride;
131 const mfem::real_t *metric_el = elMetric_d + e*metric_stride;
132 const mfem::real_t *radius_el =
133 dc.axisymmetric ? elRadius_d + e*jac_stride : nullptr;
134#ifdef SUBCELL_FV_BLENDING
135 const mfem::real_t alpha_fv = alpha_d[e];
136 if (alpha_fv > 1e-16) {
137 const mfem::real_t alpha_dg = 1.0 - alpha_fv;
138 mfem::real_t *du_fv = dUfv_d + element_offset;
139 const mfem::real_t *el_metric_xi =
140 metric_xi_d + e*npe_metric_xi*dim;
141 const mfem::real_t *el_metric_eta = dim > 1 ?
142 metric_eta_d + e*npe_metric_eta*dim : nullptr;
143 const mfem::real_t *el_metric_zeta = dim > 2 ?
144 metric_zeta_d + e*npe_metric_zeta*dim : nullptr;
145 DGSEMIntegrator::ComputeFVFluxesKernel(
146 dc, u_el, jac_el, el_metric_xi, el_metric_eta,
147 el_metric_zeta, du_fv);
148 for (int value = 0; value < estride; ++value) {
149 du_el[value] =
150 alpha_dg*du_el[value] + alpha_fv*du_fv[value];
151 }
152 }
153#endif
154
156 dc, u_el, radius_el, jac_el, metric_el, du_el);
157 });
158#else
159 mfem::forall(ne, [=] MFEM_HOST_DEVICE (int e)
160 {
161
162 const mfem::real_t *jac_el = elJac_d + e * jac_stride;
163 const mfem::real_t *metric_el = elMetric_d + e * metric_stride;
164 const mfem::real_t *radius_el = dc.axisymmetric ?
165 elRadius_d + e * jac_stride : nullptr;
166
167 const int attr = elem_attr_d[e];
168 if (attr_marker_d[attr-1] == 0) {
169 return;
170 }
171
172 const int eoff = e * estride;
173 const mfem::real_t *u_el = Ue_d + eoff;
174 mfem::real_t *du_el = dUe_d + eoff;
175
176 // Additive on du_el (*zero first if needed*)
177 DGSEMIntegrator::AssembleElementVolumeKernel(dc, u_el,
178 jac_el, metric_el, du_el);
179#ifdef SUBCELL_FV_BLENDING
180 mfem::real_t alpha_fv = alpha_d[e];
181 if(alpha_fv > 1e-16){
182 mfem::real_t alpha_inv = (1.0 - alpha_fv);
183 mfem::real_t *du_fv = dUfv_d + eoff;
184 const mfem::real_t *el_metric_xi = metric_xi_d + e * npe_metric_xi * dim;
185 const mfem::real_t *el_metric_eta = (dim > 1 ? metric_eta_d + e * npe_metric_eta * dim :
186 nullptr);
187 const mfem::real_t *el_metric_zeta = (dim > 2 ? metric_zeta_d + e * npe_metric_zeta * dim :
188 nullptr);
189 DGSEMIntegrator::ComputeFVFluxesKernel(
190 dc, u_el, jac_el, el_metric_xi, el_metric_eta,
191 el_metric_zeta, du_fv);
192
193 for(int ipt = 0;ipt < estride;ipt++){
194 du_el[ipt] = alpha_inv * du_el[ipt] + alpha_fv * du_fv[ipt];
195 }
196
197 }
198#endif
199
201 dc, u_el, radius_el, jac_el, metric_el, du_el);
202
203 });
204#endif
205
206 // Scatter RHS back to storage
207 operator_cache.restr_v->AddMultTranspose(dUe, pdudt);
208
209 }
210
211 template<typename PhysicsT>
212 void EulerOperator<PhysicsT>::MultEuler_InteriorFaces(const mfem::Vector &pu, mfem::Vector &pdudt) const
213 {
214 Theseus::ScopedTimer timer("MultEuler_InteriorFaces");
215 auto dc = device_cache;
216 const int dim = dc.dim;
217 const int neq = dc.num_equations;
218 const int nfp = dc.num_face_points;
219 const int nval_restr = operator_cache.restr_f->Height();
220 const int nfaces = nval_restr / (nfp * neq * 2); // (+/-)
221 const int npoints = nfaces * nfp;
222 const int face_size = 2*nfp*neq;
223
224 if(operator_cache.uInt.Size() != nval_restr){
225 operator_cache.uInt.SetSize(nval_restr);
226 operator_cache.uInt.UseDevice(true);
227 }
228 mfem::Vector &u_faces(operator_cache.uInt);
229 if(!operator_cache.u_int_restr_ready){
230 operator_cache.restr_f->Mult(pu, u_faces);
231 operator_cache.u_int_restr_ready = true;
232 }
233
234 if(operator_cache.rhsInt.Size() != nval_restr){
235 operator_cache.rhsInt.SetSize(nval_restr);
236 operator_cache.rhsInt.UseDevice(true);
237 }
238 mfem::Vector &rhs_faces(operator_cache.rhsInt);
239
240 // For now, just keep this copy - i think it is device-friendly
241 mfem::Vector faces_dudt(pdudt);
242 // Zeroing unneeded: we clobber anything there on assignment
243 faces_dudt.UseDevice(true);
244
245 const mfem::real_t *u_d = u_faces.Read();
246 mfem::real_t *rhs_d = rhs_faces.Write();
247
248 const mfem::real_t *nor_d = dc.nor_d; // size nfaces*nfp*dim
249 const mfem::real_t *inv1_d = dc.fw_minus_d; // size nfaces*nfp
250 const mfem::real_t *inv2_d = dc.fw_plus_d; // size nfaces*nfp
251
252#ifdef POINT_PARALLEL_INTERIOR_FACES
253 mfem::forall(npoints, [=] MFEM_HOST_DEVICE (int p)
254 {
255 const int f = p / nfp;
256 const int fp = p % nfp;
257 const int face_offset = f*face_size;
258 const int point_offset = f*nfp + fp;
259
260 const mfem::real_t *u_face_d = u_d + face_offset;
261 mfem::real_t *rhs_face_d = rhs_d + face_offset;
262
263 DGSEMIntegrator::AssembleFacePointKernel(
264 dc, u_face_d, nor_d + point_offset*dim,
265 inv1_d[point_offset], inv2_d[point_offset], fp, rhs_face_d);
266 });
267#else
268 const int norm_size = nfp*dim;
269 mfem::forall(nfaces, [=] MFEM_HOST_DEVICE (int f)
270 {
271 const int face_offset = f*face_size;
272 const int n_offset = f*norm_size;
273 const int w_offset = f*nfp;
274
275 const mfem::real_t *u_face_d = u_d + face_offset;
276 mfem::real_t *rhs_face_d = rhs_d + face_offset;
277 const mfem::real_t *nor_face_d = nor_d + n_offset;
278 const mfem::real_t *w_minus_d = inv1_d + w_offset;
279 const mfem::real_t *w_plus_d = inv2_d + w_offset;
280
281 DGSEMIntegrator::AssembleElementFaceKernel(
282 dc, u_face_d, nor_face_d, w_minus_d, w_plus_d, rhs_face_d);
283
284 });
285#endif
286
287 operator_cache.restr_f->MultTranspose(rhs_faces, faces_dudt);
288 pdudt += faces_dudt; // on device?
289
290 }
291
292 template<typename PhysicsT>
293 void EulerOperator<PhysicsT>::MultEuler_BoundaryFaces(const mfem::Vector &pu, mfem::Vector &pdudt) const
294 {
295 Theseus::ScopedTimer timer("MultEuler_BoundaryFaces");
296
297 auto dc = device_cache;
298 const int dim = dc.dim;
299 const int neq = dc.num_equations;
300 const int nfp = dc.num_face_points;
301 const int face_size = nfp * neq;
302 const int restr_size = operator_cache.restr_b->Height();
303 const int nfaces_restr = restr_size / face_size;
304 const int norm_size = nfp * dc.dim;
305 const int npoints_bnd = nfaces_restr * nfp;
306
307 if(restr_size == 0){
308 return;
309 }
310
311 if(operator_cache.uBnd.Size() != restr_size)
312 {
313 operator_cache.uBnd.SetSize(restr_size);
314 operator_cache.uBnd.UseDevice();
315 }
316 mfem::Vector &u_faces(operator_cache.uBnd);
317 if(!operator_cache.u_bnd_restr_ready){
318 operator_cache.restr_b->Mult(pu, u_faces);
319 operator_cache.u_bnd_restr_ready = true;
320 }
321 if(operator_cache.rhsBnd.Size() != restr_size){
322 operator_cache.rhsBnd.SetSize(restr_size);
323 operator_cache.rhsBnd.UseDevice(true);
324 }
325 if(operator_cache.dudtBnd.Size() != restr_size){
326 operator_cache.dudtBnd.SetSize(pdudt.Size());
327 operator_cache.dudtBnd.UseDevice(true);
328 }
329
330 mfem::Vector &rhs_faces(operator_cache.rhsBnd);
331 mfem::Vector faces_dudt(pdudt);
332 faces_dudt.UseDevice(true);
333
334 // Zero on device:
335 {
336 mfem::real_t *rd = rhs_faces.Write();
337 mfem::forall(rhs_faces.Size(), [=] MFEM_HOST_DEVICE (int i)
338 { rd[i] = mfem::real_t(0);});
339 }
340
341
342 const mfem::real_t *u_d = u_faces.Read();
343 mfem::real_t *rhs_d = rhs_faces.Write();
344
345 const mfem::real_t *nor_d = dc.bnd_nor_d; // size nfaces*nfp*dim
346 const mfem::real_t *inv1_d = dc.bnd_wt_d; // size nfaces*nfp
347 const int *bnd_marker_index_d = dc.bnd_marker_index_d;
348 mfem::forall(npoints_bnd, [=] MFEM_HOST_DEVICE (int p)
349 {
350 const int f = p / nfp;
351 const int fp = p % nfp;
352
353 int bnd_face_marker_index = bnd_marker_index_d[f];
354 if(bnd_face_marker_index < 0){
355 return;
356 }
357 // int bc_index = bnd_marker_to_bc_descr_d[bnd_face_marker_index];
358 int bc_index = bnd_face_marker_index; // no mapping atm
359 if(bc_index < 0){
360 return;
361 }
362 const Theseus::BCDescriptor &bc = dc.bc_descr_d[bc_index];
363 if (bc.type == int(Theseus::BCType::Invalid))
364 {
365 return;
366 }
367
368 const int face_offset = f * face_size;
369 const int n_offset = f * norm_size;
370 const int w_offset = f * nfp;
371
372 const mfem::real_t *u_face_d = u_d + face_offset;
373 mfem::real_t *rhs_face_d = rhs_d + face_offset;
374 const mfem::real_t *nor_face_d = nor_d + n_offset;
375 const mfem::real_t *w_minus_d = inv1_d + w_offset;
376 const mfem::real_t *nor_point = nor_face_d + fp*dim;
377 mfem::real_t scale = -w_minus_d[fp];
378 mfem::real_t state1[Theseus::MAXEQ];
379 mfem::real_t fluxN[Theseus::MAXEQ];
380
381 Theseus::Kernels::el_gather_state(u_face_d, nfp, neq, fp, state1);
383 nor_point, fluxN);
384 Theseus::Kernels::el_scatter_add(fluxN, nfp, neq, fp, scale, rhs_face_d);
385
386 });
387
388 operator_cache.restr_b->MultTranspose(rhs_faces, faces_dudt);
389
390 pdudt += faces_dudt; // on device? (likely yes)
391
392 }
393
394
395 // Top level MULT for inviscid cases, called from DGSEMOperator
396 template<typename PhysicsT>
397 void EulerOperator<PhysicsT>::FlowMult(const mfem::Vector &u, mfem::Vector &pdudt) const
398 {
399 Theseus::ScopedTimer timer("EulerMult");
400
401 auto report_bad = [&](const char *name, const mfem::Vector &v)
402 {
403 int nbad = CBE(v);
404 if (nbad)
405 {
406 mfem::out << "BAD VALUES IN: (" << name << "), count=" << nbad << std::endl;
407 }
408 };
409
410 const mfem::Vector &pu(this->Prolongate(u));
411
412 // This step overwrites contents of pdudt
413 MultEuler_Volume(pu, pdudt);
414
415 MultEuler_InteriorFaces(pu, pdudt);
416 // report_bad("int rhs", pdudt);
417
418 MultEuler_BoundaryFaces(pu, pdudt);
419 // report_bad("bnd rhs", pdudt);
420
421 }
422
423}
void MultEuler_Volume(const mfem::Vector &pu, mfem::Vector &pdudt) const
Definition EulerOperator_impl.hpp:29
void MultEuler_InteriorFaces(const mfem::Vector &pu, mfem::Vector &pdudt) const
Definition EulerOperator_impl.hpp:212
void MultEuler_BoundaryFaces(const mfem::Vector &pu, mfem::Vector &pdudt) const
Definition EulerOperator_impl.hpp:293
void FlowMult(const mfem::Vector &pu, mfem::Vector &pdudt) const override
Definition EulerOperator_impl.hpp:397
Definition timer.hpp:22
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 el_scatter_add(const mfem::real_t *f, const int dof, const int num_eq, const int id, const mfem::real_t scale, mfem::real_t *du)
Definition theseus_kernels.hpp:153
MFEM_HOST_DEVICE void el_gather_state(const mfem::real_t *u, const int dof, const int num_eq, const int id, mfem::real_t *dst)
Definition theseus_kernels.hpp:135
MFEM_HOST_DEVICE bool is_bad_value(mfem::real_t x)
Definition theseus_kernels.hpp:203
Definition AxisymmetricGeometry.hpp:15
MFEM_HOST_DEVICE void AddAxisymmetricEulerElementSource(const ContextT &ctx, const mfem::real_t *element_state, const mfem::real_t *element_radius, const mfem::real_t *element_jacobian, const mfem::real_t *element_metric, mfem::real_t *element_rate)
Definition AxisymmetricSource.hpp:221
int CBE(const mfem::Vector &v)
Definition EulerOperator_impl.hpp:10
constexpr const int MAXEQ
Definition theseus_kernels.hpp:13
Definition bc_cache_utilities.hpp:35
int type
Definition bc_cache_utilities.hpp:36