LCOV - code coverage report
Current view: top level - src/base - NekRSProblem.C (source / functions) Hit Total Coverage
Test: neams-th-coe/cardinal: be601f Lines: 452 477 94.8 %
Date: 2025-07-15 20:50:38 Functions: 27 27 100.0 %
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 "NekRSProblem.h"
      22             : #include "CardinalUtils.h"
      23             : #include "DimensionalizeAction.h"
      24             : #include "FieldTransferBase.h"
      25             : #include "NekMeshDeformation.h"
      26             : 
      27             : registerMooseObject("CardinalApp", NekRSProblem);
      28             : 
      29             : bool NekRSProblem::_first = true;
      30             : 
      31             : InputParameters
      32        2421 : NekRSProblem::validParams()
      33             : {
      34        2421 :   InputParameters params = CardinalProblem::validParams();
      35        4842 :   params.addRequiredParam<std::string>(
      36             :       "casename",
      37             :       "Case name for the NekRS input files; "
      38             :       "this is <case> in <case>.par, <case>.udf, <case>.oudf, and <case>.re2.");
      39             : 
      40        4842 :   params.addParam<unsigned int>(
      41             :       "n_usrwrk_slots",
      42        4842 :       7,
      43             :       "Number of slots to allocate in nrs->usrwrk to hold fields either related to coupling "
      44             :       "(which will be populated by Cardinal), or other custom usages, such as a distance-to-wall "
      45             :       "calculation (which will be populated by the user from the case files)");
      46             : 
      47        4842 :   params.addParam<std::vector<unsigned int>>(
      48             :       "usrwrk_output",
      49             :       "Usrwrk slot(s) to output to NekRS field files; this can be used for viewing the quantities "
      50             :       "passed from MOOSE to NekRS after interpolation to the CFD mesh. Can also be used for any "
      51             :       "slots "
      52             :       "in usrwrk that are written by the user, but unused for coupling.");
      53        4842 :   params.addParam<std::vector<std::string>>(
      54             :       "usrwrk_output_prefix",
      55             :       "String prefix to use for naming the field file(s); "
      56             :       "only the first three characters are used in the name based on limitations in NekRS");
      57             : 
      58        4842 :   params.addParam<bool>(
      59             :       "write_fld_files",
      60        4842 :       false,
      61             :       "Whether to write NekRS field file output "
      62             :       "from Cardinal. If true, this will disable any output writing by NekRS itself, and "
      63             :       "instead produce output files with names a01...a99pin, b01...b99pin, etc.");
      64        4842 :   params.addParam<bool>(
      65        4842 :       "disable_fld_file_output", false, "Whether to turn off all NekRS field file output writing");
      66             : 
      67        4842 :   params.addParam<bool>("skip_final_field_file",
      68        4842 :                         false,
      69             :                         "By default, we write a NekRS field file "
      70             :                         "on the last time step; set this to true to disable");
      71             : 
      72        4842 :   params.addParam<MooseEnum>(
      73             :       "synchronization_interval",
      74        4842 :       getSynchronizationEnum(),
      75             :       "When to synchronize the NekRS solution with the mesh mirror. By default, the NekRS solution "
      76             :       "is mapped to/receives data from the mesh mirror for every time step.");
      77        4842 :   params.addParam<unsigned int>("constant_interval",
      78        4842 :                                 1,
      79             :                                 "Constant interval (in units of number of time steps) with which "
      80             :                                 "to synchronize the NekRS solution");
      81        2421 :   return params;
      82           0 : }
      83             : 
      84         799 : NekRSProblem::NekRSProblem(const InputParameters & params)
      85             :   : CardinalProblem(params),
      86         799 :     _serialized_solution(NumericVector<Number>::build(_communicator).release()),
      87        1598 :     _casename(getParam<std::string>("casename")),
      88        1598 :     _write_fld_files(getParam<bool>("write_fld_files")),
      89        1598 :     _disable_fld_file_output(getParam<bool>("disable_fld_file_output")),
      90        1598 :     _n_usrwrk_slots(getParam<unsigned int>("n_usrwrk_slots")),
      91        1598 :     _constant_interval(getParam<unsigned int>("constant_interval")),
      92        1598 :     _skip_final_field_file(getParam<bool>("skip_final_field_file")),
      93         799 :     _start_time(nekrs::startTime()),
      94         799 :     _elapsedStepSum(0.0),
      95        1598 :     _elapsedTime(nekrs::getNekSetupTime()),
      96         799 :     _tSolveStepMin(std::numeric_limits<double>::max()),
      97        1598 :     _tSolveStepMax(std::numeric_limits<double>::min())
      98             : {
      99         799 :   const auto & actions = getMooseApp().actionWarehouse().getActions<DimensionalizeAction>();
     100         799 :   _nondimensional = actions.size();
     101         799 :   nekrs::nondimensional(_nondimensional);
     102             : 
     103        1598 :   _sync_interval = getParam<MooseEnum>("synchronization_interval")
     104             :                        .getEnum<synchronization::SynchronizationEnum>();
     105         799 :   if (_sync_interval == synchronization::parent_app)
     106             :   {
     107             :     // the way the data transfers are detected depend on nekRS being a sub-application,
     108             :     // so these settings are not invalid if nekRS is the master app (though you could
     109             :     // relax this in the future by reversing the synchronization step identification
     110             :     // from the nekRS-subapp case to the nekRS-master app case - it's just not implemented yet).
     111          47 :     if (_app.isUltimateMaster())
     112             :     {
     113           0 :       mooseWarning("The 'synchronization_interval = parent_app' capability "
     114             :                    "requires that nekRS is receiving and sending data to a parent application, but "
     115             :                    "in your case nekRS is the main application.\n\n"
     116             :                    "We are reverting synchronization_interval to 'constant'.");
     117           0 :       _sync_interval = synchronization::constant;
     118             :     }
     119             : 
     120          94 :     checkUnusedParam(params, "constant_interval", "synchronizing based on the 'parent_app'");
     121             :   }
     122             : 
     123         799 :   if (_disable_fld_file_output && _write_fld_files)
     124           0 :     mooseError("Cannot both disable all field file output and write custom field files! "
     125             :                "'write_fld_files' and 'disable_fld_file_output' cannot both be true!");
     126             : 
     127         799 :   if (_app.isUltimateMaster() && _write_fld_files)
     128           1 :     mooseError("The 'write_fld_files' setting should only be true when multiple Nek simulations "
     129             :                "are run as sub-apps on a master app. Your input has Nek as the master app.");
     130             : 
     131         798 :   _nek_mesh = dynamic_cast<NekRSMesh *>(&mesh());
     132             : 
     133         798 :   if (!_nek_mesh)
     134           2 :     mooseError("The mesh for NekRSProblem must be of type 'NekRSMesh', but you have specified a '" +
     135           0 :                mesh().type() + "'!");
     136             : 
     137             :   // The mesh movement error checks are triggered based on whether the NekRS input files
     138             :   // have a moving mesh. From there, we impose the necessary checks on the [Mesh] block
     139             :   // and the existence of the NekMeshDeformation object.
     140         797 :   if (nekrs::hasMovingMesh())
     141             :   {
     142          50 :     if (!_nek_mesh->getMesh().is_replicated())
     143           0 :       mooseError("Distributed mesh features are not yet implemented for moving mesh cases!");
     144             :   }
     145             : 
     146         797 :   _moose_Nq = _nek_mesh->order() + 2;
     147             : 
     148             :   // the Problem constructor is called right after building the mesh. In order
     149             :   // to have pretty screen output without conflicting with the timed print messages,
     150             :   // print diagnostic info related to the mesh here. If running in JIT mode, this
     151             :   // diagnostic info was never set, so the numbers that would be printed are garbage.
     152         797 :   if (!nekrs::buildOnly())
     153         797 :     _nek_mesh->printMeshInfo();
     154             : 
     155             :   // boundary-specific data
     156         797 :   _n_surface_elems = _nek_mesh->numSurfaceElems();
     157         797 :   _n_vertices_per_surface = _nek_mesh->numVerticesPerSurface();
     158             : 
     159             :   // volume-specific data
     160         797 :   _n_vertices_per_volume = _nek_mesh->numVerticesPerVolume();
     161             : 
     162         797 :   if (_nek_mesh->volume())
     163         481 :     _n_points =
     164         481 :         _nek_mesh->numVolumeElems() * _n_vertices_per_volume * _nek_mesh->nBuildPerVolumeElem();
     165             :   else
     166         316 :     _n_points = _n_surface_elems * _n_vertices_per_surface * _nek_mesh->nBuildPerSurfaceElem();
     167             : 
     168         797 :   initializeInterpolationMatrices();
     169             : 
     170             :   // we can save some effort for the low-order situations where the interpolation
     171             :   // matrix is the identity matrix (i.e. for which equi-spaced libMesh nodes are an
     172             :   // exact subset of the nekRS GLL points). This will happen for any first-order mesh,
     173             :   // and if a second-order mesh is used with a polynomial order of 2 in nekRS. Because
     174             :   // we pretty much always use a polynomial order greater than 2 in nekRS, let's just
     175             :   // check the first case because this will simplify our code in the nekrs::boundarySolution
     176             :   // function. If you change this line, you MUST change the innermost if/else statement
     177             :   // in nekrs::boundarySolution!
     178         797 :   _needs_interpolation = _nek_mesh->numQuadraturePoints1D() > 2;
     179             : 
     180        3188 :   checkJointParams(
     181             :       params, {"usrwrk_output", "usrwrk_output_prefix"}, "outputting usrwrk slots to field files");
     182             : 
     183        1594 :   if (isParamValid("usrwrk_output"))
     184             :   {
     185          52 :     _usrwrk_output = &getParam<std::vector<unsigned int>>("usrwrk_output");
     186          52 :     _usrwrk_output_prefix = &getParam<std::vector<std::string>>("usrwrk_output_prefix");
     187             : 
     188          64 :     for (const auto & s : *_usrwrk_output)
     189          39 :       if (s >= _n_usrwrk_slots)
     190           1 :         mooseError("Cannot write field file for usrwrk slot greater than the total number of "
     191             :                    "allocated slots: ",
     192           1 :                    _n_usrwrk_slots,
     193             :                    "!");
     194             : 
     195          25 :     if (_usrwrk_output->size() != _usrwrk_output_prefix->size())
     196           1 :       mooseError("The length of 'usrwrk_output' must match the length of 'usrwrk_output_prefix'!");
     197             :   }
     198        2389 : }
     199             : 
     200        1468 : NekRSProblem::~NekRSProblem()
     201             : {
     202             :   // write nekRS solution to output if not already written for this step; nekRS does this
     203             :   // behavior, so we duplicate it
     204         734 :   if (!_is_output_step && !_skip_final_field_file)
     205             :   {
     206         190 :     if (_write_fld_files)
     207           0 :       mooseWarning(
     208             :           "When 'write_fld_files' is enabled, we skip Nek field file writing on end time!\n"
     209             :           "Depending on how many ranks you used, MOOSE may use the same object to run multiple\n"
     210             :           "sub-applications. By the time we get to the last time step, we've collapsed back to\n"
     211             :           "this singular state and don't have access to the individual Nek solves, so we cannot\n"
     212             :           "write the last time step solution to field files.\n\n"
     213             :           "To hide this warning, set 'skip_final_field_file = true'.");
     214             :     else
     215         190 :       writeFieldFile(_time, _t_step);
     216             :   }
     217             : 
     218         734 :   if (nekrs::runTimeStatFreq())
     219         734 :     if (_t_step % nekrs::runTimeStatFreq())
     220         734 :       nekrs::printRuntimeStatistics(_t_step);
     221             : 
     222         734 :   freePointer(_interpolation_outgoing);
     223         734 :   freePointer(_interpolation_incoming);
     224         734 :   nekrs::freeScratch();
     225             : 
     226         734 :   nekrs::finalize();
     227        1468 : }
     228             : 
     229             : void
     230        2197 : NekRSProblem::writeFieldFile(const Real & step_end_time, const int & step) const
     231             : {
     232        2197 :   if (_disable_fld_file_output)
     233             :     return;
     234             : 
     235        2197 :   Real t = _timestepper->nondimensionalDT(step_end_time);
     236             : 
     237        2197 :   if (_write_fld_files)
     238             :   {
     239             :     // this is the app number, but a single app may run Nek multiple times
     240          56 :     auto app_number = std::to_string(_app.multiAppNumber());
     241             : 
     242             :     // apps may also have numbers in their names, so we first need to get the actual raw app name;
     243             :     // we strip out the app_number from the end of the app name
     244          56 :     if (!stringHasEnding(_app.name(), app_number))
     245           0 :       mooseError("Internal error: app name '" + _app.name() +
     246           0 :                  "' does not end with app number: " + app_number);
     247             : 
     248          56 :     auto name = _app.name().substr(0, _app.name().size() - app_number.size());
     249          56 :     auto full_path = _app.getOutputFileBase();
     250          56 :     std::string last_element(full_path.substr(full_path.rfind(name) + name.size()));
     251             : 
     252          56 :     auto prefix = fieldFilePrefix(std::stoi(last_element));
     253             : 
     254          56 :     nekrs::write_field_file(prefix, t, step);
     255             :   }
     256             :   else
     257        2141 :     nekrs::outfld(t, step);
     258             : }
     259             : 
     260             : void
     261         797 : NekRSProblem::initializeInterpolationMatrices()
     262             : {
     263         797 :   mesh_t * mesh = nekrs::entireMesh();
     264             : 
     265             :   // determine the interpolation matrix for the outgoing transfer
     266         797 :   int starting_points = mesh->Nq;
     267         797 :   int ending_points = _nek_mesh->numQuadraturePoints1D();
     268         797 :   _interpolation_outgoing = (double *)calloc(starting_points * ending_points, sizeof(double));
     269         797 :   nekrs::interpolationMatrix(_interpolation_outgoing, starting_points, ending_points);
     270             : 
     271             :   // determine the interpolation matrix for the incoming transfer
     272             :   std::swap(starting_points, ending_points);
     273         797 :   _interpolation_incoming = (double *)calloc(starting_points * ending_points, sizeof(double));
     274         797 :   nekrs::interpolationMatrix(_interpolation_incoming, starting_points, ending_points);
     275         797 : }
     276             : 
     277             : std::string
     278          56 : NekRSProblem::fieldFilePrefix(const int & number) const
     279             : {
     280          56 :   const std::string alphabet = "abcdefghijklmnopqrstuvwxyz";
     281          56 :   int letter = number / 26;
     282          56 :   int remainder = number % 100;
     283          56 :   std::string s = remainder < 10 ? "0" : "";
     284             : 
     285         168 :   return alphabet[letter] + s + std::to_string(remainder);
     286             : }
     287             : 
     288             : void
     289         744 : NekRSProblem::initialSetup()
     290             : {
     291         744 :   CardinalProblem::initialSetup();
     292             : 
     293         744 :   auto executioner = _app.getExecutioner();
     294         744 :   _transient_executioner = dynamic_cast<Transient *>(executioner);
     295             : 
     296             :   // NekRS only supports transient simulations - therefore, it does not make
     297             :   // sense to use anything except a Transient-derived executioner
     298         744 :   if (!_transient_executioner)
     299           2 :     mooseError(
     300           1 :         "A 'Transient' executioner must be used with NekRSProblem, but you have specified the '" +
     301             :         executioner->type() + "' executioner!");
     302             : 
     303             :   // To get the correct time stepping information on the MOOSE side, we also
     304             :   // must use the NekTimeStepper
     305             :   TimeStepper * stepper = _transient_executioner->getTimeStepper();
     306         743 :   _timestepper = dynamic_cast<NekTimeStepper *>(stepper);
     307         743 :   if (!_timestepper)
     308           2 :     mooseError("The 'NekTimeStepper' stepper must be used with NekRSProblem, but you have "
     309           1 :                "specified the '" +
     310             :                stepper->type() + "' time stepper!");
     311             : 
     312             :   // Set the NekRS start time to whatever is set on Executioner/start_time; print
     313             :   // a message if those times don't match the .par file
     314         742 :   const auto moose_start_time = _transient_executioner->getStartTime();
     315         742 :   nekrs::setStartTime(_timestepper->nondimensionalDT(moose_start_time));
     316         742 :   _start_time = moose_start_time;
     317             : 
     318         742 :   if (_sync_interval == synchronization::parent_app)
     319          47 :     _transfer_in = &getPostprocessorValueByName("transfer_in");
     320             : 
     321             :   // Find all of the data transfer objects
     322         742 :   TheWarehouse::Query query = theWarehouse().query().condition<AttribSystem>("FieldTransfer");
     323         742 :   query.queryInto(_field_transfers);
     324             : 
     325             :   // Find all of the scalar data transfer objects
     326         742 :   TheWarehouse::Query uo_query = theWarehouse().query().condition<AttribSystem>("ScalarTransfer");
     327         742 :   uo_query.queryInto(_scalar_transfers);
     328             : 
     329             :   // We require a NekMeshDeformation object to exist if the NekRS model has a moving mesh
     330         742 :   if (nekrs::hasMovingMesh())
     331             :   {
     332             :     bool has_deformation = false;
     333         153 :     for (const auto & t : _field_transfers)
     334             :     {
     335         106 :       NekMeshDeformation * deform = dynamic_cast<NekMeshDeformation *>(t);
     336         106 :       if (deform)
     337             :         has_deformation = true;
     338             :     }
     339             : 
     340          47 :     if (has_deformation && !_app.actionWarehouse().displacedMesh())
     341           1 :       mooseError("Moving mesh problems require 'displacements' in the [Mesh] block! The names of "
     342             :                  "the 'displacements' variables must match the variables created by a "
     343             :                  "NekMeshDeformation object.");
     344             :   }
     345             : 
     346             :   // save initial mesh for moving mesh problems to match deformation in exodus output files
     347         741 :   if (nekrs::hasMovingMesh() && !_disable_fld_file_output)
     348          46 :     nekrs::outfld(_timestepper->nondimensionalDT(_time), _t_step);
     349             : 
     350             :   VariadicTable<int, std::string, std::string, std::string> vt(
     351        3705 :       {"Slot", "MOOSE quantity", "How to Access (.oudf)", "How to Access (.udf)"});
     352             : 
     353             :   // fill a set with all of the slots managed by Cardinal, coming from either field transfers
     354             :   // or userobjects
     355             :   auto field_usrwrk_map = FieldTransferBase::usrwrkMap();
     356        1191 :   for (const auto & field : field_usrwrk_map)
     357         450 :     _usrwrk_slots.insert(field.first);
     358         790 :   for (const auto & uo : _scalar_transfers)
     359          49 :     _usrwrk_slots.insert(uo->usrwrkSlot());
     360             : 
     361             :   // fill out table, being careful to only write information if owned by a field transfer,
     362             :   // a user object, or neither
     363        5443 :   for (int i = 0; i < _n_usrwrk_slots; ++i)
     364             :   {
     365        4702 :     std::string oudf = "bc->usrwrk[" + std::to_string(i) + " * bc->fieldOffset+bc->idM]";
     366        4702 :     std::string udf = "nrs->usrwrk[" + std::to_string(i) + " * nrs->fieldOffset + n]";
     367             : 
     368        4702 :     if (field_usrwrk_map.find(i) != field_usrwrk_map.end())
     369             :     {
     370             :       // a field transfer owns it
     371         900 :       vt.addRow(i, field_usrwrk_map[i], oudf, udf);
     372             :     }
     373             :     else
     374             :     {
     375             :       // a user object might own it, or it could be unused
     376             :       bool owned_by_uo = false;
     377        4461 :       for (const auto & uo : _scalar_transfers)
     378             :       {
     379         209 :         if (uo->usrwrkSlot() == i)
     380             :         {
     381             :           owned_by_uo = true;
     382          49 :           auto slot = std::to_string(uo->usrwrkSlot());
     383          49 :           auto count = std::to_string(uo->offset());
     384          98 :           vt.addRow(i,
     385          49 :                     uo->name(),
     386         147 :                     "bc->usrwrk[" + slot + " * bc->fieldOffset + " + count + "]",
     387         147 :                     "nrs->usrwrk[" + slot + " * nrs->fieldOffset + " + count + "]");
     388             :         }
     389             :       }
     390             : 
     391        4252 :       if (!owned_by_uo)
     392        8422 :         vt.addRow(i, "unused", oudf, udf);
     393             :     }
     394             :   }
     395             : 
     396         741 :   if (_n_usrwrk_slots == 0)
     397             :   {
     398          20 :     _console << "Skipping allocation of NekRS scratch space because 'n_usrwrk_slots' is 0\n"
     399          20 :              << std::endl;
     400             :   }
     401             :   else
     402             :   {
     403             :     _console
     404         721 :         << "\n ===================>     MAPPING FROM MOOSE TO NEKRS      <===================\n"
     405         721 :         << std::endl;
     406         721 :     _console << "           Slot:  slice in scratch space holding the data" << std::endl;
     407         721 :     _console << " MOOSE quantity:  name of the AuxVariable or Postprocessor that gets written into"
     408         721 :              << std::endl;
     409         721 :     _console << "                  this slot. If 'unused', this means that the space has been"
     410         721 :              << std::endl;
     411         721 :     _console << "                  allocated, but Cardinal is not otherwise using it for coupling"
     412         721 :              << std::endl;
     413         721 :     _console << "  How to Access:  C++ code to use in NekRS files; for the .udf instructions,"
     414         721 :              << std::endl;
     415         721 :     _console << "                  'n' indicates a loop variable over GLL points\n" << std::endl;
     416         721 :     vt.print(_console);
     417         721 :     _console << std::endl;
     418             :   }
     419             : 
     420             :   // nekRS calls UDF_ExecuteStep once before the time stepping begins; the isLastStep stuff is
     421             :   // copy-pasta from NekRS main(), except that if Nek is a sub-app, we give full control of
     422             :   // time stepping to the main app
     423             :   bool isLastStep = false;
     424         741 :   if (_app.isUltimateMaster())
     425         407 :     isLastStep = !((nekrs::endTime() > nekrs::startTime() || nekrs::numSteps() > _t_step));
     426         741 :   nekrs::lastStep(isLastStep);
     427             : 
     428         741 :   nekrs::udfExecuteStep(
     429         741 :       _timestepper->nondimensionalDT(_start_time), _t_step, false /* not an output step */);
     430        1482 :   nekrs::resetTimer("udfExecuteStep");
     431        2223 : }
     432             : 
     433             : void
     434       33358 : NekRSProblem::externalSolve()
     435             : {
     436       33358 :   if (nekrs::buildOnly())
     437           0 :     return;
     438             : 
     439       33358 :   const double timeStartStep = MPI_Wtime();
     440             : 
     441             :   // _dt reflects the time step that MOOSE wants Nek to
     442             :   // take. For instance, if Nek is controlled by a master app and subcycling is used,
     443             :   // Nek must advance to the time interval taken by the master app. If the time step
     444             :   // that MOOSE wants nekRS to take (i.e. _dt) is smaller than we'd like nekRS to take, error.
     445       33358 :   if (_dt < _timestepper->minDT())
     446           0 :     mooseError("Requested time step of " + std::to_string(_dt) +
     447             :                " is smaller than the minimum "
     448           0 :                "time step of " +
     449           0 :                Moose::stringify(_timestepper->minDT()) +
     450             :                " allowed in NekRS!\n\n"
     451             :                "You can control this behavior with the 'min_dt' parameter on 'NekTimeStepper'.");
     452             : 
     453             :   // _time represents the time that we're simulating _to_, but we need to pass sometimes slightly
     454             :   // different times into the nekRS routines, which assume that the "time" passed into their
     455             :   // routines is sometimes a different interpretation.
     456       33358 :   double step_start_time = _time - _dt;
     457       33358 :   double step_end_time = _time;
     458             : 
     459       33358 :   _is_output_step = isOutputStep();
     460             : 
     461             :   // tell NekRS what the value of nrs->isOutputStep should be
     462       33358 :   nekrs::outputStep(_is_output_step);
     463             : 
     464             :   // NekRS prints out verbose info for the first 1000 time steps
     465       33358 :   if (_t_step <= 1000)
     466       24982 :     nekrs::verboseInfo(true);
     467             : 
     468             :   // Tell NekRS what the time step size is
     469       33358 :   nekrs::initStep(_timestepper->nondimensionalDT(step_start_time),
     470       33358 :                   _timestepper->nondimensionalDT(_dt),
     471       33358 :                   _t_step);
     472             : 
     473             :   // Run a nekRS time step. After the time step, this also calls UDF_ExecuteStep,
     474             :   // evaluated at (step_end_time, _t_step) == (nek_step_start_time + nek_dt, t_step)
     475             :   int corrector = 1;
     476             :   bool converged = false;
     477             :   do
     478             :   {
     479       33358 :     converged = nekrs::runStep(corrector++);
     480       33358 :   } while (!converged);
     481             : 
     482             :   // TODO: time is somehow corrected here
     483       33358 :   nekrs::finishStep();
     484             : 
     485             :   // copy-pasta from Nek's main() for calling timers and printing
     486       33358 :   if (nekrs::updateFileCheckFreq())
     487       33358 :     if (_t_step % nekrs::updateFileCheckFreq())
     488       31847 :       nekrs::processUpdFile();
     489             : 
     490             :   // Note: here, we copy to both the nrs solution arrays and to the Nek5000 backend arrays,
     491             :   // because it is possible that users may interact using the legacy usr-file approach.
     492             :   // If we move away from the Nek5000 backend entirely, we could replace this line with
     493             :   // direct OCCA memcpy calls. But we do definitely need some type of copy here for _every_
     494             :   // time step, even if we're not technically passing data to another app, because we have
     495             :   // postprocessors that touch the `nrs` arrays that can be called in an arbitrary fashion
     496             :   // by the user.
     497       33358 :   nek::ocopyToNek(_timestepper->nondimensionalDT(step_end_time), _t_step);
     498             : 
     499       33358 :   if (nekrs::printInfoFreq())
     500       33358 :     if (_t_step % nekrs::printInfoFreq() == 0)
     501       33358 :       nekrs::printInfo(_timestepper->nondimensionalDT(_time), _t_step, false, true);
     502             : 
     503       33358 :   if (_is_output_step)
     504             :   {
     505        2007 :     writeFieldFile(step_end_time, _t_step);
     506             : 
     507             :     // TODO: I could not figure out why this can't be called from the destructor, to
     508             :     // add another field file on Cardinal's last time step. Revisit in the future.
     509        2007 :     if (_usrwrk_output)
     510             :     {
     511          32 :       static std::vector<bool> first_fld(_usrwrk_output->size(), true);
     512             : 
     513          84 :       for (unsigned int i = 0; i < _usrwrk_output->size(); ++i)
     514             :       {
     515          52 :         bool write_coords = first_fld[i] ? true : false;
     516             : 
     517          52 :         nekrs::write_usrwrk_field_file((*_usrwrk_output)[i],
     518          52 :                                        (*_usrwrk_output_prefix)[i],
     519          52 :                                        _timestepper->nondimensionalDT(step_end_time),
     520          52 :                                        _t_step,
     521             :                                        write_coords);
     522             : 
     523             :         first_fld[i] = false;
     524             :       }
     525             :     }
     526             :   }
     527             : 
     528       33358 :   MPI_Barrier(comm().get());
     529       33358 :   const double elapsedStep = MPI_Wtime() - timeStartStep;
     530       33358 :   _tSolveStepMin = std::min(elapsedStep, _tSolveStepMin);
     531       33358 :   _tSolveStepMax = std::max(elapsedStep, _tSolveStepMax);
     532       33358 :   nekrs::updateTimer("minSolveStep", _tSolveStepMin);
     533       33358 :   nekrs::updateTimer("maxSolveStep", _tSolveStepMax);
     534             : 
     535       33358 :   _elapsedStepSum += elapsedStep;
     536       33358 :   _elapsedTime += elapsedStep;
     537       33358 :   nekrs::updateTimer("elapsedStep", elapsedStep);
     538       33358 :   nekrs::updateTimer("elapsedStepSum", _elapsedStepSum);
     539       33358 :   nekrs::updateTimer("elapsed", _elapsedTime);
     540             : 
     541       33358 :   if (nekrs::printInfoFreq())
     542       33358 :     if (_t_step % nekrs::printInfoFreq() == 0)
     543       33358 :       nekrs::printInfo(_timestepper->nondimensionalDT(_time), _t_step, true, false);
     544             : 
     545       33358 :   if (nekrs::runTimeStatFreq())
     546       33358 :     if (_t_step % nekrs::runTimeStatFreq() == 0)
     547          32 :       nekrs::printRuntimeStatistics(_t_step);
     548             : 
     549       33358 :   _time += _dt;
     550             : }
     551             : 
     552             : bool
     553       66719 : NekRSProblem::isDataTransferHappening(ExternalProblem::Direction direction)
     554             : {
     555       66719 :   if (nekrs::buildOnly())
     556             :     return false;
     557             : 
     558       66719 :   switch (direction)
     559             :   {
     560       33361 :     case ExternalProblem::Direction::TO_EXTERNAL_APP:
     561       33361 :       return synchronizeIn();
     562       33358 :     case ExternalProblem::Direction::FROM_EXTERNAL_APP:
     563       33358 :       return synchronizeOut();
     564           0 :     default:
     565           0 :       mooseError("Unhandled DirectionEnum in NekRSProblem!");
     566             :   }
     567             : }
     568             : 
     569             : void
     570       66719 : NekRSProblem::syncSolutions(ExternalProblem::Direction direction)
     571             : {
     572       66719 :   auto & solution = _aux->solution();
     573             : 
     574       66719 :   if (!isDataTransferHappening(direction))
     575             :     return;
     576             : 
     577       64746 :   switch (direction)
     578             :   {
     579       32374 :     case ExternalProblem::Direction::TO_EXTERNAL_APP:
     580             :     {
     581       32374 :       if (_first)
     582             :       {
     583         740 :         _serialized_solution->init(_aux->sys().n_dofs(), false, SERIAL);
     584         740 :         _first = false;
     585             :       }
     586             : 
     587       32374 :       solution.localize(*_serialized_solution);
     588             : 
     589             :       // execute all incoming field transfers
     590      136154 :       for (const auto & t : _field_transfers)
     591      103782 :         if (t->direction() == "to_nek")
     592       15350 :           t->sendDataToNek();
     593             : 
     594             :       // execute all incoming scalar transfers
     595       33022 :       for (const auto & t : _scalar_transfers)
     596         650 :         if (t->direction() == "to_nek")
     597         650 :           t->sendDataToNek();
     598             : 
     599       32372 :       if (udf.properties)
     600             :       {
     601       16141 :         nrs_t * nrs = (nrs_t *)nekrs::nrsPtr();
     602       16141 :         evaluateProperties(nrs, _timestepper->nondimensionalDT(_time));
     603             :       }
     604             : 
     605       32372 :       copyScratchToDevice();
     606             : 
     607       32372 :       break;
     608             : 
     609             :       return;
     610             :     }
     611       32372 :     case ExternalProblem::Direction::FROM_EXTERNAL_APP:
     612             :     {
     613             :       // execute all outgoing field transfers
     614      136152 :       for (const auto & t : _field_transfers)
     615      103780 :         if (t->direction() == "from_nek")
     616       88432 :           t->readDataFromNek();
     617             : 
     618             :       // execute all outgoing scalar transfers
     619       33022 :       for (const auto & t : _scalar_transfers)
     620         650 :         if (t->direction() == "from_nek")
     621           0 :           t->sendDataToNek();
     622             : 
     623             :       break;
     624             :     }
     625           0 :     default:
     626           0 :       mooseError("Unhandled Transfer::DIRECTION enum!");
     627             :   }
     628             : 
     629       64744 :   solution.close();
     630       64744 :   _aux->system().update();
     631             : }
     632             : 
     633             : bool
     634       33361 : NekRSProblem::synchronizeIn()
     635             : {
     636             :   bool synchronize = true;
     637             : 
     638       33361 :   switch (_sync_interval)
     639             :   {
     640         977 :     case synchronization::parent_app:
     641             :     {
     642             :       // For the minimized incoming synchronization to work correctly, the value
     643             :       // of the incoming postprocessor must not be zero. We only need to check this for the very
     644             :       // first time we evaluate this function. This ensures that you don't accidentally set a
     645             :       // zero value as a default in the master application's postprocessor.
     646         977 :       if (_first && *_transfer_in == false)
     647           1 :         mooseError("The default value for the 'transfer_in' postprocessor received by nekRS "
     648             :                    "must not be false! Make sure that the master application's "
     649             :                    "postprocessor is not zero.");
     650             : 
     651         976 :       if (*_transfer_in == false)
     652             :         synchronize = false;
     653             :       else
     654         828 :         setPostprocessorValueByName("transfer_in", false, 0);
     655             : 
     656             :       break;
     657             :     }
     658       32384 :     case synchronization::constant:
     659             :     {
     660       32384 :       synchronize = timeStep() % _constant_interval == 0;
     661       32384 :       break;
     662             :     }
     663           0 :     default:
     664           0 :       mooseError("Unhandled SynchronizationEnum in NekRSProblem!");
     665             :   }
     666             : 
     667       33360 :   return synchronize;
     668             : }
     669             : 
     670             : bool
     671       33358 : NekRSProblem::synchronizeOut()
     672             : {
     673             :   bool synchronize = true;
     674             : 
     675       33358 :   switch (_sync_interval)
     676             :   {
     677         976 :     case synchronization::parent_app:
     678             :     {
     679         976 :       if (std::abs(_time - _dt - _transient_executioner->getTargetTime()) >
     680         976 :           _transient_executioner->timestepTol())
     681             :         synchronize = false;
     682             :       break;
     683             :     }
     684       32382 :     case synchronization::constant:
     685             :     {
     686       32382 :       synchronize = timeStep() % _constant_interval == 0;
     687       32382 :       break;
     688             :     }
     689           0 :     default:
     690           0 :       mooseError("Unhandled SynchronizationEnum in NekRSProblem!");
     691             :   }
     692             : 
     693       33358 :   return synchronize;
     694             : }
     695             : 
     696             : bool
     697       33358 : NekRSProblem::isOutputStep() const
     698             : {
     699       33358 :   if (_app.isUltimateMaster())
     700             :   {
     701       17714 :     bool last_step = nekrs::lastStep(
     702       17714 :         _timestepper->nondimensionalDT(_time), _t_step, 0.0 /* dummy elapsed time */);
     703             : 
     704             :     // if Nek is controlled by a master application, then the last time step
     705             :     // is controlled by that master application, in which case we don't want to
     706             :     // write at what nekRS thinks is the last step (since it may or may not be
     707             :     // the actual end step), especially because we already ensure that we write on the
     708             :     // last time step from MOOSE's perspective in NekRSProblem's destructor.
     709       17714 :     if (last_step)
     710             :       return true;
     711             :   }
     712             : 
     713             :   // this routine does not check if we are on the last step - just whether we have
     714             :   // met the requested runtime or time step interval
     715       32928 :   return nekrs::outputStep(_timestepper->nondimensionalDT(_time), _t_step);
     716             : }
     717             : 
     718             : void
     719         774 : NekRSProblem::addExternalVariables()
     720             : {
     721             :   // Creation of variables for data transfers is handled by the FieldTransferBase objects
     722             : 
     723         774 :   if (_sync_interval == synchronization::parent_app)
     724             :   {
     725          94 :     auto pp_params = _factory.getValidParams("Receiver");
     726         141 :     pp_params.set<std::vector<OutputName>>("outputs") = {"none"};
     727             : 
     728             :     // we do not need to check for duplicate names because MOOSE already handles it
     729          94 :     addPostprocessor("Receiver", "transfer_in", pp_params);
     730          47 :   }
     731         774 : }
     732             : 
     733             : void
     734       68022 : NekRSProblem::volumeSolution(const field::NekFieldEnum & field, double * T)
     735             : {
     736       68022 :   mesh_t * mesh = nekrs::entireMesh();
     737       68022 :   auto vc = _nek_mesh->volumeCoupling();
     738             : 
     739             :   double (*f)(int);
     740       68022 :   f = nekrs::solutionPointer(field);
     741             : 
     742       68022 :   int start_1d = mesh->Nq;
     743       68022 :   int end_1d = _moose_Nq;
     744       68022 :   int start_3d = start_1d * start_1d * start_1d;
     745       68022 :   int end_3d = end_1d * end_1d * end_1d;
     746             : 
     747       68022 :   int n_to_write = vc.n_elems * end_3d * _nek_mesh->nBuildPerVolumeElem();
     748             : 
     749             :   // allocate temporary space:
     750             :   // - Ttmp: results of the search for each process
     751             :   // - Telem: scratch space for volume interpolation to avoid reallocating a bunch (only used if
     752             :   // interpolating)
     753       68022 :   double * Ttmp = (double *)calloc(n_to_write, sizeof(double));
     754       68022 :   double * Telem = (double *)calloc(start_3d, sizeof(double));
     755             : 
     756             :   auto indices = _nek_mesh->cornerIndices();
     757             : 
     758             :   int c = 0;
     759     3365896 :   for (int k = 0; k < mesh->Nelements; ++k)
     760             :   {
     761     3297874 :     int offset = k * start_3d;
     762             : 
     763     7212788 :     for (int build = 0; build < _nek_mesh->nBuildPerVolumeElem(); ++build)
     764             :     {
     765     3914914 :       if (_needs_interpolation)
     766             :       {
     767             :         // get the solution on the element
     768   574516016 :         for (int v = 0; v < start_3d; ++v)
     769   572841440 :           Telem[v] = f(offset + v);
     770             : 
     771             :         // and then interpolate it
     772     1674576 :         nekrs::interpolateVolumeHex3D(_interpolation_outgoing, Telem, start_1d, &(Ttmp[c]), end_1d);
     773     1674576 :         c += end_3d;
     774             :       }
     775             :       else
     776             :       {
     777             :         // get the solution on the element - no need to interpolate
     778    20163042 :         for (int v = 0; v < end_3d; ++v, ++c)
     779    17922704 :           Ttmp[c] = f(offset + indices[build][v]);
     780             :       }
     781             :     }
     782             :   }
     783             : 
     784             :   // dimensionalize the solution if needed
     785    63204278 :   for (int v = 0; v < n_to_write; ++v)
     786    63136256 :     Ttmp[v] = Ttmp[v] * nekrs::nondimensionalDivisor(field) + nekrs::nondimensionalAdditive(field);
     787             : 
     788       68022 :   nekrs::allgatherv(vc.mirror_counts, Ttmp, T, end_3d);
     789             : 
     790             :   freePointer(Ttmp);
     791             :   freePointer(Telem);
     792       68022 : }
     793             : 
     794             : void
     795       20410 : NekRSProblem::boundarySolution(const field::NekFieldEnum & field, double * T)
     796             : {
     797       20410 :   mesh_t * mesh = nekrs::entireMesh();
     798       20410 :   auto bc = _nek_mesh->boundaryCoupling();
     799             : 
     800             :   double (*f)(int);
     801       20410 :   f = nekrs::solutionPointer(field);
     802             : 
     803       20410 :   int start_1d = mesh->Nq;
     804       20410 :   int end_1d = _moose_Nq;
     805       20410 :   int start_2d = start_1d * start_1d;
     806       20410 :   int end_2d = end_1d * end_1d;
     807             : 
     808       20410 :   int n_to_write = bc.n_faces * end_2d * _nek_mesh->nBuildPerSurfaceElem();
     809             : 
     810             :   // allocate temporary space:
     811             :   // - Ttmp: results of the search for each process
     812             :   // - Tface: scratch space for face solution to avoid reallocating a bunch (only used if
     813             :   // interpolating)
     814             :   // - scratch: scratch for the interpolatino process to avoid reallocating a bunch (only used if
     815             :   // interpolating0
     816       20410 :   double * Ttmp = (double *)calloc(n_to_write, sizeof(double));
     817       20410 :   double * Tface = (double *)calloc(start_2d, sizeof(double));
     818       20410 :   double * scratch = (double *)calloc(start_1d * end_1d, sizeof(double));
     819             : 
     820             :   auto indices = _nek_mesh->cornerIndices();
     821             : 
     822             :   int c = 0;
     823     2173114 :   for (int k = 0; k < bc.total_n_faces; ++k)
     824             :   {
     825     2152704 :     if (bc.process[k] == nekrs::commRank())
     826             :     {
     827      382640 :       int i = bc.element[k];
     828      382640 :       int j = bc.face[k];
     829      382640 :       int offset = i * mesh->Nfaces * start_2d + j * start_2d;
     830             : 
     831     1133568 :       for (int build = 0; build < _nek_mesh->nBuildPerSurfaceElem(); ++build)
     832             :       {
     833      750928 :         if (_needs_interpolation)
     834             :         {
     835             :           // get the solution on the face
     836     3623648 :           for (int v = 0; v < start_2d; ++v)
     837             :           {
     838     3529344 :             int id = mesh->vmapM[offset + v];
     839     3529344 :             Tface[v] = f(id);
     840             :           }
     841             : 
     842             :           // and then interpolate it
     843       94304 :           nekrs::interpolateSurfaceFaceHex3D(
     844       94304 :               scratch, _interpolation_outgoing, Tface, start_1d, &(Ttmp[c]), end_1d);
     845       94304 :           c += end_2d;
     846             :         }
     847             :         else
     848             :         {
     849             :           // get the solution on the face - no need to interpolate
     850     3283120 :           for (int v = 0; v < end_2d; ++v, ++c)
     851             :           {
     852     2626496 :             int id = mesh->vmapM[offset + indices[build][v]];
     853     2626496 :             Ttmp[c] = f(id);
     854             :           }
     855             :         }
     856             :       }
     857             :     }
     858             :   }
     859             : 
     860             :   // dimensionalize the solution if needed
     861     3495642 :   for (int v = 0; v < n_to_write; ++v)
     862     3475232 :     Ttmp[v] = Ttmp[v] * nekrs::nondimensionalDivisor(field) + nekrs::nondimensionalAdditive(field);
     863             : 
     864       20410 :   nekrs::allgatherv(bc.mirror_counts, Ttmp, T, end_2d);
     865             : 
     866             :   freePointer(Ttmp);
     867             :   freePointer(Tface);
     868             :   freePointer(scratch);
     869       20410 : }
     870             : 
     871             : void
     872     1439356 : NekRSProblem::writeVolumeSolution(const int elem_id,
     873             :                                   const field::NekWriteEnum & field,
     874             :                                   double * T,
     875             :                                   const std::vector<double> * add)
     876             : {
     877     1439356 :   mesh_t * mesh = nekrs::entireMesh();
     878             :   void (*write_solution)(int, dfloat);
     879     1439356 :   write_solution = nekrs::solutionPointer(field);
     880             : 
     881     1439356 :   auto vc = _nek_mesh->volumeCoupling();
     882     1439356 :   int id = vc.element[elem_id] * mesh->Np;
     883             : 
     884     1439356 :   if (_nek_mesh->exactMirror())
     885             :   {
     886             :     // can write directly into the NekRS solution
     887     3807400 :     for (int v = 0; v < mesh->Np; ++v)
     888             :     {
     889     3681792 :       double extra = (add == nullptr) ? 0.0 : (*add)[id + v];
     890     3681792 :       write_solution(id + v, T[v] + extra);
     891             :     }
     892             :   }
     893             :   else
     894             :   {
     895             :     // need to interpolate onto the higher-order Nek mesh
     896     1313748 :     double * tmp = (double *)calloc(mesh->Np, sizeof(double));
     897             : 
     898     1313748 :     interpolateVolumeSolutionToNek(elem_id, T, tmp);
     899             : 
     900   185363348 :     for (int v = 0; v < mesh->Np; ++v)
     901             :     {
     902   184049600 :       double extra = (add == nullptr) ? 0.0 : (*add)[id + v];
     903   184049600 :       write_solution(id + v, tmp[v] + extra);
     904             :     }
     905             : 
     906             :     freePointer(tmp);
     907             :   }
     908     1439356 : }
     909             : 
     910             : void
     911      423168 : NekRSProblem::writeBoundarySolution(const int elem_id,
     912             :                                     const field::NekWriteEnum & field,
     913             :                                     double * T)
     914             : {
     915      423168 :   mesh_t * mesh = nekrs::temperatureMesh();
     916             :   void (*write_solution)(int, dfloat);
     917      423168 :   write_solution = nekrs::solutionPointer(field);
     918             : 
     919      423168 :   const auto & bc = _nek_mesh->boundaryCoupling();
     920      423168 :   int offset = bc.element[elem_id] * mesh->Nfaces * mesh->Nfp + bc.face[elem_id] * mesh->Nfp;
     921             : 
     922      423168 :   if (_nek_mesh->exactMirror())
     923             :   {
     924             :     // can write directly into the NekRS solution
     925      638144 :     for (int i = 0; i < mesh->Nfp; ++i)
     926      613600 :       write_solution(mesh->vmapM[offset + i], T[i]);
     927             :   }
     928             :   else
     929             :   {
     930             :     // need to interpolate onto the higher-order Nek mesh
     931      398624 :     double * tmp = (double *)calloc(mesh->Nfp, sizeof(double));
     932      398624 :     interpolateBoundarySolutionToNek(T, tmp);
     933             : 
     934     7322872 :     for (int i = 0; i < mesh->Nfp; ++i)
     935     6924248 :       write_solution(mesh->vmapM[offset + i], tmp[i]);
     936             : 
     937             :     freePointer(tmp);
     938             :   }
     939      423168 : }
     940             : 
     941             : void
     942     1313748 : NekRSProblem::interpolateVolumeSolutionToNek(const int elem_id,
     943             :                                              double * incoming_moose_value,
     944             :                                              double * outgoing_nek_value)
     945             : {
     946     1313748 :   mesh_t * mesh = nekrs::entireMesh();
     947             : 
     948     1313748 :   nekrs::interpolateVolumeHex3D(
     949     1313748 :       _interpolation_incoming, incoming_moose_value, _moose_Nq, outgoing_nek_value, mesh->Nq);
     950     1313748 : }
     951             : 
     952             : void
     953      398624 : NekRSProblem::interpolateBoundarySolutionToNek(double * incoming_moose_value,
     954             :                                                double * outgoing_nek_value)
     955             : {
     956      398624 :   mesh_t * mesh = nekrs::temperatureMesh();
     957             : 
     958      398624 :   double * scratch = (double *)calloc(_moose_Nq * mesh->Nq, sizeof(double));
     959             : 
     960      398624 :   nekrs::interpolateSurfaceFaceHex3D(scratch,
     961      398624 :                                      _interpolation_incoming,
     962             :                                      incoming_moose_value,
     963             :                                      _moose_Nq,
     964             :                                      outgoing_nek_value,
     965             :                                      mesh->Nq);
     966             : 
     967             :   freePointer(scratch);
     968      398624 : }
     969             : 
     970             : void
     971       32372 : NekRSProblem::copyScratchToDevice()
     972             : {
     973       50126 :   for (const auto & slot : _usrwrk_slots)
     974       17754 :     copyIndividualScratchSlot(slot);
     975             : 
     976       32372 :   if (nekrs::hasMovingMesh())
     977        1504 :     nekrs::copyDeformationToDevice();
     978       32372 : }
     979             : 
     980             : bool
     981          35 : NekRSProblem::isUsrWrkSlotReservedForCoupling(const unsigned int & slot) const
     982             : {
     983          35 :   return std::find(_usrwrk_slots.begin(), _usrwrk_slots.end(), slot) != _usrwrk_slots.end();
     984             : }
     985             : 
     986             : void
     987       17781 : NekRSProblem::copyIndividualScratchSlot(const unsigned int & slot) const
     988             : {
     989       17781 :   auto n = nekrs::fieldOffset();
     990       17781 :   auto nbytes = n * sizeof(dfloat);
     991             : 
     992       17781 :   nrs_t * nrs = (nrs_t *)nekrs::nrsPtr();
     993       17781 :   nrs->o_usrwrk.copyFrom(nrs->usrwrk + slot * n, nbytes, slot * nbytes);
     994       17781 : }
     995             : 
     996             : void
     997      423168 : NekRSProblem::mapFaceDataToNekFace(const unsigned int & e,
     998             :                                    const unsigned int & var_num,
     999             :                                    const Real & divisor_scale,
    1000             :                                    const Real & additive_scale,
    1001             :                                    double ** outgoing_data)
    1002             : {
    1003      423168 :   auto sys_number = _aux->number();
    1004      423168 :   auto & mesh = _nek_mesh->getMesh();
    1005      423168 :   auto indices = _nek_mesh->cornerIndices();
    1006             : 
    1007     1214496 :   for (int build = 0; build < _nek_mesh->nMoosePerNek(); ++build)
    1008             :   {
    1009      791328 :     auto elem_ptr = mesh.query_elem_ptr(e * _nek_mesh->nMoosePerNek() + build);
    1010             : 
    1011             :     // Only work on elements we can find on our local chunk of a
    1012             :     // distributed mesh
    1013      791328 :     if (!elem_ptr)
    1014             :     {
    1015             :       libmesh_assert(!mesh.is_serial());
    1016           0 :       continue;
    1017             :     }
    1018             : 
    1019     4628640 :     for (unsigned int n = 0; n < _n_vertices_per_surface; n++)
    1020             :     {
    1021             :       auto node_ptr = elem_ptr->node_ptr(n);
    1022             : 
    1023             :       // convert libMesh node index into the ordering used by NekRS
    1024     3837312 :       int node_index = _nek_mesh->exactMirror() ? indices[build][_nek_mesh->boundaryNodeIndex(n)]
    1025     2266496 :                                                 : _nek_mesh->boundaryNodeIndex(n);
    1026             : 
    1027     3837312 :       auto dof_idx = node_ptr->dof_number(sys_number, var_num, 0);
    1028     3837312 :       (*outgoing_data)[node_index] =
    1029     3837312 :           ((*_serialized_solution)(dof_idx)-additive_scale) / divisor_scale;
    1030             :     }
    1031             :   }
    1032      423168 : }
    1033             : 
    1034             : void
    1035     1039536 : NekRSProblem::mapFaceDataToNekVolume(const unsigned int & e,
    1036             :                                      const unsigned int & var_num,
    1037             :                                      const Real & divisor_scale,
    1038             :                                      const Real & additive_scale,
    1039             :                                      double ** outgoing_data)
    1040             : {
    1041     1039536 :   auto sys_number = _aux->number();
    1042     1039536 :   auto & mesh = _nek_mesh->getMesh();
    1043     1039536 :   auto indices = _nek_mesh->cornerIndices();
    1044             : 
    1045     2491232 :   for (int build = 0; build < _nek_mesh->nMoosePerNek(); ++build)
    1046             :   {
    1047     1451696 :     int n_faces_on_boundary = _nek_mesh->facesOnBoundary(e);
    1048             : 
    1049             :     // the only meaningful values are on the coupling boundaries, so we can skip this
    1050             :     // interpolation if this volume element isn't on a coupling boundary
    1051     1451696 :     if (n_faces_on_boundary > 0)
    1052             :     {
    1053      288568 :       auto elem_ptr = mesh.query_elem_ptr(e * _nek_mesh->nMoosePerNek() + build);
    1054             : 
    1055             :       // Only work on elements we can find on our local chunk of a
    1056             :       // distributed mesh
    1057      288568 :       if (!elem_ptr)
    1058             :       {
    1059             :         libmesh_assert(!mesh.is_serial());
    1060           0 :         continue;
    1061             :       }
    1062             : 
    1063     5606712 :       for (unsigned int n = 0; n < _n_vertices_per_volume; ++n)
    1064             :       {
    1065             :         auto node_ptr = elem_ptr->node_ptr(n);
    1066             : 
    1067             :         // convert libMesh node index into the ordering used by NekRS
    1068     5318144 :         int node_index = _nek_mesh->exactMirror() ? indices[build][_nek_mesh->volumeNodeIndex(n)]
    1069     5154304 :                                                   : _nek_mesh->volumeNodeIndex(n);
    1070             : 
    1071     5318144 :         auto dof_idx = node_ptr->dof_number(sys_number, var_num, 0);
    1072     5318144 :         (*outgoing_data)[node_index] =
    1073     5318144 :             ((*_serialized_solution)(dof_idx)-additive_scale) / divisor_scale;
    1074             :       }
    1075             :     }
    1076             :   }
    1077     1039536 : }
    1078             : 
    1079             : void
    1080      399820 : NekRSProblem::mapVolumeDataToNekVolume(const unsigned int & e,
    1081             :                                        const unsigned int & var_num,
    1082             :                                        const Real & divisor,
    1083             :                                        const Real & additive,
    1084             :                                        double ** outgoing_data)
    1085             : {
    1086      399820 :   auto sys_number = _aux->number();
    1087      399820 :   auto & mesh = _nek_mesh->getMesh();
    1088      399820 :   auto indices = _nek_mesh->cornerIndices();
    1089             : 
    1090     1415848 :   for (int build = 0; build < _nek_mesh->nMoosePerNek(); ++build)
    1091             :   {
    1092     1016028 :     auto elem_ptr = mesh.query_elem_ptr(e * _nek_mesh->nMoosePerNek() + build);
    1093             : 
    1094             :     // Only work on elements we can find on our local chunk of a
    1095             :     // distributed mesh
    1096     1016028 :     if (!elem_ptr)
    1097             :     {
    1098             :       libmesh_assert(!mesh.is_serial());
    1099           0 :       continue;
    1100             :     }
    1101    10625416 :     for (unsigned int n = 0; n < _n_vertices_per_volume; n++)
    1102             :     {
    1103             :       auto node_ptr = elem_ptr->node_ptr(n);
    1104             : 
    1105             :       // convert libMesh node index into the ordering used by NekRS
    1106     9609388 :       int node_index = _nek_mesh->exactMirror() ? indices[build][_nek_mesh->volumeNodeIndex(n)]
    1107     4145900 :                                                 : _nek_mesh->volumeNodeIndex(n);
    1108             : 
    1109     9609388 :       auto dof_idx = node_ptr->dof_number(sys_number, var_num, 0);
    1110     9609388 :       (*outgoing_data)[node_index] = ((*_serialized_solution)(dof_idx)-additive) / divisor;
    1111             :     }
    1112             :   }
    1113      399820 : }
    1114             : 
    1115             : #endif

Generated by: LCOV version 1.14