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 : }
|