LCOV - code coverage report
Current view: top level - src/base - NekRSProblem.C (source / functions) Hit Total Coverage
Test: neams-th-coe/cardinal: ddd5f2 Lines: 427 463 92.2 %
Date: 2026-06-07 19:35:24 Functions: 25 26 96.2 %
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        2424 : NekRSProblem::validParams()
      33             : {
      34        2424 :   InputParameters params = CardinalProblem::validParams();
      35        4848 :   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        4848 :   params.addParam<unsigned int>(
      41             :       "n_usrwrk_slots",
      42        4848 :       0,
      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        4848 :   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        4848 :   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        4848 :   params.addParam<bool>(
      59             :       "write_fld_files",
      60        4848 :       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        4848 :   params.addParam<bool>(
      65        4848 :       "disable_fld_file_output", false, "Whether to turn off all NekRS field file output writing");
      66             : 
      67        4848 :   params.addParam<bool>("skip_final_field_file",
      68        4848 :                         false,
      69             :                         "By default, we write a NekRS field file "
      70             :                         "on the last time step; set this to true to disable");
      71             : 
      72        4848 :   params.addParam<MooseEnum>(
      73             :       "synchronization_interval",
      74        4848 :       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        4848 :   params.addParam<unsigned int>("constant_interval",
      78        4848 :                                 1,
      79             :                                 "Constant interval (in units of number of time steps) with which "
      80             :                                 "to synchronize the NekRS solution");
      81        2424 :   return params;
      82           0 : }
      83             : 
      84         800 : NekRSProblem::NekRSProblem(const InputParameters & params)
      85             :   : CardinalProblem(params),
      86         800 :     _serialized_solution(NumericVector<Number>::build(_communicator).release()),
      87        1600 :     _casename(getParam<std::string>("casename")),
      88        1600 :     _write_fld_files(getParam<bool>("write_fld_files")),
      89        1600 :     _disable_fld_file_output(getParam<bool>("disable_fld_file_output")),
      90        1600 :     _n_usrwrk_slots(getParam<unsigned int>("n_usrwrk_slots")),
      91        1600 :     _constant_interval(getParam<unsigned int>("constant_interval")),
      92        1600 :     _skip_final_field_file(getParam<bool>("skip_final_field_file")),
      93         800 :     _start_time(nekrs::startTime()),
      94         800 :     _elapsedStepSum(0.0),
      95        1600 :     _elapsedTime(nekrs::getNekSetupTime()),
      96         800 :     _tSolveStepMin(std::numeric_limits<double>::max()),
      97        1600 :     _tSolveStepMax(std::numeric_limits<double>::min())
      98             : {
      99         800 :   const auto & actions = getMooseApp().actionWarehouse().getActions<DimensionalizeAction>();
     100         800 :   _nondimensional = actions.size();
     101         800 :   nekrs::nondimensional(_nondimensional);
     102             : 
     103        1600 :   _sync_interval = getParam<MooseEnum>("synchronization_interval")
     104             :                        .getEnum<synchronization::SynchronizationEnum>();
     105         800 :   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          39 :     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          78 :     checkUnusedParam(params, "constant_interval", "synchronizing based on the 'parent_app'");
     121             :   }
     122             : 
     123         800 :   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         800 :   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         799 :   _nek_mesh = dynamic_cast<NekRSMesh *>(&mesh());
     132             : 
     133         799 :   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         798 :   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         798 :   _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         798 :   if (!nekrs::buildOnly())
     153         798 :     _nek_mesh->printMeshInfo();
     154             : 
     155             :   // boundary-specific data
     156         798 :   _n_surface_elems = _nek_mesh->numSurfaceElems();
     157         798 :   _n_vertices_per_surface = _nek_mesh->numVerticesPerSurface();
     158             : 
     159             :   // volume-specific data
     160         798 :   _n_vertices_per_volume = _nek_mesh->numVerticesPerVolume();
     161             : 
     162         798 :   if (_nek_mesh->volume())
     163         467 :     _n_points =
     164         467 :         _nek_mesh->numVolumeElems() * _n_vertices_per_volume * _nek_mesh->nBuildPerVolumeElem();
     165             :   else
     166         331 :     _n_points = _n_surface_elems * _n_vertices_per_surface * _nek_mesh->nBuildPerSurfaceElem();
     167             : 
     168         798 :   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         798 :   _needs_interpolation = _nek_mesh->numQuadraturePoints1D() > 2;
     179             : 
     180         798 :   checkJointParams(
     181             :       params, {"usrwrk_output", "usrwrk_output_prefix"}, "outputting usrwrk slots to field files");
     182             : 
     183        1596 :   if (isParamValid("usrwrk_output"))
     184             :   {
     185          40 :     _usrwrk_output = &getParam<std::vector<unsigned int>>("usrwrk_output");
     186          40 :     _usrwrk_output_prefix = &getParam<std::vector<std::string>>("usrwrk_output_prefix");
     187             : 
     188          52 :     for (const auto & s : *_usrwrk_output)
     189          33 :       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             :                    "! Please increase 'n_usrwrk_slots'.");
     194             : 
     195          19 :     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         796 : }
     199             : 
     200         732 : 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         732 :   if (!_is_output_step && !_skip_final_field_file)
     205             :   {
     206         162 :     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         162 :       writeFieldFile(_time, _t_step);
     216             :   }
     217             : 
     218         732 :   if (nekrs::runTimeStatFreq())
     219         732 :     if (_t_step % nekrs::runTimeStatFreq())
     220         732 :       nekrs::printRuntimeStatistics(_t_step);
     221             : 
     222         732 :   freePointer(_interpolation_outgoing);
     223         732 :   freePointer(_interpolation_incoming);
     224         732 :   nekrs::freeScratch();
     225             : 
     226         732 :   nekrs::finalize();
     227         732 : }
     228             : 
     229             : void
     230        2053 : NekRSProblem::writeFieldFile(const Real & step_end_time, const int & step) const
     231             : {
     232        2053 :   if (_disable_fld_file_output)
     233             :     return;
     234             : 
     235        2053 :   Real t = _timestepper->nondimensionalDT(step_end_time);
     236             : 
     237        2053 :   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        1997 :     nekrs::outfld(t, step);
     258             : }
     259             : 
     260             : void
     261         798 : NekRSProblem::initializeInterpolationMatrices()
     262             : {
     263         798 :   mesh_t * mesh = nekrs::entireMesh();
     264             : 
     265             :   // determine the interpolation matrix for the outgoing transfer
     266         798 :   int starting_points = mesh->Nq;
     267         798 :   int ending_points = _nek_mesh->numQuadraturePoints1D();
     268         798 :   _interpolation_outgoing = (double *)calloc(starting_points * ending_points, sizeof(double));
     269         798 :   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         798 :   _interpolation_incoming = (double *)calloc(starting_points * ending_points, sizeof(double));
     274         798 :   nekrs::interpolationMatrix(_interpolation_incoming, starting_points, ending_points);
     275         798 : }
     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         742 : NekRSProblem::initialSetup()
     290             : {
     291         742 :   CardinalProblem::initialSetup();
     292             : 
     293         742 :   auto executioner = _app.getExecutioner();
     294         742 :   _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         742 :   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         741 :   _timestepper = dynamic_cast<NekTimeStepper *>(stepper);
     307         741 :   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         740 :   const auto moose_start_time = _transient_executioner->getStartTime();
     315         740 :   nekrs::setStartTime(_timestepper->nondimensionalDT(moose_start_time));
     316         740 :   _start_time = moose_start_time;
     317             : 
     318         740 :   if (_sync_interval == synchronization::parent_app)
     319          39 :     _transfer_in = &getPostprocessorValueByName("transfer_in");
     320             : 
     321             :   // Find all of the data transfer objects
     322         740 :   TheWarehouse::Query query = theWarehouse().query().condition<AttribSystem>("FieldTransfer");
     323         740 :   query.queryInto(_field_transfers);
     324             : 
     325             :   // Find all of the scalar data transfer objects
     326         740 :   TheWarehouse::Query uo_query = theWarehouse().query().condition<AttribSystem>("ScalarTransfer");
     327         740 :   uo_query.queryInto(_scalar_transfers);
     328             : 
     329             :   // We require a NekMeshDeformation object to exist if the NekRS model has a moving mesh
     330         740 :   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         739 :   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         739 :       {"Slot", "Data Written", "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             :   auto field_usrwrk_scales = FieldTransferBase::usrwrkScales();
     357        1191 :   for (const auto & field : field_usrwrk_map)
     358         452 :     _usrwrk_slots.insert(field.first);
     359         782 :   for (const auto & uo : _scalar_transfers)
     360          43 :     _usrwrk_slots.insert(uo->usrwrkSlot());
     361             : 
     362             :   // fill out table, being careful to only write information if owned by a field transfer,
     363             :   // a user object, or neither
     364        1432 :   for (int i = 0; i < _n_usrwrk_slots; ++i)
     365             :   {
     366         693 :     std::string oudf = "bc->usrwrk[" + std::to_string(i) + "*bc->fieldOffset+bc->idM]";
     367         693 :     std::string udf = "nrs->usrwrk[" + std::to_string(i) + "*nrs->fieldOffset+n]";
     368             : 
     369         693 :     if (field_usrwrk_map.find(i) != field_usrwrk_map.end())
     370             :     {
     371             :       // a field transfer owns it
     372         452 :       auto scales = field_usrwrk_scales[field_usrwrk_map[i]];
     373             : 
     374             :       std::string top;
     375         452 :       if (MooseUtils::absoluteFuzzyEqual(scales.first, 0.0))
     376         888 :         top = field_usrwrk_map[i];
     377             :       else
     378          16 :         top = field_usrwrk_map[i] + "-" + std::to_string(scales.first);
     379             : 
     380         452 :       if (!MooseUtils::absoluteFuzzyEqual(scales.second, 1.0))
     381             :       {
     382          36 :         if (MooseUtils::absoluteFuzzyEqual(scales.first, 0.0))
     383          84 :           top = "(" + top + ")/" + std::to_string(scales.second);
     384             :         else
     385          16 :           top = top + "/" + std::to_string(scales.second);
     386             :       }
     387             : 
     388         904 :       vt.addRow(i, top, oudf, udf);
     389             :     }
     390             :     else
     391             :     {
     392             :       // a user object might own it, or it could be unused
     393             :       bool owned_by_uo = false;
     394         326 :       for (const auto & uo : _scalar_transfers)
     395             :       {
     396          85 :         if (uo->usrwrkSlot() == i)
     397             :         {
     398             :           owned_by_uo = true;
     399          43 :           auto slot = std::to_string(uo->usrwrkSlot());
     400          43 :           auto count = std::to_string(uo->offset());
     401             : 
     402          43 :           std::string top = uo->name();
     403          43 :           if (!MooseUtils::absoluteFuzzyEqual(uo->scaling(), 1.0))
     404           0 :             top += top + "*" + std::to_string(uo->scaling());
     405             : 
     406          86 :           vt.addRow(i,
     407             :                     top,
     408         129 :                     "bc->usrwrk[" + slot + "*bc->fieldOffset+" + count + "]",
     409         129 :                     "nrs->usrwrk[" + slot + "*nrs->fieldOffset+" + count + "]");
     410             :         }
     411             :       }
     412             : 
     413         241 :       if (!owned_by_uo)
     414         412 :         vt.addRow(i, "unused", oudf, udf);
     415             :     }
     416             :   }
     417             : 
     418         739 :   if (_n_usrwrk_slots == 0)
     419             :   {
     420         353 :     _console << "Skipping allocation of NekRS scratch space because 'n_usrwrk_slots' is 0\n"
     421         353 :              << std::endl;
     422             :   }
     423             :   else
     424             :   {
     425             :     _console
     426         386 :         << "\n ===================>     MAPPING FROM MOOSE TO NEKRS      <===================\n"
     427         386 :         << std::endl;
     428         386 :     _console << "           Slot:  slice in scratch space holding the data\n" << std::endl;
     429         386 :     _console << "   Data written:  data that gets written into this slot. This data is shown"
     430         386 :              << std::endl;
     431         386 :     _console << "                  in the form actually written into NekRS (which will be"
     432         386 :              << std::endl;
     433         386 :     _console << "                  non-dimensional quantities if using the [Dimensionalize]"
     434         386 :              << std::endl;
     435         386 :     _console << "                  block). Words refer to MOOSE AuxVariables/Postprocessors."
     436         386 :              << std::endl;
     437         386 :     _console << "                  If 'unused', this means that the space has been allocated,"
     438         386 :              << std::endl;
     439         386 :     _console << "                  but Cardinal is not otherwise using it for coupling.\n"
     440         386 :              << std::endl;
     441         386 :     _console << "  How to Access:  C++ code to use in NekRS files; for the .udf instructions,"
     442         386 :              << std::endl;
     443         386 :     _console << "                  'n' indicates a loop variable over GLL points\n" << std::endl;
     444         386 :     vt.print(_console);
     445         386 :     _console << std::endl;
     446             :   }
     447             : 
     448             :   // nekRS calls UDF_ExecuteStep once before the time stepping begins; the isLastStep stuff is
     449             :   // copy-pasta from NekRS main(), except that if Nek is a sub-app, we give full control of
     450             :   // time stepping to the main app
     451             :   bool isLastStep = false;
     452         739 :   if (_app.isUltimateMaster())
     453         427 :     isLastStep = !((nekrs::endTime() > nekrs::startTime() || nekrs::numSteps() > _t_step));
     454         739 :   nekrs::lastStep(isLastStep);
     455             : 
     456         739 :   nekrs::udfExecuteStep(
     457         739 :       _timestepper->nondimensionalDT(_start_time), _t_step, false /* not an output step */);
     458        1478 :   nekrs::resetTimer("udfExecuteStep");
     459         739 : }
     460             : 
     461             : void
     462       33972 : NekRSProblem::externalSolve()
     463             : {
     464       33972 :   if (nekrs::buildOnly())
     465           0 :     return;
     466             : 
     467       33972 :   const double timeStartStep = MPI_Wtime();
     468             : 
     469             :   // _dt reflects the time step that MOOSE wants Nek to
     470             :   // take. For instance, if Nek is controlled by a master app and subcycling is used,
     471             :   // Nek must advance to the time interval taken by the master app. If the time step
     472             :   // that MOOSE wants nekRS to take (i.e. _dt) is smaller than we'd like nekRS to take, error.
     473       33972 :   if (_dt < _timestepper->minDT())
     474           0 :     mooseError("Requested time step of " + std::to_string(_dt) +
     475             :                " is smaller than the minimum "
     476           0 :                "time step of " +
     477           0 :                Moose::stringify(_timestepper->minDT()) +
     478             :                " allowed in NekRS!\n\n"
     479             :                "You can control this behavior with the 'min_dt' parameter on 'NekTimeStepper'.");
     480             : 
     481             :   // _time represents the time that we're simulating _to_, but we need to pass sometimes slightly
     482             :   // different times into the nekRS routines, which assume that the "time" passed into their
     483             :   // routines is sometimes a different interpretation.
     484       33972 :   double step_start_time = _time - _dt;
     485       33972 :   double step_end_time = _time;
     486             : 
     487       33972 :   _is_output_step = isOutputStep();
     488             : 
     489             :   // tell NekRS what the value of nrs->isOutputStep should be
     490       33972 :   nekrs::outputStep(_is_output_step);
     491             : 
     492             :   // NekRS prints out verbose info for the first 1000 time steps
     493       33972 :   if (_t_step <= 1000)
     494       25596 :     nekrs::verboseInfo(true);
     495             : 
     496             :   // Tell NekRS what the time step size is
     497       33972 :   nekrs::initStep(_timestepper->nondimensionalDT(step_start_time),
     498       33972 :                   _timestepper->nondimensionalDT(_dt),
     499       33972 :                   _t_step);
     500             : 
     501             :   // Run a nekRS time step. After the time step, this also calls UDF_ExecuteStep,
     502             :   // evaluated at (step_end_time, _t_step) == (nek_step_start_time + nek_dt, t_step)
     503             :   int corrector = 1;
     504             :   bool converged = false;
     505             :   do
     506             :   {
     507       33972 :     converged = nekrs::runStep(corrector++);
     508       33972 :   } while (!converged);
     509             : 
     510             :   // TODO: time is somehow corrected here
     511       33972 :   nekrs::finishStep();
     512             : 
     513             :   // copy-pasta from Nek's main() for calling timers and printing
     514       33972 :   if (nekrs::updateFileCheckFreq())
     515       33972 :     if (_t_step % nekrs::updateFileCheckFreq())
     516       32413 :       nekrs::processUpdFile();
     517             : 
     518             :   // Note: here, we copy to both the nrs solution arrays and to the Nek5000 backend arrays,
     519             :   // because it is possible that users may interact using the legacy usr-file approach.
     520             :   // If we move away from the Nek5000 backend entirely, we could replace this line with
     521             :   // direct OCCA memcpy calls. But we do definitely need some type of copy here for _every_
     522             :   // time step, even if we're not technically passing data to another app, because we have
     523             :   // postprocessors that touch the `nrs` arrays that can be called in an arbitrary fashion
     524             :   // by the user.
     525       33972 :   nek::ocopyToNek(_timestepper->nondimensionalDT(step_end_time), _t_step);
     526             : 
     527       33972 :   if (nekrs::printInfoFreq())
     528       33972 :     if (_t_step % nekrs::printInfoFreq() == 0)
     529       33972 :       nekrs::printInfo(_timestepper->nondimensionalDT(_time), _t_step, false, true);
     530             : 
     531       33972 :   if (_is_output_step)
     532             :   {
     533        1891 :     writeFieldFile(step_end_time, _t_step);
     534             : 
     535             :     // TODO: I could not figure out why this can't be called from the destructor, to
     536             :     // add another field file on Cardinal's last time step. Revisit in the future.
     537        1891 :     if (_usrwrk_output)
     538             :     {
     539          34 :       static std::vector<bool> first_fld(_usrwrk_output->size(), true);
     540             : 
     541          96 :       for (unsigned int i = 0; i < _usrwrk_output->size(); ++i)
     542             :       {
     543          62 :         bool write_coords = first_fld[i] ? true : false;
     544             : 
     545          62 :         nekrs::write_usrwrk_field_file((*_usrwrk_output)[i],
     546          62 :                                        (*_usrwrk_output_prefix)[i],
     547          62 :                                        _timestepper->nondimensionalDT(step_end_time),
     548          62 :                                        _t_step,
     549             :                                        write_coords);
     550             : 
     551             :         first_fld[i] = false;
     552             :       }
     553             :     }
     554             :   }
     555             : 
     556       33972 :   MPI_Barrier(comm().get());
     557       33972 :   const double elapsedStep = MPI_Wtime() - timeStartStep;
     558       33972 :   _tSolveStepMin = std::min(elapsedStep, _tSolveStepMin);
     559       33972 :   _tSolveStepMax = std::max(elapsedStep, _tSolveStepMax);
     560       33972 :   nekrs::updateTimer("minSolveStep", _tSolveStepMin);
     561       33972 :   nekrs::updateTimer("maxSolveStep", _tSolveStepMax);
     562             : 
     563       33972 :   _elapsedStepSum += elapsedStep;
     564       33972 :   _elapsedTime += elapsedStep;
     565       33972 :   nekrs::updateTimer("elapsedStep", elapsedStep);
     566       33972 :   nekrs::updateTimer("elapsedStepSum", _elapsedStepSum);
     567       33972 :   nekrs::updateTimer("elapsed", _elapsedTime);
     568             : 
     569       33972 :   if (nekrs::printInfoFreq())
     570       33972 :     if (_t_step % nekrs::printInfoFreq() == 0)
     571       33972 :       nekrs::printInfo(_timestepper->nondimensionalDT(_time), _t_step, true, false);
     572             : 
     573       33972 :   if (nekrs::runTimeStatFreq())
     574       33972 :     if (_t_step % nekrs::runTimeStatFreq() == 0)
     575          32 :       nekrs::printRuntimeStatistics(_t_step);
     576             : 
     577       33972 :   _time += _dt;
     578             : }
     579             : 
     580             : bool
     581       67947 : NekRSProblem::isDataTransferHappening(ExternalProblem::Direction direction)
     582             : {
     583       67947 :   if (nekrs::buildOnly())
     584             :     return false;
     585             : 
     586       67947 :   switch (direction)
     587             :   {
     588       33975 :     case ExternalProblem::Direction::TO_EXTERNAL_APP:
     589       33975 :       return synchronizeIn();
     590       33972 :     case ExternalProblem::Direction::FROM_EXTERNAL_APP:
     591       33972 :       return synchronizeOut();
     592           0 :     default:
     593           0 :       mooseError("Unhandled DirectionEnum in NekRSProblem!");
     594             :   }
     595             : }
     596             : 
     597             : void
     598       67947 : NekRSProblem::syncSolutions(ExternalProblem::Direction direction)
     599             : {
     600       67947 :   auto & solution = _aux->solution();
     601             : 
     602       67947 :   if (!isDataTransferHappening(direction))
     603             :     return;
     604             : 
     605       65990 :   switch (direction)
     606             :   {
     607       32996 :     case ExternalProblem::Direction::TO_EXTERNAL_APP:
     608             :     {
     609       32996 :       if (_first)
     610             :       {
     611         738 :         _serialized_solution->init(_aux->sys().n_dofs(), false, SERIAL);
     612         738 :         _first = false;
     613             :       }
     614             : 
     615       32996 :       solution.localize(*_serialized_solution);
     616             : 
     617             :       // execute all incoming field transfers
     618      134576 :       for (const auto & t : _field_transfers)
     619      101582 :         if (t->direction() == "to_nek")
     620       14832 :           t->sendDataToNek();
     621             : 
     622             :       // execute all incoming scalar transfers
     623       33244 :       for (const auto & t : _scalar_transfers)
     624         250 :         if (t->direction() == "to_nek")
     625         250 :           t->sendDataToNek();
     626             : 
     627       32994 :       if (udf.properties)
     628             :       {
     629       15741 :         nrs_t * nrs = (nrs_t *)nekrs::nrsPtr();
     630       15741 :         evaluateProperties(nrs, _timestepper->nondimensionalDT(_time));
     631             :       }
     632             : 
     633       32994 :       copyScratchToDevice();
     634             : 
     635       32994 :       break;
     636             : 
     637             :       return;
     638             :     }
     639       32994 :     case ExternalProblem::Direction::FROM_EXTERNAL_APP:
     640             :     {
     641             :       // execute all outgoing field transfers
     642      134574 :       for (const auto & t : _field_transfers)
     643      101580 :         if (t->direction() == "from_nek")
     644       86750 :           t->readDataFromNek();
     645             : 
     646             :       // execute all outgoing scalar transfers
     647       33244 :       for (const auto & t : _scalar_transfers)
     648         250 :         if (t->direction() == "from_nek")
     649           0 :           t->sendDataToNek();
     650             : 
     651             :       break;
     652             :     }
     653           0 :     default:
     654           0 :       mooseError("Unhandled Transfer::DIRECTION enum!");
     655             :   }
     656             : 
     657       65988 :   solution.close();
     658       65988 :   _aux->system().update();
     659             : }
     660             : 
     661             : bool
     662       33975 : NekRSProblem::synchronizeIn()
     663             : {
     664             :   bool synchronize = true;
     665             : 
     666       33975 :   switch (_sync_interval)
     667             :   {
     668         961 :     case synchronization::parent_app:
     669             :     {
     670             :       // For the minimized incoming synchronization to work correctly, the value
     671             :       // of the incoming postprocessor must not be zero. We only need to check this for the very
     672             :       // first time we evaluate this function. This ensures that you don't accidentally set a
     673             :       // zero value as a default in the master application's postprocessor.
     674         961 :       if (_first && *_transfer_in == false)
     675           1 :         mooseError("The default value for the 'transfer_in' postprocessor received by nekRS "
     676             :                    "must not be false! Make sure that the master application's "
     677             :                    "postprocessor is not zero.");
     678             : 
     679         960 :       if (*_transfer_in == false)
     680             :         synchronize = false;
     681             :       else
     682         812 :         setPostprocessorValueByName("transfer_in", false, 0);
     683             : 
     684             :       break;
     685             :     }
     686       33014 :     case synchronization::constant:
     687             :     {
     688       33014 :       synchronize = timeStep() % _constant_interval == 0;
     689       33014 :       break;
     690             :     }
     691           0 :     default:
     692           0 :       mooseError("Unhandled SynchronizationEnum in NekRSProblem!");
     693             :   }
     694             : 
     695       33974 :   return synchronize;
     696             : }
     697             : 
     698             : bool
     699       33972 : NekRSProblem::synchronizeOut()
     700             : {
     701             :   bool synchronize = true;
     702             : 
     703       33972 :   switch (_sync_interval)
     704             :   {
     705         960 :     case synchronization::parent_app:
     706             :     {
     707         960 :       if (std::abs(_time - _dt - _transient_executioner->getTargetTime()) >
     708         960 :           _transient_executioner->timestepTol())
     709             :         synchronize = false;
     710             :       break;
     711             :     }
     712       33012 :     case synchronization::constant:
     713             :     {
     714       33012 :       synchronize = timeStep() % _constant_interval == 0;
     715       33012 :       break;
     716             :     }
     717           0 :     default:
     718           0 :       mooseError("Unhandled SynchronizationEnum in NekRSProblem!");
     719             :   }
     720             : 
     721       33972 :   return synchronize;
     722             : }
     723             : 
     724             : bool
     725       33972 : NekRSProblem::isOutputStep() const
     726             : {
     727       33972 :   if (_app.isUltimateMaster())
     728             :   {
     729       18886 :     bool last_step = nekrs::lastStep(
     730       18886 :         _timestepper->nondimensionalDT(_time), _t_step, 0.0 /* dummy elapsed time */);
     731             : 
     732             :     // if Nek is controlled by a master application, then the last time step
     733             :     // is controlled by that master application, in which case we don't want to
     734             :     // write at what nekRS thinks is the last step (since it may or may not be
     735             :     // the actual end step), especially because we already ensure that we write on the
     736             :     // last time step from MOOSE's perspective in NekRSProblem's destructor.
     737       18886 :     if (last_step)
     738             :       return true;
     739             :   }
     740             : 
     741             :   // this routine does not check if we are on the last step - just whether we have
     742             :   // met the requested runtime or time step interval
     743       33522 :   return nekrs::outputStep(_timestepper->nondimensionalDT(_time), _t_step);
     744             : }
     745             : 
     746             : void
     747         773 : NekRSProblem::addExternalVariables()
     748             : {
     749             :   // Creation of variables for data transfers is handled by the FieldTransferBase objects
     750             : 
     751         773 :   if (_sync_interval == synchronization::parent_app)
     752             :   {
     753          78 :     auto pp_params = _factory.getValidParams("Receiver");
     754         117 :     pp_params.set<std::vector<OutputName>>("outputs") = {"none"};
     755             : 
     756             :     // we do not need to check for duplicate names because MOOSE already handles it
     757          78 :     addPostprocessor("Receiver", "transfer_in", pp_params);
     758          39 :   }
     759         773 : }
     760             : 
     761             : void
     762     1178848 : NekRSProblem::interpolateVolumeSolutionToNek(const int elem_id,
     763             :                                              double * incoming_moose_value,
     764             :                                              double * outgoing_nek_value)
     765             : {
     766     1178848 :   mesh_t * mesh = nekrs::entireMesh();
     767             : 
     768     1178848 :   nekrs::interpolateVolumeHex3D(
     769     1178848 :       _interpolation_incoming, incoming_moose_value, _moose_Nq, outgoing_nek_value, mesh->Nq);
     770     1178848 : }
     771             : 
     772             : void
     773      384046 : NekRSProblem::interpolateBoundarySolutionToNek(double * incoming_moose_value,
     774             :                                                double * outgoing_nek_value)
     775             : {
     776      384046 :   mesh_t * mesh = nekrs::temperatureMesh();
     777             : 
     778      384046 :   double * scratch = (double *)calloc(_moose_Nq * mesh->Nq, sizeof(double));
     779             : 
     780      384046 :   nekrs::interpolateSurfaceFaceHex3D(scratch,
     781      384046 :                                      _interpolation_incoming,
     782             :                                      incoming_moose_value,
     783             :                                      _moose_Nq,
     784             :                                      outgoing_nek_value,
     785             :                                      mesh->Nq);
     786             : 
     787             :   freePointer(scratch);
     788      384046 : }
     789             : 
     790             : void
     791       32994 : NekRSProblem::copyScratchToDevice()
     792             : {
     793       49830 :   for (const auto & slot : _usrwrk_slots)
     794       16836 :     copyIndividualScratchSlot(slot);
     795             : 
     796       32994 :   if (nekrs::hasMovingMesh())
     797        1504 :     nekrs::copyDeformationToDevice();
     798       32994 : }
     799             : 
     800             : bool
     801          27 : NekRSProblem::isUsrWrkSlotReservedForCoupling(const unsigned int & slot) const
     802             : {
     803          27 :   return std::find(_usrwrk_slots.begin(), _usrwrk_slots.end(), slot) != _usrwrk_slots.end();
     804             : }
     805             : 
     806             : void
     807       16863 : NekRSProblem::copyIndividualScratchSlot(const unsigned int & slot) const
     808             : {
     809       16863 :   auto n = nekrs::fieldOffset();
     810       16863 :   auto nbytes = n * sizeof(dfloat);
     811             : 
     812       16863 :   nrs_t * nrs = (nrs_t *)nekrs::nrsPtr();
     813       16863 :   nrs->o_usrwrk.copyFrom(nrs->usrwrk + slot * n, nbytes, slot * nbytes);
     814       16863 : }
     815             : 
     816             : void
     817      408590 : NekRSProblem::mapFaceDataToNekFace(const unsigned int & e,
     818             :                                    const unsigned int & var_num,
     819             :                                    const Real & divisor_scale,
     820             :                                    const Real & additive_scale,
     821             :                                    double ** outgoing_data)
     822             : {
     823      408590 :   auto sys_number = _aux->number();
     824      408590 :   auto & mesh = _nek_mesh->getMesh();
     825      408590 :   auto indices = _nek_mesh->cornerIndices();
     826             : 
     827     1185340 :   for (int build = 0; build < _nek_mesh->nMoosePerNek(); ++build)
     828             :   {
     829      776750 :     auto elem_ptr = mesh.query_elem_ptr(e * _nek_mesh->nMoosePerNek() + build);
     830             : 
     831             :     // Only work on elements we can find on our local chunk of a
     832             :     // distributed mesh
     833      776750 :     if (!elem_ptr)
     834             :     {
     835             :       libmesh_assert(!mesh.is_serial());
     836           0 :       continue;
     837             :     }
     838             : 
     839     4555750 :     for (unsigned int n = 0; n < _n_vertices_per_surface; n++)
     840             :     {
     841             :       auto node_ptr = elem_ptr->node_ptr(n);
     842             : 
     843             :       // convert libMesh node index into the ordering used by NekRS
     844     3779000 :       int node_index = _nek_mesh->exactMirror() ? indices[build][_nek_mesh->boundaryNodeIndex(n)]
     845     2208184 :                                                 : _nek_mesh->boundaryNodeIndex(n);
     846             : 
     847     3779000 :       auto dof_idx = node_ptr->dof_number(sys_number, var_num, 0);
     848     3779000 :       (*outgoing_data)[node_index] =
     849     3779000 :           ((*_serialized_solution)(dof_idx)-additive_scale) / divisor_scale;
     850             :     }
     851             :   }
     852      408590 : }
     853             : 
     854             : void
     855     1032320 : NekRSProblem::mapFaceDataToNekVolume(const unsigned int & e,
     856             :                                      const unsigned int & var_num,
     857             :                                      const Real & divisor_scale,
     858             :                                      const Real & additive_scale,
     859             :                                      double ** outgoing_data)
     860             : {
     861     1032320 :   auto sys_number = _aux->number();
     862     1032320 :   auto & mesh = _nek_mesh->getMesh();
     863     1032320 :   auto indices = _nek_mesh->cornerIndices();
     864             : 
     865     2888960 :   for (int build = 0; build < _nek_mesh->nMoosePerNek(); ++build)
     866             :   {
     867     1856640 :     int n_faces_on_boundary = _nek_mesh->facesOnBoundary(e);
     868             : 
     869             :     // the only meaningful values are on the coupling boundaries, so we can skip this
     870             :     // interpolation if this volume element isn't on a coupling boundary
     871     1856640 :     if (n_faces_on_boundary > 0)
     872             :     {
     873      305280 :       auto elem_ptr = mesh.query_elem_ptr(e * _nek_mesh->nMoosePerNek() + build);
     874             : 
     875             :       // Only work on elements we can find on our local chunk of a
     876             :       // distributed mesh
     877      305280 :       if (!elem_ptr)
     878             :       {
     879             :         libmesh_assert(!mesh.is_serial());
     880           0 :         continue;
     881             :       }
     882             : 
     883     5757120 :       for (unsigned int n = 0; n < _n_vertices_per_volume; ++n)
     884             :       {
     885             :         auto node_ptr = elem_ptr->node_ptr(n);
     886             : 
     887             :         // convert libMesh node index into the ordering used by NekRS
     888     5451840 :         int node_index = _nek_mesh->exactMirror() ? indices[build][_nek_mesh->volumeNodeIndex(n)]
     889     5124160 :                                                   : _nek_mesh->volumeNodeIndex(n);
     890             : 
     891     5451840 :         auto dof_idx = node_ptr->dof_number(sys_number, var_num, 0);
     892     5451840 :         (*outgoing_data)[node_index] =
     893     5451840 :             ((*_serialized_solution)(dof_idx)-additive_scale) / divisor_scale;
     894             :       }
     895             :     }
     896             :   }
     897     1032320 : }
     898             : 
     899             : void
     900      385972 : NekRSProblem::mapVolumeDataToNekVolume(const unsigned int & e,
     901             :                                        const unsigned int & var_num,
     902             :                                        const Real & divisor,
     903             :                                        const Real & additive,
     904             :                                        double ** outgoing_data)
     905             : {
     906      385972 :   auto sys_number = _aux->number();
     907      385972 :   auto & mesh = _nek_mesh->getMesh();
     908      385972 :   auto indices = _nek_mesh->cornerIndices();
     909             : 
     910     1698288 :   for (int build = 0; build < _nek_mesh->nMoosePerNek(); ++build)
     911             :   {
     912     1312316 :     auto elem_ptr = mesh.query_elem_ptr(e * _nek_mesh->nMoosePerNek() + build);
     913             : 
     914             :     // Only work on elements we can find on our local chunk of a
     915             :     // distributed mesh
     916     1312316 :     if (!elem_ptr)
     917             :     {
     918             :       libmesh_assert(!mesh.is_serial());
     919           0 :       continue;
     920             :     }
     921    13017838 :     for (unsigned int n = 0; n < _n_vertices_per_volume; n++)
     922             :     {
     923             :       auto node_ptr = elem_ptr->node_ptr(n);
     924             : 
     925             :       // convert libMesh node index into the ordering used by NekRS
     926    11705522 :       int node_index = _nek_mesh->exactMirror() ? indices[build][_nek_mesh->volumeNodeIndex(n)]
     927     3321298 :                                                 : _nek_mesh->volumeNodeIndex(n);
     928             : 
     929    11705522 :       auto dof_idx = node_ptr->dof_number(sys_number, var_num, 0);
     930    11705522 :       (*outgoing_data)[node_index] = ((*_serialized_solution)(dof_idx)-additive) / divisor;
     931             :     }
     932             :   }
     933      385972 : }
     934             : 
     935             : void
     936        3072 : NekRSProblem::writeVolumeDisplacement(const int elem_id,
     937             :                                       double * s,
     938             :                                       const field::NekWriteEnum f,
     939             :                                       const std::vector<double> * add)
     940             : {
     941        3072 :   mesh_t * mesh = nekrs::entireMesh();
     942        3072 :   nrs_t * nrs = (nrs_t *)nekrs::nrsPtr();
     943             : 
     944        3072 :   auto vc = _nek_mesh->volumeCoupling();
     945        3072 :   int id = vc.element[elem_id] * mesh->Np;
     946             : 
     947        3072 :   if (_nek_mesh->exactMirror())
     948             :   {
     949             :     // can write directly into the NekRS solution
     950           0 :     for (int v = 0; v < mesh->Np; ++v)
     951             :     {
     952           0 :       double extra = (add == nullptr) ? 0.0 : (*add)[id + v];
     953             : 
     954           0 :       if (f == field::x_displacement)
     955           0 :         mesh->x[id + v] = s[v] + extra;
     956           0 :       else if (f == field::y_displacement)
     957           0 :         mesh->y[id + v] = s[v] + extra;
     958           0 :       else if (f == field::z_displacement)
     959           0 :         mesh->z[id + v] = s[v] + extra;
     960             :       else
     961           0 :         mooseError("Unhandled NekWriteEnum in writeVolumeDisplacement!");
     962             :     }
     963             :   }
     964             :   else
     965             :   {
     966             :     // need to interpolate onto the higher-order Nek mesh
     967        3072 :     double * tmp = (double *)calloc(mesh->Np, sizeof(double));
     968             : 
     969        3072 :     interpolateVolumeSolutionToNek(elem_id, s, tmp);
     970             : 
     971       86016 :     for (int v = 0; v < mesh->Np; ++v)
     972             :     {
     973       82944 :       double extra = (add == nullptr) ? 0.0 : (*add)[id + v];
     974       82944 :       if (f == field::x_displacement)
     975       27648 :         mesh->x[id + v] = s[v] + extra;
     976       55296 :       else if (f == field::y_displacement)
     977       27648 :         mesh->y[id + v] = s[v] + extra;
     978       27648 :       else if (f == field::z_displacement)
     979       27648 :         mesh->z[id + v] = s[v] + extra;
     980             :       else
     981           0 :         mooseError("Unhandled NekWriteEnum in writeVolumeDisplacement!");
     982             :     }
     983             : 
     984             :     freePointer(tmp);
     985             :   }
     986        3072 : }
     987             : 
     988             : void
     989     1415220 : NekRSProblem::writeVolumeSolution(const int slot,
     990             :                                   const int elem_id,
     991             :                                   double * s,
     992             :                                   const std::vector<double> * add)
     993             : {
     994     1415220 :   mesh_t * mesh = nekrs::entireMesh();
     995     1415220 :   nrs_t * nrs = (nrs_t *)nekrs::nrsPtr();
     996             : 
     997     1415220 :   auto vc = _nek_mesh->volumeCoupling();
     998     1415220 :   int id = vc.element[elem_id] * mesh->Np;
     999             : 
    1000     1415220 :   if (_nek_mesh->exactMirror())
    1001             :   {
    1002             :     // can write directly into the NekRS solution
    1003     6849620 :     for (int v = 0; v < mesh->Np; ++v)
    1004             :     {
    1005     6610176 :       double extra = (add == nullptr) ? 0.0 : (*add)[id + v];
    1006     6610176 :       nrs->usrwrk[slot + id + v] = s[v] + extra;
    1007             :     }
    1008             :   }
    1009             :   else
    1010             :   {
    1011             :     // need to interpolate onto the higher-order Nek mesh
    1012     1175776 :     double * tmp = (double *)calloc(mesh->Np, sizeof(double));
    1013             : 
    1014     1175776 :     interpolateVolumeSolutionToNek(elem_id, s, tmp);
    1015             : 
    1016   150797040 :     for (int v = 0; v < mesh->Np; ++v)
    1017             :     {
    1018   149621264 :       double extra = (add == nullptr) ? 0.0 : (*add)[id + v];
    1019   149621264 :       nrs->usrwrk[slot + id + v] = tmp[v] + extra;
    1020             :     }
    1021             : 
    1022             :     freePointer(tmp);
    1023             :   }
    1024     1415220 : }
    1025             : 
    1026             : void
    1027      408590 : NekRSProblem::writeBoundarySolution(const int slot, const int elem_id, double * s)
    1028             : {
    1029      408590 :   mesh_t * mesh = nekrs::temperatureMesh();
    1030      408590 :   nrs_t * nrs = (nrs_t *)nekrs::nrsPtr();
    1031             : 
    1032      408590 :   const auto & bc = _nek_mesh->boundaryCoupling();
    1033      408590 :   int offset = bc.element[elem_id] * mesh->Nfaces * mesh->Nfp + bc.face[elem_id] * mesh->Nfp;
    1034             : 
    1035      408590 :   if (_nek_mesh->exactMirror())
    1036             :   {
    1037             :     // can write directly into the NekRS solution
    1038      638144 :     for (int i = 0; i < mesh->Nfp; ++i)
    1039      613600 :       nrs->usrwrk[slot + mesh->vmapM[offset + i]] = s[i];
    1040             :   }
    1041             :   else
    1042             :   {
    1043             :     // need to interpolate onto the higher-order Nek mesh
    1044      384046 :     double * tmp = (double *)calloc(mesh->Nfp, sizeof(double));
    1045      384046 :     interpolateBoundarySolutionToNek(s, tmp);
    1046             : 
    1047     6959422 :     for (int i = 0; i < mesh->Nfp; ++i)
    1048     6575376 :       nrs->usrwrk[slot + mesh->vmapM[offset + i]] = tmp[i];
    1049             : 
    1050             :     freePointer(tmp);
    1051             :   }
    1052      408590 : }
    1053             : #endif

Generated by: LCOV version 1.14