LCOV - code coverage report
Current view: top level - src/actions - NekInitAction.C (source / functions) Hit Total Coverage
Test: neams-th-coe/cardinal: 9f356e Lines: 88 95 92.6 %
Date: 2026-08-19 15:42:22 Functions: 4 4 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 "NekInitAction.h"
      22             : #include "NekInterface.h"
      23             : 
      24             : #include "MooseApp.h"
      25             : #include <chrono>
      26             : 
      27             : registerMooseAction("CardinalApp", NekInitAction, "nek_init");
      28             : 
      29             : int NekInitAction::_n_cases = 0;
      30             : 
      31             : InputParameters
      32        1382 : NekInitAction::validParams()
      33             : {
      34        1382 :   InputParameters params = MooseObjectAction::validParams();
      35        2764 :   params.addParam<std::string>("type", "Problem type");
      36        2764 :   params.addParam<std::string>(
      37             :       "casename",
      38             :       "Case name for the NekRS input files; "
      39             :       "this is <case> in <case>.par, <case>.udf, <case>.oudf, and <case>.re2.");
      40             : 
      41        2764 :   params.addParam<bool>(
      42             :       "initialize_usrwrk",
      43        2764 :       true,
      44             :       "Whether Cardinal should allocate space for the urswrk array; if false, the user is "
      45             :       "responsible for allocating this array for their usage in the .udf file");
      46        2764 :   params.addParam<unsigned int>(
      47             :       "n_usrwrk_slots",
      48        2764 :       0,
      49             :       "If initialize_usrwrk is true, the number of slots that Cardinal will allocate in "
      50             :       "platform->app->bc->o_usrwrk. These entries can be used to hold fields related to coupling "
      51             :       "(which will be "
      52             :       "populated by Cardinal), or other custom usages, such as a distance-to-wall calculation");
      53             : 
      54        1382 :   return params;
      55           0 : }
      56             : 
      57         954 : NekInitAction::NekInitAction(const InputParameters & parameters)
      58             :   : MooseObjectAction(parameters),
      59         954 :     _initialize_usrwrk(getParam<bool>("initialize_usrwrk")),
      60        2862 :     _n_usrwrk_slots(getParam<unsigned int>("n_usrwrk_slots"))
      61             : {
      62         954 :   if (!_initialize_usrwrk)
      63           0 :     checkUnusedParam(parameters, "n_usrwrk_slots", "'initialize_usrwrk' is false");
      64         954 : }
      65             : 
      66             : void
      67         954 : NekInitAction::act()
      68             : {
      69         954 :   if (_type != "NekRSProblem")
      70         187 :     return;
      71             : 
      72         767 :   const auto timeStart = std::chrono::high_resolution_clock::now();
      73             : 
      74             :   // NekRS does some checks in main(); because we don't call that function
      75             :   // directly, repeat those checks here
      76         767 :   if (!getenv("NEKRS_HOME"))
      77           0 :     mooseError("Cannot find environment variable NEKRS_HOME!");
      78             : 
      79         767 :   std::string bin(getenv("NEKRS_HOME"));
      80             :   bin += "/bin/nekrs";
      81         767 :   const char * ptr = realpath(bin.c_str(), NULL);
      82         767 :   if (!ptr)
      83           0 :     mooseError("Cannot find '", bin, "'! Did you set NEKRS_HOME to the correct location?");
      84             : 
      85        1534 :   std::string setup_file = getParam<std::string>("casename");
      86             : 
      87             :   // If the casename is a directory path (i.e. not just a file name), then standalone
      88             :   // NekRS will cd into that directory so that the casename really is just the case file name,
      89             :   // i.e. with the path subtracted out. We will replicate this behavior here.
      90         767 :   std::size_t last_slash = setup_file.rfind('/') + 1;
      91         767 :   std::string casepath = setup_file.substr(0, last_slash);
      92         767 :   std::string casename = setup_file.substr(last_slash, setup_file.length() - last_slash);
      93         767 :   if (casepath.length() > 0)
      94             :   {
      95           5 :     int fail = chdir(casepath.c_str());
      96           5 :     if (fail)
      97           1 :       mooseError("Failed to find '", casepath.c_str(), "'! Did you set the 'casename' correctly?");
      98             :   }
      99             : 
     100             :   setup_file = casename;
     101             : 
     102             :   const int size_target =
     103        1532 :       _app.isParamValid("nekrs_build_only") ? _app.getParam<int>("nekrs_build_only") : 0;
     104        1540 :   const int ci_mode = _app.isParamValid("nekrs_cimode") ? _app.getParam<int>("nekrs_cimode") : 0;
     105             :   const std::string backend =
     106        1532 :       _app.isParamValid("nekrs_backend") ? _app.getParam<std::string>("nekrs_backend") : "";
     107             :   const std::string device_id =
     108        1532 :       _app.isParamValid("nekrs_device_id") ? _app.getParam<std::string>("nekrs_device_id") : "";
     109             : 
     110         766 :   const int build_only = size_target > 0 ? 1 : 0;
     111         766 :   nekrs::buildOnly(build_only);
     112             : 
     113         766 :   MPI_Comm comm = *static_cast<const MPI_Comm *>(&_communicator.get());
     114             : 
     115             :   // If this MPI communicator has already created one case, then we cannot also create a
     116             :   // second NekRS case. For instance, if you have 4 Nek sub-apps, but only 3 processes, then
     117             :   // NekRS doesn't like trying to set up a case with a communicator, then immediately try
     118             :   // to set up another case with the same communicator. If you un-comment this error message,
     119             :   // you'll get an error when reading the mesh file that is basically missing a "/".
     120             :   // This error is probably due to NekRS relying a lot on static variables.
     121         766 :   if (_n_cases > 0)
     122             :   {
     123             :     int size;
     124           2 :     MPI_Comm_size(comm, &size);
     125           2 :     mooseError(
     126             :         "NekRS does not currently support setting up multiple cases with the same "
     127             :         "MPI communicator.\nThat is, you need at least one MPI process in a master "
     128             :         "application per Nek sub-application.\n\n"
     129           2 :         "The MPI communicator has " +
     130           2 :         std::to_string(size) +
     131             :         " ranks and is trying to "
     132           2 :         "construct " +
     133           2 :         std::to_string(_n_cases + 1) +
     134             :         "+ cases.\n\n"
     135             :         "If you are running a Stochastic Tools simulation in either 'normal' or 'batch-reset'\n"
     136             :         "mode, you will need at least 'min_procs_per_app' * 'num_rows' MPI ranks. OR, we\n"
     137             :         "recommend using the 'batch-restore' mode, which does not have any such limitations.");
     138             :   }
     139             : 
     140         764 :   auto par = readPar(setup_file, comm);
     141             : 
     142        1528 :   nekrs::setup(
     143             :       comm /* global communicator, like for Nek-Nek : NOT SUPPORTED, so we use same comm */,
     144             :       comm /* local communicator */,
     145             :       build_only,
     146             :       size_target,
     147             :       ci_mode,
     148             :       par,
     149             :       casename,
     150             :       backend,
     151             :       device_id,
     152             :       1 /* n sessions */,
     153             :       0 /* session ID */,
     154             :       0 /* debug mode */);
     155             : 
     156         764 :   _n_cases++;
     157             : 
     158             :   // copy-pasta from NekRS's main()
     159         764 :   double elapsedTime = 0;
     160         764 :   const auto timeStop = std::chrono::high_resolution_clock::now();
     161         764 :   elapsedTime += std::chrono::duration<double, std::milli>(timeStop - timeStart).count() / 1e3;
     162         764 :   MPI_Allreduce(MPI_IN_PLACE, &elapsedTime, 1, MPI_DOUBLE, MPI_MAX, comm);
     163         764 :   nekrs::updateTimer("setup", elapsedTime);
     164         764 :   nekrs::setNekSetupTime(elapsedTime);
     165             : 
     166         764 :   _console << "initialization took " << elapsedTime << " s" << std::endl;
     167             : 
     168             :   // Initialize host Nek arrays
     169         764 :   nekrs::initializeNekHostArrays();
     170             : 
     171         764 :   if (_initialize_usrwrk)
     172             :   {
     173         764 :     if (!nekrs::scratchAvailable())
     174           1 :       mooseError("Because 'initialize_usrwrk' is true, the platform->app->bc->o_usrwrk array is "
     175             :                  "allocated by Cardinal, but you have tried allocating it separately inside your "
     176             :                  "case files. Please remove the manual allocation of this array in your case "
     177             :                  "files, and be sure to only write such that the space reserved for Cardinal "
     178             :                  "coupling data is untouched. Cardinal can allocate the size of this array by "
     179             :                  "providing the 'n_usrwrk_slots' parameter for NekRSProblem.\n\nIf you prefer to "
     180             :                  "initialize the usrwrk array manually, set 'initialize_usrwrk' to false.");
     181             : 
     182             :     // Initialize scratch space in NekRS to write data incoming data from MOOSE
     183         763 :     nekrs::initializeScratch(_n_usrwrk_slots);
     184             :   }
     185             : }
     186             : 
     187             : std::map<std::string, std::map<std::string, std::string>>
     188         764 : NekInitAction::readPar(const std::string & _setupFile, MPI_Comm comm)
     189             : {
     190             :   int rank;
     191         764 :   MPI_Comm_rank(comm, &rank);
     192             : 
     193         764 :   const auto setupFile = _setupFile + ".par";
     194             : 
     195         764 :   if (rank == 0)
     196         342 :     std::cout << "reading " << setupFile << std::endl;
     197             : 
     198         764 :   int err = 0;
     199         764 :   if (rank == 0)
     200             :   {
     201         684 :     if (!std::filesystem::exists(setupFile))
     202             :     {
     203           0 :       std::cerr << "Cannot find setup file " << setupFile << std::endl;
     204           0 :       err++;
     205             :     }
     206             :   }
     207         764 :   MPI_Allreduce(MPI_IN_PLACE, &err, 1, MPI_INT, MPI_MAX, comm);
     208         764 :   if (err)
     209             :   {
     210           0 :     MPI_Abort(comm, EXIT_FAILURE);
     211             :   }
     212             : 
     213             :   char * rbuf;
     214             :   long fsize;
     215             : 
     216         764 :   if (rank == 0)
     217             :   {
     218         342 :     FILE * f = fopen(setupFile.c_str(), "rb");
     219         342 :     fseek(f, 0, SEEK_END);
     220         342 :     fsize = ftell(f);
     221         342 :     fseek(f, 0, SEEK_SET);
     222         342 :     rbuf = new char[fsize];
     223         342 :     auto s = fread(rbuf, 1, fsize, f);
     224         342 :     fclose(f);
     225             :   }
     226         764 :   MPI_Bcast(&fsize, sizeof(fsize), MPI_BYTE, 0, comm);
     227             : 
     228         764 :   if (rank != 0)
     229         422 :     rbuf = new char[fsize];
     230         764 :   MPI_Bcast(rbuf, fsize, MPI_CHAR, 0, comm);
     231             : 
     232         764 :   auto par = new inipp::Ini();
     233             : 
     234         764 :   std::stringstream is;
     235         764 :   is.write(rbuf, fsize);
     236             : 
     237         764 :   par->parse(is);
     238         764 :   par->interpolate();
     239             : 
     240         764 :   return par->sections;
     241         764 : }
     242             : 
     243             : #endif

Generated by: LCOV version 1.14