LCOV - code coverage report
Current view: top level - src/base - NekInterface.C (source / functions) Hit Total Coverage
Test: neams-th-coe/cardinal: be601f Lines: 752 790 95.2 %
Date: 2025-07-15 20:50:38 Functions: 117 121 96.7 %
Legend: Lines: hit not hit

          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             : nekrs::usrwrkIndices indices;
      30             : 
      31             : namespace nekrs
      32             : {
      33             : static double setup_time;
      34             : 
      35             : // various constants for controlling tolerances
      36             : static double abs_tol;
      37             : static double rel_tol;
      38             : 
      39             : void
      40         348 : setAbsoluteTol(double tol)
      41             : {
      42         348 :   abs_tol = tol;
      43         348 : }
      44             : 
      45             : void
      46         348 : setRelativeTol(double tol)
      47             : {
      48         348 :   rel_tol = tol;
      49         348 : }
      50             : 
      51             : void
      52         810 : setNekSetupTime(const double & time)
      53             : {
      54         810 :   setup_time = time;
      55         810 : }
      56             : 
      57             : double
      58         799 : getNekSetupTime()
      59             : {
      60         799 :   return setup_time;
      61             : }
      62             : 
      63             : void
      64         742 : setStartTime(const double & start)
      65             : {
      66        1484 :   platform->options.setArgs("START TIME", to_string_f(start));
      67         742 : }
      68             : 
      69             : void
      70          52 : write_usrwrk_field_file(const int & slot, const std::string & prefix, const dfloat & time, const int & step, const bool & write_coords)
      71             : {
      72          52 :   int num_bytes = fieldOffset() * sizeof(dfloat);
      73             : 
      74          52 :   nrs_t * nrs = (nrs_t *)nrsPtr();
      75          52 :   occa::memory o_write = platform->device.malloc(num_bytes);
      76         104 :   o_write.copyFrom(nrs->o_usrwrk, num_bytes /* length we are copying */,
      77          52 :     0 /* where to place data */, num_bytes * slot /* where to source data */);
      78             : 
      79          52 :   occa::memory o_null;
      80          52 :   writeFld(prefix.c_str(), time, step, write_coords, 1 /* FP64 */, o_null, o_null, o_write, 1);
      81          52 : }
      82             : 
      83             : void
      84          56 : write_field_file(const std::string & prefix, const dfloat time, const int & step)
      85             : {
      86          56 :   nrs_t * nrs = (nrs_t *)nrsPtr();
      87             : 
      88             :   int Nscalar = 0;
      89          56 :   occa::memory o_s;
      90          56 :   if (nrs->Nscalar)
      91             :   {
      92          56 :     o_s = nrs->cds->o_S;
      93          56 :     Nscalar = nrs->Nscalar;
      94             :   }
      95             : 
      96          56 :   writeFld(
      97          56 :       prefix.c_str(), time, step, 1 /* coords */, 1 /* FP64 */, nrs->o_U, nrs->o_P, o_s, Nscalar);
      98          56 : }
      99             : 
     100             : void
     101         812 : buildOnly(int buildOnly)
     102             : {
     103         812 :   build_only = buildOnly;
     104         812 : }
     105             : 
     106             : int
     107      103249 : buildOnly()
     108             : {
     109      103249 :   return build_only;
     110             : }
     111             : 
     112             : bool
     113           0 : hasCHT()
     114             : {
     115           0 :   return entireMesh()->cht;
     116             : }
     117             : 
     118             : bool
     119       36013 : hasMovingMesh()
     120             : {
     121       36013 :   return platform->options.compareArgs("MOVING MESH", "TRUE");
     122             : }
     123             : 
     124             : bool
     125       32598 : hasVariableDt()
     126             : {
     127       32598 :   return platform->options.compareArgs("VARIABLE DT", "TRUE");
     128             : }
     129             : 
     130             : bool
     131         953 : hasBlendingSolver()
     132             : {
     133        1906 :   return !platform->options.compareArgs("MESH SOLVER", "NONE") && hasMovingMesh();
     134             : }
     135             : 
     136             : bool
     137         954 : hasUserMeshSolver()
     138             : {
     139        1908 :   return platform->options.compareArgs("MESH SOLVER", "NONE") && hasMovingMesh();
     140             : }
     141             : 
     142             : bool
     143        1524 : endControlElapsedTime()
     144             : {
     145        3048 :   return !platform->options.getArgs("STOP AT ELAPSED TIME").empty();
     146             : }
     147             : 
     148             : bool
     149        1524 : endControlTime()
     150             : {
     151        1524 :   return endTime() > 0;
     152             : }
     153             : 
     154             : bool
     155         762 : endControlNumSteps()
     156             : {
     157         762 :   return !endControlElapsedTime() && !endControlTime();
     158             : }
     159             : 
     160             : bool
     161   262370574 : hasTemperatureVariable()
     162             : {
     163   262370574 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     164   786661596 :   return nrs->Nscalar ? platform->options.compareArgs("SCALAR00 IS TEMPERATURE", "TRUE") : false;
     165             : }
     166             : 
     167             : bool
     168         680 : hasTemperatureSolve()
     169             : {
     170         680 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     171         680 :   return hasTemperatureVariable() ? nrs->cds->compute[0] : false;
     172             : }
     173             : 
     174             : bool
     175        1123 : hasScalarVariable(int scalarId)
     176             : {
     177        1123 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     178        1123 :   return (scalarId < nrs->Nscalar);
     179             : }
     180             : 
     181             : bool
     182          93 : hasHeatSourceKernel()
     183             : {
     184          93 :   return static_cast<bool>(udf.sEqnSource);
     185             : }
     186             : 
     187             : bool
     188         808 : isInitialized()
     189             : {
     190         808 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     191         808 :   return nrs;
     192             : }
     193             : 
     194             : int
     195      144214 : scalarFieldOffset()
     196             : {
     197      144214 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     198      144214 :   return nrs->cds->fieldOffset[0];
     199             : }
     200             : 
     201             : int
     202     4358488 : velocityFieldOffset()
     203             : {
     204     4358488 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     205     4358488 :   return nrs->fieldOffset;
     206             : }
     207             : 
     208             : int
     209       21006 : fieldOffset()
     210             : {
     211       21006 :   if (hasTemperatureVariable())
     212       20966 :     return scalarFieldOffset();
     213             :   else
     214          40 :     return velocityFieldOffset();
     215             : }
     216             : 
     217             : mesh_t *
     218   262260886 : entireMesh()
     219             : {
     220   262260886 :   if (hasTemperatureVariable())
     221   172380524 :     return temperatureMesh();
     222             :   else
     223    89880362 :     return flowMesh();
     224             : }
     225             : 
     226             : mesh_t *
     227    90305473 : flowMesh()
     228             : {
     229    90305473 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     230    90305473 :   return nrs->meshV;
     231             : }
     232             : 
     233             : mesh_t *
     234   173215445 : temperatureMesh()
     235             : {
     236   173215445 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     237   173215445 :   return nrs->cds->mesh[0];
     238             : }
     239             : 
     240             : mesh_t *
     241      398932 : getMesh(const nek_mesh::NekMeshEnum pp_mesh)
     242             : {
     243      398932 :   if (pp_mesh == nek_mesh::fluid)
     244        1328 :     return flowMesh();
     245      397604 :   else if (pp_mesh == nek_mesh::all)
     246      397603 :     return entireMesh();
     247             :   else
     248           1 :     mooseError("This object does not support operations on the solid part of the NekRS mesh!\n"
     249             :       "Valid options for 'mesh' are 'fluid' or 'all'.");
     250             : }
     251             : 
     252             : int
     253    13137500 : commRank()
     254             : {
     255    13137500 :   return platform->comm.mpiRank;
     256             : }
     257             : 
     258             : int
     259     1066670 : commSize()
     260             : {
     261     1066670 :   return platform->comm.mpiCommSize;
     262             : }
     263             : 
     264             : bool
     265         810 : scratchAvailable()
     266             : {
     267         810 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     268             : 
     269             :   // Because these scratch spaces are available for whatever the user sees fit, it is
     270             :   // possible that the user wants to use these arrays for a _different_ purpose aside from
     271             :   // transferring in MOOSE values. In nekrs::setup, we call the UDF_Setup0, UDF_Setup,
     272             :   // and UDF_ExecuteStep routines. These scratch space arrays aren't initialized anywhere
     273             :   // else in the core base, so we will make sure to throw an error from MOOSE if these
     274             :   // arrays are already in use, because otherwise our MOOSE transfer might get overwritten
     275             :   // by whatever other operation the user is trying to do.
     276         810 :   if (nrs->usrwrk)
     277           1 :     return false;
     278             : 
     279             :   return true;
     280             : }
     281             : 
     282             : void
     283         809 : initializeScratch(const unsigned int & n_slots)
     284             : {
     285         809 :   if (n_slots == 0)
     286             :     return;
     287             : 
     288         788 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     289         788 :   mesh_t * mesh = entireMesh();
     290             : 
     291             :   // clear them just to be sure
     292         788 :   freeScratch();
     293             : 
     294             :   // In order to make indexing simpler in the device user functions (which is where the
     295             :   // boundary conditions are then actually applied), we define these scratch arrays
     296             :   // as volume arrays.
     297         788 :   nrs->usrwrk = (double *)calloc(n_slots * fieldOffset(), sizeof(double));
     298         788 :   nrs->o_usrwrk = platform->device.malloc(n_slots * fieldOffset() * sizeof(double), nrs->usrwrk);
     299             : 
     300         788 :   n_usrwrk_slots = n_slots;
     301             : }
     302             : 
     303             : void
     304        1522 : freeScratch()
     305             : {
     306        1522 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     307             : 
     308        1522 :   freePointer(nrs->usrwrk);
     309        1522 :   nrs->o_usrwrk.free();
     310        1522 : }
     311             : 
     312             : double
     313         276 : viscosity()
     314             : {
     315             :   dfloat mu;
     316         276 :   setupAide & options = platform->options;
     317         276 :   options.getArgs("VISCOSITY", mu);
     318             : 
     319             :   // because we set rho_ref, U_ref, and L_ref all equal to 1 if our input is dimensional,
     320             :   // we don't need to have separate treatments for dimensional vs. nondimensional cases
     321         276 :   dfloat Re = 1.0 / mu;
     322         276 :   return scales.rho_ref * scales.U_ref * scales.L_ref / Re;
     323             : }
     324             : 
     325             : double
     326          92 : Pr()
     327             : {
     328             :   dfloat rho, rho_cp, k;
     329          92 :   setupAide & options = platform->options;
     330          92 :   options.getArgs("DENSITY", rho);
     331          92 :   options.getArgs("SCALAR00 DENSITY", rho_cp);
     332          92 :   options.getArgs("SCALAR00 DIFFUSIVITY", k);
     333             : 
     334          92 :   dfloat Pe = 1.0 / k;
     335          92 :   dfloat conductivity = scales.rho_ref * scales.U_ref * scales.Cp_ref * scales.L_ref / Pe;
     336          92 :   dfloat Cp = rho_cp / rho * scales.Cp_ref;
     337          92 :   return viscosity() * Cp / conductivity;
     338             : }
     339             : 
     340             : void
     341        1594 : interpolationMatrix(double * I, int starting_points, int ending_points)
     342             : {
     343        1594 :   DegreeRaiseMatrix1D(starting_points - 1, ending_points - 1, I);
     344        1594 : }
     345             : 
     346             : void
     347     2988324 : interpolateVolumeHex3D(const double * I, double * x, int N, double * Ix, int M)
     348             : {
     349     2988324 :   double * Ix1 = (dfloat *)calloc(N * N * M, sizeof(double));
     350     2988324 :   double * Ix2 = (dfloat *)calloc(N * M * M, sizeof(double));
     351             : 
     352    17185080 :   for (int k = 0; k < N; ++k)
     353    99718696 :     for (int j = 0; j < N; ++j)
     354   352372548 :       for (int i = 0; i < M; ++i)
     355             :       {
     356             :         dfloat tmp = 0;
     357  2077224048 :         for (int n = 0; n < N; ++n)
     358  1810373440 :           tmp += I[i * N + n] * x[k * N * N + j * N + n];
     359   266850608 :         Ix1[k * N * M + j * M + i] = tmp;
     360             :       }
     361             : 
     362    17185080 :   for (int k = 0; k < N; ++k)
     363    61015468 :     for (int j = 0; j < M; ++j)
     364   217899584 :       for (int i = 0; i < M; ++i)
     365             :       {
     366             :         dfloat tmp = 0;
     367  1045345368 :         for (int n = 0; n < N; ++n)
     368   874264496 :           tmp += I[j * N + n] * Ix1[k * N * M + n * M + i];
     369   171080872 :         Ix2[k * M * M + j * M + i] = tmp;
     370             :       }
     371             : 
     372    13762868 :   for (int k = 0; k < M; ++k)
     373    56333344 :     for (int j = 0; j < M; ++j)
     374   274821952 :       for (int i = 0; i < M; ++i)
     375             :       {
     376             :         dfloat tmp = 0;
     377   957314472 :         for (int n = 0; n < N; ++n)
     378   728051320 :           tmp += I[k * N + n] * Ix2[n * M * M + j * M + i];
     379   229263152 :         Ix[k * M * M + j * M + i] = tmp;
     380             :       }
     381             : 
     382             :   freePointer(Ix1);
     383             :   freePointer(Ix2);
     384     2988324 : }
     385             : 
     386             : void
     387      492928 : interpolateSurfaceFaceHex3D(
     388             :     double * scratch, const double * I, double * x, int N, double * Ix, int M)
     389             : {
     390     1952000 :   for (int j = 0; j < N; ++j)
     391     6683616 :     for (int i = 0; i < M; ++i)
     392             :     {
     393             :       double tmp = 0;
     394    24306720 :       for (int n = 0; n < N; ++n)
     395             :       {
     396    19082176 :         tmp += I[i * N + n] * x[j * N + n];
     397             :       }
     398     5224544 :       scratch[j * M + i] = tmp;
     399             :     }
     400             : 
     401     2395376 :   for (int j = 0; j < M; ++j)
     402     9675432 :     for (int i = 0; i < M; ++i)
     403             :     {
     404             :       double tmp = 0;
     405    27577896 :       for (int n = 0; n < N; ++n)
     406             :       {
     407    19804912 :         tmp += I[j * N + n] * scratch[n * M + i];
     408             :       }
     409     7772984 :       Ix[j * M + i] = tmp;
     410             :     }
     411      492928 : }
     412             : 
     413             : void
     414       95240 : displacementAndCounts(const std::vector<int> & base_counts,
     415             :                       int * counts,
     416             :                       int * displacement,
     417             :                       const int multiplier = 1.0)
     418             : {
     419      484834 :   for (int i = 0; i < commSize(); ++i)
     420      389594 :     counts[i] = base_counts[i] * multiplier;
     421             : 
     422       95240 :   displacement[0] = 0;
     423      389594 :   for (int i = 1; i < commSize(); i++)
     424      294354 :     displacement[i] = displacement[i - 1] + counts[i - 1];
     425       95240 : }
     426             : 
     427             : double
     428        2240 : usrwrkVolumeIntegral(const unsigned int & slot, const nek_mesh::NekMeshEnum pp_mesh)
     429             : {
     430        2240 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     431        2240 :   const auto & mesh = getMesh(pp_mesh);
     432             : 
     433        2240 :   double integral = 0.0;
     434             : 
     435      795480 :   for (int k = 0; k < mesh->Nelements; ++k)
     436             :   {
     437      793240 :     int offset = k * mesh->Np;
     438             : 
     439   237332376 :     for (int v = 0; v < mesh->Np; ++v)
     440   236539136 :       integral += nrs->usrwrk[slot + offset + v] * vgeo[mesh->Nvgeo * offset + v + mesh->Np * JWID];
     441             :   }
     442             : 
     443             :   // sum across all processes
     444             :   double total_integral;
     445        2240 :   MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
     446             : 
     447        2240 :   return total_integral;
     448             : }
     449             : 
     450             : void
     451        1108 : scaleUsrwrk(const unsigned int & slot, const dfloat & value)
     452             : {
     453        1108 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     454        1108 :   mesh_t * mesh = getMesh(nek_mesh::all);
     455             : 
     456      397600 :   for (int k = 0; k < mesh->Nelements; ++k)
     457             :   {
     458      396492 :     int id = k * mesh->Np;
     459             : 
     460   118662604 :     for (int v = 0; v < mesh->Np; ++v)
     461   118266112 :       nrs->usrwrk[slot + id + v] *= value;
     462             :   }
     463        1108 : }
     464             : 
     465             : std::vector<double>
     466       26459 : usrwrkSideIntegral(const unsigned int & slot,
     467             :                    const std::vector<int> & boundary,
     468             :                    const nek_mesh::NekMeshEnum pp_mesh)
     469             : {
     470       26459 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     471       26459 :   const auto & mesh = getMesh(pp_mesh);
     472             : 
     473       26459 :   std::vector<double> integral(boundary.size(), 0.0);
     474             : 
     475     8260330 :   for (int i = 0; i < mesh->Nelements; ++i)
     476             :   {
     477    57637097 :     for (int j = 0; j < mesh->Nfaces; ++j)
     478             :     {
     479    49403226 :       int face_id = mesh->EToB[i * mesh->Nfaces + j];
     480             : 
     481    49403226 :       if (std::find(boundary.begin(), boundary.end(), face_id) != boundary.end())
     482             :       {
     483      841040 :         auto it = std::find(boundary.begin(), boundary.end(), face_id);
     484             :         auto b_index = it - boundary.begin();
     485             : 
     486      841040 :         int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
     487             : 
     488    16010176 :         for (int v = 0; v < mesh->Nfp; ++v)
     489    15169136 :           integral[b_index] += nrs->usrwrk[slot + mesh->vmapM[offset + v]] *
     490    15169136 :                                sgeo[mesh->Nsgeo * (offset + v) + WSJID];
     491             :       }
     492             :     }
     493             :   }
     494             : 
     495             :   // sum across all processes; this can probably be done more efficiently
     496       26459 :   std::vector<double> total_integral(boundary.size(), 0.0);
     497       53039 :   for (std::size_t i = 0; i < boundary.size(); ++i)
     498       26580 :     MPI_Allreduce(&integral[i], &total_integral[i], 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
     499             : 
     500       26459 :   return total_integral;
     501             : }
     502             : 
     503             : void
     504           0 : limitTemperature(const double * min_T, const double * max_T)
     505             : {
     506             :   // if no limiters are provided, simply return
     507           0 :   if (!min_T && !max_T)
     508             :     return;
     509             : 
     510           0 :   double minimum = min_T ? *min_T : std::numeric_limits<double>::min();
     511           0 :   double maximum = max_T ? *max_T : std::numeric_limits<double>::max();
     512             : 
     513             :   // nondimensionalize if necessary
     514           0 :   minimum = (minimum - scales.T_ref) / scales.dT_ref;
     515           0 :   maximum = (maximum - scales.T_ref) / scales.dT_ref;
     516             : 
     517           0 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     518           0 :   mesh_t * mesh = temperatureMesh();
     519             : 
     520           0 :   for (int i = 0; i < mesh->Nelements; ++i)
     521             :   {
     522           0 :     for (int j = 0; j < mesh->Np; ++j)
     523             :     {
     524           0 :       int id = i * mesh->Np + j;
     525             : 
     526           0 :       if (nrs->cds->S[id] < minimum)
     527           0 :         nrs->cds->S[id] = minimum;
     528           0 :       if (nrs->cds->S[id] > maximum)
     529           0 :         nrs->cds->S[id] = maximum;
     530             :     }
     531             :   }
     532             : 
     533             :   // when complete, copy to device
     534           0 :   nrs->cds->o_S.copyFrom(nrs->cds->S);
     535             : }
     536             : 
     537             : void
     538        1504 : copyDeformationToDevice()
     539             : {
     540        1504 :   mesh_t * mesh = entireMesh();
     541        1504 :   mesh->o_x.copyFrom(mesh->x);
     542        1504 :   mesh->o_y.copyFrom(mesh->y);
     543        1504 :   mesh->o_z.copyFrom(mesh->z);
     544        1504 :   mesh->update();
     545             : 
     546        1504 :   updateHostMeshParameters();
     547        1504 : }
     548             : 
     549             : void
     550         807 : initializeHostMeshParameters()
     551             : {
     552         807 :   mesh_t * mesh = entireMesh();
     553         807 :   sgeo = (dfloat *)calloc(mesh->o_sgeo.size(), sizeof(dfloat));
     554         807 :   vgeo = (dfloat *)calloc(mesh->o_vgeo.size(), sizeof(dfloat));
     555         807 : }
     556             : 
     557             : void
     558        2311 : updateHostMeshParameters()
     559             : {
     560        2311 :   mesh_t * mesh = entireMesh();
     561        2311 :   mesh->o_sgeo.copyTo(sgeo);
     562        2311 :   mesh->o_vgeo.copyTo(vgeo);
     563        2311 : }
     564             : 
     565             : dfloat *
     566         128 : getSgeo()
     567             : {
     568         128 :   return sgeo;
     569             : }
     570             : 
     571             : dfloat *
     572         507 : getVgeo()
     573             : {
     574         507 :   return vgeo;
     575             : }
     576             : 
     577             : double
     578       24812 : sideExtremeValue(const std::vector<int> & boundary_id, const field::NekFieldEnum & field,
     579             :              const nek_mesh::NekMeshEnum pp_mesh, const bool max)
     580             : {
     581       24812 :   mesh_t * mesh = getMesh(pp_mesh);
     582             : 
     583       24812 :   double value = max ? -std::numeric_limits<double>::max() : std::numeric_limits<double>::max();
     584             : 
     585             :   double (*f)(int);
     586       24812 :   f = solutionPointer(field);
     587             : 
     588     2935048 :   for (int i = 0; i < mesh->Nelements; ++i)
     589             :   {
     590    20371652 :     for (int j = 0; j < mesh->Nfaces; ++j)
     591             :     {
     592    17461416 :       int face_id = mesh->EToB[i * mesh->Nfaces + j];
     593             : 
     594    17461416 :       if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
     595             :       {
     596      282524 :         int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
     597    12898268 :         for (int v = 0; v < mesh->Nfp; ++v)
     598             :         {
     599    12615744 :           if (max)
     600     6383348 :             value = std::max(value, f(mesh->vmapM[offset + v]));
     601             :           else
     602     6369614 :             value = std::min(value, f(mesh->vmapM[offset + v]));
     603             :         }
     604             :       }
     605             :     }
     606             :   }
     607             : 
     608             :   // find extreme value across all processes
     609             :   double reduced_value;
     610       24812 :   auto op = max ? MPI_MAX : MPI_MIN;
     611       24812 :   MPI_Allreduce(&value, &reduced_value, 1, MPI_DOUBLE, op, platform->comm.mpiComm);
     612             : 
     613             :   // dimensionalize the field if needed
     614       24812 :   reduced_value = reduced_value * nondimensionalDivisor(field) + nondimensionalAdditive(field);
     615             : 
     616       24812 :   return reduced_value;
     617             : }
     618             : 
     619             : double
     620       47966 : volumeExtremeValue(const field::NekFieldEnum & field, const nek_mesh::NekMeshEnum pp_mesh, const bool max)
     621             : {
     622       47966 :   double value = max ? -std::numeric_limits<double>::max() : std::numeric_limits<double>::max();
     623             : 
     624             :   double (*f)(int);
     625       47966 :   f = solutionPointer(field);
     626             : 
     627             :   mesh_t * mesh;
     628             :   int start_id;
     629             : 
     630       47966 :   switch (pp_mesh)
     631             :   {
     632       47954 :     case nek_mesh::fluid:
     633             :     case nek_mesh::all:
     634             :     {
     635       47954 :       mesh = getMesh(pp_mesh);
     636             :       start_id = 0;
     637             :       break;
     638             :     }
     639          12 :     case nek_mesh::solid:
     640             :     {
     641          12 :       mesh = entireMesh();
     642          12 :       start_id = flowMesh()->Nelements;
     643          12 :       break;
     644             :     }
     645           0 :     default:
     646           0 :       mooseError("Unhandled NekMeshEnum in volumeExtremeValue");
     647             :   }
     648             : 
     649     8299594 :   for (int i = start_id; i < mesh->Nelements; ++i)
     650             :   {
     651  1164789844 :     for (int j = 0; j < mesh->Np; ++j)
     652             :     {
     653  1156538216 :       if (max)
     654   662079182 :         value = std::max(value, f(i * mesh->Np + j));
     655             :       else
     656   496912621 :         value = std::min(value, f(i * mesh->Np + j));
     657             :     }
     658             :   }
     659             : 
     660             :   // find extreme value across all processes
     661             :   double reduced_value;
     662       47966 :   auto op = max ? MPI_MAX : MPI_MIN;
     663       47966 :   MPI_Allreduce(&value, &reduced_value, 1, MPI_DOUBLE, op, platform->comm.mpiComm);
     664             : 
     665             :   // dimensionalize the field if needed
     666       47966 :   reduced_value = reduced_value * nondimensionalDivisor(field) + nondimensionalAdditive(field);
     667             : 
     668       47966 :   return reduced_value;
     669             : }
     670             : 
     671             : Point
     672   218869152 : gllPoint(int local_elem_id, int local_node_id)
     673             : {
     674   218869152 :   mesh_t * mesh = entireMesh();
     675             : 
     676   218869152 :   int id = local_elem_id * mesh->Np + local_node_id;
     677   218869152 :   Point p(mesh->x[id], mesh->y[id], mesh->z[id]);
     678             :   p *= scales.L_ref;
     679   218869152 :   return p;
     680             : }
     681             : 
     682             : Point
     683      951552 : gllPointFace(int local_elem_id, int local_face_id, int local_node_id)
     684             : {
     685      951552 :   mesh_t * mesh = entireMesh();
     686      951552 :   int face_id = mesh->EToB[local_elem_id * mesh->Nfaces + local_face_id];
     687      951552 :   int offset = local_elem_id * mesh->Nfaces * mesh->Nfp + local_face_id * mesh->Nfp;
     688      951552 :   int id = mesh->vmapM[offset + local_node_id];
     689      951552 :   Point p(mesh->x[id], mesh->y[id], mesh->z[id]);
     690             :   p *= scales.L_ref;
     691      951552 :   return p;
     692             : }
     693             : 
     694             : Point
     695      234240 : centroidFace(int local_elem_id, int local_face_id)
     696             : {
     697      234240 :   mesh_t * mesh = entireMesh();
     698             : 
     699             :   double x_c = 0.0;
     700             :   double y_c = 0.0;
     701             :   double z_c = 0.0;
     702             :   double mass = 0.0;
     703             : 
     704      234240 :   int offset = local_elem_id * mesh->Nfaces * mesh->Nfp + local_face_id * mesh->Nfp;
     705    14150400 :   for (int v = 0; v < mesh->Np; ++v)
     706             :   {
     707    13916160 :     int id = mesh->vmapM[offset + v];
     708    13916160 :     double mass_matrix = sgeo[mesh->Nsgeo * (offset + v) + WSJID];
     709    13916160 :     x_c += mesh->x[id] * mass_matrix;
     710    13916160 :     y_c += mesh->y[id] * mass_matrix;
     711    13916160 :     z_c += mesh->z[id] * mass_matrix;
     712    13916160 :     mass += mass_matrix;
     713             :   }
     714             : 
     715             :   Point c(x_c, y_c, z_c);
     716      234240 :   return c / mass * scales.L_ref;
     717             : }
     718             : 
     719             : Point
     720    26997504 : centroid(int local_elem_id)
     721             : {
     722    26997504 :   mesh_t * mesh = entireMesh();
     723             : 
     724             :   double x_c = 0.0;
     725             :   double y_c = 0.0;
     726             :   double z_c = 0.0;
     727             :   double mass = 0.0;
     728             : 
     729  6192016128 :   for (int v = 0; v < mesh->Np; ++v)
     730             :   {
     731  6165018624 :     int id = local_elem_id * mesh->Np + v;
     732  6165018624 :     double mass_matrix = vgeo[local_elem_id * mesh->Np * mesh->Nvgeo + JWID * mesh->Np + v];
     733  6165018624 :     x_c += mesh->x[id] * mass_matrix;
     734  6165018624 :     y_c += mesh->y[id] * mass_matrix;
     735  6165018624 :     z_c += mesh->z[id] * mass_matrix;
     736  6165018624 :     mass += mass_matrix;
     737             :   }
     738             : 
     739             :   Point c(x_c, y_c, z_c);
     740    26997504 :   return c / mass * scales.L_ref;
     741             : }
     742             : 
     743             : double
     744       17264 : volume(const nek_mesh::NekMeshEnum pp_mesh)
     745             : {
     746       17264 :   mesh_t * mesh = getMesh(pp_mesh);
     747       17264 :   double integral = 0.0;
     748             : 
     749     3673920 :   for (int k = 0; k < mesh->Nelements; ++k)
     750             :   {
     751     3656656 :     int offset = k * mesh->Np;
     752             : 
     753   519575744 :     for (int v = 0; v < mesh->Np; ++v)
     754   515919088 :       integral += vgeo[mesh->Nvgeo * offset + v + mesh->Np * JWID];
     755             :   }
     756             : 
     757             :   // sum across all processes
     758             :   double total_integral;
     759       17264 :   MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
     760             : 
     761       17264 :   total_integral *= scales.V_ref;
     762             : 
     763       17264 :   return total_integral;
     764             : }
     765             : 
     766             : void
     767       49731 : dimensionalizeVolume(double & integral)
     768             : {
     769       49731 :   integral *= scales.V_ref;
     770       49731 : }
     771             : 
     772             : void
     773         216 : dimensionalizeArea(double & integral)
     774             : {
     775         216 :   integral *= scales.A_ref;
     776         216 : }
     777             : 
     778             : void
     779       50546 : dimensionalizeVolumeIntegral(const field::NekFieldEnum & integrand,
     780             :                              const Real & volume,
     781             :                              double & integral)
     782             : {
     783             :   // dimensionalize the field if needed
     784       50546 :   integral *= nondimensionalDivisor(integrand);
     785             : 
     786             :   // scale the volume integral
     787       50546 :   integral *= scales.V_ref;
     788             : 
     789             :   // for quantities with a relative scaling, we need to add back the reference
     790             :   // contribution to the volume integral
     791       50546 :   integral += nondimensionalAdditive(integrand) * volume;
     792       50546 : }
     793             : 
     794             : void
     795         296 : dimensionalizeSideIntegral(const field::NekFieldEnum & integrand,
     796             :                            const Real & area,
     797             :                            double & integral)
     798             : {
     799             :   // dimensionalize the field if needed
     800         296 :   integral *= nondimensionalDivisor(integrand);
     801             : 
     802             :   // scale the boundary integral
     803         296 :   integral *= scales.A_ref;
     804             : 
     805             :   // for quantities with a relative scaling, we need to add back the reference
     806             :   // contribution to the side integral
     807         296 :   integral += nondimensionalAdditive(integrand) * area;
     808         296 : }
     809             : 
     810             : void
     811       31712 : dimensionalizeSideIntegral(const field::NekFieldEnum & integrand,
     812             :                            const std::vector<int> & boundary_id,
     813             :                            double & integral,
     814             :                            const nek_mesh::NekMeshEnum pp_mesh)
     815             : {
     816             :   // dimensionalize the field if needed
     817       31712 :   integral *= nondimensionalDivisor(integrand);
     818             : 
     819             :   // scale the boundary integral
     820       31712 :   integral *= scales.A_ref;
     821             : 
     822             :   // for quantities with a relative scaling, we need to add back the reference
     823             :   // contribution to the side integral; we need this form here to avoid a recursive loop
     824       31712 :   auto add = nondimensionalAdditive(integrand);
     825       31712 :   if (std::abs(add) > 1e-8)
     826         360 :     integral += add * area(boundary_id, pp_mesh);
     827       31712 : }
     828             : 
     829             : double
     830       10530 : volumeIntegral(const field::NekFieldEnum & integrand, const Real & volume,
     831             :                const nek_mesh::NekMeshEnum pp_mesh)
     832             : {
     833       10530 :   mesh_t * mesh = getMesh(pp_mesh);
     834             : 
     835       10530 :   double integral = 0.0;
     836             : 
     837             :   double (*f)(int);
     838       10530 :   f = solutionPointer(integrand);
     839             : 
     840     2054116 :   for (int k = 0; k < mesh->Nelements; ++k)
     841             :   {
     842     2043586 :     int offset = k * mesh->Np;
     843             : 
     844   312285298 :     for (int v = 0; v < mesh->Np; ++v)
     845   310241712 :       integral += f(offset + v) * vgeo[mesh->Nvgeo * offset + v + mesh->Np * JWID];
     846             :   }
     847             : 
     848             :   // sum across all processes
     849             :   double total_integral;
     850       10530 :   MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
     851             : 
     852       10530 :   dimensionalizeVolumeIntegral(integrand, volume, total_integral);
     853             : 
     854       10530 :   return total_integral;
     855             : }
     856             : 
     857             : double
     858       13500 : area(const std::vector<int> & boundary_id, const nek_mesh::NekMeshEnum pp_mesh)
     859             : {
     860       13500 :   mesh_t * mesh = getMesh(pp_mesh);
     861             : 
     862       13500 :   double integral = 0.0;
     863             : 
     864     2520762 :   for (int i = 0; i < mesh->Nelements; ++i)
     865             :   {
     866    17550834 :     for (int j = 0; j < mesh->Nfaces; ++j)
     867             :     {
     868    15043572 :       int face_id = mesh->EToB[i * mesh->Nfaces + j];
     869             : 
     870    15043572 :       if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
     871             :       {
     872      415204 :         int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
     873     8766664 :         for (int v = 0; v < mesh->Nfp; ++v)
     874             :         {
     875     8351460 :           integral += sgeo[mesh->Nsgeo * (offset + v) + WSJID];
     876             :         }
     877             :       }
     878             :     }
     879             :   }
     880             : 
     881             :   // sum across all processes
     882             :   double total_integral;
     883       13500 :   MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
     884             : 
     885       13500 :   dimensionalizeSideIntegral(field::unity, boundary_id, total_integral, pp_mesh);
     886             : 
     887       13500 :   return total_integral;
     888             : }
     889             : 
     890             : double
     891       18177 : sideIntegral(const std::vector<int> & boundary_id, const field::NekFieldEnum & integrand,
     892             :              const nek_mesh::NekMeshEnum pp_mesh)
     893             : {
     894       18177 :   mesh_t * mesh = getMesh(pp_mesh);
     895             : 
     896       18176 :   double integral = 0.0;
     897             : 
     898             :   double (*f)(int);
     899       18176 :   f = solutionPointer(integrand);
     900             : 
     901     3014542 :   for (int i = 0; i < mesh->Nelements; ++i)
     902             :   {
     903    20974562 :     for (int j = 0; j < mesh->Nfaces; ++j)
     904             :     {
     905    17978196 :       int face_id = mesh->EToB[i * mesh->Nfaces + j];
     906             : 
     907    17978196 :       if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
     908             :       {
     909      555758 :         int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
     910    11470402 :         for (int v = 0; v < mesh->Nfp; ++v)
     911             :         {
     912    10914644 :           integral += f(mesh->vmapM[offset + v]) * sgeo[mesh->Nsgeo * (offset + v) + WSJID];
     913             :         }
     914             :       }
     915             :     }
     916             :   }
     917             : 
     918             :   // sum across all processes
     919             :   double total_integral;
     920       18176 :   MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
     921             : 
     922       18176 :   dimensionalizeSideIntegral(integrand, boundary_id, total_integral, pp_mesh);
     923             : 
     924       18176 :   return total_integral;
     925             : }
     926             : 
     927             : double
     928         868 : massFlowrate(const std::vector<int> & boundary_id, const nek_mesh::NekMeshEnum pp_mesh)
     929             : {
     930         868 :   mesh_t * mesh = getMesh(pp_mesh);
     931         868 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     932             : 
     933             :   // TODO: This function only works correctly if the density is constant, because
     934             :   // otherwise we need to copy the density from device to host
     935             :   double rho;
     936         868 :   platform->options.getArgs("DENSITY", rho);
     937             : 
     938         868 :   double integral = 0.0;
     939             : 
     940      412012 :   for (int i = 0; i < mesh->Nelements; ++i)
     941             :   {
     942     2878008 :     for (int j = 0; j < mesh->Nfaces; ++j)
     943             :     {
     944     2466864 :       int face_id = mesh->EToB[i * mesh->Nfaces + j];
     945             : 
     946     2466864 :       if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
     947             :       {
     948       21112 :         int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
     949      586564 :         for (int v = 0; v < mesh->Nfp; ++v)
     950             :         {
     951      565452 :           int vol_id = mesh->vmapM[offset + v];
     952      565452 :           int surf_offset = mesh->Nsgeo * (offset + v);
     953             : 
     954             :           double normal_velocity =
     955      565452 :               nrs->U[vol_id + 0 * velocityFieldOffset()] * sgeo[surf_offset + NXID] +
     956      565452 :               nrs->U[vol_id + 1 * velocityFieldOffset()] * sgeo[surf_offset + NYID] +
     957      565452 :               nrs->U[vol_id + 2 * velocityFieldOffset()] * sgeo[surf_offset + NZID];
     958             : 
     959      565452 :           integral += rho * normal_velocity * sgeo[surf_offset + WSJID];
     960             :         }
     961             :       }
     962             :     }
     963             :   }
     964             : 
     965             :   // sum across all processes
     966             :   double total_integral;
     967         868 :   MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
     968             : 
     969             :   // dimensionalize the mass flux and area
     970         868 :   total_integral *= scales.rho_ref * scales.U_ref * scales.A_ref;
     971             : 
     972         868 :   return total_integral;
     973             : }
     974             : 
     975             : double
     976        1280 : sideMassFluxWeightedIntegral(const std::vector<int> & boundary_id,
     977             :                              const field::NekFieldEnum & integrand,
     978             :                              const nek_mesh::NekMeshEnum pp_mesh)
     979             : {
     980        1280 :   mesh_t * mesh = getMesh(pp_mesh);
     981        1280 :   nrs_t * nrs = (nrs_t *)nrsPtr();
     982             : 
     983             :   // TODO: This function only works correctly if the density is constant, because
     984             :   // otherwise we need to copy the density from device to host
     985             :   double rho;
     986        1280 :   platform->options.getArgs("DENSITY", rho);
     987             : 
     988        1280 :   double integral = 0.0;
     989             : 
     990             :   double (*f)(int);
     991        1280 :   f = solutionPointer(integrand);
     992             : 
     993      605936 :   for (int i = 0; i < mesh->Nelements; ++i)
     994             :   {
     995     4232592 :     for (int j = 0; j < mesh->Nfaces; ++j)
     996             :     {
     997     3627936 :       int face_id = mesh->EToB[i * mesh->Nfaces + j];
     998             : 
     999     3627936 :       if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
    1000             :       {
    1001       27498 :         int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
    1002      914862 :         for (int v = 0; v < mesh->Nfp; ++v)
    1003             :         {
    1004      887364 :           int vol_id = mesh->vmapM[offset + v];
    1005      887364 :           int surf_offset = mesh->Nsgeo * (offset + v);
    1006             :           double normal_velocity =
    1007      887364 :               nrs->U[vol_id + 0 * velocityFieldOffset()] * sgeo[surf_offset + NXID] +
    1008      887364 :               nrs->U[vol_id + 1 * velocityFieldOffset()] * sgeo[surf_offset + NYID] +
    1009      887364 :               nrs->U[vol_id + 2 * velocityFieldOffset()] * sgeo[surf_offset + NZID];
    1010      887364 :           integral += f(vol_id) * rho * normal_velocity * sgeo[surf_offset + WSJID];
    1011             :         }
    1012             :       }
    1013             :     }
    1014             :   }
    1015             : 
    1016             :   // sum across all processes
    1017             :   double total_integral;
    1018        1280 :   MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
    1019             : 
    1020             :   // dimensionalize the field if needed
    1021        1280 :   total_integral *= nondimensionalDivisor(integrand);
    1022             : 
    1023             :   // dimensionalize the mass flux and area
    1024        1280 :   total_integral *= scales.rho_ref * scales.U_ref * scales.A_ref;
    1025             : 
    1026             :   // for quantities with a relative scaling, we need to add back the reference
    1027             :   // contribution to the mass flux integral; we need this form here to avoid an infinite
    1028             :   // recursive loop
    1029        1280 :   auto add = nondimensionalAdditive(integrand);
    1030        1280 :   if (std::abs(add) > 1e-8)
    1031         376 :     total_integral += add * massFlowrate(boundary_id, pp_mesh);
    1032             : 
    1033        1280 :   return total_integral;
    1034             : }
    1035             : 
    1036             : double
    1037          36 : pressureSurfaceForce(const std::vector<int> & boundary_id, const Point & direction, const nek_mesh::NekMeshEnum pp_mesh)
    1038             : {
    1039          36 :   mesh_t * mesh = getMesh(pp_mesh);
    1040          36 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1041             : 
    1042          36 :   double integral = 0.0;
    1043             : 
    1044       20232 :   for (int i = 0; i < mesh->Nelements; ++i)
    1045             :   {
    1046      141372 :     for (int j = 0; j < mesh->Nfaces; ++j)
    1047             :     {
    1048      121176 :       int face_id = mesh->EToB[i * mesh->Nfaces + j];
    1049             : 
    1050      121176 :       if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
    1051             :       {
    1052       12780 :         int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
    1053      472860 :         for (int v = 0; v < mesh->Nfp; ++v)
    1054             :         {
    1055      460080 :           int vol_id = mesh->vmapM[offset + v];
    1056      460080 :           int surf_offset = mesh->Nsgeo * (offset + v);
    1057             : 
    1058      460080 :           double p_normal = nrs->P[vol_id] * (sgeo[surf_offset + NXID] * direction(0) +
    1059      460080 :                                               sgeo[surf_offset + NYID] * direction(1) +
    1060      460080 :                                               sgeo[surf_offset + NZID] * direction(2));
    1061             : 
    1062      460080 :           integral += p_normal * sgeo[surf_offset + WSJID];
    1063             :         }
    1064             :       }
    1065             :     }
    1066             :   }
    1067             : 
    1068             :   // sum across all processes
    1069             :   double total_integral;
    1070          36 :   MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
    1071             : 
    1072          36 :   dimensionalizeSideIntegral(field::pressure, boundary_id, total_integral, pp_mesh);
    1073             : 
    1074          36 :   return total_integral;
    1075             : }
    1076             : 
    1077             : double
    1078       11396 : heatFluxIntegral(const std::vector<int> & boundary_id, const nek_mesh::NekMeshEnum pp_mesh)
    1079             : {
    1080       11396 :   mesh_t * mesh = getMesh(pp_mesh);
    1081       11396 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1082             : 
    1083             :   // TODO: This function only works correctly if the conductivity is constant, because
    1084             :   // otherwise we need to copy the conductivity from device to host
    1085             :   double k;
    1086       11396 :   platform->options.getArgs("SCALAR00 DIFFUSIVITY", k);
    1087             : 
    1088       11396 :   double integral = 0.0;
    1089       11396 :   double * grad_T = (double *)calloc(3 * mesh->Np, sizeof(double));
    1090             : 
    1091     2211276 :   for (int i = 0; i < mesh->Nelements; ++i)
    1092             :   {
    1093    15399160 :     for (int j = 0; j < mesh->Nfaces; ++j)
    1094             :     {
    1095    13199280 :       int face_id = mesh->EToB[i * mesh->Nfaces + j];
    1096             : 
    1097    13199280 :       if (std::find(boundary_id.begin(), boundary_id.end(), face_id) != boundary_id.end())
    1098             :       {
    1099             :         // some inefficiency if an element has more than one face on the sideset of interest,
    1100             :         // because we will recompute the gradient in the element more than one time - but this
    1101             :         // is of little practical interest because this will be a minority of cases.
    1102      223308 :         gradient(mesh->Np, i, nrs->cds->S, grad_T, pp_mesh);
    1103             : 
    1104      223308 :         int offset = i * mesh->Nfaces * mesh->Nfp + j * mesh->Nfp;
    1105     5745756 :         for (int v = 0; v < mesh->Nfp; ++v)
    1106             :         {
    1107     5522448 :           int vol_id = mesh->vmapM[offset + v] - i * mesh->Np;
    1108     5522448 :           int surf_offset = mesh->Nsgeo * (offset + v);
    1109             : 
    1110     5522448 :           double normal_grad_T = grad_T[vol_id + 0 * mesh->Np] * sgeo[surf_offset + NXID] +
    1111     5522448 :                                  grad_T[vol_id + 1 * mesh->Np] * sgeo[surf_offset + NYID] +
    1112     5522448 :                                  grad_T[vol_id + 2 * mesh->Np] * sgeo[surf_offset + NZID];
    1113             : 
    1114     5522448 :           integral += -k * normal_grad_T * sgeo[surf_offset + WSJID];
    1115             :         }
    1116             :       }
    1117             :     }
    1118             :   }
    1119             : 
    1120             :   freePointer(grad_T);
    1121             : 
    1122             :   // sum across all processes
    1123             :   double total_integral;
    1124       11396 :   MPI_Allreduce(&integral, &total_integral, 1, MPI_DOUBLE, MPI_SUM, platform->comm.mpiComm);
    1125             : 
    1126             :   // multiply by the reference heat flux and an area factor to dimensionalize
    1127       11396 :   total_integral *= scales.flux_ref * scales.A_ref;
    1128             : 
    1129       11396 :   return total_integral;
    1130             : }
    1131             : 
    1132             : void
    1133      223308 : gradient(const int offset,
    1134             :          const int e,
    1135             :          const double * f,
    1136             :          double * grad_f,
    1137             :          const nek_mesh::NekMeshEnum pp_mesh)
    1138             : {
    1139      223308 :   mesh_t * mesh = getMesh(pp_mesh);
    1140             : 
    1141     1298124 :   for (int k = 0; k < mesh->Nq; ++k)
    1142             :   {
    1143     6597264 :     for (int j = 0; j < mesh->Nq; ++j)
    1144             :     {
    1145    36425184 :       for (int i = 0; i < mesh->Nq; ++i)
    1146             :       {
    1147    30902736 :         const int gid = e * mesh->Np * mesh->Nvgeo + k * mesh->Nq * mesh->Nq + j * mesh->Nq + i;
    1148    30902736 :         const double drdx = vgeo[gid + RXID * mesh->Np];
    1149    30902736 :         const double drdy = vgeo[gid + RYID * mesh->Np];
    1150    30902736 :         const double drdz = vgeo[gid + RZID * mesh->Np];
    1151    30902736 :         const double dsdx = vgeo[gid + SXID * mesh->Np];
    1152    30902736 :         const double dsdy = vgeo[gid + SYID * mesh->Np];
    1153    30902736 :         const double dsdz = vgeo[gid + SZID * mesh->Np];
    1154    30902736 :         const double dtdx = vgeo[gid + TXID * mesh->Np];
    1155    30902736 :         const double dtdy = vgeo[gid + TYID * mesh->Np];
    1156    30902736 :         const double dtdz = vgeo[gid + TZID * mesh->Np];
    1157             : 
    1158             :         // compute 'r' and 's' derivatives of (q_m) at node n
    1159             :         double dpdr = 0.f, dpds = 0.f, dpdt = 0.f;
    1160             : 
    1161   222432864 :         for (int n = 0; n < mesh->Nq; ++n)
    1162             :         {
    1163   191530128 :           const double Dr = mesh->D[i * mesh->Nq + n];
    1164   191530128 :           const double Ds = mesh->D[j * mesh->Nq + n];
    1165   191530128 :           const double Dt = mesh->D[k * mesh->Nq + n];
    1166             : 
    1167   191530128 :           dpdr += Dr * f[e * mesh->Np + k * mesh->Nq * mesh->Nq + j * mesh->Nq + n];
    1168   191530128 :           dpds += Ds * f[e * mesh->Np + k * mesh->Nq * mesh->Nq + n * mesh->Nq + i];
    1169   191530128 :           dpdt += Dt * f[e * mesh->Np + n * mesh->Nq * mesh->Nq + j * mesh->Nq + i];
    1170             :         }
    1171             : 
    1172    30902736 :         const int id = k * mesh->Nq * mesh->Nq + j * mesh->Nq + i;
    1173    30902736 :         grad_f[id + 0 * offset] = drdx * dpdr + dsdx * dpds + dtdx * dpdt;
    1174    30902736 :         grad_f[id + 1 * offset] = drdy * dpdr + dsdy * dpds + dtdy * dpdt;
    1175    30902736 :         grad_f[id + 2 * offset] = drdz * dpdr + dsdz * dpds + dtdz * dpdt;
    1176             :       }
    1177             :     }
    1178             :   }
    1179      223308 : }
    1180             : 
    1181             : bool
    1182         219 : isHeatFluxBoundary(const int boundary)
    1183             : {
    1184             :   // the heat flux boundary is now named 'codedFixedGradient', but 'fixedGradient'
    1185             :   // will be present for backwards compatibility
    1186         876 :   return (bcMap::text(boundary, "scalar00") == "fixedGradient") ||
    1187        1095 :          (bcMap::text(boundary, "scalar00") == "codedFixedGradient");
    1188             : }
    1189             : 
    1190             : bool
    1191          17 : isMovingMeshBoundary(const int boundary)
    1192             : {
    1193          34 :   return bcMap::text(boundary, "mesh") == "codedFixedValue";
    1194             : }
    1195             : 
    1196             : bool
    1197           0 : isTemperatureBoundary(const int boundary)
    1198             : {
    1199           0 :   return bcMap::text(boundary, "scalar00") == "fixedValue";
    1200             : }
    1201             : 
    1202             : const std::string
    1203           1 : temperatureBoundaryType(const int boundary)
    1204             : {
    1205           2 :   return bcMap::text(boundary, "scalar00");
    1206             : }
    1207             : 
    1208             : int
    1209        1603 : polynomialOrder()
    1210             : {
    1211        1603 :   return entireMesh()->N;
    1212             : }
    1213             : 
    1214             : int
    1215         806 : Nelements()
    1216             : {
    1217         806 :   int n_local = entireMesh()->Nelements;
    1218             :   int n_global;
    1219         806 :   MPI_Allreduce(&n_local, &n_global, 1, MPI_INT, MPI_SUM, platform->comm.mpiComm);
    1220         806 :   return n_global;
    1221             : }
    1222             : 
    1223             : int
    1224    11866645 : Nfaces()
    1225             : {
    1226    11866645 :   return entireMesh()->Nfaces;
    1227             : }
    1228             : 
    1229             : int
    1230         807 : dim()
    1231             : {
    1232         807 :   return entireMesh()->dim;
    1233             : }
    1234             : 
    1235             : int
    1236           0 : NfaceVertices()
    1237             : {
    1238           0 :   return entireMesh()->NfaceVertices;
    1239             : }
    1240             : 
    1241             : int
    1242         806 : NboundaryFaces()
    1243             : {
    1244         806 :   return entireMesh()->NboundaryFaces;
    1245             : }
    1246             : 
    1247             : int
    1248        7221 : NboundaryID()
    1249             : {
    1250        7221 :   if (entireMesh()->cht)
    1251         182 :     return nekData.NboundaryIDt;
    1252             :   else
    1253        7039 :     return nekData.NboundaryID;
    1254             : }
    1255             : 
    1256             : bool
    1257        2553 : validBoundaryIDs(const std::vector<int> & boundary_id, int & first_invalid_id, int & n_boundaries)
    1258             : {
    1259        2553 :   n_boundaries = NboundaryID();
    1260             : 
    1261             :   bool valid_boundary_ids = true;
    1262        5933 :   for (const auto & b : boundary_id)
    1263             :   {
    1264        3380 :     if ((b > n_boundaries) || (b <= 0))
    1265             :     {
    1266           3 :       first_invalid_id = b;
    1267             :       valid_boundary_ids = false;
    1268             :     }
    1269             :   }
    1270             : 
    1271        2553 :   return valid_boundary_ids;
    1272             : }
    1273             : 
    1274             : double
    1275       35440 : scalar01(const int id)
    1276             : {
    1277       35440 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1278       35440 :   return nrs->cds->S[id + 1 * scalarFieldOffset()];
    1279             : }
    1280             : 
    1281             : double
    1282       61552 : scalar02(const int id)
    1283             : {
    1284       61552 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1285       61552 :   return nrs->cds->S[id + 2 * scalarFieldOffset()];
    1286             : }
    1287             : 
    1288             : double
    1289       26224 : scalar03(const int id)
    1290             : {
    1291       26224 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1292       26224 :   return nrs->cds->S[id + 3 * scalarFieldOffset()];
    1293             : }
    1294             : 
    1295             : double
    1296      505472 : usrwrk00(const int id)
    1297             : {
    1298      505472 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1299      505472 :   return nrs->usrwrk[id];
    1300             : }
    1301             : 
    1302             : double
    1303       20672 : usrwrk01(const int id)
    1304             : {
    1305       20672 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1306       20672 :   return nrs->usrwrk[id + nrs->fieldOffset];
    1307             : }
    1308             : 
    1309             : double
    1310       20672 : usrwrk02(const int id)
    1311             : {
    1312       20672 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1313       20672 :   return nrs->usrwrk[id + 2 * nrs->fieldOffset];
    1314             : }
    1315             : 
    1316             : double
    1317  1328356616 : temperature(const int id)
    1318             : {
    1319  1328356616 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1320  1328356616 :   return nrs->cds->S[id];
    1321             : }
    1322             : 
    1323             : double
    1324   223118976 : pressure(const int id)
    1325             : {
    1326   223118976 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1327   223118976 :   return nrs->P[id];
    1328             : }
    1329             : 
    1330             : double
    1331    86179604 : unity(const int /* id */)
    1332             : {
    1333    86179604 :   return 1.0;
    1334             : }
    1335             : 
    1336             : double
    1337   209818528 : velocity_x(const int id)
    1338             : {
    1339   209818528 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1340   209818528 :   return nrs->U[id + 0 * nrs->fieldOffset];
    1341             : }
    1342             : 
    1343             : double
    1344   147149728 : velocity_y(const int id)
    1345             : {
    1346   147149728 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1347   147149728 :   return nrs->U[id + 1 * nrs->fieldOffset];
    1348             : }
    1349             : 
    1350             : double
    1351   208312272 : velocity_z(const int id)
    1352             : {
    1353   208312272 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1354   208312272 :   return nrs->U[id + 2 * nrs->fieldOffset];
    1355             : }
    1356             : 
    1357             : double
    1358     2027844 : velocity(const int id)
    1359             : {
    1360     2027844 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1361     2027844 :   int offset = nrs->fieldOffset;
    1362             : 
    1363     2027844 :   return std::sqrt(nrs->U[id + 0 * offset] * nrs->U[id + 0 * offset] +
    1364     2027844 :                    nrs->U[id + 1 * offset] * nrs->U[id + 1 * offset] +
    1365     2027844 :                    nrs->U[id + 2 * offset] * nrs->U[id + 2 * offset]);
    1366             : }
    1367             : 
    1368             : double
    1369       47744 : velocity_x_squared(const int id)
    1370             : {
    1371       47744 :   return std::pow(velocity_x(id), 2);
    1372             : }
    1373             : 
    1374             : double
    1375       47744 : velocity_y_squared(const int id)
    1376             : {
    1377       47744 :   return std::pow(velocity_y(id), 2);
    1378             : }
    1379             : 
    1380             : double
    1381       52912 : velocity_z_squared(const int id)
    1382             : {
    1383       52912 :   return std::pow(velocity_z(id), 2);
    1384             : }
    1385             : 
    1386             : void
    1387    64817272 : flux(const int id, const dfloat value)
    1388             : {
    1389    64817272 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1390    64817272 :   nrs->usrwrk[indices.flux + id] = value;
    1391    64817272 : }
    1392             : 
    1393             : void
    1394   118273024 : heat_source(const int id, const dfloat value)
    1395             : {
    1396   118273024 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1397   118273024 :   nrs->usrwrk[indices.heat_source + id] = value;
    1398   118273024 : }
    1399             : 
    1400             : void
    1401       27648 : x_displacement(const int id, const dfloat value)
    1402             : {
    1403       27648 :   mesh_t * mesh = entireMesh();
    1404       27648 :   mesh->x[id] = value;
    1405       27648 : }
    1406             : 
    1407             : void
    1408       27648 : y_displacement(const int id, const dfloat value)
    1409             : {
    1410       27648 :   mesh_t * mesh = entireMesh();
    1411       27648 :   mesh->y[id] = value;
    1412       27648 : }
    1413             : 
    1414             : void
    1415       27648 : z_displacement(const int id, const dfloat value)
    1416             : {
    1417       27648 :   mesh_t * mesh = entireMesh();
    1418       27648 :   mesh->z[id] = value;
    1419       27648 : }
    1420             : 
    1421             : void
    1422     4032000 : mesh_velocity_x(const int id, const dfloat value)
    1423             : {
    1424     4032000 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1425     4032000 :   nrs->usrwrk[indices.mesh_velocity_x + id] = value;
    1426     4032000 : }
    1427             : 
    1428             : void
    1429     4032000 : mesh_velocity_y(const int id, const dfloat value)
    1430             : {
    1431     4032000 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1432     4032000 :   nrs->usrwrk[indices.mesh_velocity_y + id] = value;
    1433     4032000 : }
    1434             : 
    1435             : void
    1436     4032000 : mesh_velocity_z(const int id, const dfloat value)
    1437             : {
    1438     4032000 :   nrs_t * nrs = (nrs_t *)nrsPtr();
    1439     4032000 :   nrs->usrwrk[indices.mesh_velocity_z + id] = value;
    1440     4032000 : }
    1441             : 
    1442             : void
    1443      195040 : checkFieldValidity(const field::NekFieldEnum & field)
    1444             : {
    1445             :   // by placing this check here, as opposed to inside the NekFieldInterface,
    1446             :   // we can also leverage this error checking for the 'outputs' of NekRSProblem,
    1447             :   // which does not inherit from NekFieldInterface but still accesses the solutionPointers.
    1448             :   // If this gets moved elsewhere, need to be sure to add dedicated testing for
    1449             :   // the 'outputs' on NekRSProblem.
    1450             : 
    1451             :   // TODO: would be nice for NekRSProblem to only access field information via the
    1452             :   // NekFieldInterface; refactor later
    1453             : 
    1454      195040 :   switch (field)
    1455             :   {
    1456       87372 :     case field::temperature:
    1457       87372 :       if (!hasTemperatureVariable())
    1458           1 :         mooseError("Cannot find 'temperature' "
    1459             :                    "because your Nek case files do not have a temperature variable!");
    1460             :       break;
    1461          85 :     case field::scalar01:
    1462          85 :       if (!hasScalarVariable(1))
    1463           1 :         mooseError("Cannot find 'scalar01' "
    1464             :                    "because your Nek case files do not have a scalar01 variable!");
    1465             :       break;
    1466         201 :     case field::scalar02:
    1467         201 :       if (!hasScalarVariable(2))
    1468           1 :         mooseError("Cannot find 'scalar02' "
    1469             :                    "because your Nek case files do not have a scalar02 variable!");
    1470             :       break;
    1471          57 :     case field::scalar03:
    1472          57 :       if (!hasScalarVariable(3))
    1473           1 :         mooseError("Cannot find 'scalar03' "
    1474             :                    "because your Nek case files do not have a scalar03 variable!");
    1475             :       break;
    1476         454 :     case field::usrwrk00:
    1477         454 :       if (n_usrwrk_slots < 1)
    1478           2 :         mooseError("Cannot find 'usrwrk00' because you have only allocated 'n_usrwrk_slots = " +
    1479           1 :                    std::to_string(n_usrwrk_slots) + "'");
    1480             :       break;
    1481          46 :     case field::usrwrk01:
    1482          46 :       if (n_usrwrk_slots < 2)
    1483           2 :         mooseError("Cannot find 'usrwrk01' because you have only allocated 'n_usrwrk_slots = " +
    1484           1 :                    std::to_string(n_usrwrk_slots) + "'");
    1485             :       break;
    1486          46 :     case field::usrwrk02:
    1487          46 :       if (n_usrwrk_slots < 3)
    1488           2 :         mooseError("Cannot find 'usrwrk02' because you have only allocated 'n_usrwrk_slots = " +
    1489           1 :                    std::to_string(n_usrwrk_slots) + "'");
    1490             :       break;
    1491             :   }
    1492      195033 : }
    1493             : 
    1494      191592 : double (*solutionPointer(const field::NekFieldEnum & field))(int)
    1495             : {
    1496             :   // we include this here as well, in addition to within the NekFieldInterface, because
    1497             :   // the NekRSProblem accesses these methods without inheriting from NekFieldInterface
    1498      191592 :   checkFieldValidity(field);
    1499             : 
    1500             :   double (*f)(int);
    1501             : 
    1502      191592 :   switch (field)
    1503             :   {
    1504             :     case field::velocity_x:
    1505             :       f = &velocity_x;
    1506             :       break;
    1507       17892 :     case field::velocity_y:
    1508             :       f = &velocity_y;
    1509       17892 :       break;
    1510       14984 :     case field::velocity_z:
    1511             :       f = &velocity_z;
    1512       14984 :       break;
    1513         196 :     case field::velocity:
    1514             :       f = &velocity;
    1515         196 :       break;
    1516           0 :     case field::velocity_component:
    1517           0 :       mooseError("The 'velocity_component' field is not compatible with the solutionPointer "
    1518             :                  "interface!");
    1519             :       break;
    1520          48 :     case field::velocity_x_squared:
    1521             :       f = &velocity_x_squared;
    1522          48 :       break;
    1523          48 :     case field::velocity_y_squared:
    1524             :       f = &velocity_y_squared;
    1525          48 :       break;
    1526          52 :     case field::velocity_z_squared:
    1527             :       f = &velocity_z_squared;
    1528          52 :       break;
    1529       86120 :     case field::temperature:
    1530             :       f = &temperature;
    1531       86120 :       break;
    1532       31004 :     case field::pressure:
    1533             :       f = &pressure;
    1534       31004 :       break;
    1535          48 :     case field::scalar01:
    1536             :       f = &scalar01;
    1537          48 :       break;
    1538         176 :     case field::scalar02:
    1539             :       f = &scalar02;
    1540         176 :       break;
    1541          32 :     case field::scalar03:
    1542             :       f = &scalar03;
    1543          32 :       break;
    1544        9148 :     case field::unity:
    1545             :       f = &unity;
    1546        9148 :       break;
    1547         420 :     case field::usrwrk00:
    1548             :       f = &usrwrk00;
    1549         420 :       break;
    1550          16 :     case field::usrwrk01:
    1551             :       f = &usrwrk01;
    1552          16 :       break;
    1553          16 :     case field::usrwrk02:
    1554             :       f = &usrwrk02;
    1555          16 :       break;
    1556           0 :     default:
    1557           0 :       throw std::runtime_error("Unhandled 'NekFieldEnum'!");
    1558             :   }
    1559             : 
    1560      191592 :   return f;
    1561             : }
    1562             : 
    1563     1862524 : void (*solutionPointer(const field::NekWriteEnum & field))(int, dfloat)
    1564             : {
    1565             :   void (*f)(int, dfloat);
    1566             : 
    1567     1862524 :   switch (field)
    1568             :   {
    1569             :     case field::flux:
    1570             :       f = &flux;
    1571             :       break;
    1572      396748 :     case field::heat_source:
    1573             :       f = &heat_source;
    1574      396748 :       break;
    1575        1024 :     case field::x_displacement:
    1576             :       f = &x_displacement;
    1577        1024 :       break;
    1578        1024 :     case field::y_displacement:
    1579             :       f = &y_displacement;
    1580        1024 :       break;
    1581        1024 :     case field::z_displacement:
    1582             :       f = &z_displacement;
    1583        1024 :       break;
    1584      179200 :     case field::mesh_velocity_x:
    1585             :       f = &mesh_velocity_x;
    1586      179200 :       break;
    1587      179200 :     case field::mesh_velocity_y:
    1588             :       f = &mesh_velocity_y;
    1589      179200 :       break;
    1590      179200 :     case field::mesh_velocity_z:
    1591             :       f = &mesh_velocity_z;
    1592      179200 :       break;
    1593           0 :     default:
    1594           0 :       throw std::runtime_error("Unhandled NekWriteEnum!");
    1595             :   }
    1596             : 
    1597     1862524 :   return f;
    1598             : }
    1599             : 
    1600             : void
    1601         115 : initializeDimensionalScales(const double U,
    1602             :                             const double T,
    1603             :                             const double dT,
    1604             :                             const double L,
    1605             :                             const double rho,
    1606             :                             const double Cp,
    1607             :                             const double s01,
    1608             :                             const double ds01,
    1609             :                             const double s02,
    1610             :                             const double ds02,
    1611             :                             const double s03,
    1612             :                             const double ds03)
    1613             : {
    1614         115 :   scales.U_ref = U;
    1615         115 :   scales.T_ref = T;
    1616         115 :   scales.dT_ref = dT;
    1617         115 :   scales.L_ref = L;
    1618         115 :   scales.A_ref = L * L;
    1619         115 :   scales.V_ref = L * L * L;
    1620         115 :   scales.rho_ref = rho;
    1621         115 :   scales.Cp_ref = Cp;
    1622         115 :   scales.t_ref = L / U;
    1623         115 :   scales.P_ref = rho * U * U;
    1624             : 
    1625         115 :   scales.s01_ref = s01;
    1626         115 :   scales.ds01_ref = ds01;
    1627         115 :   scales.s02_ref = s02;
    1628         115 :   scales.ds02_ref = ds02;
    1629         115 :   scales.s03_ref = s03;
    1630         115 :   scales.ds03_ref = ds03;
    1631             : 
    1632         115 :   scales.flux_ref = rho * U * Cp * dT;
    1633         115 :   scales.source_ref = scales.flux_ref / L;
    1634         115 : }
    1635             : 
    1636             : double
    1637         457 : referenceLength()
    1638             : {
    1639         457 :   return scales.L_ref;
    1640             : }
    1641             : 
    1642             : double
    1643      271566 : referenceTime()
    1644             : {
    1645      271566 :   return scales.t_ref;
    1646             : }
    1647             : 
    1648             : double
    1649         246 : referenceArea()
    1650             : {
    1651         246 :   return scales.A_ref;
    1652             : }
    1653             : 
    1654             : double
    1655        1132 : referenceVolume()
    1656             : {
    1657        1132 :   return scales.V_ref;
    1658             : }
    1659             : 
    1660             : Real
    1661    66768244 : nondimensionalAdditive(const field::NekFieldEnum & field)
    1662             : {
    1663    66768244 :   switch (field)
    1664             :   {
    1665    24750048 :     case field::temperature:
    1666    24750048 :       return scales.T_ref;
    1667        8660 :     case field::scalar01:
    1668        8660 :       return scales.s01_ref;
    1669       40904 :     case field::scalar02:
    1670       40904 :       return scales.s02_ref;
    1671        5576 :     case field::scalar03:
    1672        5576 :       return scales.s03_ref;
    1673             :     default:
    1674             :       return 0;
    1675             :   }
    1676             : }
    1677             : 
    1678             : Real
    1679       15350 : nondimensionalAdditive(const field::NekWriteEnum & field)
    1680             : {
    1681       15350 :   switch (field)
    1682             :   {
    1683       15350 :     case field::flux:
    1684             :     case field::heat_source:
    1685             :     case field::x_displacement:
    1686             :     case field::y_displacement:
    1687             :     case field::z_displacement:
    1688             :     case field::mesh_velocity_x:
    1689             :     case field::mesh_velocity_y:
    1690             :     case field::mesh_velocity_z:
    1691       15350 :       return 0.0;
    1692           0 :     default:
    1693           0 :       mooseError("Unhandled NekWriteEnum in nondimensionalAdditive!");
    1694             :   }
    1695             : }
    1696             : 
    1697             : Real
    1698       44752 : nondimensionalDivisor(const field::NekWriteEnum & field)
    1699             : {
    1700       44752 :   switch (field)
    1701             :   {
    1702       40341 :     case field::flux:
    1703       40341 :       return scales.flux_ref;
    1704        3507 :     case field::heat_source:
    1705        3507 :       return scales.source_ref;
    1706         904 :     case field::x_displacement:
    1707             :     case field::y_displacement:
    1708             :     case field::z_displacement:
    1709         904 :       return scales.L_ref;
    1710           0 :     case field::mesh_velocity_x:
    1711             :     case field::mesh_velocity_y:
    1712             :     case field::mesh_velocity_z:
    1713           0 :       return scales.U_ref;
    1714           0 :     default:
    1715           0 :       mooseError("Unhandled NekWriteEnum in nondimensionalDivisor!");
    1716             :   }
    1717             : }
    1718             : 
    1719             : Real
    1720    67305958 : nondimensionalDivisor(const field::NekFieldEnum & field)
    1721             : {
    1722    67305958 :   switch (field)
    1723             :   {
    1724    32127380 :     case field::velocity_x:
    1725             :     case field::velocity_y:
    1726             :     case field::velocity_z:
    1727             :     case field::velocity:
    1728             :     case field::velocity_component:
    1729    32127380 :       return scales.U_ref;
    1730        5336 :     case field::velocity_x_squared:
    1731             :     case field::velocity_y_squared:
    1732             :     case field::velocity_z_squared:
    1733        5336 :       return scales.U_ref * scales.U_ref;
    1734    24750048 :     case field::temperature:
    1735    24750048 :       return scales.dT_ref;
    1736    10342943 :     case field::pressure:
    1737    10342943 :       return scales.P_ref;
    1738        8660 :     case field::scalar01:
    1739        8660 :       return scales.ds01_ref;
    1740       40904 :     case field::scalar02:
    1741       40904 :       return scales.ds02_ref;
    1742        5576 :     case field::scalar03:
    1743        5576 :       return scales.ds03_ref;
    1744             :     case field::unity:
    1745             :       // no dimensionalization needed
    1746             :       return 1.0;
    1747         445 :     case field::usrwrk00:
    1748         445 :       return scratchUnits(0);
    1749          29 :     case field::usrwrk01:
    1750          29 :       return scratchUnits(1);
    1751          29 :     case field::usrwrk02:
    1752          29 :       return scratchUnits(2);
    1753           0 :     default:
    1754           0 :       throw std::runtime_error("Unhandled 'NekFieldEnum'!");
    1755             :   }
    1756             : }
    1757             : 
    1758             : Real
    1759         503 : scratchUnits(const int slot)
    1760             : {
    1761         503 :   if (indices.flux != -1 && slot == indices.flux / nekrs::fieldOffset())
    1762         424 :     return scales.flux_ref;
    1763          79 :   else if (indices.heat_source != -1 && slot == indices.heat_source / nekrs::fieldOffset())
    1764           4 :     return scales.source_ref;
    1765          75 :   else if (is_nondimensional)
    1766             :   {
    1767             :     // TODO: we are lazy and did not include all the usrwrk indices
    1768          66 :     mooseDoOnce(mooseWarning(
    1769             :         "The units of 'usrwrk0" + std::to_string(slot) +
    1770             :         "' are unknown, so we cannot dimensionalize any objects using 'field = usrwrk0" +
    1771             :         std::to_string(slot) +
    1772             :         "'. The output for this quantity will be given in non-dimensional form.\n\nYou will need "
    1773             :         "to manipulate the data manually from Cardinal if you need to dimensionalize it."));
    1774             :   }
    1775             : 
    1776             :   return 1.0;
    1777             : }
    1778             : 
    1779             : void
    1780         799 : nondimensional(const bool n)
    1781             : {
    1782         799 :   is_nondimensional = n;
    1783         799 : }
    1784             : 
    1785             : template <>
    1786             : MPI_Datatype
    1787      182672 : resolveType<double>()
    1788             : {
    1789      182672 :   return MPI_DOUBLE;
    1790             : }
    1791             : 
    1792             : template <>
    1793             : MPI_Datatype
    1794        7024 : resolveType<int>()
    1795             : {
    1796        7024 :   return MPI_INT;
    1797             : }
    1798             : 
    1799             : } // end namespace nekrs
    1800             : 
    1801             : #endif

Generated by: LCOV version 1.14