LCOV - code coverage report
Current view: top level - src/multiapps - SamplerFullSolveMultiApp.C (source / functions) Hit Total Coverage
Test: idaholab/moose stochastic_tools: f45d79 Lines: 174 198 87.9 %
Date: 2025-07-25 05:00:46 Functions: 11 11 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : // StochasticTools includes
      11             : #include "SamplerFullSolveMultiApp.h"
      12             : #include "Sampler.h"
      13             : #include "StochasticToolsTransfer.h"
      14             : #include "Console.h"
      15             : #include "VariadicTable.h"
      16             : 
      17             : registerMooseObject("StochasticToolsApp", SamplerFullSolveMultiApp);
      18             : 
      19             : InputParameters
      20        7508 : SamplerFullSolveMultiApp::validParams()
      21             : {
      22        7508 :   InputParameters params = FullSolveMultiApp::validParams();
      23        7508 :   params += SamplerInterface::validParams();
      24        7508 :   params += ReporterInterface::validParams();
      25        7508 :   params.addClassDescription(
      26             :       "Creates a full-solve type sub-application for each row of each Sampler matrix.");
      27       15016 :   params.addRequiredParam<SamplerName>("sampler",
      28             :                                        "The Sampler object to utilize for creating MultiApps.");
      29        7508 :   params.suppressParameter<std::vector<Point>>("positions");
      30        7508 :   params.suppressParameter<bool>("output_in_position");
      31        7508 :   params.suppressParameter<std::vector<FileName>>("positions_file");
      32        7508 :   params.suppressParameter<Real>("move_time");
      33        7508 :   params.suppressParameter<std::vector<Point>>("move_positions");
      34        7508 :   params.suppressParameter<std::vector<unsigned int>>("move_apps");
      35        7508 :   params.set<bool>("use_positions") = false;
      36             : 
      37       15016 :   MooseEnum modes("normal=0 batch-reset=1 batch-restore=2", "normal");
      38       15016 :   params.addParam<MooseEnum>(
      39             :       "mode",
      40             :       modes,
      41             :       "The operation mode, 'normal' creates one sub-application for each row in the Sampler and "
      42             :       "'batch-reset' and 'batch-restore' creates N sub-applications, where N is the minimum of "
      43             :       "'num_rows' in the Sampler and floor(number of processes / min_procs_per_app). To run "
      44             :       "the rows in the Sampler, 'batch-reset' will destroy and re-create sub-apps as needed, "
      45             :       "whereas the 'batch-restore' will backup and restore sub-apps to the initial state prior "
      46             :       "to execution, without destruction.");
      47       15016 :   params.addParam<ReporterName>(
      48             :       "should_run_reporter",
      49             :       "Vector reporter value determining whether a certain multiapp should be run with this "
      50             :       "multiapp. This only works in batch-reset or batch-restore mode.");
      51        7508 :   return params;
      52        7508 : }
      53             : 
      54        3738 : SamplerFullSolveMultiApp::SamplerFullSolveMultiApp(const InputParameters & parameters)
      55             :   : FullSolveMultiApp(parameters),
      56             :     SamplerInterface(this),
      57             :     ReporterInterface(this),
      58        3738 :     _sampler(getSampler("sampler")),
      59        7476 :     _mode(getParam<MooseEnum>("mode").getEnum<StochasticTools::MultiAppMode>()),
      60        3738 :     _local_batch_app_index(0),
      61        7476 :     _solved_once(false)
      62             : {
      63        7476 :   if (getParam<unsigned int>("min_procs_per_app") !=
      64       11214 :           _sampler.getParam<unsigned int>("min_procs_per_row") ||
      65        7476 :       getParam<unsigned int>("max_procs_per_app") !=
      66       11214 :           _sampler.getParam<unsigned int>("max_procs_per_row"))
      67           0 :     paramError("sampler",
      68             :                "Sampler and multiapp communicator configuration inconsistent. Please ensure that "
      69             :                "'MultiApps/",
      70             :                name(),
      71             :                "/min(max)_procs_per_app' and 'Samplers/",
      72           0 :                _sampler.name(),
      73             :                "/min(max)_procs_per_row' are the same.");
      74             : 
      75        3738 :   init(_sampler.getNumberOfRows(),
      76        3738 :        _sampler.getRankConfig(_mode == StochasticTools::MultiAppMode::BATCH_RESET ||
      77             :                               _mode == StochasticTools::MultiAppMode::BATCH_RESTORE));
      78        3738 :   _number_of_sampler_rows = _sampler.getNumberOfRows();
      79             : 
      80       11214 :   if (isParamValid("should_run_reporter") && _mode == StochasticTools::MultiAppMode::NORMAL)
      81           4 :     paramError("should_run_reporter",
      82             :                "Conditionally run sampler multiapp only works in batch modes.");
      83        3734 : }
      84             : 
      85             : void
      86        8974 : SamplerFullSolveMultiApp::preTransfer(Real /*dt*/, Real /*target_time*/)
      87             : {
      88             :   // Reinitialize MultiApp size
      89        8974 :   const auto num_rows = _sampler.getNumberOfRows();
      90        8974 :   if (num_rows != _number_of_sampler_rows)
      91             :   {
      92          96 :     init(num_rows,
      93          96 :          _sampler.getRankConfig(_mode == StochasticTools::MultiAppMode::BATCH_RESET ||
      94             :                                 _mode == StochasticTools::MultiAppMode::BATCH_RESTORE));
      95          96 :     _number_of_sampler_rows = num_rows;
      96             :     _row_data.clear();
      97             :   }
      98             : 
      99             :   // Reinitialize app to original state prior to solve, if a solve has occured.
     100             :   // Since the app is reinitialized in the solve step either way, we skip this
     101             :   // for batch-reset mode.
     102        8974 :   if (_solved_once && _mode != StochasticTools::MultiAppMode::BATCH_RESET)
     103        2962 :     initialSetup();
     104             : 
     105       17948 :   if (isParamValid("should_run_reporter"))
     106        2436 :     _should_run = &getReporterValue<std::vector<bool>>("should_run_reporter");
     107        8974 : }
     108             : 
     109             : bool
     110        8894 : SamplerFullSolveMultiApp::solveStep(Real dt, Real target_time, bool auto_advance)
     111             : {
     112       17788 :   TIME_SECTION("solveStep", 3, "Solving SamplerFullSolveMultiApp");
     113             : 
     114             :   mooseAssert(_my_num_apps, _sampler.getNumberOfLocalRows());
     115             : 
     116             :   bool last_solve_converged = true;
     117             : 
     118        8894 :   if (_mode == StochasticTools::MultiAppMode::BATCH_RESET ||
     119             :       _mode == StochasticTools::MultiAppMode::BATCH_RESTORE)
     120        4488 :     last_solve_converged = solveStepBatch(dt, target_time, auto_advance);
     121             :   else
     122        4406 :     last_solve_converged = FullSolveMultiApp::solveStep(dt, target_time, auto_advance);
     123             : 
     124        8890 :   _solved_once = true;
     125             : 
     126        8890 :   return last_solve_converged;
     127             : }
     128             : 
     129             : bool
     130        4488 : SamplerFullSolveMultiApp::solveStepBatch(Real dt, Real target_time, bool auto_advance)
     131             : {
     132        8976 :   TIME_SECTION("solveStepBatch", 3, "Solving Step Batch For SamplerFullSolveMultiApp");
     133             : 
     134        4488 :   if (_should_run && _should_run->size() < _sampler.getNumberOfLocalRows())
     135           0 :     paramError("should_run_reporter",
     136             :                "Reporter deteriming multiapp run must be of size greater than or equal to the "
     137             :                "number of local rows in the sampler, ",
     138           0 :                _should_run->size(),
     139             :                " < ",
     140           0 :                _sampler.getNumberOfLocalRows(),
     141             :                ".");
     142             : 
     143             :   // Value to return
     144             :   bool last_solve_converged = true;
     145             : 
     146             :   // List of active relevant Transfer objects
     147             :   std::vector<std::shared_ptr<StochasticToolsTransfer>> to_transfers =
     148        4488 :       getActiveStochasticToolsTransfers(MultiAppTransfer::TO_MULTIAPP);
     149             :   std::vector<std::shared_ptr<StochasticToolsTransfer>> from_transfers =
     150        4488 :       getActiveStochasticToolsTransfers(MultiAppTransfer::FROM_MULTIAPP);
     151             : 
     152             :   // Initialize to/from transfers
     153        8446 :   for (auto transfer : to_transfers)
     154             :   {
     155        3958 :     transfer->setGlobalMultiAppIndex(_rank_config.first_local_app_index);
     156        3958 :     transfer->initializeToMultiapp();
     157             :   }
     158        9196 :   for (auto transfer : from_transfers)
     159             :   {
     160        4708 :     transfer->setGlobalMultiAppIndex(_rank_config.first_local_app_index);
     161        4708 :     transfer->initializeFromMultiapp();
     162             :   }
     163             : 
     164        4488 :   if (_mode == StochasticTools::MultiAppMode::BATCH_RESTORE)
     165        1128 :     backup();
     166             : 
     167             :   // Perform batch MultiApp solves
     168        4488 :   _local_batch_app_index = 0;
     169       27500 :   for (dof_id_type i = _rank_config.first_local_sim_index;
     170       27500 :        i < _rank_config.first_local_sim_index + _rank_config.num_local_sims;
     171             :        ++i)
     172             :   {
     173       23012 :     updateRowData(_local_batch_app_index);
     174             : 
     175       23012 :     bool run = true;
     176       23012 :     if (_should_run)
     177             :     {
     178        2732 :       if (isRootProcessor())
     179        2210 :         run = (*_should_run)[_local_batch_app_index];
     180        2732 :       _my_communicator.broadcast(run, 0);
     181             :     }
     182       23012 :     if (!run)
     183             :     {
     184        1532 :       _local_batch_app_index++;
     185        1532 :       continue;
     186             :     }
     187             : 
     188             :     // Given that we don't initialize in preTransfer for batch-reset mode, we need
     189             :     // a different logic for resetting the apps for every sample:
     190             :     // - batch-restore: after (re-)initializing the problem, we only need to restore
     191             :     //   starting from the second sample
     192       21480 :     if (_mode == StochasticTools::MultiAppMode::BATCH_RESTORE)
     193             :     {
     194       11170 :       if (i != _rank_config.first_local_sim_index)
     195       10042 :         restore();
     196             :     }
     197             :     // - batch-reset: we don't need to initialize for the first sample in the first
     198             :     //   solve. After that, we initialize every time. This is mainly to avoid unnecessary
     199             :     //   initializations for cases when the multiapp does not need to be executed (conditional runs)
     200             :     else
     201             :     {
     202       10310 :       if (i != _rank_config.first_local_sim_index || _solved_once)
     203        9320 :         initialSetup();
     204             :     }
     205             : 
     206       21480 :     execBatchTransfers(to_transfers,
     207             :                        i,
     208       21480 :                        _row_data,
     209             :                        MultiAppTransfer::TO_MULTIAPP,
     210       21480 :                        _fe_problem.verboseMultiApps(),
     211       21480 :                        _console);
     212             : 
     213             :     // Set the file base based on the current row
     214       42960 :     for (unsigned int ai = 0; ai < _my_num_apps; ++ai)
     215             :     {
     216       21480 :       const std::string mname = getMultiAppName(name(), i, _number_of_sampler_rows);
     217       64440 :       _apps[ai]->setOutputFileBase(_app.getOutputFileBase() + "_" + mname);
     218             :     }
     219             : 
     220             :     const bool curr_last_solve_converged =
     221       21480 :         FullSolveMultiApp::solveStep(dt, target_time, auto_advance);
     222       21480 :     last_solve_converged = last_solve_converged && curr_last_solve_converged;
     223             : 
     224       21480 :     execBatchTransfers(from_transfers,
     225             :                        i,
     226             :                        _row_data,
     227             :                        MultiAppTransfer::FROM_MULTIAPP,
     228       21480 :                        _fe_problem.verboseMultiApps(),
     229             :                        _console);
     230             : 
     231       21480 :     _local_batch_app_index++;
     232             :   }
     233        4488 :   _local_batch_app_index = 0;
     234             : 
     235             :   // Finalize to/from transfers
     236        8446 :   for (auto transfer : to_transfers)
     237        3958 :     transfer->finalizeToMultiapp();
     238        9196 :   for (auto transfer : from_transfers)
     239        4708 :     transfer->finalizeFromMultiapp();
     240             : 
     241        4488 :   return last_solve_converged;
     242        4488 : }
     243             : 
     244             : void
     245       46360 : SamplerFullSolveMultiApp::execBatchTransfers(
     246             :     const std::vector<std::shared_ptr<StochasticToolsTransfer>> & transfers,
     247             :     dof_id_type global_row_index,
     248             :     const std::vector<Real> & row_data,
     249             :     Transfer::DIRECTION direction,
     250             :     bool verbose,
     251             :     const ConsoleStream & console)
     252             : {
     253       46360 :   if (verbose && transfers.size())
     254             :   {
     255           0 :     console << COLOR_CYAN << "\nBatch transfers for row " << global_row_index;
     256           0 :     if (direction == MultiAppTransfer::TO_MULTIAPP)
     257           0 :       console << " To ";
     258           0 :     else if (direction == MultiAppTransfer::FROM_MULTIAPP)
     259           0 :       console << " From ";
     260           0 :     console << "MultiApps" << COLOR_DEFAULT << ":" << std::endl;
     261             : 
     262           0 :     console << "Sampler row " << global_row_index << " data: [" << Moose::stringify(row_data) << "]"
     263           0 :             << std::endl;
     264             : 
     265             :     // Build Table of Transfer Info
     266             :     VariadicTable<std::string, std::string, std::string, std::string> table(
     267           0 :         {"Name", "Type", "From", "To"});
     268           0 :     for (const auto & transfer : transfers)
     269           0 :       table.addRow(
     270           0 :           transfer->name(), transfer->type(), transfer->getFromName(), transfer->getToName());
     271           0 :     table.print(console);
     272           0 :   }
     273             : 
     274       85890 :   for (auto & transfer : transfers)
     275             :   {
     276       39530 :     transfer->setGlobalRowIndex(global_row_index);
     277             :     transfer->setCurrentRow(row_data);
     278       39530 :     if (direction == MultiAppTransfer::TO_MULTIAPP)
     279       15780 :       transfer->executeToMultiapp();
     280       23750 :     else if (direction == MultiAppTransfer::FROM_MULTIAPP)
     281       23750 :       transfer->executeFromMultiapp();
     282             :   }
     283             : 
     284       46360 :   if (verbose && transfers.size())
     285           0 :     console << COLOR_CYAN << "Batch transfers for row " << global_row_index << " Are Finished\n"
     286           0 :             << COLOR_DEFAULT << std::endl;
     287       46360 : }
     288             : 
     289             : void
     290       35218 : SamplerFullSolveMultiApp::showStatusMessage(unsigned int i) const
     291             : {
     292             :   // Local row is the app index if in normal mode, otherwise it's _local_batch_app_index
     293             :   const dof_id_type local_row =
     294       35218 :       _mode == StochasticTools::MultiAppMode::NORMAL ? (dof_id_type)i : _local_batch_app_index;
     295             :   // If the local row is less than the number of local sims, we aren't finished yet
     296       35218 :   if (local_row < _rank_config.num_local_sims - 1)
     297             :     return;
     298             : 
     299             :   // Loop through processors to communicate completeness
     300       25164 :   for (const auto & pid : make_range(n_processors()))
     301             :   {
     302             :     // This is what is being sent to trigger completeness
     303       35532 :     dof_id_type last_row = _rank_config.is_first_local_rank
     304       17766 :                                ? _rank_config.first_local_sim_index + _rank_config.num_local_sims
     305             :                                : 0;
     306             :     // Cannot send/receive to the same processor, so avoid if root
     307       17766 :     if (pid > 0)
     308             :     {
     309             :       // Send data to root
     310       10368 :       if (pid == processor_id())
     311        3554 :         _communicator.send(0, last_row);
     312             :       // Receive data from source
     313        6814 :       else if (processor_id() == 0)
     314        3554 :         _communicator.receive(pid, last_row);
     315             :     }
     316             : 
     317             :     // Output the samples that are complete if it's the main processor for the batch
     318       17766 :     if (last_row)
     319       15168 :       _console << COLOR_CYAN << type() << " [" << name() << "] " << last_row << "/"
     320       14720 :                << _number_of_sampler_rows << " samples complete!" << std::endl;
     321             :   }
     322             : }
     323             : 
     324             : std::vector<std::shared_ptr<StochasticToolsTransfer>>
     325        8976 : SamplerFullSolveMultiApp::getActiveStochasticToolsTransfers(Transfer::DIRECTION direction)
     326             : {
     327             :   std::vector<std::shared_ptr<StochasticToolsTransfer>> output;
     328             :   const ExecuteMooseObjectWarehouse<Transfer> & warehouse =
     329        8976 :       _fe_problem.getMultiAppTransferWarehouse(direction);
     330       18826 :   for (std::shared_ptr<Transfer> transfer : warehouse.getActiveObjects())
     331             :   {
     332        9850 :     auto ptr = std::dynamic_pointer_cast<StochasticToolsTransfer>(transfer);
     333       19700 :     if (ptr && ptr->getMultiApp().get() == this)
     334        8666 :       output.push_back(ptr);
     335             :   }
     336        8976 :   return output;
     337           0 : }
     338             : 
     339             : std::vector<std::string>
     340       18242 : SamplerFullSolveMultiApp::getCommandLineArgs(const unsigned int local_app)
     341             : {
     342             :   std::vector<std::string> args;
     343             : 
     344             :   // With multiple processors per app, there are no local rows for non-root processors
     345       18242 :   if (isRootProcessor())
     346             :   {
     347             :     // Since we only store param_names in cli_args, we need to find the values for each param from
     348             :     // sampler data and combine them to get full command line option strings.
     349       17932 :     updateRowData(_mode == StochasticTools::MultiAppMode::NORMAL ? local_app
     350             :                                                                  : _local_batch_app_index);
     351             : 
     352       35860 :     args = sampledCommandLineArgs(_row_data, FullSolveMultiApp::getCommandLineArgs(local_app));
     353             :   }
     354             : 
     355       18238 :   _my_communicator.broadcast(args);
     356       18238 :   return args;
     357           0 : }
     358             : 
     359             : void
     360       40944 : SamplerFullSolveMultiApp::updateRowData(dof_id_type local_index)
     361             : {
     362       40944 :   if (!isRootProcessor())
     363             :     return;
     364             : 
     365             :   mooseAssert(local_index < _sampler.getNumberOfLocalRows(),
     366             :               "Local index must be less than number of local rows.");
     367             : 
     368       39722 :   if (_row_data.empty() ||
     369       36834 :       (_local_row_index == _sampler.getNumberOfLocalRows() - 1 && local_index == 0))
     370             :   {
     371             :     mooseAssert(local_index == 0,
     372             :                 "The first time calling updateRowData must have a local index of 0.");
     373        5970 :     _local_row_index = 0;
     374       11940 :     _row_data = _sampler.getNextLocalRow();
     375             :   }
     376       33752 :   else if (local_index - _local_row_index == 1)
     377             :   {
     378       25990 :     _local_row_index++;
     379       51980 :     _row_data = _sampler.getNextLocalRow();
     380             :   }
     381             : 
     382             :   mooseAssert(local_index == _local_row_index,
     383             :               "Local index must be equal or one greater than the index previously called.");
     384             : }
     385             : 
     386             : std::vector<std::string>
     387       39672 : SamplerFullSolveMultiApp::sampledCommandLineArgs(const std::vector<Real> & row,
     388             :                                                  const std::vector<std::string> & full_args_name)
     389             : {
     390             :   std::vector<std::string> args;
     391             : 
     392             :   // Find parameters that are meant to be assigned by sampler values
     393             :   std::vector<std::string> cli_args_name;
     394      147334 :   for (const auto & fan : full_args_name)
     395             :   {
     396             :     // If it has an '=', then it is not meant to be modified
     397      107662 :     if (fan.find("=") == std::string::npos)
     398      106198 :       cli_args_name.push_back(fan);
     399             :     else
     400        1464 :       args.push_back(fan);
     401             :   }
     402             : 
     403             :   // Make sure the parameters either all have brackets, or none of them do
     404             :   bool has_brackets = false;
     405       39672 :   if (cli_args_name.size())
     406             :   {
     407       39050 :     has_brackets = cli_args_name[0].find("[") != std::string::npos;
     408      106198 :     for (unsigned int i = 1; i < cli_args_name.size(); ++i)
     409       67148 :       if (has_brackets != (cli_args_name[i].find("[") != std::string::npos))
     410           0 :         ::mooseError("If the bracket is used, it must be provided to every parameter.");
     411             :   }
     412       39672 :   if (!has_brackets && cli_args_name.size() && cli_args_name.size() != row.size())
     413           8 :     ::mooseError("Number of command line arguments does not match number of sampler columns.");
     414             : 
     415      145842 :   for (unsigned int i = 0; i < cli_args_name.size(); ++i)
     416             :   {
     417             :     // Assign bracketed parameters
     418      106182 :     if (has_brackets)
     419             :     {
     420             :       // Split param name and vector assignment: "param[0,(3.14),1]" -> {"param", "0,(3.14),1]"}
     421        3102 :       const std::vector<std::string> & vector_param = MooseUtils::split(cli_args_name[i], "[");
     422             :       // Get indices of vector: "0,(3.14),1]" -> {"0", "(3.14)", "1"}
     423             :       const std::vector<std::string> & index_string =
     424        6204 :           MooseUtils::split(vector_param[1].substr(0, vector_param[1].find("]")), ",");
     425             : 
     426             :       // Loop through indices and assign parameter: param='row[0] 3.14 row[1]'
     427             :       std::vector<std::string> values;
     428       14622 :       for (const auto & istr : index_string)
     429             :       {
     430             :         Real value;
     431             : 
     432             :         // If the value is enclosed in parentheses, then it isn't an index, it's a value
     433       11524 :         if (istr.find("(") != std::string::npos)
     434          68 :           value = std::stod(istr.substr(istr.find("(") + 1));
     435             :         // Assign the value from row if it is an index
     436             :         else
     437             :         {
     438       11490 :           unsigned int index = MooseUtils::stringToInteger(istr);
     439       11490 :           if (index >= row.size())
     440           4 :             ::mooseError("The provided global column index (",
     441             :                          index,
     442             :                          ") for ",
     443             :                          vector_param[0],
     444             :                          " is out of bound.");
     445       11486 :           value = row[index];
     446             :         }
     447             : 
     448       23040 :         values.push_back(Moose::stringifyExact(value));
     449             :       }
     450             : 
     451        6196 :       args.push_back(vector_param[0] + "='" + MooseUtils::stringJoin(values) + "'");
     452        3098 :     }
     453             :     // Assign scalar parameters
     454             :     else
     455      206160 :       args.push_back(cli_args_name[i] + "=" + Moose::stringifyExact(row[i]));
     456             :   }
     457             : 
     458       39660 :   return args;
     459       39660 : }

Generated by: LCOV version 1.14