LCOV - code coverage report
Current view: top level - src/actions - ParameterStudyAction.C (source / functions) Hit Total Coverage
Test: idaholab/moose stochastic_tools: f45d79 Lines: 392 396 99.0 %
Date: 2025-07-25 05:00:46 Functions: 17 17 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             : #include "ParameterStudyAction.h"
      11             : 
      12             : #include "StochasticToolsAction.h"
      13             : #include "FEProblemBase.h"
      14             : #include "Control.h"
      15             : #include "Calculators.h"
      16             : 
      17             : registerMooseAction("StochasticToolsApp", ParameterStudyAction, "meta_action");
      18             : registerMooseAction("StochasticToolsApp", ParameterStudyAction, "add_distribution");
      19             : registerMooseAction("StochasticToolsApp", ParameterStudyAction, "add_sampler");
      20             : registerMooseAction("StochasticToolsApp", ParameterStudyAction, "add_multi_app");
      21             : registerMooseAction("StochasticToolsApp", ParameterStudyAction, "add_transfer");
      22             : registerMooseAction("StochasticToolsApp", ParameterStudyAction, "add_output");
      23             : registerMooseAction("StochasticToolsApp", ParameterStudyAction, "add_reporter");
      24             : registerMooseAction("StochasticToolsApp", ParameterStudyAction, "add_control");
      25             : 
      26             : InputParameters
      27         452 : ParameterStudyAction::validParams()
      28             : {
      29         452 :   InputParameters params = Action::validParams();
      30         452 :   params.addClassDescription("Builds objects to set up a basic parameter study.");
      31             : 
      32             :   // Parameters to define what we are studying
      33         904 :   params.addRequiredParam<FileName>(
      34             :       "input", "The input file containing the physics for the parameter study.");
      35         904 :   params.addRequiredParam<std::vector<std::string>>(
      36             :       "parameters", "List of parameters being perturbed for the study.");
      37         904 :   params.addParam<std::vector<ReporterName>>(
      38             :       "quantities_of_interest",
      39             :       "List of the reporter names (object_name/value_name) "
      40             :       "that represent the quantities of interest for the study.");
      41             : 
      42             :   // Statistics Parameters
      43         904 :   params.addParam<bool>(
      44             :       "compute_statistics",
      45         904 :       true,
      46             :       "Whether or not to compute statistics on the 'quantities_of_interest'. "
      47             :       "The default is to compute mean and standard deviation with 0.01, 0.05, 0.1, 0.9, "
      48             :       "0.95, and 0.99 confidence intervals.");
      49         452 :   MultiMooseEnum stats = StochasticTools::makeCalculatorEnum();
      50         452 :   stats = "mean stddev";
      51         904 :   params.addParam<MultiMooseEnum>(
      52             :       "statistics", stats, "The statistic(s) to compute for the study.");
      53        1356 :   params.addParam<std::vector<Real>>("ci_levels",
      54         904 :                                      std::vector<Real>({0.01, 0.05, 0.1, 0.9, 0.95, 0.99}),
      55             :                                      "A vector of confidence levels to consider for statistics "
      56             :                                      "confidence intervals, values must be in (0, 1).");
      57         904 :   params.addParam<unsigned int>(
      58             :       "ci_replicates",
      59         904 :       1000,
      60             :       "The number of replicates to use when computing confidence level intervals for statistics.");
      61             : 
      62             :   // Parameters for the sampling scheme
      63         904 :   params.addRequiredParam<MooseEnum>(
      64         904 :       "sampling_type", samplingTypes(), "The type of sampling to use for the parameter study.");
      65         904 :   params.addParam<unsigned int>("seed", 0, "Random number generator initial seed");
      66             : 
      67             :   // Parameters for multi app
      68             :   MooseEnum modes(
      69         904 :       "normal=0 batch-reset=1 batch-restore=2 batch-keep-solution=3 batch-no-restore=4");
      70         904 :   params.addParam<MooseEnum>(
      71             :       "multiapp_mode",
      72             :       modes,
      73             :       "The operation mode, 'normal' creates one sub-application for each sample."
      74             :       "'batch' creates one sub-app for each processor and re-executes for each local sample. "
      75             :       "'reset' re-initializes the sub-app for every sample in the batch. "
      76             :       "'restore' does not re-initialize and instead restores to first sample's initialization. "
      77             :       "'keep-solution' re-uses the solution obtained from the first sample in the batch. "
      78             :       "'no-restore' does not restore the sub-app."
      79             :       "The default will be inferred based on the study.");
      80         904 :   params.addParam<unsigned int>(
      81             :       "min_procs_per_sample",
      82         904 :       1,
      83             :       "Minimum number of processors to give to each sample. Useful for larger, distributed mesh "
      84             :       "solves where there are memory constraints.");
      85         904 :   params.addParam<bool>("ignore_solve_not_converge",
      86         904 :                         false,
      87             :                         "True to continue main app even if a sub app's solve does not converge.");
      88             : 
      89             :   // Samplers ///////////////////////////
      90             :   // Parameters for Monte Carlo and LHS
      91         904 :   params.addParam<dof_id_type>(
      92             :       "num_samples", "The number of samples to generate for 'monte-carlo' and 'lhs' sampling.");
      93         904 :   params.addParam<MultiMooseEnum>(
      94             :       "distributions",
      95         904 :       distributionTypes(),
      96             :       "The types of distribution to use for 'monte-carlo' and "
      97             :       "'lhs' sampling. The number of entries defines the number of columns in the matrix.");
      98             :   // Parameters for cartesian product
      99         904 :   params.addParam<std::vector<Real>>(
     100             :       "linear_space_items",
     101             :       "Parameter for defining the 'cartesian-prodcut' sampling scheme. A list of triplets, each "
     102             :       "item should include the min, step size, and number of steps.");
     103             :   // Parameters for CSV sampler
     104         904 :   params.addParam<FileName>(
     105             :       "csv_samples_file",
     106             :       "Name of the CSV file that contains the sample matrix for 'csv' sampling.");
     107         904 :   params.addParam<std::vector<dof_id_type>>(
     108             :       "csv_column_indices",
     109             :       "Column indices in the CSV file to be sampled from for 'csv' sampling. Number of indices "
     110             :       "here "
     111             :       "will be the same as the number of columns per matrix.");
     112         904 :   params.addParam<std::vector<std::string>>(
     113             :       "csv_column_names",
     114             :       "Column names in the CSV file to be sampled from for 'csv' sampling. Number of columns names "
     115             :       "here will be the same as the number of columns per matrix.");
     116             :   // Parameters for input matrix
     117         904 :   params.addParam<RealEigenMatrix>("input_matrix", "Sampling matrix for 'input-matrix' sampling.");
     118             : 
     119             :   // Distributions ///////////////////////////
     120             :   // Parameters for normal distributions
     121         904 :   params.addParam<std::vector<Real>>("normal_mean",
     122             :                                      "Means (or expectations) of the 'normal' distributions.");
     123         904 :   params.addParam<std::vector<Real>>("normal_standard_deviation",
     124             :                                      "Standard deviations of the 'normal' distributions.");
     125             :   // Parameters for uniform distributions
     126         904 :   params.addParam<std::vector<Real>>("uniform_lower_bound",
     127             :                                      "Lower bounds for 'uniform' distributions.");
     128         904 :   params.addParam<std::vector<Real>>("uniform_upper_bound",
     129             :                                      "Upper bounds 'uniform' distributions.");
     130             :   // Parameters for Weibull distributions
     131         904 :   params.addParam<std::vector<Real>>("weibull_location",
     132             :                                      "Location parameter (a or low) for 'weibull' distributions.");
     133         904 :   params.addParam<std::vector<Real>>("weibull_scale",
     134             :                                      "Scale parameter (b or lambda) for 'weibull' distributions.");
     135         904 :   params.addParam<std::vector<Real>>("weibull_shape",
     136             :                                      "Shape parameter (c or k) for 'weibull' distributions.");
     137             :   // Parameters for lognormal distributions
     138         904 :   params.addParam<std::vector<Real>>(
     139             :       "lognormal_location", "The 'lognormal' distributions' location parameter (m or mu).");
     140         904 :   params.addParam<std::vector<Real>>(
     141             :       "lognormal_scale", "The 'lognormal' distributions' scale parameter (s or sigma).");
     142             :   // Parameters for truncated normal distributions
     143         904 :   params.addParam<std::vector<Real>>("tnormal_mean",
     144             :                                      "Means (or expectations) of the 'tnormal' distributions.");
     145         904 :   params.addParam<std::vector<Real>>("tnormal_standard_deviation",
     146             :                                      "Standard deviations of the 'tnormal' distributions.");
     147         904 :   params.addParam<std::vector<Real>>("tnormal_lower_bound", "'tnormal' distributions' lower bound");
     148         904 :   params.addParam<std::vector<Real>>("tnormal_upper_bound", "'tnormal' distributions' upper bound");
     149             : 
     150             :   // Outputting parameters
     151         452 :   MultiMooseEnum out_type("none=0 csv=1 json=2", "json");
     152         904 :   params.addParam<MultiMooseEnum>(
     153             :       "output_type",
     154             :       out_type,
     155             :       "Method in which to output sampler matrix and quantities of interest. Warning: "
     156             :       "'csv' output will not include vector-type quantities.");
     157         904 :   params.addParam<std::vector<ReporterValueName>>(
     158             :       "sampler_column_names",
     159             :       "Names of the sampler columns for outputting the sampling matrix. If 'parameters' are not "
     160             :       "bracketed, the default is based on these values. Otherwise, the default is based on the "
     161             :       "sampler name.");
     162             : 
     163             :   // Debug parameters
     164         904 :   params.addParam<bool>("show_study_objects",
     165         904 :                         false,
     166             :                         "Set to true to show all the objects being built by this action.");
     167         452 :   return params;
     168         452 : }
     169             : 
     170         452 : ParameterStudyAction::ParameterStudyAction(const InputParameters & parameters)
     171             :   : Action(parameters),
     172         452 :     _parameters(getParam<std::vector<std::string>>("parameters")),
     173         904 :     _sampling_type(getParam<MooseEnum>("sampling_type")),
     174        1704 :     _distributions(isParamValid("distributions") ? getParam<MultiMooseEnum>("distributions")
     175             :                                                  : MultiMooseEnum("")),
     176         452 :     _multiapp_mode(inferMultiAppMode()),
     177        1808 :     _compute_stats(isParamValid("quantities_of_interest") && getParam<bool>("compute_statistics")),
     178        1356 :     _show_objects(getParam<bool>("show_study_objects"))
     179             : {
     180             :   // Check sampler parameters
     181         452 :   const auto sampler_params = samplerParameters();
     182         452 :   const auto this_sampler_params = sampler_params[_sampling_type];
     183             :   // Check required sampler parameters
     184        1336 :   for (const auto & param : this_sampler_params)
     185         912 :     if (param.second && !isParamValid(param.first))
     186          28 :       paramError("sampling_type",
     187             :                  "The ",
     188             :                  param.first,
     189             :                  " parameter is required to build the requested sampling type.");
     190             :   // Check unused parameters
     191         424 :   std::string msg = "";
     192        2544 :   for (unsigned int i = 0; i < sampler_params.size(); ++i)
     193        5936 :     for (const auto & param : sampler_params[i])
     194        6084 :       if (this_sampler_params.find(param.first) == this_sampler_params.end() &&
     195        2268 :           parameters.isParamSetByUser(param.first))
     196           8 :         msg += (msg.empty() ? "" : ", ") + param.first;
     197         424 :   if (!msg.empty())
     198           4 :     paramError("sampling_type",
     199             :                "The following parameters are unused for the selected sampling type: ",
     200             :                msg);
     201             : 
     202             :   // Check distribution parameters
     203         420 :   const auto distribution_params = distributionParameters();
     204         420 :   std::vector<unsigned int> dist_count(distribution_params.size(), 0);
     205        1284 :   for (const auto & dist : _distributions)
     206         864 :     dist_count[(unsigned int)dist]++;
     207             :   msg = "";
     208        2488 :   for (unsigned int i = 0; i < distribution_params.size(); ++i)
     209        7448 :     for (const auto & param : distribution_params[i])
     210             :     {
     211             :       // Check if parameter was set
     212        6476 :       if (dist_count[i] > 0 && !isParamValid(param))
     213           4 :         paramError("distributions",
     214             :                    "The ",
     215             :                    param,
     216             :                    " parameter is required to build the listed distributions.");
     217             :       // Check if parameter has correct size
     218        6468 :       else if (dist_count[i] > 0 && getParam<std::vector<Real>>(param).size() != dist_count[i])
     219           4 :         paramError("distributions",
     220             :                    "The number of entries in ",
     221             :                    param,
     222             :                    " does not match the number of required entries (",
     223             :                    dist_count[i],
     224             :                    ") to build the listed distributions.");
     225             :       // Check if parameter was set and unused
     226        5372 :       else if (dist_count[i] == 0 && parameters.isParamSetByUser(param))
     227           8 :         msg += (msg.empty() ? "" : ", ") + param;
     228             :     }
     229         412 :   if (!msg.empty())
     230           4 :     paramError(
     231             :         "distributions", "The following parameters are unused for the listed distributions: ", msg);
     232             : 
     233             :   // Check statistics parameters
     234         408 :   if (!_compute_stats)
     235             :   {
     236             :     msg = "";
     237         208 :     for (const auto & param : statisticsParameters())
     238         156 :       if (parameters.isParamSetByUser(param))
     239          32 :         msg += (msg.empty() ? "" : ", ") + param;
     240          52 :     if (!msg.empty())
     241           4 :       paramError("compute_statistics",
     242             :                  "The following parameters are unused since statistics are not being computed: ",
     243             :                  msg);
     244             :   }
     245         808 : }
     246             : 
     247             : MooseEnum
     248         452 : ParameterStudyAction::samplingTypes()
     249             : {
     250         904 :   return MooseEnum("monte-carlo=0 lhs=1 cartesian-product=2 csv=3 input-matrix=4");
     251             : }
     252             : 
     253             : MultiMooseEnum
     254         856 : ParameterStudyAction::distributionTypes()
     255             : {
     256        1712 :   return MultiMooseEnum("normal=0 uniform=1 weibull=2 lognormal=3 tnormal=4");
     257             : }
     258             : 
     259             : void
     260        3212 : ParameterStudyAction::act()
     261             : {
     262        3212 :   if (_current_task == "meta_action")
     263             :   {
     264         404 :     const auto stm_actions = _awh.getActions<StochasticToolsAction>();
     265         404 :     if (stm_actions.empty())
     266             :     {
     267         404 :       auto params = _action_factory.getValidParams("StochasticToolsAction");
     268         404 :       params.set<bool>("_built_by_moose") = true;
     269         404 :       params.set<std::string>("registered_identifier") = "(AutoBuilt)";
     270             : 
     271         404 :       std::shared_ptr<Action> action = _action_factory.create(
     272         808 :           "StochasticToolsAction", _name + "_stochastic_tools_action", params);
     273         808 :       _awh.addActionBlock(action);
     274             : 
     275         404 :       if (_show_objects)
     276         512 :         showObject("StochasticToolsAction", _name + "_stochastic_tools_action", params);
     277         404 :     }
     278             :   }
     279        2808 :   else if (_current_task == "add_distribution")
     280             :   {
     281             :     // This map is used to keep track of how many of a certain
     282             :     // distribution is being created.
     283             :     std::unordered_map<std::string, unsigned int> dist_count;
     284        2424 :     for (const auto & dt : distributionTypes().getNames())
     285        2424 :       dist_count[dt] = 0;
     286             : 
     287             :     // We will have a single call to addDistribution for each entry
     288             :     // So declare these quantities and set in the if statements
     289             :     std::string distribution_type;
     290         404 :     InputParameters params = emptyInputParameters();
     291             : 
     292             :     // Loop through the inputted distributions
     293             :     unsigned int full_count = 0;
     294        1236 :     for (const auto & dist : _distributions)
     295             :     {
     296             :       // Convenient reference to the current count
     297             :       unsigned int & count = dist_count[dist.name()];
     298             : 
     299             :       // Set the distribution type and parameters
     300         832 :       if (dist == "normal")
     301             :       {
     302             :         distribution_type = "Normal";
     303          96 :         params = _factory.getValidParams(distribution_type);
     304          96 :         params.set<Real>("mean") = getDistributionParam<Real>("normal_mean", count);
     305          96 :         params.set<Real>("standard_deviation") =
     306         192 :             getDistributionParam<Real>("normal_standard_deviation", count);
     307             :       }
     308         736 :       else if (dist == "uniform")
     309             :       {
     310             :         distribution_type = "Uniform";
     311         624 :         params = _factory.getValidParams(distribution_type);
     312         624 :         params.set<Real>("lower_bound") = getDistributionParam<Real>("uniform_lower_bound", count);
     313         624 :         params.set<Real>("upper_bound") = getDistributionParam<Real>("uniform_upper_bound", count);
     314             :       }
     315         112 :       else if (dist == "weibull")
     316             :       {
     317             :         distribution_type = "Weibull";
     318          48 :         params = _factory.getValidParams(distribution_type);
     319          48 :         params.set<Real>("location") = getDistributionParam<Real>("weibull_location", count);
     320          48 :         params.set<Real>("scale") = getDistributionParam<Real>("weibull_scale", count);
     321          48 :         params.set<Real>("shape") = getDistributionParam<Real>("weibull_shape", count);
     322             :       }
     323          64 :       else if (dist == "lognormal")
     324             :       {
     325             :         distribution_type = "Lognormal";
     326          32 :         params = _factory.getValidParams(distribution_type);
     327          32 :         params.set<Real>("location") = getDistributionParam<Real>("lognormal_location", count);
     328          32 :         params.set<Real>("scale") = getDistributionParam<Real>("lognormal_scale", count);
     329             :       }
     330          32 :       else if (dist == "tnormal")
     331             :       {
     332             :         distribution_type = "TruncatedNormal";
     333          32 :         params = _factory.getValidParams(distribution_type);
     334          32 :         params.set<Real>("mean") = getDistributionParam<Real>("tnormal_mean", count);
     335          32 :         params.set<Real>("standard_deviation") =
     336          32 :             getDistributionParam<Real>("tnormal_standard_deviation", count);
     337          32 :         params.set<Real>("lower_bound") = getDistributionParam<Real>("tnormal_lower_bound", count);
     338          32 :         params.set<Real>("upper_bound") = getDistributionParam<Real>("tnormal_upper_bound", count);
     339             :       }
     340             :       else
     341           0 :         paramError("distributions", "Unknown distribution type.");
     342             : 
     343             :       // Add the distribution
     344         832 :       _problem->addDistribution(distribution_type, distributionName(full_count), params);
     345         832 :       if (_show_objects)
     346        1776 :         showObject(distribution_type, distributionName(full_count), params);
     347             : 
     348             :       // Increment the counts
     349         832 :       count++;
     350         832 :       full_count++;
     351             :     }
     352         404 :   }
     353        2404 :   else if (_current_task == "add_sampler")
     354             :   {
     355             :     // We will have a single call to addSampler
     356             :     // So declare these quantities and set in the if statements
     357             :     std::string sampler_type;
     358         404 :     InputParameters params = emptyInputParameters();
     359             : 
     360             :     // Set the distribution type and parameters
     361             :     // monte-carlo or lhs
     362         404 :     if (_sampling_type == 0 || _sampling_type == 1)
     363             :     {
     364         320 :       sampler_type = _sampling_type == 0 ? "MonteCarlo" : "LatinHypercube";
     365         320 :       params = _factory.getValidParams(sampler_type);
     366         640 :       params.set<dof_id_type>("num_rows") = getParam<dof_id_type>("num_samples");
     367         320 :       params.set<std::vector<DistributionName>>("distributions") =
     368         640 :           distributionNames(_distributions.size());
     369             :     }
     370             :     // cartesian-product
     371          84 :     else if (_sampling_type == 2)
     372             :     {
     373             :       sampler_type = "CartesianProduct";
     374          16 :       params = _factory.getValidParams(sampler_type);
     375          32 :       params.set<std::vector<Real>>("linear_space_items") =
     376          48 :           getParam<std::vector<Real>>("linear_space_items");
     377             :     }
     378             :     // csv
     379          68 :     else if (_sampling_type == 3)
     380             :     {
     381             :       sampler_type = "CSVSampler";
     382          52 :       params = _factory.getValidParams(sampler_type);
     383         156 :       params.set<FileName>("samples_file") = getParam<FileName>("csv_samples_file");
     384         144 :       if (isParamValid("csv_column_indices") && isParamValid("csv_column_names"))
     385           4 :         paramError("csv_column_indices",
     386             :                    "'csv_column_indices' and 'csv_column_names' cannot both be set.");
     387          96 :       else if (isParamValid("csv_column_indices"))
     388          32 :         params.set<std::vector<dof_id_type>>("column_indices") =
     389          48 :             getParam<std::vector<dof_id_type>>("csv_column_indices");
     390          64 :       else if (isParamValid("csv_column_names"))
     391          32 :         params.set<std::vector<std::string>>("column_names") =
     392          48 :             getParam<std::vector<std::string>>("csv_column_names");
     393             :     }
     394             :     // input-matrix
     395          16 :     else if (_sampling_type == 4)
     396             :     {
     397             :       sampler_type = "InputMatrix";
     398          16 :       params = _factory.getValidParams(sampler_type);
     399          48 :       params.set<RealEigenMatrix>("matrix") = getParam<RealEigenMatrix>("input_matrix");
     400             :     }
     401             :     else
     402           0 :       paramError("sampling_type", "Unknown sampling type.");
     403             : 
     404             :     // Need to set the right execute_on for command-line control
     405         400 :     if (_multiapp_mode <= 1)
     406         240 :       params.set<ExecFlagEnum>("execute_on") = {EXEC_PRE_MULTIAPP_SETUP};
     407             :     else
     408         960 :       params.set<ExecFlagEnum>("execute_on") = {EXEC_INITIAL};
     409             : 
     410             :     // Set the minimum number of procs
     411         800 :     params.set<unsigned int>("min_procs_per_row") = getParam<unsigned int>("min_procs_per_sample");
     412             : 
     413             :     // Add the sampler
     414         400 :     _problem->addSampler(sampler_type, samplerName(), params);
     415         400 :     if (_show_objects)
     416         768 :       showObject(sampler_type, samplerName(), params);
     417         400 :   }
     418        2000 :   else if (_current_task == "add_multi_app")
     419             :   {
     420         400 :     auto params = _factory.getValidParams("SamplerFullSolveMultiApp");
     421             : 
     422             :     // Dealing with failed solves
     423         800 :     params.set<bool>("ignore_solve_not_converge") = getParam<bool>("ignore_solve_not_converge");
     424             : 
     425             :     // Set input file
     426        1200 :     params.set<std::vector<FileName>>("input_files") = {getParam<FileName>("input")};
     427             : 
     428             :     // Set the Sampler
     429         800 :     params.set<SamplerName>("sampler") = samplerName();
     430             : 
     431             :     // Set parameters based on the sampling mode
     432             :     // normal
     433         400 :     if (_multiapp_mode == 0)
     434          32 :       params.set<MooseEnum>("mode") = "normal";
     435             :     // batch-reset
     436         384 :     else if (_multiapp_mode == 1)
     437         128 :       params.set<MooseEnum>("mode") = "batch-reset";
     438             :     // batch-restore variants
     439             :     else
     440             :     {
     441             :       // Set the mode to 'batch-restore'
     442         640 :       params.set<MooseEnum>("mode") = "batch-restore";
     443             : 
     444             :       // If we are doing batch-restore, the parameters must be controllable.
     445             :       // So we will add the necessary control to the sub-app using command-line
     446         320 :       std::string clia = "Controls/" + samplerReceiverName() + "/type=SamplerReceiver";
     447         640 :       params.set<std::vector<CLIArgString>>("cli_args").push_back(clia);
     448             : 
     449             :       // batch-keep-solution
     450         320 :       if (_multiapp_mode == 3)
     451          32 :         params.set<bool>("keep_solution_during_restore") = true;
     452             :       // batch-no-restore
     453         288 :       else if (_multiapp_mode == 4)
     454         240 :         params.set<bool>("no_restore") = true;
     455             :     }
     456             : 
     457             :     // Set the minimum number of procs
     458         800 :     params.set<unsigned int>("min_procs_per_app") = getParam<unsigned int>("min_procs_per_sample");
     459             : 
     460             :     // Setting execute_on to make sure things happen in the correct order
     461        1200 :     params.set<ExecFlagEnum>("execute_on") = {EXEC_TIMESTEP_BEGIN};
     462             : 
     463             :     // Add the multiapp
     464         800 :     _problem->addMultiApp("SamplerFullSolveMultiApp", multiappName(), params);
     465         400 :     if (_show_objects)
     466         768 :       showObject("SamplerFullSolveMultiApp", multiappName(), params);
     467         400 :   }
     468        1600 :   else if (_current_task == "add_transfer")
     469             :   {
     470             :     // Add the parameter transfer if we are doing 'batch-restore'
     471         400 :     if (_multiapp_mode >= 2)
     472             :     {
     473         640 :       auto params = _factory.getValidParams("SamplerParameterTransfer");
     474         640 :       params.set<MultiAppName>("to_multi_app") = multiappName();
     475         640 :       params.set<SamplerName>("sampler") = samplerName();
     476         320 :       params.set<std::vector<std::string>>("parameters") = _parameters;
     477         640 :       _problem->addTransfer("SamplerParameterTransfer", parameterTransferName(), params);
     478         320 :       if (_show_objects)
     479         384 :         showObject("SamplerParameterTransfer", parameterTransferName(), params);
     480         320 :     }
     481             : 
     482             :     // Add reporter transfer if QoIs have been specified
     483         800 :     if (isParamValid("quantities_of_interest"))
     484             :     {
     485         800 :       auto params = _factory.getValidParams("SamplerReporterTransfer");
     486         800 :       params.set<MultiAppName>("from_multi_app") = multiappName();
     487         800 :       params.set<SamplerName>("sampler") = samplerName();
     488         800 :       params.set<std::string>("stochastic_reporter") = stochasticReporterName();
     489         800 :       params.set<std::vector<ReporterName>>("from_reporter") =
     490         800 :           getParam<std::vector<ReporterName>>("quantities_of_interest");
     491         400 :       params.set<std::string>("prefix") = "";
     492         800 :       _problem->addTransfer("SamplerReporterTransfer", reporterTransferName(), params);
     493         400 :       if (_show_objects)
     494         512 :         showObject("SamplerReporterTransfer", reporterTransferName(), params);
     495         400 :     }
     496             :   }
     497        1200 :   else if (_current_task == "add_output")
     498             :   {
     499         400 :     const auto & output = getParam<MultiMooseEnum>("output_type");
     500             : 
     501             :     // Add csv output
     502         800 :     if (output.isValueSet("csv"))
     503             :     {
     504         336 :       auto params = _factory.getValidParams("CSV");
     505        1008 :       params.set<ExecFlagEnum>("execute_on") = {EXEC_TIMESTEP_END};
     506        1008 :       _problem->addOutput("CSV", outputName("csv"), params);
     507         336 :       if (_show_objects)
     508         832 :         showObject("CSV", outputName("csv"), params);
     509         336 :     }
     510             : 
     511             :     // Add json output
     512         800 :     if (output.isValueSet("json") || _compute_stats)
     513             :     {
     514         352 :       auto params = _factory.getValidParams("JSON");
     515        1056 :       params.set<ExecFlagEnum>("execute_on") = {EXEC_TIMESTEP_END};
     516        1056 :       _problem->addOutput("JSON", outputName("json"), params);
     517         352 :       if (_show_objects)
     518         832 :         showObject("JSON", outputName("json"), params);
     519         352 :     }
     520             :   }
     521         800 :   else if (_current_task == "add_reporter")
     522             :   {
     523             :     // Add stochastic reporter object
     524         400 :     auto params = _factory.getValidParams("StochasticMatrix");
     525             : 
     526             :     // Ideally this would be based on the number of samples since gathering
     527             :     // data onto a single processor can be memory and run-time expensive,
     528             :     // but most people want everything in one output file
     529         800 :     params.set<MooseEnum>("parallel_type") = "ROOT";
     530             : 
     531             :     // Supply the sampler for output
     532         800 :     params.set<SamplerName>("sampler") = samplerName();
     533             : 
     534             :     // Set the column names if supplied or identifiable with "parameters"
     535         400 :     auto & names = params.set<std::vector<ReporterValueName>>("sampler_column_names");
     536         800 :     if (isParamValid("sampler_column_names"))
     537          48 :       names = getParam<std::vector<ReporterValueName>>("sampler_column_names");
     538             :     else
     539             :     {
     540             :       // There isn't a guaranteed mapping if using brackets
     541             :       bool has_bracket = false;
     542        1344 :       for (const auto & param : _parameters)
     543         960 :         if (param.find("[") != std::string::npos)
     544             :           has_bracket = true;
     545             : 
     546             :       // If no brackets, then there is mapping, so use parameter names
     547         384 :       if (!has_bracket)
     548        1344 :         for (auto param : _parameters)
     549             :         {
     550             :           // Reporters don't like '/' in the name, so replace those with '_'
     551             :           std::replace(param.begin(), param.end(), '/', '_');
     552         960 :           names.push_back(param);
     553             :         }
     554             :     }
     555             : 
     556             :     // Specify output objects
     557         400 :     const auto & output_type = getParam<MultiMooseEnum>("output_type");
     558         400 :     auto & outputs = params.set<std::vector<OutputName>>("outputs");
     559         800 :     if (output_type.isValueSet("csv"))
     560         672 :       outputs.push_back(outputName("csv"));
     561         800 :     if (output_type.isValueSet("json"))
     562          96 :       outputs.push_back(outputName("json"));
     563         800 :     if (output_type.isValueSet("none"))
     564          48 :       outputs = {"none"};
     565             : 
     566        1200 :     params.set<ExecFlagEnum>("execute_on") = {EXEC_TIMESTEP_END};
     567         800 :     _problem->addReporter("StochasticMatrix", stochasticReporterName(), params);
     568         400 :     if (_show_objects)
     569         512 :       showObject("StochasticReporter", stochasticReporterName(), params);
     570             : 
     571             :     // Add statistics object
     572         400 :     if (_compute_stats)
     573             :     {
     574         352 :       auto params = _factory.getValidParams("StatisticsReporter");
     575         352 :       auto & reps = params.set<std::vector<ReporterName>>("reporters");
     576        1264 :       for (const auto & qoi : getParam<std::vector<ReporterName>>("quantities_of_interest"))
     577        1120 :         reps.push_back(quantityOfInterestName(qoi));
     578        1056 :       params.set<MultiMooseEnum>("compute") = getParam<MultiMooseEnum>("statistics");
     579         704 :       params.set<MooseEnum>("ci_method") = "percentile";
     580        1056 :       params.set<std::vector<Real>>("ci_levels") = getParam<std::vector<Real>>("ci_levels");
     581         704 :       params.set<unsigned int>("ci_replicates") = getParam<unsigned int>("ci_replicates");
     582        1056 :       params.set<ExecFlagEnum>("execute_on") = {EXEC_TIMESTEP_END};
     583        1056 :       params.set<std::vector<OutputName>>("outputs") = {outputName("json")};
     584         704 :       _problem->addReporter("StatisticsReporter", statisticsName(), params);
     585         352 :       if (_show_objects)
     586         416 :         showObject("StatisticsReporter", statisticsName(), params);
     587         352 :     }
     588         400 :   }
     589         400 :   else if (_current_task == "add_control")
     590             :   {
     591             :     // Add command-line control if the multiapp mode warrants it
     592         400 :     if (_multiapp_mode <= 1)
     593             :     {
     594         160 :       auto params = _factory.getValidParams("MultiAppSamplerControl");
     595         160 :       params.set<MultiAppName>("multi_app") = multiappName();
     596         160 :       params.set<SamplerName>("sampler") = samplerName();
     597          80 :       params.set<std::vector<std::string>>("param_names") = _parameters;
     598             :       auto control =
     599         160 :           _factory.create<Control>("MultiAppSamplerControl", multiappControlName(), params);
     600         160 :       _problem->getControlWarehouse().addObject(control);
     601          80 :       if (_show_objects)
     602         128 :         showObject("MultiAppSamplerControl", multiappControlName(), params);
     603          80 :     }
     604             :   }
     605        6952 : }
     606             : 
     607             : DistributionName
     608        2256 : ParameterStudyAction::distributionName(unsigned int count) const
     609             : {
     610        4512 :   return "study_distribution_" + std::to_string(count);
     611             : }
     612             : 
     613             : std::vector<DistributionName>
     614         320 : ParameterStudyAction::distributionNames(unsigned int full_count) const
     615             : {
     616             :   std::vector<DistributionName> dist_names;
     617        1152 :   for (const auto & i : make_range(full_count))
     618        1664 :     dist_names.push_back(distributionName(i));
     619         320 :   return dist_names;
     620           0 : }
     621             : 
     622             : ReporterName
     623         560 : ParameterStudyAction::quantityOfInterestName(const ReporterName & qoi) const
     624             : {
     625        2240 :   return ReporterName(stochasticReporterName(), qoi.getObjectName() + ":" + qoi.getValueName());
     626             : }
     627             : 
     628             : void
     629        2752 : ParameterStudyAction::showObject(std::string type,
     630             :                                  std::string name,
     631             :                                  const InputParameters & params) const
     632             : {
     633             :   // Output basic information
     634        2752 :   std::string base_type = params.have_parameter<std::string>("_moose_base")
     635        2752 :                               ? params.get<std::string>("_moose_base")
     636        2752 :                               : "Unknown";
     637        2752 :   _console << "[ParameterStudy] "
     638        8256 :            << "Base Type:  " << COLOR_YELLOW << base_type << COLOR_DEFAULT << "\n"
     639        8256 :            << "                 Type:       " << COLOR_YELLOW << type << COLOR_DEFAULT << "\n"
     640        8256 :            << "                 Name:       " << COLOR_YELLOW << name << COLOR_DEFAULT;
     641             : 
     642             :   // Gather parameters and their values if:
     643             :   // - It is not a private parameter
     644             :   // - Doesn't start with '_' (which usually indicates private)
     645             :   // - Parameter is set by this action
     646             :   // - Is not the "type" parameter
     647             :   std::map<std::string, std::string> param_map;
     648       99312 :   for (const auto & it : params)
     649      106944 :     if (!params.isPrivate(it.first) && it.first[0] != '_' && params.isParamSetByUser(it.first) &&
     650             :         it.first != "type")
     651             :     {
     652        9792 :       std::stringstream ss;
     653        9792 :       it.second->print(ss);
     654        9792 :       param_map[it.first] = ss.str();
     655        9792 :     }
     656             : 
     657             :   // Print the gathered parameters
     658        2752 :   if (!param_map.empty())
     659        2496 :     _console << "\n                 Parameters: ";
     660             :   bool first = true;
     661       12544 :   for (const auto & it : param_map)
     662             :   {
     663        9792 :     if (!first)
     664       21888 :       _console << "\n" << std::string(29, ' ');
     665       39168 :     _console << COLOR_YELLOW << std::setw(24) << it.first << COLOR_DEFAULT << " : " << COLOR_MAGENTA
     666       19584 :              << it.second << COLOR_DEFAULT;
     667             :     first = false;
     668             :   }
     669             : 
     670        2752 :   _console << std::endl;
     671        2752 : }
     672             : 
     673             : std::vector<std::map<std::string, bool>>
     674         452 : ParameterStudyAction::samplerParameters()
     675             : {
     676             :   // monte-carlo, lhs, cartesian-product, csv, input-matrix
     677             :   return {{{"num_samples", true}, {"distributions", true}},
     678             :           {{"num_samples", true}, {"distributions", true}},
     679             :           {{"linear_space_items", true}},
     680             :           {{"csv_samples_file", true}, {"csv_column_indices", false}, {"csv_column_names", false}},
     681        6780 :           {{"input_matrix", true}}};
     682        6780 : }
     683             : 
     684             : std::vector<std::vector<std::string>>
     685         420 : ParameterStudyAction::distributionParameters()
     686             : {
     687             :   // normal, uniform, weibull, lognormal, tnormal
     688             :   return {
     689             :       {"normal_mean", "normal_standard_deviation"},
     690             :       {"uniform_lower_bound", "uniform_upper_bound"},
     691             :       {"weibull_location", "weibull_scale", "weibull_shape"},
     692             :       {"lognormal_location", "lognormal_scale"},
     693        7980 :       {"tnormal_mean", "tnormal_standard_deviation", "tnormal_lower_bound", "tnormal_upper_bound"}};
     694        6300 : }
     695             : 
     696             : std::set<std::string>
     697          52 : ParameterStudyAction::statisticsParameters()
     698             : {
     699         208 :   return {"statistics", "ci_levels", "ci_replicates"};
     700         104 : }
     701             : 
     702             : unsigned int
     703         452 : ParameterStudyAction::inferMultiAppMode()
     704             : {
     705         904 :   if (isParamValid("multiapp_mode"))
     706         160 :     return getParam<MooseEnum>("multiapp_mode");
     707             : 
     708             :   const unsigned int default_mode = 1;
     709             : 
     710             :   // First obvious thing is if it is a parsed parameter, indicated by the lack of '/'
     711        1084 :   for (const auto & param : _parameters)
     712         744 :     if (param.find("/") == std::string::npos)
     713             :       return default_mode;
     714             :   // Next we'll see if there is a GlobalParam
     715        1052 :   for (const auto & param : _parameters)
     716         712 :     if (param.find("GlobalParams") != std::string::npos)
     717             :       return default_mode;
     718             : 
     719             :   // Now for the difficult check
     720             :   // Parse input file and create root hit node
     721         680 :   const auto input_filename = MooseUtils::realpath(getParam<FileName>("input"));
     722         340 :   std::ifstream f(input_filename);
     723         340 :   std::string input((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
     724         340 :   std::unique_ptr<hit::Node> root(hit::parse(input_filename, input));
     725         340 :   hit::explode(root.get());
     726             : 
     727             :   // Walk through the input and see if every param is controllable
     728         340 :   AreParametersControllableWalker control_walker(_parameters, _app);
     729         340 :   root->walk(&control_walker, hit::NodeType::Section);
     730         340 :   if (!control_walker.areControllable())
     731             :     return default_mode;
     732             : 
     733             :   // Walk through the input and determine how the problem is being executed
     734             :   ExecutionTypeWalker exec_walker;
     735         324 :   root->walk(&exec_walker, hit::NodeType::Section);
     736             :   // If it is steady-state, then we don't need to restore
     737         324 :   if (exec_walker.getExecutionType() == 1)
     738             :     return 4;
     739             :   // If it is pseudo-transeint, then we can keep the solution
     740          48 :   else if (exec_walker.getExecutionType() == 2)
     741             :     return 3;
     742             :   // If it's transient or unknown, don't keep the solution and restore
     743             :   else
     744          32 :     return 2;
     745         680 : }
     746             : 
     747         340 : AreParametersControllableWalker::AreParametersControllableWalker(
     748         340 :     const std::vector<std::string> & parameters, MooseApp & app)
     749         340 :   : _app(app), _is_controllable(parameters.size(), false)
     750             : {
     751             :   // Seperate the object from the parameter into a list of pairs
     752        1052 :   for (const auto & param : parameters)
     753             :   {
     754         712 :     const auto pos = param.rfind("/");
     755        1424 :     _pars.emplace_back(param.substr(0, pos), param.substr(pos + 1));
     756             :   }
     757         340 : }
     758             : 
     759             : void
     760        5244 : AreParametersControllableWalker::walk(const std::string & fullpath,
     761             :                                       const std::string & /*nodename*/,
     762             :                                       hit::Node * n)
     763             : {
     764       16372 :   for (const auto & i : index_range(_pars))
     765             :   {
     766       11128 :     const std::string obj = _pars[i].first;
     767       11128 :     const std::string par = _pars[i].second;
     768       11128 :     if (obj == fullpath)
     769             :     {
     770         712 :       const auto typeit = n->find("type");
     771         712 :       if (typeit && typeit != n && typeit->type() == hit::NodeType::Field)
     772             :       {
     773         712 :         const std::string obj_type = n->param<std::string>("type");
     774         712 :         const auto params = _app.getFactory().getValidParams(obj_type);
     775         712 :         _is_controllable[i] = params.isControllable(par);
     776         712 :       }
     777             :     }
     778             :   }
     779        5244 : }
     780             : 
     781             : bool
     782         340 : AreParametersControllableWalker::areControllable() const
     783             : {
     784         340 :   for (const auto & ic : _is_controllable)
     785         696 :     if (!ic)
     786             :       return false;
     787         324 :   return true;
     788             : }
     789             : 
     790             : void
     791        5004 : ExecutionTypeWalker::walk(const std::string & fullpath,
     792             :                           const std::string & /*nodename*/,
     793             :                           hit::Node * n)
     794             : {
     795        5004 :   if (fullpath == "Executioner")
     796             :   {
     797             :     // This should not be hit since there shouldn't be two Executioner blocks
     798             :     // But if it does happen, then go back to not knowing
     799         324 :     if (_found_exec)
     800           0 :       _exec_type = 0;
     801             :     else
     802             :     {
     803             :       // Get the type of executioner
     804         324 :       std::string executioner_type = "Unknown";
     805         324 :       const auto typeit = n->find("type");
     806         324 :       if (typeit && typeit != n && typeit->type() == hit::NodeType::Field)
     807         648 :         executioner_type = n->param<std::string>("type");
     808             : 
     809             :       // If it's Steady or Eigenvalue, then it's a steady-state problem
     810         388 :       if (executioner_type == "Steady" || executioner_type == "Eigenvalue")
     811         276 :         _exec_type = 1;
     812             :       // If it's Transient
     813          48 :       else if (executioner_type == "Transient")
     814             :       {
     815             :         // Now we'll see if it's a pseudo transient
     816          48 :         const auto it = n->find("steady_state_detection");
     817          48 :         if (it && it != n && it->type() == hit::NodeType::Field &&
     818          64 :             n->param<bool>("steady_state_detection"))
     819          16 :           _exec_type = 2;
     820             :         else
     821          32 :           _exec_type = 3;
     822             :       }
     823             :     }
     824             : 
     825         324 :     _found_exec = true;
     826             :   }
     827        5004 : }

Generated by: LCOV version 1.14