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