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 mfem::real_t 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 mfem::real_t *ws_d = dc.elWaveSpeed_d;
103
104 // Inside the FORALL below, executed on device
105 mfem::forall(ne, [=] MFEM_HOST_DEVICE (int e)
106 {
107
108 const mfem::real_t *jac_el = elJac_d + e * jac_stride;
109 const mfem::real_t *metric_el = elMetric_d + e * metric_stride;
110 const mfem::real_t *radius_el = dc.axisymmetric ?
111 elRadius_d + e * jac_stride : nullptr;
112
113 const int attr = elem_attr_d[e];
114 if (attr_marker_d[attr-1] == 0) {
115 ws_d[e] = 0.0;
116 return;
117 }
118
119 const int eoff = e * estride;
120 const mfem::real_t *u_el = Ue_d + eoff;
121 mfem::real_t *du_el = dUe_d + eoff;
122
123 // Additive on du_el (*zero first if needed*)
124 mfem::real_t cs_el = \
125 DGSEMIntegrator::AssembleElementVolumeKernel(dc, u_el,
126 jac_el, metric_el, du_el);
127#ifdef SUBCELL_FV_BLENDING
128 mfem::real_t alpha_fv = alpha_d[e];
129 if(alpha_fv > 1e-16){
130 mfem::real_t alpha_inv = (1.0 - alpha_fv);
131 mfem::real_t *du_fv = dUfv_d + eoff;
132 const mfem::real_t *el_metric_xi = metric_xi_d + e * npe_metric_xi * dim;
133 const mfem::real_t *el_metric_eta = (dim > 1 ? metric_eta_d + e * npe_metric_eta * dim :
134 nullptr);
135 const mfem::real_t *el_metric_zeta = (dim > 2 ? metric_zeta_d + e * npe_metric_zeta * dim :
136 nullptr);
137 const mfem::real_t cs_fv = \
138 DGSEMIntegrator::ComputeFVFluxesKernel(dc, u_el, jac_el, el_metric_xi, el_metric_eta,
139 el_metric_zeta, du_fv);
140
141 for(int ipt = 0;ipt < estride;ipt++){
142 du_el[ipt] = alpha_inv * du_el[ipt] + alpha_fv * du_fv[ipt];
143 }
144
145 cs_el = Kernels::rmax(cs_el, cs_fv);
146 }
147#endif
148
150 dc, u_el, radius_el, jac_el, metric_el, du_el);
151
152 ws_d[e] = cs_el;
153
154 });
155
156 // Scatter RHS back to storage
157 operator_cache.restr_v->AddMultTranspose(dUe, pdudt);
158
159 // Finish up on the host: (not sure how to reduce on-device)
160 // - Reduce for rank-local max_char_speed
161 const mfem::real_t *ws = operator_cache.elWaveSpeed.HostRead();
162 mfem::real_t max_char_speed = 0.0;
163 for(int e = 0;e < operator_cache.num_elements;e++)
164 {
165 max_char_speed = std::max(max_char_speed, ws[e]);
166 }
167
168 return max_char_speed;
169 }
170
171 template<typename PhysicsT>
172 mfem::real_t EulerOperator<PhysicsT>::MultEuler_InteriorFaces(const mfem::Vector &pu, mfem::Vector &pdudt) const
173 {
174 Theseus::ScopedTimer timer("MultEuler_InteriorFaces");
175 auto dc = device_cache;
176 const int dim = dc.dim;
177 const int neq = dc.num_equations;
178 const int nfp = dc.num_face_points;
179 const int nval_restr = operator_cache.restr_f->Height();
180 const int nfaces = nval_restr / (nfp * neq * 2); // (+/-)
181 const int face_size = 2*nfp*neq;
182 const int norm_size = nfp*dim;
183
184 if(operator_cache.uInt.Size() != nval_restr){
185 operator_cache.uInt.SetSize(nval_restr);
186 operator_cache.uInt.UseDevice(true);
187 }
188 mfem::Vector &u_faces(operator_cache.uInt);
189 if(!operator_cache.u_int_restr_ready){
190 operator_cache.restr_f->Mult(pu, u_faces);
191 operator_cache.u_int_restr_ready = true;
192 }
193
194 if(operator_cache.rhsInt.Size() != nval_restr){
195 operator_cache.rhsInt.SetSize(nval_restr);
196 operator_cache.rhsInt.UseDevice(true);
197 }
198 mfem::Vector &rhs_faces(operator_cache.rhsInt);
199
200 // For now, just keep this copy - i think it is device-friendly
201 mfem::Vector faces_dudt(pdudt);
202 // Zeroing unneeded: we clobber anything there on assignment
203 faces_dudt.UseDevice(true);
204
205 const mfem::real_t *u_d = u_faces.Read();
206 mfem::real_t *rhs_d = rhs_faces.Write();
207
208 const mfem::real_t *nor_d = dc.nor_d; // size nfaces*nfp*dim
209 const mfem::real_t *inv1_d = dc.fw_minus_d; // size nfaces*nfp
210 const mfem::real_t *inv2_d = dc.fw_plus_d; // size nfaces*nfp
211
212 mfem::real_t *ws_d = dc.ifWaveSpeed_d;
213
214 mfem::forall(nfaces, [=] MFEM_HOST_DEVICE (int i)
215 {
216 const int face_offset = i*face_size;
217 const int n_offset = i*norm_size;
218 const int w_offset = i*nfp;
219
220 const mfem::real_t *u_face_d = u_d + face_offset;
221 mfem::real_t *rhs_face_d = rhs_d + face_offset;
222 const mfem::real_t *nor_face_d = nor_d + n_offset;
223 const mfem::real_t *w_minus_d = inv1_d + w_offset;
224 const mfem::real_t *w_plus_d = inv2_d + w_offset;
225
226 mfem::real_t ws = DGSEMIntegrator::AssembleElementFaceKernel(dc, u_face_d, nor_face_d,
227 w_minus_d, w_plus_d, rhs_face_d);
228 ws_d[i] = ws;
229
230 });
231
232 operator_cache.restr_f->MultTranspose(rhs_faces, faces_dudt);
233 pdudt += faces_dudt; // on device?
234
235 // Finish up on the host:
236 // - Reduce for rank-local max_char_speed
237 const mfem::real_t *ws = operator_cache.ifWaveSpeed.HostRead();
238 mfem::real_t max_char_speed_facial = 0.0;
239 for(int f = 0;f < operator_cache.num_interior_faces;f++)
240 {
241 max_char_speed_facial = std::max(max_char_speed_facial, ws[f]);
242 }
243
244 return max_char_speed_facial;
245 }
246
247 template<typename PhysicsT>
248 mfem::real_t EulerOperator<PhysicsT>::MultEuler_BoundaryFaces(const mfem::Vector &pu, mfem::Vector &pdudt) const
249 {
250 Theseus::ScopedTimer timer("MultEuler_BoundaryFaces");
251
252 auto dc = device_cache;
253 const int dim = dc.dim;
254 const int neq = dc.num_equations;
255 const int nfp = dc.num_face_points;
256 const int face_size = nfp * neq;
257 const int restr_size = operator_cache.restr_b->Height();
258 const int nfaces_restr = restr_size / face_size;
259 const int norm_size = nfp * dc.dim;
260 const int npoints_bnd = nfaces_restr * nfp;
261
262 if(restr_size == 0){
263 return 0.0;
264 }
265
266 if(operator_cache.uBnd.Size() != restr_size)
267 {
268 operator_cache.uBnd.SetSize(restr_size);
269 operator_cache.uBnd.UseDevice();
270 }
271 mfem::Vector &u_faces(operator_cache.uBnd);
272 if(!operator_cache.u_bnd_restr_ready){
273 operator_cache.restr_b->Mult(pu, u_faces);
274 operator_cache.u_bnd_restr_ready = true;
275 }
276 if(operator_cache.rhsBnd.Size() != restr_size){
277 operator_cache.rhsBnd.SetSize(restr_size);
278 operator_cache.rhsBnd.UseDevice(true);
279 }
280 if(operator_cache.dudtBnd.Size() != restr_size){
281 operator_cache.dudtBnd.SetSize(pdudt.Size());
282 operator_cache.dudtBnd.UseDevice(true);
283 }
284
285 mfem::Vector &rhs_faces(operator_cache.rhsBnd);
286 mfem::Vector faces_dudt(pdudt);
287 faces_dudt.UseDevice(true);
288
289 // Zero on device:
290 {
291 mfem::real_t *rd = rhs_faces.Write();
292 mfem::forall(rhs_faces.Size(), [=] MFEM_HOST_DEVICE (int i)
293 { rd[i] = mfem::real_t(0);});
294 }
295
296
297 const mfem::real_t *u_d = u_faces.Read();
298 mfem::real_t *rhs_d = rhs_faces.Write();
299
300 const mfem::real_t *nor_d = dc.bnd_nor_d; // size nfaces*nfp*dim
301 const mfem::real_t *inv1_d = dc.bnd_wt_d; // size nfaces*nfp
302 const int *bnd_marker_index_d = dc.bnd_marker_index_d;
303 mfem::real_t *ws_d = dc.bndWaveSpeed_d;
304
305 mfem::forall(npoints_bnd, [=] MFEM_HOST_DEVICE (int p)
306 {
307 const int f = p / nfp;
308 const int fp = p % nfp;
309
310 int bnd_face_marker_index = bnd_marker_index_d[f];
311 if(bnd_face_marker_index < 0){
312 ws_d[p] = 0.0;
313 return;
314 }
315 // int bc_index = bnd_marker_to_bc_descr_d[bnd_face_marker_index];
316 int bc_index = bnd_face_marker_index; // no mapping atm
317 if(bc_index < 0){
318 ws_d[p] = 0.0;
319 return;
320 }
321 const Theseus::BCDescriptor &bc = dc.bc_descr_d[bc_index];
322 if (bc.type == int(Theseus::BCType::Invalid))
323 {
324 ws_d[p] = 0.0;
325 return;
326 }
327
328 const int face_offset = f * face_size;
329 const int n_offset = f * norm_size;
330 const int w_offset = f * nfp;
331
332 const mfem::real_t *u_face_d = u_d + face_offset;
333 mfem::real_t *rhs_face_d = rhs_d + face_offset;
334 const mfem::real_t *nor_face_d = nor_d + n_offset;
335 const mfem::real_t *w_minus_d = inv1_d + w_offset;
336 const mfem::real_t *nor_point = nor_face_d + fp*dim;
337 mfem::real_t scale = -w_minus_d[fp];
338 mfem::real_t state1[Theseus::MAXEQ];
339 mfem::real_t fluxN[Theseus::MAXEQ];
340
341 Theseus::Kernels::el_gather_state(u_face_d, nfp, neq, fp, state1);
342 const mfem::real_t ws = \
343 Theseus::BC::ApplyBoundaryConditionInviscid(dc, bc, state1,
344 nor_point, fluxN);
345 Theseus::Kernels::el_scatter_add(fluxN, nfp, neq, fp, scale, rhs_face_d);
346 ws_d[p] = ws;
347
348 });
349
350 operator_cache.restr_b->MultTranspose(rhs_faces, faces_dudt);
351
352 pdudt += faces_dudt; // on device? (likely yes)
353
354 // Finish up on the host:
355 // - Reduce for rank-local max_char_speed
356 const mfem::real_t *ws = operator_cache.bndWaveSpeed.HostRead();
357 mfem::real_t max_char_speed_facial = 0.0;
358 for(int p = 0;p < npoints_bnd;p++)
359 {
360 max_char_speed_facial = std::max(max_char_speed_facial, ws[p]);
361 }
362
363 return max_char_speed_facial;
364 }
365
366
367 // Top level MULT for inviscid cases, called from DGSEMOperator
368 template<typename PhysicsT>
369 mfem::real_t EulerOperator<PhysicsT>::FlowMult(const mfem::Vector &u, mfem::Vector &pdudt) const
370 {
371 Theseus::ScopedTimer timer("EulerMult");
372
373 auto report_bad = [&](const char *name, const mfem::Vector &v)
374 {
375 int nbad = CBE(v);
376 if (nbad)
377 {
378 mfem::out << "BAD VALUES IN: (" << name << "), count=" << nbad << std::endl;
379 }
380 };
381
382 mfem::real_t max_char_speed = 0.0;
383
384 const mfem::Vector &pu(this->Prolongate(u));
385
386 // This step overwrites contents of pdudt
387 max_char_speed = MultEuler_Volume(pu, pdudt);
388
389 mfem::real_t max_char_speed_facial = 0.0;
390 max_char_speed_facial = MultEuler_InteriorFaces(pu, pdudt);
391 // report_bad("int rhs", pdudt);
392
393 max_char_speed = std::max(max_char_speed, max_char_speed_facial);
394 mfem::real_t max_char_speed_bnd = 0.0;
395 max_char_speed_bnd = MultEuler_BoundaryFaces(pu, pdudt);
396 // report_bad("bnd rhs", pdudt);
397
398 return std::max(max_char_speed, max_char_speed_bnd);
399
400 }
401
402}
mfem::real_t MultEuler_BoundaryFaces(const mfem::Vector &pu, mfem::Vector &pdudt) const
Definition EulerOperator_impl.hpp:248
mfem::real_t MultEuler_InteriorFaces(const mfem::Vector &pu, mfem::Vector &pdudt) const
Definition EulerOperator_impl.hpp:172
mfem::real_t MultEuler_Volume(const mfem::Vector &pu, mfem::Vector &pdudt) const
Definition EulerOperator_impl.hpp:29
mfem::real_t FlowMult(const mfem::Vector &pu, mfem::Vector &pdudt) const override
Definition EulerOperator_impl.hpp:369
Definition timer.hpp:17
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
MFEM_HOST_DEVICE mfem::real_t rmax(mfem::real_t a, mfem::real_t b)
Definition theseus_kernels.hpp:17
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