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