Line data Source code
1 : /********************************************************************/
2 : /* SOFTWARE COPYRIGHT NOTIFICATION */
3 : /* Cardinal */
4 : /* */
5 : /* (c) 2021 UChicago Argonne, LLC */
6 : /* ALL RIGHTS RESERVED */
7 : /* */
8 : /* Prepared by UChicago Argonne, LLC */
9 : /* Under Contract No. DE-AC02-06CH11357 */
10 : /* With the U. S. Department of Energy */
11 : /* */
12 : /* Prepared by Battelle Energy Alliance, LLC */
13 : /* Under Contract No. DE-AC07-05ID14517 */
14 : /* With the U. S. Department of Energy */
15 : /* */
16 : /* See LICENSE for full restrictions */
17 : /********************************************************************/
18 :
19 : #ifdef ENABLE_NEK_COUPLING
20 :
21 : #include "NekInterface.h"
22 : #include "CardinalUtils.h"
23 :
24 : static nekrs::characteristicScales scales;
25 : static dfloat * sgeo;
26 : static dfloat * vgeo;
27 : static unsigned int n_usrwrk_slots;
28 : static bool is_nondimensional;
29 :
30 : namespace nekrs
31 : {
32 : static double setup_time;
33 :
34 : // various constants for controlling tolerances
35 : static double abs_tol;
36 : static double rel_tol;
37 :
38 : void
39 339 : setAbsoluteTol(double tol)
40 : {
41 339 : abs_tol = tol;
42 339 : }
43 :
44 : void
45 339 : setRelativeTol(double tol)
46 : {
47 339 : rel_tol = tol;
48 339 : }
49 :
50 : void
51 811 : setNekSetupTime(const double & time)
52 : {
53 811 : setup_time = time;
54 811 : }
55 :
56 : double
57 800 : getNekSetupTime()
58 : {
59 800 : return setup_time;
60 : }
61 :
62 : void
63 740 : setStartTime(const double & start)
64 : {
65 1480 : platform->options.setArgs("START TIME", to_string_f(start));
66 740 : }
67 :
68 : void
69 62 : write_usrwrk_field_file(const int & slot, const std::string & prefix, const dfloat & time, const int & step, const bool & write_coords)
70 : {
71 62 : int num_bytes = fieldOffset() * sizeof(dfloat);
72 :
73 62 : nrs_t * nrs = (nrs_t *)nrsPtr();
74 62 : occa::memory o_write = platform->device.malloc(num_bytes);
75 124 : o_write.copyFrom(nrs->o_usrwrk, num_bytes /* length we are copying */,
76 62 : 0 /* where to place data */, num_bytes * slot /* where to source data */);
77 :
78 62 : occa::memory o_null;
79 62 : writeFld(prefix.c_str(), time, step, write_coords, 1 /* FP64 */, o_null, o_null, o_write, 1);
80 62 : }
81 :
82 : void
83 56 : write_field_file(const std::string & prefix, const dfloat time, const int & step)
84 : {
85 56 : nrs_t * nrs = (nrs_t *)nrsPtr();
86 :
87 : int Nscalar = 0;
88 56 : occa::memory o_s;
89 56 : if (nrs->Nscalar)
90 : {
91 56 : o_s = nrs->cds->o_S;
92 56 : Nscalar = nrs->Nscalar;
93 : }
94 :
95 56 : writeFld(
96 56 : prefix.c_str(), time, step, 1 /* coords */, 1 /* FP64 */, nrs->o_U, nrs->o_P, o_s, Nscalar);
97 56 : }
98 :
99 : void
100 813 : buildOnly(int buildOnly)
101 : {
102 813 : build_only = buildOnly;
103 813 : }
104 :
105 : int
106 105093 : buildOnly()
107 : {
108 105093 : return build_only;
109 : }
110 :
111 : bool
112 0 : hasCHT()
113 : {
114 0 : return entireMesh()->cht;
115 : }
116 :
117 : bool
118 36604 : hasMovingMesh()
119 : {
120 36604 : return platform->options.compareArgs("MOVING MESH", "TRUE");
121 : }
122 :
123 : bool
124 33218 : hasVariableDt()
125 : {
126 33218 : return platform->options.compareArgs("VARIABLE DT", "TRUE");
127 : }
128 :
129 : bool
130 953 : hasBlendingSolver()
131 : {
132 1906 : return !platform->options.compareArgs("MESH SOLVER", "NONE") && hasMovingMesh();
133 : }
134 :
135 : bool
136 954 : hasUserMeshSolver()
137 : {
138 1908 : return platform->options.compareArgs("MESH SOLVER", "NONE") && hasMovingMesh();
139 : }
140 :
141 : bool
142 1522 : endControlElapsedTime()
143 : {
144 3044 : return !platform->options.getArgs("STOP AT ELAPSED TIME").empty();
145 : }
146 :
147 : bool
148 1522 : endControlTime()
149 : {
150 1522 : return endTime() > 0;
151 : }
152 :
153 : bool
154 761 : endControlNumSteps()
155 : {
156 761 : return !endControlElapsedTime() && !endControlTime();
157 : }
158 :
159 : bool
160 148986037 : hasTemperatureVariable()
161 : {
162 148986037 : nrs_t * nrs = (nrs_t *)nrsPtr();
163 446485487 : return nrs->Nscalar ? platform->options.compareArgs("SCALAR00 IS TEMPERATURE", "TRUE") : false;
164 : }
165 :
166 : bool
167 627 : hasTemperatureSolve()
168 : {
169 627 : nrs_t * nrs = (nrs_t *)nrsPtr();
170 627 : return hasTemperatureVariable() ? nrs->cds->compute[0] : false;
171 : }
172 :
173 : bool
174 1207 : hasScalarVariable(int scalarId)
175 : {
176 1207 : nrs_t * nrs = (nrs_t *)nrsPtr();
177 1207 : return (scalarId < nrs->Nscalar);
178 : }
179 :
180 : bool
181 93 : hasHeatSourceKernel()
182 : {
183 93 : return static_cast<bool>(udf.sEqnSource);
184 : }
185 :
186 : bool
187 809 : isInitialized()
188 : {
189 809 : nrs_t * nrs = (nrs_t *)nrsPtr();
190 809 : return nrs;
191 : }
192 :
193 : int
194 9125063 : scalarFieldOffset()
195 : {
196 9125063 : nrs_t * nrs = (nrs_t *)nrsPtr();
197 9125063 : return nrs->cds->fieldOffset[0];
198 : }
199 :
200 : int
201 1831210 : velocityFieldOffset()
202 : {
203 1831210 : nrs_t * nrs = (nrs_t *)nrsPtr();
204 1831210 : return nrs->fieldOffset;
205 : }
206 :
207 : int
208 9001831 : fieldOffset()
209 : {
210 9001831 : if (hasTemperatureVariable())
211 9001815 : return scalarFieldOffset();
212 : else
213 16 : return velocityFieldOffset();
214 : }
215 :
216 : mesh_t *
217 139898829 : entireMesh()
218 : {
219 139898829 : if (hasTemperatureVariable())
220 139662537 : return temperatureMesh();
221 : else
222 236292 : return flowMesh();
223 : }
224 :
225 : mesh_t *
226 584145 : flowMesh()
227 : {
228 584145 : nrs_t * nrs = (nrs_t *)nrsPtr();
229 584145 : return nrs->meshV;
230 : }
231 :
232 : mesh_t *
233 140467810 : temperatureMesh()
234 : {
235 140467810 : nrs_t * nrs = (nrs_t *)nrsPtr();
236 140467810 : return nrs->cds->mesh[0];
237 : }
238 :
239 : mesh_t *
240 396170 : getMesh(const nek_mesh::NekMeshEnum pp_mesh)
241 : {
242 396170 : if (pp_mesh == nek_mesh::fluid)
243 2188 : return flowMesh();
244 393982 : else if (pp_mesh == nek_mesh::all)
245 393981 : return entireMesh();
246 : else
247 1 : mooseError("This object does not support operations on the solid part of the NekRS mesh!\n"
248 : "Valid options for 'mesh' are 'fluid' or 'all'.");
249 : }
250 :
251 : int
252 12765306 : commRank()
253 : {
254 12765306 : return platform->comm.mpiRank;
255 : }
256 :
257 : int
258 1048657 : commSize()
259 : {
260 1048657 : return platform->comm.mpiCommSize;
261 : }
262 :
263 : bool
264 811 : scratchAvailable()
265 : {
266 811 : nrs_t * nrs = (nrs_t *)nrsPtr();
267 :
268 : // Because these scratch spaces are available for whatever the user sees fit, it is
269 : // possible that the user wants to use these arrays for a _different_ purpose aside from
270 : // transferring in MOOSE values. In nekrs::setup, we call the UDF_Setup0, UDF_Setup,
271 : // and UDF_ExecuteStep routines. These scratch space arrays aren't initialized anywhere
272 : // else in the core base, so we will make sure to throw an error from MOOSE if these
273 : // arrays are already in use, because otherwise our MOOSE transfer might get overwritten
274 : // by whatever other operation the user is trying to do.
275 811 : if (nrs->usrwrk)
276 1 : return false;
277 :
278 : return true;
279 : }
280 :
281 : void
282 810 : initializeScratch(const unsigned int & n_slots)
283 : {
284 810 : if (n_slots == 0)
285 : return;
286 :
287 413 : nrs_t * nrs = (nrs_t *)nrsPtr();
288 413 : mesh_t * mesh = entireMesh();
289 :
290 : // clear them just to be sure
291 413 : freeScratch();
292 :
293 : // In order to make indexing simpler in the device user functions (which is where the
294 : // boundary conditions are then actually applied), we define these scratch arrays
295 : // as volume arrays.
296 413 : nrs->usrwrk = (double *)calloc(n_slots * fieldOffset(), sizeof(double));
297 413 : nrs->o_usrwrk = platform->device.malloc(n_slots * fieldOffset() * sizeof(double), nrs->usrwrk);
298 :
299 413 : n_usrwrk_slots = n_slots;
300 : }
301 :
302 : void
303 1145 : freeScratch()
304 : {
305 1145 : nrs_t * nrs = (nrs_t *)nrsPtr();
306 :
307 1145 : freePointer(nrs->usrwrk);
308 1145 : nrs->o_usrwrk.free();
309 1145 : }
310 :
311 : double
312 276 : viscosity()
313 : {
314 : dfloat mu;
315 276 : setupAide & options = platform->options;
316 276 : options.getArgs("VISCOSITY", mu);
317 :
318 : // because we set rho_ref, U_ref, and L_ref all equal to 1 if our input is dimensional,
319 : // we don't need to have separate treatments for dimensional vs. nondimensional cases
320 276 : dfloat Re = 1.0 / mu;
321 276 : return scales.rho_ref * scales.U_ref * scales.L_ref / Re;
322 : }
323 :
324 : double
325 92 : Pr()
326 : {
327 : dfloat rho, rho_cp, k;
328 92 : setupAide & options = platform->options;
329 92 : options.getArgs("DENSITY", rho);
330 92 : options.getArgs("SCALAR00 DENSITY", rho_cp);
331 92 : options.getArgs("SCALAR00 DIFFUSIVITY", k);
332 :
333 92 : dfloat Pe = 1.0 / k;
334 92 : dfloat conductivity = scales.rho_ref * scales.U_ref * scales.Cp_ref * scales.L_ref / Pe;
335 92 : dfloat Cp = rho_cp / rho * scales.Cp_ref;
336 92 : return viscosity() * Cp / conductivity;
337 : }
338 :
339 : void
340 1596 : interpolationMatrix(double * I, int starting_points, int ending_points)
341 : {
342 1596 : DegreeRaiseMatrix1D(starting_points - 1, ending_points - 1, I);
343 1596 : }
344 :
345 : void
346 2198994 : interpolateVolumeHex3D(const double * I, double * x, int N, double * Ix, int M)
347 : {
348 2198994 : double * Ix1 = (dfloat *)calloc(N * N * M, sizeof(double));
349 2198994 : double * Ix2 = (dfloat *)calloc(N * M * M, sizeof(double));
350 :
351 10888880 : for (int k = 0; k < N; ++k)
352 51895756 : for (int j = 0; j < N; ++j)
353 180941182 : for (int i = 0; i < M; ++i)
354 : {
355 : dfloat tmp = 0;
356 939543840 : for (int n = 0; n < N; ++n)
357 801808528 : tmp += I[i * N + n] * x[k * N * N + j * N + n];
358 137735312 : Ix1[k * N * M + j * M + i] = tmp;
359 : }
360 :
361 10888880 : for (int k = 0; k < N; ++k)
362 38012670 : for (int j = 0; j < M; ++j)
363 141667368 : for (int i = 0; i < M; ++i)
364 : {
365 : dfloat tmp = 0;
366 585034584 : for (int n = 0; n < N; ++n)
367 472690000 : tmp += I[j * N + n] * Ix1[k * N * M + n * M + i];
368 112344584 : Ix2[k * M * M + j * M + i] = tmp;
369 : }
370 :
371 10153964 : for (int k = 0; k < M; ++k)
372 42219396 : for (int j = 0; j < M; ++j)
373 211512576 : for (int i = 0; i < M; ++i)
374 : {
375 : dfloat tmp = 0;
376 688209246 : for (int n = 0; n < N; ++n)
377 510961096 : tmp += I[k * N + n] * Ix2[n * M * M + j * M + i];
378 177248150 : Ix[k * M * M + j * M + i] = tmp;
379 : }
380 :
381 : freePointer(Ix1);
382 : freePointer(Ix2);
383 2198994 : }
384 :
385 : void
386 478350 : interpolateSurfaceFaceHex3D(
387 : double * scratch, const double * I, double * x, int N, double * Ix, int M)
388 : {
389 1908266 : for (int j = 0; j < N; ++j)
390 6512948 : for (int i = 0; i < M; ++i)
391 : {
392 : double tmp = 0;
393 23882184 : for (int n = 0; n < N; ++n)
394 : {
395 18799152 : tmp += I[i * N + n] * x[j * N + n];
396 : }
397 5083032 : scratch[j * M + i] = tmp;
398 : }
399 :
400 2310042 : for (int j = 0; j < M; ++j)
401 9255804 : for (int i = 0; i < M; ++i)
402 : {
403 : double tmp = 0;
404 26531280 : for (int n = 0; n < N; ++n)
405 : {
406 19107168 : tmp += I[j * N + n] * scratch[n * M + i];
407 : }
408 7424112 : Ix[j * M + i] = tmp;
409 : }
410 478350 : }
411 :
412 : void
413 93511 : displacementAndCounts(const std::vector<int> & base_counts,
414 : int * counts,
415 : int * displacement,
416 : const int multiplier = 1.0)
417 : {
418 476702 : for (int i = 0; i < commSize(); ++i)
419 383191 : counts[i] = base_counts[i] * multiplier;
420 :
421 93511 : displacement[0] = 0;
422 383191 : for (int i = 1; i < commSize(); i++)
423 289680 : displacement[i] = displacement[i - 1] + counts[i - 1];
424 93511 : }
425 :
426 : double
427 1940 : usrwrkVolumeIntegral(const unsigned int & slot, const nek_mesh::NekMeshEnum pp_mesh)
428 : {
429 1940 : nrs_t * nrs = (nrs_t *)nrsPtr();
430 1940 : const auto & mesh = getMesh(pp_mesh);
431 :
432 1940 : double integral = 0.0;
433 :
434 763608 : for (int k = 0; k < mesh->Nelements; ++k)
435 : {
436 761668 : int offset = k * mesh->Np;
437 :
438 199804868 : for (int v = 0; v < mesh->Np; ++v)
439 199043200 : integral += nrs->usrwrk[slot + offset + v] * vgeo[mesh->Nvgeo * offset + v + mesh->Np * JWID];
440 : }
441 :
442 : // sum across all processes
443 : double total_integral;
444 1940 : MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
445 :
446 1940 : return total_integral;
447 : }
448 :
449 : void
450 958 : scaleUsrwrk(const unsigned int & slot, const dfloat & value)
451 : {
452 958 : nrs_t * nrs = (nrs_t *)nrsPtr();
453 958 : mesh_t * mesh = getMesh(nek_mesh::all);
454 :
455 381664 : for (int k = 0; k < mesh->Nelements; ++k)
456 : {
457 380706 : int id = k * mesh->Np;
458 :
459 99898850 : for (int v = 0; v < mesh->Np; ++v)
460 99518144 : nrs->usrwrk[slot + id + v] *= value;
461 : }
462 958 : }
463 :
464 : std::vector<double>
465 25359 : usrwrkSideIntegral(const unsigned int & slot,
466 : const std::vector<int> & boundary,
467 : const nek_mesh::NekMeshEnum pp_mesh)
468 : {
469 25359 : nrs_t * nrs = (nrs_t *)nrsPtr();
470 25359 : const auto & mesh = getMesh(pp_mesh);
471 :
472 25359 : std::vector<double> integral(boundary.size(), 0.0);
473 :
474 7860674 : for (int i = 0; i < mesh->Nelements; ++i)
475 : {
476 54847205 : for (int j = 0; j < mesh->Nfaces; ++j)
477 : {
478 47011890 : int face_id = mesh->EToB[i * mesh->Nfaces + j];
479 :
480 47011890 : if (std::find(boundary.begin(), boundary.end(), face_id) != boundary.end())
481 : {
482 814188 : auto it = std::find(boundary.begin(), boundary.end(), face_id);
483 : auto b_index = it - boundary.begin();
484 :
485 814188 : int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
486 :
487 15160204 : for (int v = 0; v < mesh->Nfp; ++v)
488 14346016 : integral[b_index] += nrs->usrwrk[slot + mesh->vmapM[offset + v]] *
489 14346016 : sgeo[mesh->Nsgeo * (offset + v) + WSJID];
490 : }
491 : }
492 : }
493 :
494 : // sum across all processes; this can probably be done more efficiently
495 25359 : std::vector<double> total_integral(boundary.size(), 0.0);
496 50839 : for (std::size_t i = 0; i < boundary.size(); ++i)
497 25480 : MPI_Allreduce(&integral[i], &total_integral[i], 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
498 :
499 25359 : return total_integral;
500 25359 : }
501 :
502 : void
503 0 : limitTemperature(const double * min_T, const double * max_T)
504 : {
505 : // if no limiters are provided, simply return
506 0 : if (!min_T && !max_T)
507 : return;
508 :
509 0 : double minimum = min_T ? *min_T : std::numeric_limits<double>::min();
510 0 : double maximum = max_T ? *max_T : std::numeric_limits<double>::max();
511 :
512 : // nondimensionalize if necessary
513 0 : minimum = (minimum - scales.T_ref) / scales.dT_ref;
514 0 : maximum = (maximum - scales.T_ref) / scales.dT_ref;
515 :
516 0 : nrs_t * nrs = (nrs_t *)nrsPtr();
517 0 : mesh_t * mesh = temperatureMesh();
518 :
519 0 : for (int i = 0; i < mesh->Nelements; ++i)
520 : {
521 0 : for (int j = 0; j < mesh->Np; ++j)
522 : {
523 0 : int id = i * mesh->Np + j;
524 :
525 0 : if (nrs->cds->S[id] < minimum)
526 0 : nrs->cds->S[id] = minimum;
527 0 : if (nrs->cds->S[id] > maximum)
528 0 : nrs->cds->S[id] = maximum;
529 : }
530 : }
531 :
532 : // when complete, copy to device
533 0 : nrs->cds->o_S.copyFrom(nrs->cds->S);
534 : }
535 :
536 : void
537 1504 : copyDeformationToDevice()
538 : {
539 1504 : mesh_t * mesh = entireMesh();
540 1504 : mesh->o_x.copyFrom(mesh->x);
541 1504 : mesh->o_y.copyFrom(mesh->y);
542 1504 : mesh->o_z.copyFrom(mesh->z);
543 1504 : mesh->update();
544 :
545 1504 : updateHostMeshParameters();
546 1504 : }
547 :
548 : void
549 808 : initializeHostMeshParameters()
550 : {
551 808 : mesh_t * mesh = entireMesh();
552 808 : sgeo = (dfloat *)calloc(mesh->o_sgeo.size(), sizeof(dfloat));
553 808 : vgeo = (dfloat *)calloc(mesh->o_vgeo.size(), sizeof(dfloat));
554 808 : }
555 :
556 : void
557 2312 : updateHostMeshParameters()
558 : {
559 2312 : mesh_t * mesh = entireMesh();
560 2312 : mesh->o_sgeo.copyTo(sgeo);
561 2312 : mesh->o_vgeo.copyTo(vgeo);
562 2312 : }
563 :
564 : dfloat *
565 104 : getSgeo()
566 : {
567 104 : return sgeo;
568 : }
569 :
570 : dfloat *
571 355 : getVgeo()
572 : {
573 355 : return vgeo;
574 : }
575 :
576 : double
577 24332 : sideExtremeValue(const std::vector<int> & boundary_id, const field::NekFieldEnum & field,
578 : const nek_mesh::NekMeshEnum pp_mesh, const bool max)
579 : {
580 24332 : mesh_t * mesh = getMesh(pp_mesh);
581 :
582 24332 : double value = max ? -std::numeric_limits<double>::max() : std::numeric_limits<double>::max();
583 :
584 : double (*f)(int, int);
585 24332 : f = solutionPointer(field);
586 :
587 2694568 : for (int i = 0; i < mesh->Nelements; ++i)
588 : {
589 18691652 : for (int j = 0; j < mesh->Nfaces; ++j)
590 : {
591 16021416 : int face_id = mesh->EToB[i * mesh->Nfaces + j];
592 :
593 16021416 : if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
594 : {
595 270524 : int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
596 11791388 : for (int v = 0; v < mesh->Nfp; ++v)
597 : {
598 11520864 : if (max)
599 5835328 : value = std::max(value, f(mesh->vmapM[offset + v], 0 /* unused */));
600 : else
601 5809910 : value = std::min(value, f(mesh->vmapM[offset + v], 0 /* unused */));
602 : }
603 : }
604 : }
605 : }
606 :
607 : // find extreme value across all processes
608 : double reduced_value;
609 24332 : auto op = max ? MPI_MAX : MPI_MIN;
610 24332 : MPI_Allreduce(&value, &reduced_value, 1, MPI_DOUBLE, op, platform->comm.mpiComm);
611 :
612 : // dimensionalize the field if needed
613 24332 : reduced_value = reduced_value * nondimensionalDivisor(field) + nondimensionalAdditive(field);
614 :
615 24332 : return reduced_value;
616 : }
617 :
618 : double
619 46788 : volumeExtremeValue(const field::NekFieldEnum & field, const nek_mesh::NekMeshEnum pp_mesh, const bool max)
620 : {
621 46788 : double value = max ? -std::numeric_limits<double>::max() : std::numeric_limits<double>::max();
622 :
623 : double (*f)(int, int);
624 46788 : f = solutionPointer(field);
625 :
626 : mesh_t * mesh;
627 : int start_id;
628 :
629 46788 : switch (pp_mesh)
630 : {
631 46776 : case nek_mesh::fluid:
632 : case nek_mesh::all:
633 : {
634 46776 : mesh = getMesh(pp_mesh);
635 : start_id = 0;
636 : break;
637 : }
638 12 : case nek_mesh::solid:
639 : {
640 12 : mesh = entireMesh();
641 12 : start_id = flowMesh()->Nelements;
642 12 : break;
643 : }
644 0 : default:
645 0 : mooseError("Unhandled NekMeshEnum in volumeExtremeValue");
646 : }
647 :
648 7914462 : for (int i = start_id; i < mesh->Nelements; ++i)
649 : {
650 1093968674 : for (int j = 0; j < mesh->Np; ++j)
651 : {
652 1086101000 : if (max)
653 614096211 : value = std::max(value, f(i * mesh->Np + j, 0 /* unused */));
654 : else
655 474404620 : value = std::min(value, f(i * mesh->Np + j, 0 /* unused */));
656 : }
657 : }
658 :
659 : // find extreme value across all processes
660 : double reduced_value;
661 46788 : auto op = max ? MPI_MAX : MPI_MIN;
662 46788 : MPI_Allreduce(&value, &reduced_value, 1, MPI_DOUBLE, op, platform->comm.mpiComm);
663 :
664 : // dimensionalize the field if needed
665 46788 : reduced_value = reduced_value * nondimensionalDivisor(field) + nondimensionalAdditive(field);
666 :
667 46788 : return reduced_value;
668 : }
669 :
670 : Point
671 99589536 : gllPoint(int local_elem_id, int local_node_id)
672 : {
673 99589536 : mesh_t * mesh = entireMesh();
674 :
675 99589536 : int id = local_elem_id * mesh->Np + local_node_id;
676 99589536 : Point p(mesh->x[id], mesh->y[id], mesh->z[id]);
677 : p *= scales.L_ref;
678 99589536 : return p;
679 : }
680 :
681 : Point
682 933120 : gllPointFace(int local_elem_id, int local_face_id, int local_node_id)
683 : {
684 933120 : mesh_t * mesh = entireMesh();
685 933120 : int face_id = mesh->EToB[local_elem_id * mesh->Nfaces + local_face_id];
686 933120 : int offset = local_elem_id * mesh->Nfaces * mesh->Nfp + local_face_id * mesh->Nfp;
687 933120 : int id = mesh->vmapM[offset + local_node_id];
688 933120 : Point p(mesh->x[id], mesh->y[id], mesh->z[id]);
689 : p *= scales.L_ref;
690 933120 : return p;
691 : }
692 :
693 : Point
694 234240 : centroidFace(int local_elem_id, int local_face_id)
695 : {
696 234240 : mesh_t * mesh = entireMesh();
697 :
698 : double x_c = 0.0;
699 : double y_c = 0.0;
700 : double z_c = 0.0;
701 : double mass = 0.0;
702 :
703 234240 : int offset = local_elem_id * mesh->Nfaces * mesh->Nfp + local_face_id * mesh->Nfp;
704 14150400 : for (int v = 0; v < mesh->Np; ++v)
705 : {
706 13916160 : int id = mesh->vmapM[offset + v];
707 13916160 : double mass_matrix = sgeo[mesh->Nsgeo * (offset + v) + WSJID];
708 13916160 : x_c += mesh->x[id] * mass_matrix;
709 13916160 : y_c += mesh->y[id] * mass_matrix;
710 13916160 : z_c += mesh->z[id] * mass_matrix;
711 13916160 : mass += mass_matrix;
712 : }
713 :
714 : Point c(x_c, y_c, z_c);
715 234240 : return c / mass * scales.L_ref;
716 : }
717 :
718 : Point
719 26997504 : centroid(int local_elem_id)
720 : {
721 26997504 : mesh_t * mesh = entireMesh();
722 :
723 : double x_c = 0.0;
724 : double y_c = 0.0;
725 : double z_c = 0.0;
726 : double mass = 0.0;
727 :
728 6192016128 : for (int v = 0; v < mesh->Np; ++v)
729 : {
730 6165018624 : int id = local_elem_id * mesh->Np + v;
731 6165018624 : double mass_matrix = vgeo[local_elem_id * mesh->Np * mesh->Nvgeo + JWID * mesh->Np + v];
732 6165018624 : x_c += mesh->x[id] * mass_matrix;
733 6165018624 : y_c += mesh->y[id] * mass_matrix;
734 6165018624 : z_c += mesh->z[id] * mass_matrix;
735 6165018624 : mass += mass_matrix;
736 : }
737 :
738 : Point c(x_c, y_c, z_c);
739 26997504 : return c / mass * scales.L_ref;
740 : }
741 :
742 : double
743 16250 : volume(const nek_mesh::NekMeshEnum pp_mesh)
744 : {
745 16250 : mesh_t * mesh = getMesh(pp_mesh);
746 16250 : double integral = 0.0;
747 :
748 3261198 : for (int k = 0; k < mesh->Nelements; ++k)
749 : {
750 3244948 : int offset = k * mesh->Np;
751 :
752 428948804 : for (int v = 0; v < mesh->Np; ++v)
753 425703856 : integral += vgeo[mesh->Nvgeo * offset + v + mesh->Np * JWID];
754 : }
755 :
756 : // sum across all processes
757 : double total_integral;
758 16250 : MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
759 :
760 16250 : total_integral *= scales.V_ref;
761 :
762 16250 : return total_integral;
763 : }
764 :
765 : void
766 44275 : dimensionalizeVolume(double & integral)
767 : {
768 44275 : integral *= scales.V_ref;
769 44275 : }
770 :
771 : void
772 196 : dimensionalizeArea(double & integral)
773 : {
774 196 : integral *= scales.A_ref;
775 196 : }
776 :
777 : void
778 24400 : dimensionalizeVolumeIntegral(const field::NekFieldEnum & integrand,
779 : const Real & volume,
780 : double & integral)
781 : {
782 : // dimensionalize the field if needed
783 24400 : integral *= nondimensionalDivisor(integrand);
784 :
785 : // scale the volume integral
786 24400 : integral *= scales.V_ref;
787 :
788 : // for quantities with a relative scaling, we need to add back the reference
789 : // contribution to the volume integral
790 24400 : integral += nondimensionalAdditive(integrand) * volume;
791 24400 : }
792 :
793 : void
794 196 : dimensionalizeSideIntegral(const field::NekFieldEnum & integrand,
795 : const Real & area,
796 : double & integral)
797 : {
798 : // dimensionalize the field if needed
799 196 : integral *= nondimensionalDivisor(integrand);
800 :
801 : // scale the boundary integral
802 196 : integral *= scales.A_ref;
803 :
804 : // for quantities with a relative scaling, we need to add back the reference
805 : // contribution to the side integral
806 196 : integral += nondimensionalAdditive(integrand) * area;
807 196 : }
808 :
809 : void
810 30864 : dimensionalizeSideIntegral(const field::NekFieldEnum & integrand,
811 : const std::vector<int> & boundary_id,
812 : double & integral,
813 : const nek_mesh::NekMeshEnum pp_mesh)
814 : {
815 : // dimensionalize the field if needed
816 30864 : integral *= nondimensionalDivisor(integrand);
817 :
818 : // scale the boundary integral
819 30864 : integral *= scales.A_ref;
820 :
821 : // for quantities with a relative scaling, we need to add back the reference
822 : // contribution to the side integral; we need this form here to avoid a recursive loop
823 30864 : auto add = nondimensionalAdditive(integrand);
824 30864 : if (std::abs(add) > 1e-8)
825 120 : integral += add * area(boundary_id, pp_mesh);
826 30864 : }
827 :
828 : double
829 9888 : volumeIntegral(const field::NekFieldEnum & integrand, const Real & volume,
830 : const nek_mesh::NekMeshEnum pp_mesh)
831 : {
832 9888 : mesh_t * mesh = getMesh(pp_mesh);
833 :
834 9888 : double integral = 0.0;
835 :
836 : double (*f)(int, int);
837 9888 : f = solutionPointer(integrand);
838 :
839 1795120 : for (int k = 0; k < mesh->Nelements; ++k)
840 : {
841 1785232 : int offset = k * mesh->Np;
842 :
843 263259328 : for (int v = 0; v < mesh->Np; ++v)
844 261474096 : integral += f(offset + v, 0 /* unused */) * vgeo[mesh->Nvgeo * offset + v + mesh->Np * JWID];
845 : }
846 :
847 : // sum across all processes
848 : double total_integral;
849 9888 : MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
850 :
851 9888 : dimensionalizeVolumeIntegral(integrand, volume, total_integral);
852 :
853 9888 : return total_integral;
854 : }
855 :
856 : double
857 13070 : area(const std::vector<int> & boundary_id, const nek_mesh::NekMeshEnum pp_mesh)
858 : {
859 13070 : mesh_t * mesh = getMesh(pp_mesh);
860 :
861 13070 : double integral = 0.0;
862 :
863 2026532 : for (int i = 0; i < mesh->Nelements; ++i)
864 : {
865 14094234 : for (int j = 0; j < mesh->Nfaces; ++j)
866 : {
867 12080772 : int face_id = mesh->EToB[i * mesh->Nfaces + j];
868 :
869 12080772 : if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
870 : {
871 364424 : int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
872 7277964 : for (int v = 0; v < mesh->Nfp; ++v)
873 : {
874 6913540 : integral += sgeo[mesh->Nsgeo * (offset + v) + WSJID];
875 : }
876 : }
877 : }
878 : }
879 :
880 : // sum across all processes
881 : double total_integral;
882 13070 : MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
883 :
884 13070 : dimensionalizeSideIntegral(field::unity, boundary_id, total_integral, pp_mesh);
885 :
886 13070 : return total_integral;
887 : }
888 :
889 : std::vector<dfloat>
890 12 : yPlus(const std::vector<int> & boundary_id, const unsigned int & index)
891 : {
892 12 : nrs_t * nrs = (nrs_t *)nekrs::nrsPtr();
893 :
894 : // compute the rate of strain tensor on device
895 12 : auto o_Sij = platform->o_memPool.reserve<dfloat>(2 * nrs->NVfields * nrs->fieldOffset);
896 12 : postProcessing::strainRate(nrs, true, nrs->o_U, o_Sij);
897 :
898 : // copy to host for evaluating wall-parallel stress
899 12 : dfloat * Sij = (dfloat *)calloc(o_Sij.size(), sizeof(dfloat));
900 12 : o_Sij.copyTo(Sij);
901 12 : o_Sij.free();
902 :
903 12 : double * wall_distance = (double *)nek::scPtr(index);
904 :
905 : // integrate over the boundaries in the mesh; each rank will compute contributions to the
906 : // x, y, and z components
907 12 : mesh_t * mesh = getMesh(nek_mesh::fluid);
908 :
909 : // TODO: This function only works correctly if the viscosity and rho is constant, because
910 : // otherwise we need to copy the viscosity and rho from device to host
911 : double mu;
912 12 : platform->options.getArgs("VISCOSITY", mu);
913 : double rho;
914 12 : platform->options.getArgs("DENSITY", rho);
915 12 : double nu = mu / rho;
916 :
917 12 : dfloat max_yp = -std::numeric_limits<float>::max();
918 12 : dfloat min_yp = std::numeric_limits<float>::max();
919 12 : dfloat avg_yp = 0.0;
920 12 : dfloat denom_yp = 0.0;
921 :
922 : std::vector<int> istride = {
923 12 : mesh->Nq * mesh->Nq, mesh->Nq, -1, -mesh->Nq, 1, -mesh->Nq * mesh->Nq};
924 :
925 1932 : for (int i = 0; i < mesh->Nelements; ++i)
926 : {
927 13440 : for (int j = 0; j < mesh->Nfaces; ++j)
928 : {
929 11520 : int face_id = mesh->EToB[i * mesh->Nfaces + j];
930 :
931 11520 : if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
932 : {
933 384 : int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
934 :
935 6528 : for (int v = 0; v < mesh->Nfp; ++v)
936 : {
937 6144 : int surf_offset = mesh->Nsgeo * (offset + v);
938 6144 : int vol_id = mesh->vmapM[offset + v];
939 6144 : dfloat sWJ = sgeo[surf_offset + WSJID];
940 6144 : dfloat scale = 2 * mu;
941 :
942 6144 : dfloat n1 = sgeo[surf_offset + NXID];
943 6144 : dfloat n2 = sgeo[surf_offset + NYID];
944 6144 : dfloat n3 = sgeo[surf_offset + NZID];
945 :
946 6144 : dfloat s11 = Sij[vol_id + 0 * nrs->fieldOffset];
947 6144 : dfloat s12 = Sij[vol_id + 3 * nrs->fieldOffset];
948 6144 : dfloat s13 = Sij[vol_id + 5 * nrs->fieldOffset];
949 : dfloat s21 = s12;
950 6144 : dfloat s22 = Sij[vol_id + 1 * nrs->fieldOffset];
951 6144 : dfloat s23 = Sij[vol_id + 4 * nrs->fieldOffset];
952 : dfloat s31 = s13;
953 : dfloat s32 = s23;
954 6144 : dfloat s33 = Sij[vol_id + 2 * nrs->fieldOffset];
955 :
956 : // tau_{ij}n_j - (tau_{jk} n_k n_j) * n_i
957 6144 : dfloat f1 = scale * (s11 * n1 + s12 * n2 + s13 * n3);
958 6144 : dfloat f2 = scale * (s21 * n1 + s22 * n2 + s23 * n3);
959 6144 : dfloat f3 = scale * (s31 * n1 + s32 * n2 + s33 * n3);
960 :
961 6144 : f1 -= f1 * n1 * n1;
962 6144 : f2 -= f2 * n2 * n2;
963 6144 : f3 -= f3 * n3 * n3;
964 :
965 6144 : dfloat tauw = sqrt(f1 * f1 + f2 * f2 + f3 * f3);
966 6144 : dfloat utau = sqrt(tauw / rho);
967 :
968 : // need to shift when evaluating the wall distance, because we want the wall distance
969 : // at the nearest node away from the face, not precisely on the face
970 6144 : dfloat wd = wall_distance[vol_id + istride[j]];
971 :
972 : // check that we are not at a corner
973 6144 : if (wd > 1e-8)
974 : {
975 6144 : dfloat yplus = wd * utau / nu;
976 6144 : max_yp = std::max(yplus, max_yp);
977 6144 : min_yp = std::min(yplus, min_yp);
978 6144 : avg_yp += yplus * sWJ;
979 6144 : denom_yp += sWJ;
980 : }
981 : }
982 : }
983 : }
984 : }
985 :
986 : // max across all processes
987 : double total_max_yp;
988 12 : MPI_Allreduce(&max_yp, &total_max_yp, 1, MPI_DOUBLE, MPI_MAX, platform->comm.mpiComm);
989 :
990 : // min across all processes
991 : double total_min_yp;
992 12 : MPI_Allreduce(&min_yp, &total_min_yp, 1, MPI_DOUBLE, MPI_MIN, platform->comm.mpiComm);
993 :
994 : // sum across all processes
995 : double total_avg_yp;
996 12 : MPI_Allreduce(&avg_yp, &total_avg_yp, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
997 :
998 12 : if (total_avg_yp == 0)
999 0 : mooseError("Failed to find any eligible points on boundaries for computing y+!");
1000 :
1001 : double total_denom_yp;
1002 12 : MPI_Allreduce(&denom_yp, &total_denom_yp, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
1003 :
1004 : // TODO: dimensionalize
1005 : // dimensionalizeSideIntegral(integrand, boundary_id, total_integral, pp_mesh);
1006 :
1007 : freePointer(Sij);
1008 24 : return {total_max_yp, total_min_yp, total_avg_yp / total_denom_yp};
1009 12 : }
1010 :
1011 : std::vector<dfloat>
1012 848 : viscousDrag(const std::vector<int> & boundary_id)
1013 : {
1014 848 : nrs_t * nrs = (nrs_t *)nekrs::nrsPtr();
1015 :
1016 : // compute the rate of strain tensor on device
1017 848 : auto o_Sij = platform->o_memPool.reserve<dfloat>(2 * nrs->NVfields * nrs->fieldOffset);
1018 848 : postProcessing::strainRate(nrs, true, nrs->o_U, o_Sij);
1019 :
1020 : // copy to host for evaluating drag
1021 848 : dfloat * Sij = (dfloat *)calloc(o_Sij.size(), sizeof(dfloat));
1022 848 : o_Sij.copyTo(Sij);
1023 848 : o_Sij.free();
1024 :
1025 : // integrate over the boundaries in the mesh; each rank will compute contributions to the
1026 : // x, y, and z components
1027 848 : mesh_t * mesh = getMesh(nek_mesh::fluid);
1028 :
1029 848 : double integral_x = 0.0;
1030 848 : double integral_y = 0.0;
1031 848 : double integral_z = 0.0;
1032 :
1033 : // TODO: This function only works correctly if the viscosity is constant, because
1034 : // otherwise we need to copy the viscosity from device to host
1035 : double mu;
1036 848 : platform->options.getArgs("VISCOSITY", mu);
1037 :
1038 51776 : for (int i = 0; i < mesh->Nelements; ++i)
1039 : {
1040 356496 : for (int j = 0; j < mesh->Nfaces; ++j)
1041 : {
1042 305568 : int face_id = mesh->EToB[i * mesh->Nfaces + j];
1043 :
1044 305568 : if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
1045 : {
1046 4840 : int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
1047 235080 : for (int v = 0; v < mesh->Nfp; ++v)
1048 : {
1049 230240 : int surf_offset = mesh->Nsgeo * (offset + v);
1050 230240 : int vol_id = mesh->vmapM[offset + v];
1051 230240 : dfloat sWJ = sgeo[surf_offset + WSJID];
1052 230240 : dfloat scale = -2 * mu * sWJ;
1053 :
1054 230240 : dfloat n1 = sgeo[surf_offset + NXID];
1055 230240 : dfloat n2 = sgeo[surf_offset + NYID];
1056 230240 : dfloat n3 = sgeo[surf_offset + NZID];
1057 :
1058 230240 : dfloat s11 = Sij[vol_id + 0 * nrs->fieldOffset];
1059 230240 : dfloat s12 = Sij[vol_id + 3 * nrs->fieldOffset];
1060 230240 : dfloat s13 = Sij[vol_id + 5 * nrs->fieldOffset];
1061 : dfloat s21 = s12;
1062 230240 : dfloat s22 = Sij[vol_id + 1 * nrs->fieldOffset];
1063 230240 : dfloat s23 = Sij[vol_id + 4 * nrs->fieldOffset];
1064 : dfloat s31 = s13;
1065 : dfloat s32 = s23;
1066 230240 : dfloat s33 = Sij[vol_id + 2 * nrs->fieldOffset];
1067 :
1068 230240 : dfloat dragx = scale * (s11 * n1 + s12 * n2 + s13 * n3);
1069 230240 : dfloat dragy = scale * (s21 * n1 + s22 * n2 + s23 * n3);
1070 230240 : dfloat dragz = scale * (s31 * n1 + s32 * n2 + s33 * n3);
1071 :
1072 230240 : integral_x += dragx;
1073 230240 : integral_y += dragy;
1074 230240 : integral_z += dragz;
1075 : }
1076 : }
1077 : }
1078 : }
1079 :
1080 : // sum across all processes
1081 : double total_integral_x;
1082 848 : MPI_Allreduce(&integral_x, &total_integral_x, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
1083 :
1084 : double total_integral_y;
1085 848 : MPI_Allreduce(&integral_y, &total_integral_y, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
1086 :
1087 : double total_integral_z;
1088 848 : MPI_Allreduce(&integral_z, &total_integral_z, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
1089 :
1090 : // TODO: dimensionalize
1091 : // dimensionalizeSideIntegral(integrand, boundary_id, total_integral, pp_mesh);
1092 :
1093 : freePointer(Sij);
1094 1696 : return {total_integral_x, total_integral_y, total_integral_z};
1095 848 : }
1096 :
1097 : double
1098 17759 : sideIntegral(const std::vector<int> & boundary_id, const field::NekFieldEnum & integrand,
1099 : const nek_mesh::NekMeshEnum pp_mesh)
1100 : {
1101 17759 : mesh_t * mesh = getMesh(pp_mesh);
1102 :
1103 17758 : double integral = 0.0;
1104 :
1105 : double (*f)(int, int);
1106 17758 : f = solutionPointer(integrand);
1107 :
1108 2522262 : for (int i = 0; i < mesh->Nelements; ++i)
1109 : {
1110 17531528 : for (int j = 0; j < mesh->Nfaces; ++j)
1111 : {
1112 15027024 : int face_id = mesh->EToB[i * mesh->Nfaces + j];
1113 :
1114 15027024 : if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
1115 : {
1116 505080 : int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
1117 9982212 : for (int v = 0; v < mesh->Nfp; ++v)
1118 : {
1119 9477132 : integral +=
1120 9477132 : f(mesh->vmapM[offset + v], 0 /* unused */) * sgeo[mesh->Nsgeo * (offset + v) + WSJID];
1121 : }
1122 : }
1123 : }
1124 : }
1125 :
1126 : // sum across all processes
1127 : double total_integral;
1128 17758 : MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
1129 :
1130 17758 : dimensionalizeSideIntegral(integrand, boundary_id, total_integral, pp_mesh);
1131 :
1132 17758 : return total_integral;
1133 : }
1134 :
1135 : double
1136 350 : massFlowrate(const std::vector<int> & boundary_id, const nek_mesh::NekMeshEnum pp_mesh)
1137 : {
1138 350 : mesh_t * mesh = getMesh(pp_mesh);
1139 350 : nrs_t * nrs = (nrs_t *)nrsPtr();
1140 :
1141 : // TODO: This function only works correctly if the density is constant, because
1142 : // otherwise we need to copy the density from device to host
1143 : double rho;
1144 350 : platform->options.getArgs("DENSITY", rho);
1145 :
1146 350 : double integral = 0.0;
1147 :
1148 139694 : for (int i = 0; i < mesh->Nelements; ++i)
1149 : {
1150 975408 : for (int j = 0; j < mesh->Nfaces; ++j)
1151 : {
1152 836064 : int face_id = mesh->EToB[i * mesh->Nfaces + j];
1153 :
1154 836064 : if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
1155 : {
1156 7810 : int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
1157 228040 : for (int v = 0; v < mesh->Nfp; ++v)
1158 : {
1159 220230 : int vol_id = mesh->vmapM[offset + v];
1160 220230 : int surf_offset = mesh->Nsgeo * (offset + v);
1161 :
1162 : double normal_velocity =
1163 220230 : nrs->U[vol_id + 0 * velocityFieldOffset()] * sgeo[surf_offset + NXID] +
1164 220230 : nrs->U[vol_id + 1 * velocityFieldOffset()] * sgeo[surf_offset + NYID] +
1165 220230 : nrs->U[vol_id + 2 * velocityFieldOffset()] * sgeo[surf_offset + NZID];
1166 :
1167 220230 : integral += rho * normal_velocity * sgeo[surf_offset + WSJID];
1168 : }
1169 : }
1170 : }
1171 : }
1172 :
1173 : // sum across all processes
1174 : double total_integral;
1175 350 : MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
1176 :
1177 : // dimensionalize the mass flux and area
1178 350 : total_integral *= scales.rho_ref * scales.U_ref * scales.A_ref;
1179 :
1180 350 : return total_integral;
1181 : }
1182 :
1183 : double
1184 692 : sideMassFluxWeightedIntegral(const std::vector<int> & boundary_id,
1185 : const field::NekFieldEnum & integrand,
1186 : const nek_mesh::NekMeshEnum pp_mesh)
1187 : {
1188 692 : mesh_t * mesh = getMesh(pp_mesh);
1189 692 : nrs_t * nrs = (nrs_t *)nrsPtr();
1190 :
1191 : // TODO: This function only works correctly if the density is constant, because
1192 : // otherwise we need to copy the density from device to host
1193 : double rho;
1194 692 : platform->options.getArgs("DENSITY", rho);
1195 :
1196 692 : double integral = 0.0;
1197 :
1198 : double (*f)(int, int);
1199 692 : f = solutionPointer(integrand);
1200 :
1201 186548 : for (int i = 0; i < mesh->Nelements; ++i)
1202 : {
1203 1300992 : for (int j = 0; j < mesh->Nfaces; ++j)
1204 : {
1205 1115136 : int face_id = mesh->EToB[i * mesh->Nfaces + j];
1206 :
1207 1115136 : if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
1208 : {
1209 11982 : int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
1210 402150 : for (int v = 0; v < mesh->Nfp; ++v)
1211 : {
1212 390168 : int vol_id = mesh->vmapM[offset + v];
1213 390168 : int surf_offset = mesh->Nsgeo * (offset + v);
1214 : double normal_velocity =
1215 390168 : nrs->U[vol_id + 0 * velocityFieldOffset()] * sgeo[surf_offset + NXID] +
1216 390168 : nrs->U[vol_id + 1 * velocityFieldOffset()] * sgeo[surf_offset + NYID] +
1217 390168 : nrs->U[vol_id + 2 * velocityFieldOffset()] * sgeo[surf_offset + NZID];
1218 390168 : integral += f(vol_id, 0 /* unused */) * rho * normal_velocity * sgeo[surf_offset + WSJID];
1219 : }
1220 : }
1221 : }
1222 : }
1223 :
1224 : // sum across all processes
1225 : double total_integral;
1226 692 : MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
1227 :
1228 : // dimensionalize the field if needed
1229 692 : total_integral *= nondimensionalDivisor(integrand);
1230 :
1231 : // dimensionalize the mass flux and area
1232 692 : total_integral *= scales.rho_ref * scales.U_ref * scales.A_ref;
1233 :
1234 : // for quantities with a relative scaling, we need to add back the reference
1235 : // contribution to the mass flux integral; we need this form here to avoid an infinite
1236 : // recursive loop
1237 692 : auto add = nondimensionalAdditive(integrand);
1238 692 : if (std::abs(add) > 1e-8)
1239 120 : total_integral += add * massFlowrate(boundary_id, pp_mesh);
1240 :
1241 692 : return total_integral;
1242 : }
1243 :
1244 : double
1245 36 : pressureSurfaceForce(const std::vector<int> & boundary_id, const Point & direction, const nek_mesh::NekMeshEnum pp_mesh)
1246 : {
1247 36 : mesh_t * mesh = getMesh(pp_mesh);
1248 36 : nrs_t * nrs = (nrs_t *)nrsPtr();
1249 :
1250 36 : double integral = 0.0;
1251 :
1252 20232 : for (int i = 0; i < mesh->Nelements; ++i)
1253 : {
1254 141372 : for (int j = 0; j < mesh->Nfaces; ++j)
1255 : {
1256 121176 : int face_id = mesh->EToB[i * mesh->Nfaces + j];
1257 :
1258 121176 : if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
1259 : {
1260 12780 : int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
1261 472860 : for (int v = 0; v < mesh->Nfp; ++v)
1262 : {
1263 460080 : int vol_id = mesh->vmapM[offset + v];
1264 460080 : int surf_offset = mesh->Nsgeo * (offset + v);
1265 :
1266 460080 : double p_normal = nrs->P[vol_id] * (sgeo[surf_offset + NXID] * direction(0) +
1267 460080 : sgeo[surf_offset + NYID] * direction(1) +
1268 460080 : sgeo[surf_offset + NZID] * direction(2));
1269 :
1270 460080 : integral += p_normal * sgeo[surf_offset + WSJID];
1271 : }
1272 : }
1273 : }
1274 : }
1275 :
1276 : // sum across all processes
1277 : double total_integral;
1278 36 : MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
1279 :
1280 36 : dimensionalizeSideIntegral(field::pressure, boundary_id, total_integral, pp_mesh);
1281 :
1282 36 : return total_integral;
1283 : }
1284 :
1285 : double
1286 13416 : heatFluxIntegral(const std::vector<int> & boundary_id, const nek_mesh::NekMeshEnum pp_mesh)
1287 : {
1288 13416 : mesh_t * mesh = getMesh(pp_mesh);
1289 13416 : nrs_t * nrs = (nrs_t *)nrsPtr();
1290 :
1291 : // TODO: This function only works correctly if the conductivity is constant, because
1292 : // otherwise we need to copy the conductivity from device to host
1293 : double k;
1294 13416 : platform->options.getArgs("SCALAR00 DIFFUSIVITY", k);
1295 :
1296 13416 : double integral = 0.0;
1297 13416 : double * grad_T = (double *)calloc(3 * mesh->Np, sizeof(double));
1298 :
1299 2946344 : for (int i = 0; i < mesh->Nelements; ++i)
1300 : {
1301 20530496 : for (int j = 0; j < mesh->Nfaces; ++j)
1302 : {
1303 17597568 : int face_id = mesh->EToB[i * mesh->Nfaces + j];
1304 :
1305 17597568 : if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
1306 : {
1307 : // some inefficiency if an element has more than one face on the sideset of interest,
1308 : // because we will recompute the gradient in the element more than one time - but this
1309 : // is of little practical interest because this will be a minority of cases.
1310 216420 : gradient(mesh->Np, i, nrs->cds->S, grad_T, pp_mesh);
1311 :
1312 216420 : int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
1313 5007924 : for (int v = 0; v < mesh->Nfp; ++v)
1314 : {
1315 : // special use of vol_id only when calling gradient(...), since we have written this
1316 : // function internally here so that it computes the gradient in a given element (as
1317 : // opposed to doing so for all elements at once, like in NekRS's
1318 : // gradientVolumeHex3D kernel
1319 4791504 : int vol_id = mesh->vmapM[offset + v] - i * mesh->Np;
1320 4791504 : int surf_offset = mesh->Nsgeo * (offset + v);
1321 :
1322 4791504 : double normal_grad_T = grad_T[vol_id + 0 * mesh->Np] * sgeo[surf_offset + NXID] +
1323 4791504 : grad_T[vol_id + 1 * mesh->Np] * sgeo[surf_offset + NYID] +
1324 4791504 : grad_T[vol_id + 2 * mesh->Np] * sgeo[surf_offset + NZID];
1325 :
1326 4791504 : integral += -k * normal_grad_T * sgeo[surf_offset + WSJID];
1327 : }
1328 : }
1329 : }
1330 : }
1331 :
1332 : freePointer(grad_T);
1333 :
1334 : // sum across all processes
1335 : double total_integral;
1336 13416 : MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
1337 :
1338 : // multiply by the reference heat flux and an area factor to dimensionalize
1339 13416 : total_integral *= scales.flux_ref * scales.A_ref;
1340 :
1341 13416 : return total_integral;
1342 : }
1343 :
1344 : void
1345 220452 : gradient(const int offset,
1346 : const int e,
1347 : const double * f,
1348 : double * grad_f,
1349 : const nek_mesh::NekMeshEnum pp_mesh)
1350 : {
1351 220452 : mesh_t * mesh = getMesh(pp_mesh);
1352 :
1353 1218324 : for (int k = 0; k < mesh->Nq; ++k)
1354 : {
1355 5807424 : for (int j = 0; j < mesh->Nq; ++j)
1356 : {
1357 30019584 : for (int i = 0; i < mesh->Nq; ++i)
1358 : {
1359 25210032 : const int gid = e * mesh->Np * mesh->Nvgeo + k * mesh->Nq * mesh->Nq + j * mesh->Nq + i;
1360 25210032 : const double drdx = vgeo[gid + RXID * mesh->Np];
1361 25210032 : const double drdy = vgeo[gid + RYID * mesh->Np];
1362 25210032 : const double drdz = vgeo[gid + RZID * mesh->Np];
1363 25210032 : const double dsdx = vgeo[gid + SXID * mesh->Np];
1364 25210032 : const double dsdy = vgeo[gid + SYID * mesh->Np];
1365 25210032 : const double dsdz = vgeo[gid + SZID * mesh->Np];
1366 25210032 : const double dtdx = vgeo[gid + TXID * mesh->Np];
1367 25210032 : const double dtdy = vgeo[gid + TYID * mesh->Np];
1368 25210032 : const double dtdz = vgeo[gid + TZID * mesh->Np];
1369 :
1370 : // compute 'r' and 's' derivatives of (q_m) at node n
1371 : double dpdr = 0.f, dpds = 0.f, dpdt = 0.f;
1372 :
1373 172864224 : for (int n = 0; n < mesh->Nq; ++n)
1374 : {
1375 147654192 : const double Dr = mesh->D[i * mesh->Nq + n];
1376 147654192 : const double Ds = mesh->D[j * mesh->Nq + n];
1377 147654192 : const double Dt = mesh->D[k * mesh->Nq + n];
1378 :
1379 147654192 : dpdr += Dr * f[e * mesh->Np + k * mesh->Nq * mesh->Nq + j * mesh->Nq + n];
1380 147654192 : dpds += Ds * f[e * mesh->Np + k * mesh->Nq * mesh->Nq + n * mesh->Nq + i];
1381 147654192 : dpdt += Dt * f[e * mesh->Np + n * mesh->Nq * mesh->Nq + j * mesh->Nq + i];
1382 : }
1383 :
1384 25210032 : const int id = k * mesh->Nq * mesh->Nq + j * mesh->Nq + i;
1385 25210032 : grad_f[id + 0 * offset] = drdx * dpdr + dsdx * dpds + dtdx * dpdt;
1386 25210032 : grad_f[id + 1 * offset] = drdy * dpdr + dsdy * dpds + dtdy * dpdt;
1387 25210032 : grad_f[id + 2 * offset] = drdz * dpdr + dsdz * dpds + dtdz * dpdt;
1388 : }
1389 : }
1390 : }
1391 220452 : }
1392 :
1393 : bool
1394 201 : isHeatFluxBoundary(const int boundary)
1395 : {
1396 : // the heat flux boundary is now named 'codedFixedGradient', but 'fixedGradient'
1397 : // will be present for backwards compatibility
1398 402 : return (bcMap::text(boundary, "scalar00") == "fixedGradient") ||
1399 804 : (bcMap::text(boundary, "scalar00") == "codedFixedGradient");
1400 : }
1401 :
1402 : bool
1403 17 : isMovingMeshBoundary(const int boundary)
1404 : {
1405 34 : return bcMap::text(boundary, "mesh") == "codedFixedValue";
1406 : }
1407 :
1408 : bool
1409 0 : isTemperatureBoundary(const int boundary)
1410 : {
1411 0 : return bcMap::text(boundary, "scalar00") == "fixedValue";
1412 : }
1413 :
1414 : const std::string
1415 1 : temperatureBoundaryType(const int boundary)
1416 : {
1417 2 : return bcMap::text(boundary, "scalar00");
1418 : }
1419 :
1420 : int
1421 1605 : polynomialOrder()
1422 : {
1423 1605 : return entireMesh()->N;
1424 : }
1425 :
1426 : int
1427 807 : Nelements()
1428 : {
1429 807 : int n_local = entireMesh()->Nelements;
1430 : int n_global;
1431 807 : MPI_Allreduce(&n_local, &n_global, 1, MPI_INT, MPI_SUM, platform->comm.mpiComm);
1432 807 : return n_global;
1433 : }
1434 :
1435 : int
1436 9047269 : Nfaces()
1437 : {
1438 9047269 : return entireMesh()->Nfaces;
1439 : }
1440 :
1441 : int
1442 808 : dim()
1443 : {
1444 808 : return entireMesh()->dim;
1445 : }
1446 :
1447 : int
1448 0 : NfaceVertices()
1449 : {
1450 0 : return entireMesh()->NfaceVertices;
1451 : }
1452 :
1453 : int
1454 807 : NboundaryFaces()
1455 : {
1456 807 : return entireMesh()->NboundaryFaces;
1457 : }
1458 :
1459 : int
1460 7341 : NboundaryID()
1461 : {
1462 7341 : if (entireMesh()->cht)
1463 182 : return nekData.NboundaryIDt;
1464 : else
1465 7159 : return nekData.NboundaryID;
1466 : }
1467 :
1468 : bool
1469 2553 : validBoundaryIDs(const std::vector<int> & boundary_id, int & first_invalid_id, int & n_boundaries)
1470 : {
1471 2553 : n_boundaries = NboundaryID();
1472 :
1473 : bool valid_boundary_ids = true;
1474 5893 : for (const auto & b : boundary_id)
1475 : {
1476 3340 : if ((b > n_boundaries) || (b <= 0))
1477 : {
1478 3 : first_invalid_id = b;
1479 : valid_boundary_ids = false;
1480 : }
1481 : }
1482 :
1483 2553 : return valid_boundary_ids;
1484 : }
1485 :
1486 : double
1487 35440 : get_scalar01(const int id, const int surf_offset = 0)
1488 : {
1489 35440 : nrs_t * nrs = (nrs_t *)nrsPtr();
1490 35440 : return nrs->cds->S[id + 1 * scalarFieldOffset()];
1491 : }
1492 :
1493 : double
1494 61552 : get_scalar02(const int id, const int surf_offset = 0)
1495 : {
1496 61552 : nrs_t * nrs = (nrs_t *)nrsPtr();
1497 61552 : return nrs->cds->S[id + 2 * scalarFieldOffset()];
1498 : }
1499 :
1500 : double
1501 26224 : get_scalar03(const int id, const int surf_offset = 0)
1502 : {
1503 26224 : nrs_t * nrs = (nrs_t *)nrsPtr();
1504 26224 : return nrs->cds->S[id + 3 * scalarFieldOffset()];
1505 : }
1506 :
1507 : double
1508 505472 : get_usrwrk00(const int id, const int surf_offset = 0)
1509 : {
1510 505472 : nrs_t * nrs = (nrs_t *)nrsPtr();
1511 505472 : return nrs->usrwrk[id];
1512 : }
1513 :
1514 : double
1515 20672 : get_usrwrk01(const int id, const int surf_offset = 0)
1516 : {
1517 20672 : nrs_t * nrs = (nrs_t *)nrsPtr();
1518 20672 : return nrs->usrwrk[id + nrs->fieldOffset];
1519 : }
1520 :
1521 : double
1522 20672 : get_usrwrk02(const int id, const int surf_offset = 0)
1523 : {
1524 20672 : nrs_t * nrs = (nrs_t *)nrsPtr();
1525 20672 : return nrs->usrwrk[id + 2 * nrs->fieldOffset];
1526 : }
1527 :
1528 : double
1529 1233561952 : get_temperature(const int id, const int surf_offset)
1530 : {
1531 1233561952 : nrs_t * nrs = (nrs_t *)nrsPtr();
1532 1233561952 : return nrs->cds->S[id];
1533 : }
1534 :
1535 : double
1536 4032 : get_flux(const int id, const int surf_offset)
1537 : {
1538 : // TODO: this function does not support non-constant thermal conductivity
1539 : double k;
1540 4032 : platform->options.getArgs("SCALAR00 DIFFUSIVITY", k);
1541 :
1542 4032 : nrs_t * nrs = (nrs_t *)nrsPtr();
1543 :
1544 : // this call of nek_mesh::all should be fine because flux is not a 'field' which can be
1545 : // provided to the postprocessors which have the option to operate only on part of the mesh
1546 4032 : auto mesh = getMesh(nek_mesh::all);
1547 4032 : int elem_id = id / mesh->Np;
1548 4032 : int vertex_id = id % mesh->Np;
1549 :
1550 : // This function is slightly inefficient, because we compute grad(T) for all nodes in
1551 : // an element even though we only call this function for one node at a time
1552 4032 : double * grad_T = (double *)calloc(3 * mesh->Np, sizeof(double));
1553 4032 : gradient(mesh->Np, elem_id, nrs->cds->S, grad_T, nek_mesh::all);
1554 :
1555 4032 : double normal_grad_T = grad_T[vertex_id + 0 * mesh->Np] * sgeo[surf_offset + NXID] +
1556 4032 : grad_T[vertex_id + 1 * mesh->Np] * sgeo[surf_offset + NYID] +
1557 4032 : grad_T[vertex_id + 2 * mesh->Np] * sgeo[surf_offset + NZID];
1558 : freePointer(grad_T);
1559 :
1560 4032 : return -k * normal_grad_T;
1561 : }
1562 :
1563 : double
1564 134030256 : get_pressure(const int id, const int surf_offset)
1565 : {
1566 134030256 : nrs_t * nrs = (nrs_t *)nrsPtr();
1567 134030256 : return nrs->P[id];
1568 : }
1569 :
1570 : double
1571 55253552 : get_unity(const int /* id */, const int surf_offset)
1572 : {
1573 55253552 : return 1.0;
1574 : }
1575 :
1576 : double
1577 128478784 : get_velocity_x(const int id, const int surf_offset)
1578 : {
1579 128478784 : nrs_t * nrs = (nrs_t *)nrsPtr();
1580 128478784 : return nrs->U[id + 0 * nrs->fieldOffset];
1581 : }
1582 :
1583 : double
1584 65809984 : get_velocity_y(const int id, const int surf_offset)
1585 : {
1586 65809984 : nrs_t * nrs = (nrs_t *)nrsPtr();
1587 65809984 : return nrs->U[id + 1 * nrs->fieldOffset];
1588 : }
1589 :
1590 : double
1591 45871728 : get_velocity_z(const int id, const int surf_offset)
1592 : {
1593 45871728 : nrs_t * nrs = (nrs_t *)nrsPtr();
1594 45871728 : return nrs->U[id + 2 * nrs->fieldOffset];
1595 : }
1596 :
1597 : double
1598 2027844 : get_velocity(const int id, const int surf_offset)
1599 : {
1600 2027844 : nrs_t * nrs = (nrs_t *)nrsPtr();
1601 2027844 : int offset = nrs->fieldOffset;
1602 :
1603 2027844 : return std::sqrt(nrs->U[id + 0 * offset] * nrs->U[id + 0 * offset] +
1604 2027844 : nrs->U[id + 1 * offset] * nrs->U[id + 1 * offset] +
1605 2027844 : nrs->U[id + 2 * offset] * nrs->U[id + 2 * offset]);
1606 : }
1607 :
1608 : double
1609 47744 : get_velocity_x_squared(const int id, const int surf_offset)
1610 : {
1611 47744 : return std::pow(get_velocity_x(id, surf_offset), 2);
1612 : }
1613 :
1614 : double
1615 47744 : get_velocity_y_squared(const int id, const int surf_offset)
1616 : {
1617 47744 : return std::pow(get_velocity_y(id, surf_offset), 2);
1618 : }
1619 :
1620 : double
1621 52912 : get_velocity_z_squared(const int id, const int surf_offset)
1622 : {
1623 52912 : return std::pow(get_velocity_z(id, surf_offset), 2);
1624 : }
1625 :
1626 : void
1627 244 : checkFieldValidity(const field::NekWriteEnum & field)
1628 : {
1629 244 : switch (field)
1630 : {
1631 244 : case field::flux:
1632 244 : if (!hasTemperatureVariable())
1633 0 : mooseError("Cannot get NekRS heat flux "
1634 : "because your Nek case files do not have a temperature variable!");
1635 : break;
1636 0 : case field::heat_source:
1637 0 : if (!hasTemperatureVariable())
1638 0 : mooseError("Cannot get NekRS heat source "
1639 : "because your Nek case files do not have a temperature variable!");
1640 : break;
1641 : case field::x_displacement:
1642 : case field::y_displacement:
1643 : case field::z_displacement:
1644 : case field::mesh_velocity_x:
1645 : case field::mesh_velocity_y:
1646 : case field::mesh_velocity_z:
1647 : break;
1648 0 : default:
1649 0 : mooseError("Unhandled NekWriteEnum in checkFieldValidity!");
1650 : }
1651 244 : }
1652 :
1653 : void
1654 189430 : checkFieldValidity(const field::NekFieldEnum & field)
1655 : {
1656 : // by placing this check here, as opposed to inside the NekFieldInterface,
1657 : // we can also leverage this error checking for the 'outputs' of NekRSProblem,
1658 : // which does not inherit from NekFieldInterface but still accesses the solutionPointers.
1659 : // If this gets moved elsewhere, need to be sure to add dedicated testing for
1660 : // the 'outputs' on NekRSProblem.
1661 :
1662 : // TODO: would be nice for NekRSProblem to only access field information via the
1663 : // NekFieldInterface; refactor later
1664 :
1665 189430 : switch (field)
1666 : {
1667 83860 : case field::temperature:
1668 83860 : if (!hasTemperatureVariable())
1669 1 : mooseError("Cannot find 'temperature' "
1670 : "because your Nek case files do not have a temperature variable!");
1671 : break;
1672 85 : case field::scalar01:
1673 85 : if (!hasScalarVariable(1))
1674 1 : mooseError("Cannot find 'scalar01' "
1675 : "because your Nek case files do not have a scalar01 variable!");
1676 : break;
1677 201 : case field::scalar02:
1678 201 : if (!hasScalarVariable(2))
1679 1 : mooseError("Cannot find 'scalar02' "
1680 : "because your Nek case files do not have a scalar02 variable!");
1681 : break;
1682 57 : case field::scalar03:
1683 57 : if (!hasScalarVariable(3))
1684 1 : mooseError("Cannot find 'scalar03' "
1685 : "because your Nek case files do not have a scalar03 variable!");
1686 : break;
1687 454 : case field::usrwrk00:
1688 454 : if (n_usrwrk_slots < 1)
1689 2 : mooseError("Cannot find 'usrwrk00' because you have only allocated 'n_usrwrk_slots = " +
1690 1 : std::to_string(n_usrwrk_slots) + "'");
1691 : break;
1692 42 : case field::usrwrk01:
1693 42 : if (n_usrwrk_slots < 2)
1694 2 : mooseError("Cannot find 'usrwrk01' because you have only allocated 'n_usrwrk_slots = " +
1695 1 : std::to_string(n_usrwrk_slots) + "'");
1696 : break;
1697 42 : case field::usrwrk02:
1698 42 : if (n_usrwrk_slots < 3)
1699 2 : mooseError("Cannot find 'usrwrk02' because you have only allocated 'n_usrwrk_slots = " +
1700 1 : std::to_string(n_usrwrk_slots) + "'");
1701 : break;
1702 : }
1703 189423 : }
1704 :
1705 244 : double (*solutionPointer(const field::NekWriteEnum & field))(int, int)
1706 : {
1707 : double (*f)(int, int);
1708 :
1709 244 : checkFieldValidity(field);
1710 :
1711 244 : switch (field)
1712 : {
1713 244 : case field::flux:
1714 : f = &get_flux;
1715 : break;
1716 0 : default:
1717 0 : mooseError("Unhandled NekWriteEnum in solutionPointer!");
1718 : }
1719 :
1720 244 : return f;
1721 : }
1722 :
1723 186212 : double (*solutionPointer(const field::NekFieldEnum & field))(int, int)
1724 : {
1725 : // we include this here as well, in addition to within the NekFieldInterface, because
1726 : // the NekRSProblem accesses these methods without inheriting from NekFieldInterface
1727 186212 : checkFieldValidity(field);
1728 :
1729 : double (*f)(int, int);
1730 :
1731 186212 : switch (field)
1732 : {
1733 : case field::velocity_x:
1734 : f = &get_velocity_x;
1735 : break;
1736 17796 : case field::velocity_y:
1737 : f = &get_velocity_y;
1738 17796 : break;
1739 14808 : case field::velocity_z:
1740 : f = &get_velocity_z;
1741 14808 : break;
1742 196 : case field::velocity:
1743 : f = &get_velocity;
1744 196 : break;
1745 0 : case field::velocity_component:
1746 0 : mooseError("The 'velocity_component' field is not compatible with the solutionPointer "
1747 : "interface!");
1748 : break;
1749 48 : case field::velocity_x_squared:
1750 : f = &get_velocity_x_squared;
1751 48 : break;
1752 48 : case field::velocity_y_squared:
1753 : f = &get_velocity_y_squared;
1754 48 : break;
1755 52 : case field::velocity_z_squared:
1756 : f = &get_velocity_z_squared;
1757 52 : break;
1758 82736 : case field::temperature:
1759 : f = &get_temperature;
1760 82736 : break;
1761 30416 : case field::pressure:
1762 : f = &get_pressure;
1763 30416 : break;
1764 48 : case field::scalar01:
1765 : f = &get_scalar01;
1766 48 : break;
1767 176 : case field::scalar02:
1768 : f = &get_scalar02;
1769 176 : break;
1770 32 : case field::scalar03:
1771 : f = &get_scalar03;
1772 32 : break;
1773 8108 : case field::unity:
1774 : f = &get_unity;
1775 8108 : break;
1776 420 : case field::usrwrk00:
1777 : f = &get_usrwrk00;
1778 420 : break;
1779 16 : case field::usrwrk01:
1780 : f = &get_usrwrk01;
1781 16 : break;
1782 16 : case field::usrwrk02:
1783 : f = &get_usrwrk02;
1784 16 : break;
1785 0 : default:
1786 0 : throw std::runtime_error("Unhandled 'NekFieldEnum'!");
1787 : }
1788 :
1789 186212 : return f;
1790 : }
1791 :
1792 : void
1793 129 : initializeDimensionalScales(const double U,
1794 : const double T,
1795 : const double dT,
1796 : const double L,
1797 : const double rho,
1798 : const double Cp,
1799 : const double s01,
1800 : const double ds01,
1801 : const double s02,
1802 : const double ds02,
1803 : const double s03,
1804 : const double ds03)
1805 : {
1806 129 : scales.U_ref = U;
1807 129 : scales.T_ref = T;
1808 129 : scales.dT_ref = dT;
1809 129 : scales.L_ref = L;
1810 129 : scales.A_ref = L * L;
1811 129 : scales.V_ref = L * L * L;
1812 129 : scales.rho_ref = rho;
1813 129 : scales.Cp_ref = Cp;
1814 129 : scales.t_ref = L / U;
1815 129 : scales.P_ref = rho * U * U;
1816 :
1817 129 : scales.s01_ref = s01;
1818 129 : scales.ds01_ref = ds01;
1819 129 : scales.s02_ref = s02;
1820 129 : scales.ds02_ref = ds02;
1821 129 : scales.s03_ref = s03;
1822 129 : scales.ds03_ref = ds03;
1823 :
1824 129 : scales.flux_ref = rho * U * Cp * dT;
1825 129 : scales.source_ref = scales.flux_ref / L;
1826 129 : }
1827 :
1828 : double
1829 433 : referenceLength()
1830 : {
1831 433 : return scales.L_ref;
1832 : }
1833 :
1834 : double
1835 276488 : referenceTime()
1836 : {
1837 276488 : return scales.t_ref;
1838 : }
1839 :
1840 : double
1841 241 : referenceArea()
1842 : {
1843 241 : return scales.A_ref;
1844 : }
1845 :
1846 : double
1847 982 : referenceVolume()
1848 : {
1849 982 : return scales.V_ref;
1850 : }
1851 :
1852 : Real
1853 51191106 : nondimensionalAdditive(const field::NekFieldEnum & field)
1854 : {
1855 51191106 : switch (field)
1856 : {
1857 26472776 : case field::temperature:
1858 26472776 : return scales.T_ref;
1859 8660 : case field::scalar01:
1860 8660 : return scales.s01_ref;
1861 40904 : case field::scalar02:
1862 40904 : return scales.s02_ref;
1863 5576 : case field::scalar03:
1864 5576 : return scales.s03_ref;
1865 : default:
1866 : return 0;
1867 : }
1868 : }
1869 :
1870 : Real
1871 18964 : nondimensionalAdditive(const field::NekWriteEnum & field)
1872 : {
1873 18964 : switch (field)
1874 : {
1875 18964 : case field::flux:
1876 : case field::heat_source:
1877 : case field::x_displacement:
1878 : case field::y_displacement:
1879 : case field::z_displacement:
1880 : case field::mesh_velocity_x:
1881 : case field::mesh_velocity_y:
1882 : case field::mesh_velocity_z:
1883 18964 : return 0.0;
1884 0 : default:
1885 0 : mooseError("Unhandled NekWriteEnum in nondimensionalAdditive!");
1886 : }
1887 : }
1888 :
1889 : Real
1890 46821 : nondimensionalDivisor(const field::NekWriteEnum & field)
1891 : {
1892 46821 : switch (field)
1893 : {
1894 42705 : case field::flux:
1895 42705 : return scales.flux_ref;
1896 3167 : case field::heat_source:
1897 3167 : return scales.source_ref;
1898 904 : case field::x_displacement:
1899 : case field::y_displacement:
1900 : case field::z_displacement:
1901 904 : return scales.L_ref;
1902 45 : case field::mesh_velocity_x:
1903 : case field::mesh_velocity_y:
1904 : case field::mesh_velocity_z:
1905 45 : return scales.U_ref;
1906 0 : default:
1907 0 : mooseError("Unhandled NekWriteEnum in nondimensionalDivisor!");
1908 : }
1909 : }
1910 :
1911 : Real
1912 51728832 : nondimensionalDivisor(const field::NekFieldEnum & field)
1913 : {
1914 51728832 : switch (field)
1915 : {
1916 19106132 : case field::velocity_x:
1917 : case field::velocity_y:
1918 : case field::velocity_z:
1919 : case field::velocity:
1920 : case field::velocity_component:
1921 19106132 : return scales.U_ref;
1922 5336 : case field::velocity_x_squared:
1923 : case field::velocity_y_squared:
1924 : case field::velocity_z_squared:
1925 5336 : return scales.U_ref * scales.U_ref;
1926 26472776 : case field::temperature:
1927 26472776 : return scales.dT_ref;
1928 6065815 : case field::pressure:
1929 6065815 : return scales.P_ref;
1930 8660 : case field::scalar01:
1931 8660 : return scales.ds01_ref;
1932 40904 : case field::scalar02:
1933 40904 : return scales.ds02_ref;
1934 5576 : case field::scalar03:
1935 5576 : return scales.ds03_ref;
1936 : case field::unity:
1937 : // no dimensionalization needed
1938 : return 1.0;
1939 445 : case field::usrwrk00:
1940 445 : return scratchUnits(0);
1941 25 : case field::usrwrk01:
1942 25 : return scratchUnits(1);
1943 25 : case field::usrwrk02:
1944 25 : return scratchUnits(2);
1945 0 : default:
1946 0 : throw std::runtime_error("Unhandled 'NekFieldEnum'!");
1947 : }
1948 : }
1949 :
1950 : Real
1951 495 : scratchUnits(const int slot)
1952 : {
1953 : // if (indices.flux != -1 && slot == indices.flux / nekrs::fieldOffset())
1954 : // return scales.flux_ref;
1955 : // else if (indices.heat_source != -1 && slot == indices.heat_source / nekrs::fieldOffset())
1956 : // return scales.source_ref;
1957 495 : if (is_nondimensional)
1958 : {
1959 66 : mooseDoOnce(mooseWarning(
1960 : "The units of 'usrwrk0" + std::to_string(slot) +
1961 : "' are unknown, so we cannot dimensionalize any objects using 'field = usrwrk0" +
1962 : std::to_string(slot) +
1963 : "'. The output for this quantity will be given in non-dimensional form.\n\nYou will need "
1964 : "to manipulate the data manually from Cardinal if you need to dimensionalize it."));
1965 : }
1966 :
1967 492 : return 1.0;
1968 : }
1969 :
1970 : void
1971 800 : nondimensional(const bool n)
1972 : {
1973 800 : is_nondimensional = n;
1974 800 : }
1975 :
1976 : template <>
1977 : MPI_Datatype
1978 179286 : resolveType<double>()
1979 : {
1980 179286 : return MPI_DOUBLE;
1981 : }
1982 :
1983 : template <>
1984 : MPI_Datatype
1985 6944 : resolveType<int>()
1986 : {
1987 6944 : return MPI_INT;
1988 : }
1989 :
1990 : } // end namespace nekrs
1991 :
1992 : #endif
|