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

Generated by: LCOV version 1.14