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 : // MOOSE includes
11 : #include "MultiApp.h"
12 :
13 : #include "AppFactory.h"
14 : #include "AuxiliarySystem.h"
15 : #include "DisplacedProblem.h"
16 : #include "Console.h"
17 : #include "Executioner.h"
18 : #include "FEProblem.h"
19 : #include "MooseMesh.h"
20 : #include "MooseUtils.h"
21 : #include "OutputWarehouse.h"
22 : #include "SetupInterface.h"
23 : #include "UserObject.h"
24 : #include "CommandLine.h"
25 : #include "Conversion.h"
26 : #include "NonlinearSystemBase.h"
27 : #include "DelimitedFileReader.h"
28 : #include "MooseAppCoordTransform.h"
29 : #include "MultiAppTransfer.h"
30 : #include "Positions.h"
31 : #include "Transient.h"
32 : #include "Backup.h"
33 : #include "Parser.h"
34 :
35 : #include "libmesh/mesh_tools.h"
36 : #include "libmesh/numeric_vector.h"
37 :
38 : // C++ includes
39 : #include <fstream>
40 : #include <iomanip>
41 : #include <iterator>
42 : #include <algorithm>
43 :
44 : // Call to "uname"
45 : #ifdef LIBMESH_HAVE_SYS_UTSNAME_H
46 : #include <sys/utsname.h>
47 : #endif
48 :
49 : InputParameters
50 86609 : MultiApp::validParams()
51 : {
52 86609 : InputParameters params = MooseObject::validParams();
53 86609 : params += SetupInterface::validParams();
54 :
55 173218 : params.addParam<bool>("use_displaced_mesh",
56 173218 : false,
57 : "Whether or not this object should use the "
58 : "displaced mesh for computation. Note that "
59 : "in the case this is true but no "
60 : "displacements are provided in the Mesh block "
61 : "the undisplaced mesh will still be used.");
62 :
63 86609 : std::ostringstream app_types_strings;
64 173236 : for (const auto & name_bi_pair : AppFactory::instance().registeredObjects())
65 86627 : app_types_strings << name_bi_pair.first << " ";
66 173218 : MooseEnum app_types_options(app_types_strings.str(), "", true);
67 :
68 : // Dynamic loading
69 346436 : params.addParam<MooseEnum>("app_type",
70 : app_types_options,
71 : "The type of application to build (applications not "
72 : "registered can be loaded with dynamic libraries. Parent "
73 : "application type will be used if not provided.");
74 346436 : params.addParam<std::string>("library_path",
75 : "",
76 : "Path to search for dynamic libraries (please "
77 : "avoid committing absolute paths in addition to "
78 : "MOOSE_LIBRARY_PATH)");
79 346436 : params.addParam<std::string>(
80 : "library_name",
81 : "",
82 : "The file name of the library (*.la file) that will be dynamically loaded.");
83 259827 : params.addParam<bool>("library_load_dependencies",
84 173218 : false,
85 : "Tells MOOSE to manually load library dependencies. This should not be "
86 : "necessary and is here for debugging/troubleshooting.");
87 :
88 : // Subapp positions
89 346436 : params.addParam<std::vector<Point>>(
90 : "positions",
91 : "The positions of the App locations. Each set of 3 values will represent a "
92 : "Point. This and 'positions_file' cannot be both supplied. If this and "
93 : "'positions_file'/'_objects' are not supplied, a single position (0,0,0) will be used");
94 346436 : params.addParam<std::vector<FileName>>("positions_file",
95 : "Filename(s) that should be looked in for positions. Each"
96 : " set of 3 values in that file will represent a Point. "
97 : "This and 'positions(_objects)' cannot be both supplied");
98 346436 : params.addParam<std::vector<PositionsName>>("positions_objects",
99 : "The name of a Positions object that will contain "
100 : "the locations of the sub-apps created. This and "
101 : "'positions(_file)' cannot be both supplied");
102 259827 : params.addParam<bool>(
103 : "output_in_position",
104 173218 : false,
105 : "If true this will cause the output from the MultiApp to be 'moved' by its position vector");
106 259827 : params.addParam<bool>(
107 : "run_in_position",
108 173218 : false,
109 : "If true this will cause the mesh from the MultiApp to be 'moved' by its position vector");
110 :
111 346436 : params.addRequiredParam<std::vector<FileName>>(
112 : "input_files",
113 : "The input file for each App. If this parameter only contains one input file "
114 : "it will be used for all of the Apps. When using 'positions_from_file' it is "
115 : "also admissable to provide one input_file per file.");
116 259827 : params.addParam<Real>("bounding_box_inflation",
117 173218 : 0.01,
118 : "Relative amount to 'inflate' the bounding box of this MultiApp.");
119 259827 : params.addParam<Point>("bounding_box_padding",
120 173218 : RealVectorValue(),
121 : "Additional padding added to the dimensions of the bounding box. The "
122 : "values are added to the x, y and z dimension respectively.");
123 :
124 173218 : params.addPrivateParam<MPI_Comm>("_mpi_comm");
125 :
126 : // Set the default execution time
127 173218 : params.set<ExecFlagEnum>("execute_on", true) = EXEC_TIMESTEP_BEGIN;
128 : // Add the POST_ADAPTIVITY execution flag.
129 : #ifdef LIBMESH_ENABLE_AMR
130 86609 : ExecFlagEnum & exec_enum = params.set<ExecFlagEnum>("execute_on");
131 86609 : exec_enum.addAvailableFlags(EXEC_POST_ADAPTIVITY);
132 259827 : params.setDocString("execute_on", exec_enum.getDocString());
133 : #endif
134 :
135 259827 : params.addParam<processor_id_type>("max_procs_per_app",
136 173218 : std::numeric_limits<processor_id_type>::max(),
137 : "Maximum number of processors to give to each App in this "
138 : "MultiApp. Useful for restricting small solves to just a few "
139 : "procs so they don't get spread out");
140 259827 : params.addParam<processor_id_type>("min_procs_per_app",
141 173218 : 1,
142 : "Minimum number of processors to give to each App in this "
143 : "MultiApp. Useful for larger, distributed mesh solves.");
144 259827 : params.addParam<bool>(
145 : "wait_for_first_app_init",
146 173218 : false,
147 : "Create the first sub-application on rank 0, then MPI_Barrier before "
148 : "creating the next N-1 apps (on all ranks). "
149 : "This is only needed if your sub-application needs to perform some setup "
150 : "actions in quiet, without other sub-applications working at the same time.");
151 :
152 259827 : params.addParam<Real>("global_time_offset",
153 173218 : 0,
154 : "The time offset relative to the parent application for the purpose of "
155 : "starting a subapp at a different time from the parent application. The "
156 : "global time will be ahead by the offset specified here.");
157 :
158 : // Resetting subapps
159 346436 : params.addParam<std::vector<Real>>(
160 : "reset_time",
161 : {},
162 : "The time(s) at which to reset Apps given by the 'reset_apps' parameter. "
163 : "Resetting an App means that it is destroyed and recreated, possibly "
164 : "modeling the insertion of 'new' material for that app.");
165 346436 : params.addParam<std::vector<unsigned int>>(
166 : "reset_apps",
167 : {},
168 : "The Apps that will be reset when 'reset_time' is hit. These are the App "
169 : "'numbers' starting with 0 corresponding to the order of the App positions. "
170 : "Resetting an App means that it is destroyed and recreated, possibly modeling "
171 : "the insertion of 'new' material for that app.");
172 :
173 : // Moving subapps
174 259827 : params.addParam<Real>(
175 : "move_time",
176 173218 : std::numeric_limits<Real>::max(),
177 : "The time at which Apps designated by move_apps are moved to move_positions.");
178 :
179 346436 : params.addParam<std::vector<unsigned int>>(
180 : "move_apps",
181 : {},
182 : "Apps, designated by their 'numbers' starting with 0 corresponding to the order "
183 : "of the App positions, to be moved at move_time to move_positions");
184 346436 : params.addParam<std::vector<Point>>(
185 : "move_positions", {}, "The positions corresponding to each move_app.");
186 :
187 346436 : params.addParam<std::vector<CLIArgString>>(
188 : "cli_args",
189 : {},
190 : "Additional command line arguments to pass to the sub apps. If one set is provided the "
191 : "arguments are applied to all, otherwise there must be a set for each sub app.");
192 :
193 346436 : params.addParam<std::vector<FileName>>(
194 : "cli_args_files",
195 : "File names that should be looked in for additional command line arguments "
196 : "to pass to the sub apps. Each line of a file is set to each sub app. If only "
197 : "one line is provided, it will be applied to all sub apps.");
198 :
199 : // Fixed point iterations
200 433045 : params.addRangeCheckedParam<Real>("relaxation_factor",
201 173218 : 1.0,
202 : "relaxation_factor>0 & relaxation_factor<2",
203 : "Fraction of newly computed value to keep."
204 : "Set between 0 and 2.");
205 519654 : params.addDeprecatedParam<std::vector<std::string>>(
206 : "relaxed_variables",
207 : {},
208 : "Use transformed_variables.",
209 : "List of subapp variables to relax during Multiapp coupling iterations");
210 346436 : params.addParam<std::vector<std::string>>(
211 : "transformed_variables",
212 : {},
213 : "List of subapp variables to use coupling algorithm on during Multiapp coupling iterations");
214 346436 : params.addParam<std::vector<PostprocessorName>>(
215 : "transformed_postprocessors",
216 : {},
217 : "List of subapp postprocessors to use coupling "
218 : "algorithm on during Multiapp coupling iterations");
219 259827 : params.addParam<bool>("keep_solution_during_restore",
220 173218 : false,
221 : "This is useful when doing MultiApp coupling iterations. It takes the "
222 : "final solution from the previous coupling iteration"
223 : "and re-uses it as the initial guess for the next coupling iteration");
224 259827 : params.addParam<bool>("keep_aux_solution_during_restore",
225 173218 : false,
226 : "This is useful when doing MultiApp coupling iterations. It takes the "
227 : "final auxiliary solution from the previous coupling iteration"
228 : "and re-uses it as the initial guess for the next coupling iteration");
229 259827 : params.addParam<bool>(
230 : "no_backup_and_restore",
231 173218 : false,
232 : "True to turn off restore for this multiapp. This is useful when doing steady-state "
233 : "Picard iterations where we want to use the solution of previous Picard iteration as the "
234 : "initial guess of the current Picard iteration.");
235 259827 : params.addParam<unsigned int>(
236 : "max_multiapp_level",
237 173218 : 10,
238 : "Integer set by user that will stop the simulation if the multiapp level "
239 : "exceeds it. Useful for preventing infinite loops with multiapp simulations");
240 519654 : params.deprecateParam("no_backup_and_restore", "no_restore", "01/01/2025");
241 :
242 433045 : params.addDeprecatedParam<bool>("clone_master_mesh",
243 173218 : false,
244 : "True to clone parent app mesh and use it for this MultiApp.",
245 : "clone_master_mesh is deprecated, use clone_parent_mesh instead");
246 173218 : params.addParam<bool>(
247 173218 : "clone_parent_mesh", false, "True to clone parent app mesh and use it for this MultiApp.");
248 :
249 173218 : params.addPrivateParam<bool>("use_positions", true);
250 259827 : params.declareControllable("enable");
251 433045 : params.declareControllable("cli_args", {EXEC_PRE_MULTIAPP_SETUP});
252 173218 : params.registerBase("MultiApp");
253 :
254 346436 : params.addParamNamesToGroup("use_displaced_mesh wait_for_first_app_init max_multiapp_level",
255 : "Advanced");
256 346436 : params.addParamNamesToGroup("positions positions_file positions_objects run_in_position "
257 : "output_in_position",
258 : "Positions / transformations of the MultiApp frame of reference");
259 346436 : params.addParamNamesToGroup("min_procs_per_app max_procs_per_app", "Parallelism");
260 346436 : params.addParamNamesToGroup("reset_time reset_apps", "Reset MultiApp");
261 346436 : params.addParamNamesToGroup("move_time move_apps move_positions", "Timed move of MultiApps");
262 346436 : params.addParamNamesToGroup("relaxation_factor transformed_variables transformed_postprocessors "
263 : "keep_solution_during_restore keep_aux_solution_during_restore "
264 : "no_restore",
265 : "Fixed point iteration");
266 346436 : params.addParamNamesToGroup("library_name library_path library_load_dependencies",
267 : "Dynamic loading");
268 259827 : params.addParamNamesToGroup("cli_args cli_args_files", "Passing command line argument");
269 173218 : return params;
270 173218 : }
271 :
272 7637 : MultiApp::MultiApp(const InputParameters & parameters)
273 : : MooseObject(parameters),
274 : SetupInterface(this),
275 : Restartable(this, "MultiApps"),
276 15274 : PerfGraphInterface(this, std::string("MultiApp::") + _name),
277 22911 : _fe_problem(*getCheckedPointerParam<FEProblemBase *>("_fe_problem_base")),
278 27551 : _app_type(isParamValid("app_type") ? std::string(getParam<MooseEnum>("app_type"))
279 2997 : : _fe_problem.getMooseApp().type()),
280 15274 : _use_positions(getParam<bool>("use_positions")),
281 15274 : _input_files(getParam<std::vector<FileName>>("input_files")),
282 15274 : _wait_for_first_app_init(getParam<bool>("wait_for_first_app_init")),
283 7637 : _total_num_apps(0),
284 7637 : _my_num_apps(0),
285 7637 : _first_local_app(0),
286 7637 : _orig_comm(_communicator.get()),
287 7637 : _my_communicator(),
288 7637 : _my_comm(_my_communicator.get()),
289 7637 : _my_rank(0),
290 15274 : _inflation(getParam<Real>("bounding_box_inflation")),
291 15274 : _bounding_box_padding(getParam<Point>("bounding_box_padding")),
292 15274 : _max_procs_per_app(getParam<processor_id_type>("max_procs_per_app")),
293 15274 : _min_procs_per_app(getParam<processor_id_type>("min_procs_per_app")),
294 15274 : _output_in_position(getParam<bool>("output_in_position")),
295 15274 : _global_time_offset(getParam<Real>("global_time_offset")),
296 15274 : _reset_times(getParam<std::vector<Real>>("reset_time")),
297 15274 : _reset_apps(getParam<std::vector<unsigned int>>("reset_apps")),
298 15274 : _reset_happened(false),
299 15274 : _move_time(getParam<Real>("move_time")),
300 15274 : _move_apps(getParam<std::vector<unsigned int>>("move_apps")),
301 15274 : _move_positions(getParam<std::vector<Point>>("move_positions")),
302 7637 : _move_happened(false),
303 7637 : _has_an_app(true),
304 15274 : _cli_args(getParam<std::vector<CLIArgString>>("cli_args")),
305 15274 : _keep_solution_during_restore(getParam<bool>("keep_solution_during_restore")),
306 15274 : _keep_aux_solution_during_restore(getParam<bool>("keep_aux_solution_during_restore")),
307 15274 : _no_restore(getParam<bool>("no_restore")),
308 15274 : _run_in_position(getParam<bool>("run_in_position")),
309 15274 : _sub_app_backups(declareRestartableDataWithContext<SubAppBackups>("sub_app_backups", this)),
310 30548 : _solve_step_timer(registerTimedSection("solveStep", 3, "Executing MultiApps", false)),
311 30548 : _init_timer(registerTimedSection("init", 3, "Initializing MultiApp")),
312 30548 : _backup_timer(registerTimedSection("backup", 3, "Backing Up MultiApp")),
313 30548 : _restore_timer(registerTimedSection("restore", 3, "Restoring MultiApp")),
314 114555 : _reset_timer(registerTimedSection("resetApp", 3, "Resetting MultiApp"))
315 : {
316 36143 : if (parameters.isParamSetByUser("cli_args") && parameters.isParamValid("cli_args") &&
317 11367 : parameters.isParamValid("cli_args_files"))
318 8 : paramError("cli_args",
319 : "'cli_args' and 'cli_args_files' cannot be specified simultaneously in MultiApp ");
320 :
321 15266 : if (!_use_positions && (isParamValid("positions") || isParamValid("positions_file") ||
322 7633 : isParamValid("positions_objects")))
323 0 : paramError("use_positions",
324 : "This MultiApps has been set to not use positions, "
325 : "but a 'positions' parameter has been set.");
326 :
327 22830 : if ((_reset_apps.size() > 0 && _reset_times.size() == 0) ||
328 15197 : (_reset_apps.size() == 0 && _reset_times.size() > 0))
329 0 : mooseError("reset_time and reset_apps may only be specified together");
330 :
331 : // Check that the reset times are sorted by the user
332 7633 : auto sorted_times = _reset_times;
333 7633 : std::sort(sorted_times.begin(), sorted_times.end());
334 7633 : if (_reset_times.size() && _reset_times != sorted_times)
335 8 : paramError("reset_time", "List of reset times must be sorted in increasing order");
336 7629 : }
337 :
338 : void
339 7601 : MultiApp::init(unsigned int num_apps, bool batch_mode)
340 : {
341 7601 : auto config = rankConfig(
342 : processor_id(), n_processors(), num_apps, _min_procs_per_app, _max_procs_per_app, batch_mode);
343 7601 : init(num_apps, config);
344 7597 : }
345 :
346 : void
347 7601 : MultiApp::init(unsigned int num_apps, const LocalRankConfig & config)
348 : {
349 7601 : TIME_SECTION(_init_timer);
350 :
351 7601 : _total_num_apps = num_apps;
352 7601 : _rank_config = config;
353 7601 : buildComm();
354 7601 : _sub_app_backups.resize(_my_num_apps);
355 :
356 7601 : _has_bounding_box.resize(_my_num_apps, false);
357 7601 : _reset_happened.resize(_reset_times.size(), false);
358 7601 : _bounding_box.resize(_my_num_apps);
359 :
360 7601 : if ((_cli_args.size() > 1) && (_total_num_apps != _cli_args.size()))
361 8 : paramError("cli_args",
362 : "The number of items supplied must be 1 or equal to the number of sub apps.");
363 :
364 : // if cliArgs() != _cli_args, then cliArgs() was overridden and we need to check it
365 7597 : auto cla = cliArgs();
366 15194 : if (cla != std::vector<std::string>(_cli_args.begin(), _cli_args.end()))
367 : {
368 13 : if ((cla.size() > 1) && (_total_num_apps != cla.size()))
369 0 : mooseError("The number of items supplied as command line argument to subapps must be 1 or "
370 : "equal to the number of sub apps. Note: you use a multiapp that provides its own "
371 : "command line parameters so the error is not in cli_args");
372 : }
373 7597 : }
374 :
375 : void
376 7625 : MultiApp::setupPositions()
377 : {
378 7625 : if (_use_positions)
379 : {
380 7625 : fillPositions();
381 7601 : init(_positions.size());
382 7597 : createApps();
383 : }
384 7510 : }
385 :
386 : void
387 7597 : MultiApp::createApps()
388 : {
389 7597 : if (!_has_an_app)
390 54 : return;
391 :
392 37715 : TIME_SECTION("createApps", 2, "Instantiating Sub-Apps", false);
393 :
394 : // Read commandLine arguments that will be used when creating apps
395 7543 : readCommandLineArguments();
396 :
397 7523 : Moose::ScopedCommSwapper swapper(_my_comm);
398 :
399 7523 : _apps.resize(_my_num_apps);
400 :
401 : // If the user provided an unregistered app type, see if we can load it dynamically
402 7523 : if (!AppFactory::instance().isRegistered(_app_type))
403 5 : _app.dynamicAppRegistration(_app_type,
404 : getParam<std::string>("library_path"),
405 7 : getParam<std::string>("library_name"),
406 14 : getParam<bool>("library_load_dependencies"));
407 :
408 7520 : bool rank_did_quiet_init = false;
409 7520 : unsigned int local_app = libMesh::invalid_uint;
410 7520 : if (_wait_for_first_app_init)
411 : {
412 16 : if (hasLocalApp(0))
413 : {
414 8 : rank_did_quiet_init = true;
415 8 : local_app = globalAppToLocal(0);
416 8 : createLocalApp(local_app);
417 : }
418 :
419 16 : MPI_Barrier(_orig_comm);
420 : }
421 :
422 19800 : for (unsigned int i = 0; i < _my_num_apps; i++)
423 : {
424 12344 : if (rank_did_quiet_init && i == local_app)
425 8 : continue;
426 12336 : createLocalApp(i);
427 : }
428 7456 : }
429 :
430 : void
431 12344 : MultiApp::createLocalApp(const unsigned int i)
432 : {
433 12344 : createApp(i, _global_time_offset);
434 12280 : }
435 :
436 : void
437 7414 : MultiApp::initialSetup()
438 : {
439 7414 : if (!_use_positions)
440 : // if not using positions, we create the sub-apps in initialSetup instead of right after
441 : // construction of MultiApp
442 0 : createApps();
443 7414 : }
444 :
445 : void
446 7543 : MultiApp::readCommandLineArguments()
447 : {
448 22629 : if (isParamValid("cli_args_files"))
449 : {
450 59 : _cli_args_from_file.clear();
451 :
452 118 : std::vector<FileName> cli_args_files = getParam<std::vector<FileName>>("cli_args_files");
453 118 : std::vector<FileName> input_files = getParam<std::vector<FileName>>("input_files");
454 :
455 : // If we use parameter "cli_args_files", at least one file should be provided
456 59 : if (!cli_args_files.size())
457 8 : paramError("cli_args_files", "You need to provide at least one commandLine argument file ");
458 :
459 : // If we multiple input files, then we need to check if the number of input files
460 : // match with the number of argument files
461 55 : if (cli_args_files.size() != 1 && cli_args_files.size() != input_files.size())
462 12 : paramError("cli_args_files",
463 : "The number of commandLine argument files ",
464 : cli_args_files.size(),
465 : " for MultiApp ",
466 4 : name(),
467 : " must either be only one or match the number of input files ",
468 : input_files.size());
469 :
470 : // Go through all argument files
471 51 : std::vector<std::string> cli_args;
472 107 : for (unsigned int p_file_it = 0; p_file_it < cli_args_files.size(); p_file_it++)
473 : {
474 64 : std::string cli_args_file = cli_args_files[p_file_it];
475 : // Clear up
476 64 : cli_args.clear();
477 : // Read the file on the root processor then broadcast it
478 64 : if (processor_id() == 0)
479 : {
480 52 : MooseUtils::checkFileReadable(cli_args_file);
481 :
482 52 : std::ifstream is(cli_args_file.c_str());
483 : // Read by line rather than space separated like the cli_args parameter
484 52 : std::string line;
485 212 : while (std::getline(is, line))
486 160 : cli_args.push_back(line);
487 :
488 : // We do not allow empty files
489 52 : if (!cli_args.size())
490 8 : paramError("cli_args_files",
491 : "There is no commandLine argument in the commandLine argument file ",
492 : cli_args_file);
493 :
494 : // If we have position files, we need to
495 : // make sure the number of commandLine argument strings
496 : // match with the number of positions
497 48 : if (_npositions_inputfile.size())
498 : {
499 44 : auto num_positions = _npositions_inputfile[p_file_it];
500 : // Check if the number of commandLine argument strings equal to
501 : // the number of positions
502 44 : if (cli_args.size() == 1)
503 0 : for (MooseIndex(num_positions) num = 0; num < num_positions; num++)
504 0 : _cli_args_from_file.push_back(cli_args.front());
505 44 : else if (cli_args.size() == num_positions)
506 160 : for (auto && cli_arg : cli_args)
507 120 : _cli_args_from_file.push_back(cli_arg);
508 4 : else if (cli_args.size() != num_positions)
509 8 : paramError("cli_args_files",
510 : "The number of commandLine argument strings ",
511 : cli_args.size(),
512 : " in the file ",
513 : cli_args_file,
514 : " must either be only one or match the number of positions ",
515 : num_positions);
516 : }
517 : else
518 : {
519 : // If we do not have position files, we will check if the number of
520 : // commandLine argument strings match with the total number of subapps
521 24 : for (auto && cli_arg : cli_args)
522 20 : _cli_args_from_file.push_back(cli_arg);
523 : }
524 44 : }
525 56 : }
526 :
527 : // Broad cast all arguments to everyone
528 43 : _communicator.broadcast(_cli_args_from_file);
529 43 : }
530 :
531 7570 : if (_cli_args_from_file.size() && _cli_args_from_file.size() != 1 &&
532 43 : _cli_args_from_file.size() != _total_num_apps)
533 8 : mooseError(" The number of commandLine argument strings ",
534 4 : _cli_args_from_file.size(),
535 : " must either be only one or match the total "
536 : "number of sub apps ",
537 4 : _total_num_apps);
538 :
539 7523 : if (_cli_args_from_file.size() && cliArgs().size())
540 0 : mooseError("Cannot set commandLine arguments from both input_file and external files");
541 7523 : }
542 :
543 : void
544 7506 : MultiApp::fillPositions()
545 : {
546 7506 : if (_move_apps.size() != _move_positions.size())
547 0 : mooseError("The number of apps to move and the positions to move them to must be the same for "
548 : "MultiApp ",
549 0 : _name);
550 :
551 37530 : if (isParamValid("positions") + isParamValid("positions_file") +
552 22518 : isParamValid("positions_objects") >
553 : 1)
554 4 : mooseError("Only one 'positions' parameter may be specified");
555 :
556 22506 : if (isParamValid("positions"))
557 : {
558 10572 : _positions = getParam<std::vector<Point>>("positions");
559 :
560 5286 : if (_positions.size() < _input_files.size())
561 4 : mooseError("Not enough positions for the number of input files provided in MultiApp ",
562 4 : name());
563 : }
564 6648 : else if (isParamValid("positions_file"))
565 : {
566 188 : std::vector<FileName> positions_files = getParam<std::vector<FileName>>("positions_file");
567 188 : std::vector<FileName> input_files = getParam<std::vector<FileName>>("input_files");
568 :
569 94 : if (input_files.size() != 1 && positions_files.size() != input_files.size())
570 4 : mooseError("Number of input_files for MultiApp ",
571 4 : name(),
572 : " must either be only one or match the number of positions_file files");
573 :
574 : // Clear out the _input_files because we're going to rebuild it
575 90 : if (input_files.size() != 1)
576 30 : _input_files.clear();
577 :
578 206 : for (unsigned int p_file_it = 0; p_file_it < positions_files.size(); p_file_it++)
579 : {
580 120 : std::string positions_file = positions_files[p_file_it];
581 120 : MooseUtils::DelimitedFileReader file(positions_file, &_communicator);
582 120 : file.setFormatFlag(MooseUtils::DelimitedFileReader::FormatFlag::ROWS);
583 120 : file.read();
584 :
585 120 : const std::vector<Point> & data = file.getDataAsPoints();
586 564 : for (const auto & d : data)
587 448 : _positions.push_back(d);
588 :
589 : // Save the number of positions for this input file
590 116 : _npositions_inputfile.push_back(data.size());
591 :
592 564 : for (unsigned int i = 0; i < data.size(); ++i)
593 448 : if (input_files.size() != 1)
594 120 : _input_files.push_back(input_files[p_file_it]);
595 116 : }
596 86 : }
597 6366 : else if (isParamValid("positions_objects"))
598 : {
599 928 : const auto & positions_param_objs = getParam<std::vector<PositionsName>>("positions_objects");
600 928 : const auto & input_files = getParam<std::vector<FileName>>("input_files");
601 :
602 464 : if (input_files.size() != 1 && positions_param_objs.size() != input_files.size())
603 0 : mooseError("Number of input_files for MultiApp ",
604 0 : name(),
605 : " must either be only one or match the number of positions_objects specified");
606 :
607 : // Clear out the _input_files because we're going to rebuild it
608 464 : if (input_files.size() != 1)
609 0 : _input_files.clear();
610 :
611 : // Keeps track of where each positions object start in terms of subapp numbers
612 464 : unsigned int offset = 0;
613 :
614 967 : for (const auto p_obj_it : index_range(positions_param_objs))
615 : {
616 507 : const std::string & positions_name = positions_param_objs[p_obj_it];
617 507 : auto positions_obj = &_fe_problem.getPositionsObject(positions_name);
618 :
619 507 : const auto & data = positions_obj->getPositions(true);
620 :
621 : // Append all positions from this object
622 2200 : for (const auto & d : data)
623 1697 : _positions.push_back(d);
624 :
625 : // Save the number of positions for this input file
626 503 : _npositions_inputfile.push_back(data.size());
627 :
628 503 : if (!positions_obj)
629 0 : paramError("positions_objects",
630 0 : "'" + positions_name + "' is not of the expected type. Should be a Positions");
631 :
632 : // Keep track of which positions is tied to what subapp
633 2200 : for (unsigned int i = 0; i < data.size(); ++i)
634 : {
635 1697 : if (input_files.size() != 1)
636 0 : _input_files.push_back(input_files[p_obj_it]);
637 1697 : _positions_objs.push_back(positions_obj);
638 1697 : _positions_index_offsets.push_back(offset);
639 : }
640 503 : offset += data.size();
641 : }
642 : }
643 : else
644 : {
645 1658 : _positions = {Point()};
646 :
647 1658 : if (_positions.size() < _input_files.size())
648 4 : mooseError("Not enough positions for the number of input files provided in MultiApp ",
649 4 : name());
650 : }
651 :
652 : mooseAssert(_input_files.size() == 1 || _positions.size() == _input_files.size(),
653 : "Number of positions and input files are not the same!");
654 7482 : }
655 :
656 : void
657 67935 : MultiApp::preTransfer(Real /*dt*/, Real target_time)
658 : {
659 : // Get a transient executioner to get a user-set tolerance
660 67935 : Real timestep_tol = 1e-13;
661 67935 : if (dynamic_cast<TransientBase *>(_fe_problem.getMooseApp().getExecutioner()))
662 65028 : timestep_tol =
663 65028 : dynamic_cast<TransientBase *>(_fe_problem.getMooseApp().getExecutioner())->timestepTol();
664 :
665 : // Determination on whether we need to backup the app due to changes below
666 67935 : bool backup_apps = false;
667 :
668 : // First, see if any Apps need to be reset
669 68714 : for (unsigned int i = 0; i < _reset_times.size(); i++)
670 : {
671 855 : if (!_reset_happened[i] && (target_time + timestep_tol >= _reset_times[i]))
672 : {
673 76 : _reset_happened[i] = true;
674 76 : if (_reset_apps.size() > 0)
675 152 : for (auto & app : _reset_apps)
676 76 : resetApp(app);
677 :
678 : // If we reset an application, then we delete the old objects, including the coordinate
679 : // transformation classes. Consequently we need to reset the coordinate transformation classes
680 : // in the associated transfer classes
681 109 : for (auto * const transfer : _associated_transfers)
682 33 : transfer->getAppInfo();
683 :
684 : // Similarly we need to transform the mesh again
685 76 : if (_run_in_position)
686 0 : for (const auto i : make_range(_my_num_apps))
687 : {
688 0 : auto app_ptr = _apps[i];
689 0 : if (usingPositions())
690 0 : app_ptr->getExecutioner()->feProblem().coordTransform().transformMesh(
691 0 : app_ptr->getExecutioner()->feProblem().mesh(), _positions[_first_local_app + i]);
692 : else
693 0 : app_ptr->getExecutioner()->feProblem().coordTransform().transformMesh(
694 0 : app_ptr->getExecutioner()->feProblem().mesh(), Point(0, 0, 0));
695 0 : }
696 :
697 : // If the time step covers multiple reset times, set them all as having 'happened'
698 174 : for (unsigned int j = i; j < _reset_times.size(); j++)
699 98 : if (target_time + timestep_tol >= _reset_times[j])
700 87 : _reset_happened[j] = true;
701 :
702 : // Backup in case the next solve fails
703 76 : backup_apps = true;
704 :
705 76 : break;
706 : }
707 : }
708 :
709 : // Now move any apps that should be moved
710 67935 : if (_use_positions && !_move_happened && target_time + timestep_tol >= _move_time)
711 : {
712 60 : _move_happened = true;
713 116 : for (unsigned int i = 0; i < _move_apps.size(); i++)
714 60 : moveApp(_move_apps[i], _move_positions[i]);
715 :
716 : // Backup in case the next solve fails
717 56 : backup_apps = true;
718 : }
719 :
720 67931 : if (backup_apps)
721 111 : backup();
722 67931 : }
723 :
724 : Executioner *
725 0 : MultiApp::getExecutioner(unsigned int app)
726 : {
727 0 : if (!_has_an_app)
728 0 : mooseError("No app for ", name(), " on processor ", _orig_rank);
729 :
730 0 : return _apps[globalAppToLocal(app)]->getExecutioner();
731 : }
732 :
733 : void
734 4571 : MultiApp::finalize()
735 : {
736 11325 : for (const auto & app_ptr : _apps)
737 : {
738 6754 : auto * executioner = app_ptr->getExecutioner();
739 : mooseAssert(executioner, "Executioner is nullptr");
740 :
741 6754 : executioner->feProblem().execute(EXEC_FINAL);
742 6754 : executioner->feProblem().outputStep(EXEC_FINAL);
743 : }
744 4571 : }
745 :
746 : void
747 4909 : MultiApp::postExecute()
748 : {
749 12281 : for (const auto & app_ptr : _apps)
750 : {
751 7372 : auto * executioner = app_ptr->getExecutioner();
752 : mooseAssert(executioner, "Executioner is nullptr");
753 :
754 7372 : executioner->postExecute();
755 : }
756 4909 : }
757 :
758 : void
759 25114 : MultiApp::backup()
760 : {
761 25114 : TIME_SECTION(_backup_timer);
762 :
763 25114 : if (_fe_problem.verboseMultiApps())
764 1059 : _console << "Backed up MultiApp ... ";
765 :
766 59581 : for (unsigned int i = 0; i < _my_num_apps; i++)
767 34467 : _sub_app_backups[i] = _apps[i]->backup();
768 :
769 25114 : if (_fe_problem.verboseMultiApps())
770 1059 : _console << name() << std::endl;
771 25114 : }
772 :
773 : void
774 46627 : MultiApp::restore(bool force)
775 : {
776 46627 : TIME_SECTION(_restore_timer);
777 :
778 46627 : if (force || needsRestoration())
779 : {
780 : // Must be restarting / recovering from main app so hold off on restoring
781 : // Instead - the restore will happen in sub-apps' initialSetup()
782 : // Note that _backups was already populated by dataLoad() in the main app
783 10346 : if (_fe_problem.getCurrentExecuteOnFlag() == EXEC_INITIAL)
784 552 : return;
785 :
786 : // We temporarily copy and store solutions for all subapps
787 9794 : if (_keep_solution_during_restore)
788 : {
789 355 : _end_solutions.resize(_my_num_apps);
790 :
791 706 : for (unsigned int i = 0; i < _my_num_apps; i++)
792 : {
793 355 : _end_solutions[i].resize(_apps[i]->getExecutioner()->feProblem().numSolverSystems());
794 722 : for (unsigned int j = 0; j < _apps[i]->getExecutioner()->feProblem().numSolverSystems();
795 : j++)
796 : {
797 734 : _end_solutions[i][j] = _apps[i]
798 : ->getExecutioner()
799 367 : ->feProblem()
800 367 : .getSolverSystem(/*solver_sys=*/j)
801 367 : .solution()
802 734 : .clone();
803 : }
804 : auto & sub_multiapps =
805 355 : _apps[i]->getExecutioner()->feProblem().getMultiAppWarehouse().getObjects();
806 :
807 : // multiapps of each subapp should do the same things
808 : // It is implemented recursively
809 366 : for (auto & multi_app : sub_multiapps)
810 15 : multi_app->keepSolutionDuringRestore(_keep_solution_during_restore);
811 : }
812 : }
813 :
814 : // We temporarily copy and store solutions for all subapps
815 9790 : if (_keep_aux_solution_during_restore)
816 : {
817 132 : _end_aux_solutions.resize(_my_num_apps);
818 :
819 264 : for (unsigned int i = 0; i < _my_num_apps; i++)
820 132 : _end_aux_solutions[i] =
821 264 : _apps[i]->getExecutioner()->feProblem().getAuxiliarySystem().solution().clone();
822 : }
823 :
824 9790 : if (_fe_problem.verboseMultiApps())
825 516 : _console << "Restoring MultiApp ... ";
826 :
827 19765 : for (unsigned int i = 0; i < _my_num_apps; i++)
828 : {
829 9975 : _apps[i]->restore(std::move(_sub_app_backups[i]), false);
830 9975 : _sub_app_backups[i] = _apps[i]->finalizeRestore();
831 : mooseAssert(_sub_app_backups[i], "Should have a backup");
832 : }
833 :
834 9790 : if (_fe_problem.verboseMultiApps())
835 516 : _console << name() << std::endl;
836 :
837 : // Now copy the latest solutions back for each subapp
838 9790 : if (_keep_solution_during_restore)
839 : {
840 702 : for (unsigned int i = 0; i < _my_num_apps; i++)
841 : {
842 714 : for (unsigned int j = 0; j < _apps[i]->getExecutioner()->feProblem().numSolverSystems();
843 : j++)
844 : {
845 363 : _apps[i]->getExecutioner()->feProblem().getSolverSystem(/*solver_sys=*/j).solution() =
846 726 : *_end_solutions[i][j];
847 :
848 : // We need to synchronize solution so that local_solution has the right values
849 363 : _apps[i]->getExecutioner()->feProblem().getSolverSystem(/*solver_sys=*/j).update();
850 : }
851 : }
852 :
853 351 : _end_solutions.clear();
854 : }
855 : // Now copy the latest auxiliary solutions back for each subapp
856 9790 : if (_keep_aux_solution_during_restore)
857 : {
858 264 : for (unsigned int i = 0; i < _my_num_apps; i++)
859 : {
860 132 : _apps[i]->getExecutioner()->feProblem().getAuxiliarySystem().solution() =
861 264 : *_end_aux_solutions[i];
862 :
863 : // We need to synchronize solution so that local_solution has the right values
864 132 : _apps[i]->getExecutioner()->feProblem().getAuxiliarySystem().update();
865 : }
866 :
867 132 : _end_aux_solutions.clear();
868 : }
869 :
870 : // Make sure the displaced mesh on the multiapp is up-to-date with displacement variables
871 19765 : for (const auto & app_ptr : _apps)
872 9975 : if (app_ptr->feProblem().getDisplacedProblem())
873 48 : app_ptr->feProblem().getDisplacedProblem()->updateMesh();
874 :
875 : // If we are restoring due to a failed solve, make sure reset the solved state in the sub-apps
876 9790 : if (!getMooseApp().getExecutioner()->lastSolveConverged())
877 1663 : for (auto & app_ptr : _apps)
878 918 : app_ptr->getExecutioner()->fixedPointSolve().clearFixedPointStatus();
879 : }
880 : else
881 : {
882 72562 : for (unsigned int i = 0; i < _my_num_apps; i++)
883 : {
884 36281 : for (auto & sub_app :
885 73033 : _apps[i]->getExecutioner()->feProblem().getMultiAppWarehouse().getObjects())
886 471 : sub_app->restore(false);
887 : }
888 : }
889 46623 : }
890 :
891 : void
892 15 : MultiApp::keepSolutionDuringRestore(bool keep_solution_during_restore)
893 : {
894 45 : if (_pars.isParamSetByUser("keep_solution_during_restore"))
895 8 : paramError("keep_solution_during_restore",
896 : "This parameter should only be provided in parent app");
897 :
898 11 : _keep_solution_during_restore = keep_solution_during_restore;
899 11 : }
900 :
901 : void
902 416465 : MultiApp::transformBoundingBox(BoundingBox & box, const MultiAppCoordTransform & transform)
903 : {
904 416465 : const Real min_x = box.first(0);
905 416465 : const Real max_x = box.second(0);
906 416465 : const Real min_y = box.first(1);
907 416465 : const Real max_y = box.second(1);
908 416465 : const Real min_z = box.first(2);
909 416465 : const Real max_z = box.second(2);
910 :
911 : std::array<Point, 8> box_corners = {{Point(min_x, min_y, min_z),
912 : Point(max_x, min_y, min_z),
913 : Point(min_x, max_y, min_z),
914 : Point(max_x, max_y, min_z),
915 : Point(min_x, min_y, max_z),
916 : Point(max_x, min_y, max_z),
917 : Point(min_x, max_y, max_z),
918 416465 : Point(max_x, max_y, max_z)}};
919 :
920 : // transform each corner
921 3748185 : for (auto & corner : box_corners)
922 3331720 : corner = transform(corner);
923 :
924 : // Create new bounding box
925 416465 : Point new_box_min = box_corners[0];
926 416465 : Point new_box_max = new_box_min;
927 3331720 : for (const auto p : make_range(1, 8))
928 11661020 : for (const auto d : make_range(Moose::dim))
929 : {
930 8745765 : const Point & pt = box_corners[p];
931 8745765 : if (new_box_min(d) > pt(d))
932 2540 : new_box_min(d) = pt(d);
933 :
934 8745765 : if (new_box_max(d) < pt(d))
935 1178964 : new_box_max(d) = pt(d);
936 : }
937 416465 : box.first = new_box_min;
938 416465 : box.second = new_box_max;
939 416465 : }
940 :
941 : BoundingBox
942 354231 : MultiApp::getBoundingBox(unsigned int app,
943 : bool displaced_mesh,
944 : const MultiAppCoordTransform * const coord_transform)
945 : {
946 354231 : if (!_has_an_app)
947 0 : mooseError("No app for ", name(), " on processor ", _orig_rank);
948 :
949 354231 : unsigned int local_app = globalAppToLocal(app);
950 354231 : FEProblemBase & fe_problem_base = _apps[local_app]->getExecutioner()->feProblem();
951 9078 : MooseMesh & mesh = (displaced_mesh && fe_problem_base.getDisplacedProblem().get() != NULL)
952 712146 : ? fe_problem_base.getDisplacedProblem()->mesh()
953 354231 : : fe_problem_base.mesh();
954 :
955 : {
956 354231 : Moose::ScopedCommSwapper swapper(_my_comm);
957 354231 : if (displaced_mesh)
958 4539 : _bounding_box[local_app] = MeshTools::create_bounding_box(mesh);
959 : else
960 : {
961 349692 : if (!_has_bounding_box[local_app])
962 : {
963 102 : _bounding_box[local_app] = MeshTools::create_bounding_box(mesh);
964 102 : _has_bounding_box[local_app] = true;
965 : }
966 : }
967 354231 : }
968 354231 : BoundingBox bbox = _bounding_box[local_app];
969 :
970 354231 : Point min = bbox.min();
971 354231 : min -= _bounding_box_padding;
972 354231 : Point max = bbox.max();
973 354231 : max += _bounding_box_padding;
974 :
975 354231 : Point inflation_amount = (max - min) * _inflation;
976 :
977 354231 : Point inflated_min = min - inflation_amount;
978 354231 : Point inflated_max = max + inflation_amount;
979 :
980 354231 : Point shifted_min = inflated_min;
981 354231 : Point shifted_max = inflated_max;
982 :
983 708462 : if ((!coord_transform || coord_transform->skipCoordinateCollapsing()) &&
984 708462 : fe_problem_base.getCoordSystem(*(mesh.meshSubdomains().begin())) == Moose::COORD_RZ)
985 : {
986 : // If the problem is RZ then we're going to invent a box that would cover the whole "3D" app
987 : // FIXME: Assuming all subdomains are the same coordinate system type!
988 342175 : shifted_min(0) = -inflated_max(0);
989 342175 : shifted_min(1) = inflated_min(1);
990 342175 : shifted_min(2) = -inflated_max(0);
991 :
992 342175 : shifted_max(0) = inflated_max(0);
993 342175 : shifted_max(1) = inflated_max(1);
994 342175 : shifted_max(2) = inflated_max(0);
995 : }
996 :
997 354231 : if (coord_transform)
998 : {
999 354231 : BoundingBox transformed_bbox(shifted_min, shifted_max);
1000 354231 : transformBoundingBox(transformed_bbox, *coord_transform);
1001 354231 : return transformed_bbox;
1002 : }
1003 : else
1004 : {
1005 : // This is where the app is located. We need to shift by this amount.
1006 0 : Point p = position(app);
1007 :
1008 : // Shift them to the position they're supposed to be
1009 0 : shifted_min += p;
1010 0 : shifted_max += p;
1011 0 : return BoundingBox(shifted_min, shifted_max);
1012 : }
1013 : }
1014 :
1015 : FEProblemBase &
1016 518606 : MultiApp::appProblemBase(unsigned int app)
1017 : {
1018 518606 : if (!_has_an_app)
1019 0 : mooseError("No app for ", name(), " on processor ", _orig_rank);
1020 :
1021 518606 : unsigned int local_app = globalAppToLocal(app);
1022 :
1023 518606 : return _apps[local_app]->getExecutioner()->feProblem();
1024 : }
1025 :
1026 : FEProblem &
1027 0 : MultiApp::appProblem(unsigned int app)
1028 : {
1029 0 : mooseDeprecated(
1030 : "MultiApp::appProblem() is deprecated, call MultiApp::appProblemBase() instead.\n");
1031 0 : if (!_has_an_app)
1032 0 : mooseError("No app for ", name(), " on processor ", _orig_rank);
1033 :
1034 0 : unsigned int local_app = globalAppToLocal(app);
1035 :
1036 0 : return dynamic_cast<FEProblem &>(_apps[local_app]->getExecutioner()->feProblem());
1037 : }
1038 :
1039 : const UserObject &
1040 186397 : MultiApp::appUserObjectBase(unsigned int app, const std::string & name)
1041 : {
1042 186397 : if (!_has_an_app)
1043 0 : mooseError("No app for ", MultiApp::name(), " on processor ", _orig_rank);
1044 :
1045 186397 : return appProblemBase(app).getUserObjectBase(name);
1046 : }
1047 :
1048 : Real
1049 4152 : MultiApp::appPostprocessorValue(unsigned int app, const std::string & name)
1050 : {
1051 4152 : if (!_has_an_app)
1052 0 : mooseError("No app for ", MultiApp::name(), " on processor ", _orig_rank);
1053 :
1054 4152 : return appProblemBase(app).getPostprocessorValueByName(name);
1055 : }
1056 :
1057 : NumericVector<Number> &
1058 942 : MultiApp::appTransferVector(unsigned int app, std::string var_name)
1059 : {
1060 942 : return *(appProblemBase(app).getSystem(var_name).solution);
1061 : }
1062 :
1063 : bool
1064 150 : MultiApp::isFirstLocalRank() const
1065 : {
1066 150 : return _rank_config.is_first_local_rank;
1067 : }
1068 :
1069 : bool
1070 870466 : MultiApp::hasLocalApp(unsigned int global_app) const
1071 : {
1072 870466 : if (_has_an_app && global_app >= _first_local_app &&
1073 783743 : global_app <= _first_local_app + (_my_num_apps - 1))
1074 698825 : return true;
1075 :
1076 171641 : return false;
1077 : }
1078 :
1079 : MooseApp *
1080 11187 : MultiApp::localApp(unsigned int local_app)
1081 : {
1082 : mooseAssert(local_app < _apps.size(), "Index out of range: " + Moose::stringify(local_app));
1083 11187 : return _apps[local_app].get();
1084 : }
1085 :
1086 : void
1087 76 : MultiApp::resetApp(unsigned int global_app, Real time)
1088 : {
1089 76 : TIME_SECTION(_reset_timer);
1090 :
1091 76 : Moose::ScopedCommSwapper swapper(_my_comm);
1092 :
1093 76 : if (hasLocalApp(global_app))
1094 : {
1095 76 : unsigned int local_app = globalAppToLocal(global_app);
1096 :
1097 : // Extract the file numbers from the output, so that the numbering is maintained after reset
1098 76 : std::map<std::string, unsigned int> m = _apps[local_app]->getOutputWarehouse().getFileNumbers();
1099 :
1100 76 : createApp(local_app, time);
1101 :
1102 : // Reset the file numbers of the newly reset apps
1103 76 : _apps[local_app]->getOutputWarehouse().setFileNumbers(m);
1104 76 : }
1105 76 : }
1106 :
1107 : void
1108 60 : MultiApp::moveApp(unsigned int global_app, Point p)
1109 : {
1110 60 : if (_use_positions)
1111 : {
1112 60 : _positions[global_app] = p;
1113 :
1114 60 : if (hasLocalApp(global_app))
1115 : {
1116 60 : unsigned int local_app = globalAppToLocal(global_app);
1117 :
1118 60 : if (_output_in_position)
1119 56 : _apps[local_app]->setOutputPosition(p);
1120 60 : if (_run_in_position)
1121 8 : paramError("run_in_position", "Moving apps and running apps in position is not supported");
1122 : }
1123 : }
1124 56 : }
1125 :
1126 : void
1127 21 : MultiApp::parentOutputPositionChanged()
1128 : {
1129 21 : if (_use_positions && _output_in_position)
1130 42 : for (unsigned int i = 0; i < _apps.size(); i++)
1131 21 : _apps[i]->setOutputPosition(_app.getOutputPosition() + _positions[_first_local_app + i]);
1132 21 : }
1133 :
1134 : void
1135 12420 : MultiApp::createApp(unsigned int i, Real start_time)
1136 : {
1137 : // Delete the old app if we're resetting
1138 12420 : if (_apps[i])
1139 76 : _apps[i].reset();
1140 :
1141 : // Define the app name
1142 12420 : const std::string multiapp_name = getMultiAppName(name(), _first_local_app + i, _total_num_apps);
1143 12420 : std::string full_name;
1144 :
1145 : // Only add parent name if the parent is not the main app
1146 12420 : if (_app.multiAppLevel() > 0)
1147 525 : full_name = _app.name() + "_" + multiapp_name;
1148 : else
1149 11895 : full_name = multiapp_name;
1150 :
1151 12420 : InputParameters app_params = AppFactory::instance().getValidParams(_app_type);
1152 24840 : app_params.set<FEProblemBase *>("_parent_fep") = &_fe_problem;
1153 24840 : app_params.set<std::unique_ptr<Backup> *>("_initial_backup") = &_sub_app_backups[i];
1154 :
1155 : // Build the CommandLine with the relevant options for this subapp and add the
1156 : // cli args from the input file
1157 12420 : std::vector<std::string> input_cli_args;
1158 12420 : if (cliArgs().size() > 0 || _cli_args_from_file.size() > 0)
1159 3247 : input_cli_args = getCommandLineArgs(i);
1160 : // This will mark all hit CLI command line parameters that are passed to subapps
1161 : // as used within the parent app (_app)
1162 12420 : auto app_cli = _app.commandLine()->initSubAppCommandLine(name(), multiapp_name, input_cli_args);
1163 12420 : app_cli->parse();
1164 :
1165 12420 : if (_fe_problem.verboseMultiApps())
1166 1339 : _console << COLOR_CYAN << "Creating MultiApp " << name() << " of type " << _app_type
1167 1339 : << " of level " << _app.multiAppLevel() + 1 << " and number " << _first_local_app + i
1168 1339 : << " on processor " << processor_id() << " with full name " << full_name
1169 1339 : << COLOR_DEFAULT << std::endl;
1170 24840 : app_params.set<unsigned int>("_multiapp_level") = _app.multiAppLevel() + 1;
1171 24840 : app_params.set<unsigned int>("_multiapp_number") = _first_local_app + i;
1172 24840 : app_params.set<const MooseMesh *>("_master_mesh") = &_fe_problem.mesh();
1173 : #ifdef MOOSE_MFEM_ENABLED
1174 16630 : app_params.set<std::shared_ptr<mfem::Device>>("_mfem_device") =
1175 24945 : _app.getMFEMDevice(Moose::PassKey<MultiApp>());
1176 8315 : const auto & mfem_device_set = _app.getMFEMDevices(Moose::PassKey<MultiApp>());
1177 8315 : app_params.set<std::set<std::string>>("_mfem_devices") = mfem_device_set;
1178 : #endif
1179 62100 : if (getParam<bool>("clone_master_mesh") || getParam<bool>("clone_parent_mesh"))
1180 : {
1181 849 : if (_fe_problem.verboseMultiApps())
1182 0 : _console << COLOR_CYAN << "Cloned parent app mesh will be used for MultiApp " << name()
1183 0 : << COLOR_DEFAULT << std::endl;
1184 849 : app_params.set<bool>("_use_master_mesh") = true;
1185 849 : auto displaced_problem = _fe_problem.getDisplacedProblem();
1186 849 : if (displaced_problem)
1187 0 : app_params.set<const MooseMesh *>("_master_displaced_mesh") = &displaced_problem->mesh();
1188 849 : }
1189 :
1190 : // If only one input file was provided, use it for all the solves
1191 12420 : const auto input_index = _input_files.size() == 1 ? 0 : _first_local_app + i;
1192 12420 : const auto & input_file = _input_files[input_index];
1193 :
1194 : // create new parser tree for the application and parse
1195 12420 : auto parser = std::make_unique<Parser>(input_file);
1196 12420 : parser->setCommandLineParams(app_cli->buildHitParams());
1197 12420 : parser->parse();
1198 :
1199 : // Checks on app type
1200 12420 : const auto & app_type = parser->getAppType();
1201 12420 : if (app_type.empty() && _app_type.empty())
1202 0 : mooseWarning("The application type is not specified for ",
1203 : full_name,
1204 : ". Please use [Application] block to specify the application type.");
1205 12420 : if (!app_type.empty() && app_type != _app_type && !AppFactory::instance().isRegistered(app_type))
1206 4 : mooseError("In the ",
1207 : full_name,
1208 : ", '",
1209 : app_type,
1210 : "' is not a registered application. The registered application is named: '",
1211 4 : _app_type,
1212 : "'. Please double check the [Application] block to make sure the correct "
1213 : "application is provided. \n");
1214 :
1215 12416 : if (parser->getAppType().empty())
1216 12403 : parser->setAppType(_app_type);
1217 :
1218 24832 : app_params.set<std::shared_ptr<Parser>>("_parser") = std::move(parser);
1219 24832 : app_params.set<std::shared_ptr<CommandLine>>("_command_line") = std::move(app_cli);
1220 12416 : _apps[i] = AppFactory::instance().create(_app_type, full_name, app_params, _my_comm);
1221 12416 : auto & app = _apps[i];
1222 :
1223 12416 : app->setGlobalTimeOffset(start_time);
1224 12416 : app->setOutputFileNumbers(_app.getOutputWarehouse().getFileNumbers());
1225 12416 : app->setRestart(_app.isRestarting());
1226 12416 : app->setRecover(_app.isRecovering());
1227 :
1228 37248 : if (_use_positions && getParam<bool>("output_in_position"))
1229 3286 : app->setOutputPosition(_app.getOutputPosition() + _positions[_first_local_app + i]);
1230 12416 : if (_output_in_position && _run_in_position)
1231 8 : paramError("run_in_position",
1232 : "Sub-apps are already displaced, so they are already output in position");
1233 :
1234 : // Update the MultiApp level for the app that was just created
1235 12412 : app->setupOptions();
1236 : // if multiapp does not have file base in Outputs input block, output file base will
1237 : // be empty here since setupOptions() does not set the default file base with the multiapp
1238 : // input file name. Parent app will create the default file base for multiapp by taking the
1239 : // output base of the parent app problem and appending the name of the multiapp plus a number to
1240 : // it
1241 12412 : if (app->getOutputFileBase().empty())
1242 12390 : setAppOutputFileBase(i);
1243 12412 : preRunInputFile();
1244 37236 : if (_app.multiAppLevel() > getParam<unsigned int>("max_multiapp_level"))
1245 4 : mooseError("Maximum multiapp level has been reached. This is likely caused by an infinite loop "
1246 : "in your multiapp system. If additional multiapp levels are needed, "
1247 : "max_multiapp_level can be specified in the MuliApps block.");
1248 :
1249 : // Transfer coupling relaxation information to the subapps
1250 24816 : _apps[i]->fixedPointConfig().sub_relaxation_factor = getParam<Real>("relaxation_factor");
1251 12408 : _apps[i]->fixedPointConfig().sub_transformed_vars =
1252 37224 : getParam<std::vector<std::string>>("transformed_variables");
1253 : // Handle deprecated parameter
1254 37224 : if (!parameters().isParamSetByAddParam("relaxed_variables"))
1255 0 : _apps[i]->fixedPointConfig().sub_transformed_vars =
1256 0 : getParam<std::vector<std::string>>("relaxed_variables");
1257 12408 : _apps[i]->fixedPointConfig().sub_transformed_pps =
1258 37224 : getParam<std::vector<PostprocessorName>>("transformed_postprocessors");
1259 :
1260 12408 : app->runInputFile();
1261 12360 : auto fixed_point_solve = &(_apps[i]->getExecutioner()->fixedPointSolve());
1262 12360 : if (fixed_point_solve)
1263 12360 : fixed_point_solve->allocateStorage(false);
1264 :
1265 : // Transform the app mesh if requested
1266 12360 : if (_run_in_position)
1267 : {
1268 274 : if (usingPositions())
1269 548 : app->getExecutioner()->feProblem().coordTransform().transformMesh(
1270 274 : app->getExecutioner()->feProblem().mesh(), _positions[_first_local_app + i]);
1271 : else
1272 0 : app->getExecutioner()->feProblem().coordTransform().transformMesh(
1273 0 : app->getExecutioner()->feProblem().mesh(), Point(0, 0, 0));
1274 : }
1275 12356 : }
1276 :
1277 : std::vector<std::string>
1278 3247 : MultiApp::getCommandLineArgs(const unsigned int local_app)
1279 : {
1280 3247 : const auto cla = cliArgs();
1281 3247 : auto cli_args_param = _cli_args_param;
1282 3247 : std::string combined_args;
1283 :
1284 : // Single set of args from cliArgs() to be provided to all apps
1285 3247 : if (cla.size() == 1)
1286 2323 : combined_args = cla[0];
1287 : // Single "cli_args_files" file to be provided to all apps
1288 924 : else if (_cli_args_from_file.size() == 1)
1289 : {
1290 0 : cli_args_param = "cli_args_files";
1291 0 : combined_args = _cli_args_from_file[0];
1292 : }
1293 : // Unique set of args from cliArgs() to be provided to each app
1294 924 : else if (cla.size())
1295 804 : combined_args = cla[local_app + _first_local_app];
1296 : // Unique set of args from "cli_args_files" to be provided to all apps
1297 : else
1298 : {
1299 120 : cli_args_param = "cli_args_files";
1300 120 : combined_args = _cli_args_from_file[local_app + _first_local_app];
1301 : }
1302 :
1303 : // Remove all of the beginning and end whitespace so we can recognize truly empty
1304 3247 : combined_args = MooseUtils::trim(combined_args);
1305 :
1306 : // MooseUtils::split will return a single empty entry if there is nothing,
1307 : // so exit early if we have nothing
1308 3247 : if (combined_args.empty())
1309 0 : return {};
1310 :
1311 : // Split the argument into a vector of arguments, and make sure
1312 : // that we don't have any empty arguments
1313 6494 : const auto args = MooseUtils::split(combined_args, ";");
1314 10687 : for (const auto & arg : args)
1315 : {
1316 7440 : if (arg.empty())
1317 : {
1318 0 : const auto error = "An empty MultiApp command line argument was provided. Your "
1319 : "combined command line string has a ';' with no argument after it.";
1320 0 : if (cli_args_param)
1321 0 : paramError(*cli_args_param, error);
1322 : else
1323 0 : mooseError(error);
1324 : }
1325 : }
1326 :
1327 3247 : return args;
1328 3247 : }
1329 :
1330 : LocalRankConfig
1331 8488 : rankConfig(processor_id_type rank,
1332 : processor_id_type nprocs,
1333 : dof_id_type napps,
1334 : processor_id_type min_app_procs,
1335 : processor_id_type max_app_procs,
1336 : bool batch_mode)
1337 : {
1338 8488 : if (min_app_procs > nprocs)
1339 0 : mooseError("minimum number of procs per app is higher than the available number of procs");
1340 8488 : else if (min_app_procs > max_app_procs)
1341 0 : mooseError("minimum number of procs per app must be lower than the max procs per app");
1342 :
1343 : mooseAssert(rank < nprocs, "rank must be smaller than the number of procs");
1344 :
1345 : // A "slot" is a group of procs/ranks that are grouped together to run a
1346 : // single (sub)app/sim in parallel.
1347 :
1348 : const processor_id_type slot_size =
1349 8488 : std::max(std::min(cast_int<processor_id_type>(nprocs / napps), max_app_procs), min_app_procs);
1350 8488 : const processor_id_type nslots = std::min(
1351 16976 : nprocs / slot_size,
1352 8488 : cast_int<processor_id_type>(std::min(
1353 8488 : static_cast<dof_id_type>(std::numeric_limits<processor_id_type>::max()), napps)));
1354 : mooseAssert(nprocs >= (nslots * slot_size),
1355 : "Ensure that leftover procs is represented by an unsigned type");
1356 8488 : const processor_id_type leftover_procs = nprocs - nslots * slot_size;
1357 8488 : const dof_id_type apps_per_slot = napps / nslots;
1358 8488 : const dof_id_type leftover_apps = napps % nslots;
1359 :
1360 8488 : std::vector<int> slot_for_rank(nprocs);
1361 8488 : processor_id_type slot = 0;
1362 8488 : processor_id_type procs_in_slot = 0;
1363 20577 : for (processor_id_type rankiter = 0; rankiter <= rank; rankiter++)
1364 : {
1365 12089 : if (slot < nslots)
1366 11941 : slot_for_rank[rankiter] = cast_int<int>(slot);
1367 : else
1368 148 : slot_for_rank[rankiter] = -1;
1369 12089 : procs_in_slot++;
1370 : // this slot keeps growing until we reach slot size plus possibly an extra
1371 : // proc if there were any leftover from the slotization of nprocs - this
1372 : // must also make sure we don't go over max app procs.
1373 12089 : if (procs_in_slot == slot_size + 1 * (slot < leftover_procs && slot_size < max_app_procs))
1374 : {
1375 8942 : procs_in_slot = 0;
1376 8942 : slot++;
1377 : }
1378 : }
1379 :
1380 8488 : if (slot_for_rank[rank] < 0)
1381 : // ranks assigned a negative slot don't have any apps running on them.
1382 72 : return {0, 0, 0, 0, false, 0};
1383 8416 : const processor_id_type slot_num = cast_int<processor_id_type>(slot_for_rank[rank]);
1384 :
1385 8416 : const bool is_first_local_rank = rank == 0 || (slot_for_rank[rank - 1] != slot_for_rank[rank]);
1386 8416 : const dof_id_type n_local_apps = apps_per_slot + 1 * (slot_num < leftover_apps);
1387 :
1388 8416 : processor_id_type my_first_rank = 0;
1389 10889 : for (processor_id_type rankiter = rank; rankiter > 0; rankiter--)
1390 2767 : if (slot_for_rank[rank] != slot_for_rank[rankiter])
1391 : {
1392 294 : my_first_rank = cast_int<processor_id_type>(slot_for_rank[rankiter + 1]);
1393 294 : break;
1394 : }
1395 :
1396 8416 : dof_id_type app_index = 0;
1397 9878 : for (processor_id_type slot = 0; slot < slot_num; slot++)
1398 : {
1399 1462 : const dof_id_type num_slot_apps = apps_per_slot + 1 * (slot < leftover_apps);
1400 1462 : app_index += num_slot_apps;
1401 : }
1402 :
1403 8416 : if (batch_mode)
1404 405 : return {n_local_apps, app_index, 1, slot_num, is_first_local_rank, my_first_rank};
1405 8011 : return {n_local_apps, app_index, n_local_apps, app_index, is_first_local_rank, my_first_rank};
1406 8488 : }
1407 :
1408 : void
1409 7601 : MultiApp::buildComm()
1410 : {
1411 : int ierr;
1412 :
1413 7601 : ierr = MPI_Comm_size(_communicator.get(), &_orig_num_procs);
1414 7601 : mooseCheckMPIErr(ierr);
1415 7601 : ierr = MPI_Comm_rank(_communicator.get(), &_orig_rank);
1416 7601 : mooseCheckMPIErr(ierr);
1417 :
1418 : #ifdef LIBMESH_HAVE_SYS_UTSNAME_H
1419 : struct utsname sysInfo;
1420 7601 : uname(&sysInfo);
1421 7601 : _node_name = sysInfo.nodename;
1422 : #else
1423 : _node_name = "Unknown";
1424 : #endif
1425 :
1426 : int rank;
1427 7601 : ierr = MPI_Comm_rank(_communicator.get(), &rank);
1428 7601 : mooseCheckMPIErr(ierr);
1429 :
1430 7601 : _my_num_apps = _rank_config.num_local_apps;
1431 7601 : _first_local_app = _rank_config.first_local_app_index;
1432 :
1433 7601 : _has_an_app = _rank_config.num_local_apps > 0;
1434 7601 : if (_rank_config.first_local_app_index >= _total_num_apps)
1435 0 : mooseError("Internal error, a processor has an undefined app.");
1436 :
1437 7601 : if (_has_an_app)
1438 : {
1439 7547 : _communicator.split(_rank_config.first_local_app_index, rank, _my_communicator);
1440 7547 : ierr = MPI_Comm_rank(_my_comm, &_my_rank);
1441 7547 : mooseCheckMPIErr(ierr);
1442 : }
1443 : else
1444 : {
1445 54 : _communicator.split(MPI_UNDEFINED, rank, _my_communicator);
1446 54 : _my_rank = 0;
1447 : }
1448 7601 : }
1449 :
1450 : unsigned int
1451 873057 : MultiApp::globalAppToLocal(unsigned int global_app)
1452 : {
1453 873057 : if (global_app >= _first_local_app && global_app <= _first_local_app + (_my_num_apps - 1))
1454 873057 : return global_app - _first_local_app;
1455 :
1456 0 : std::stringstream ss;
1457 0 : ss << "Requesting app " << global_app << ", but processor " << processor_id() << " ";
1458 0 : if (_my_num_apps == 0)
1459 0 : ss << "does not own any apps";
1460 0 : else if (_my_num_apps == 1)
1461 0 : ss << "owns app " << _first_local_app;
1462 : else
1463 0 : ss << "owns apps " << _first_local_app << "-" << _first_local_app + (_my_num_apps - 1);
1464 0 : ss << ".";
1465 0 : mooseError("Invalid global_app!\n", ss.str());
1466 : return 0;
1467 0 : }
1468 :
1469 : void
1470 12412 : MultiApp::preRunInputFile()
1471 : {
1472 12412 : }
1473 :
1474 : void
1475 13372 : MultiApp::addAssociatedTransfer(MultiAppTransfer & transfer)
1476 : {
1477 13372 : _associated_transfers.push_back(&transfer);
1478 13372 : }
1479 :
1480 : void
1481 0 : MultiApp::setAppOutputFileBase()
1482 : {
1483 0 : for (unsigned int i = 0; i < _my_num_apps; ++i)
1484 0 : setAppOutputFileBase(i);
1485 0 : }
1486 :
1487 : std::vector<std::string>
1488 23264 : MultiApp::cliArgs() const
1489 : {
1490 : // So that we can error out with paramError("cli_args", ...);
1491 23264 : _cli_args_param = "cli_args";
1492 46528 : return std::vector<std::string>(_cli_args.begin(), _cli_args.end());
1493 : }
1494 :
1495 : void
1496 12390 : MultiApp::setAppOutputFileBase(unsigned int index)
1497 : {
1498 : const std::string multiapp_name =
1499 12390 : getMultiAppName(name(), _first_local_app + index, _total_num_apps);
1500 12390 : _apps[index]->setOutputFileBase(_app.getOutputFileBase() + "_" + multiapp_name);
1501 12390 : }
1502 :
1503 : std::string
1504 24810 : MultiApp::getMultiAppName(const std::string & base_name, dof_id_type index, dof_id_type total)
1505 : {
1506 24810 : std::ostringstream multiapp_name;
1507 49620 : multiapp_name << base_name << std::setw(std::ceil(std::log10(total))) << std::setprecision(0)
1508 24810 : << std::setfill('0') << std::right << index;
1509 49620 : return multiapp_name.str();
1510 24810 : }
1511 :
1512 : const Point &
1513 208819 : MultiApp::position(unsigned int app) const
1514 : {
1515 : // If we're not using positions, it won't have changed
1516 208819 : if (_positions_objs.empty())
1517 192691 : return _positions[app];
1518 : else
1519 : // Find which Positions object is specifying it, and query a potentially updated value
1520 16128 : return _positions_objs[app]->getPosition(app - _positions_index_offsets[app], false);
1521 : }
1522 :
1523 : void
1524 2500 : dataStore(std::ostream & stream, SubAppBackups & backups, void * context)
1525 : {
1526 2500 : MultiApp * multi_app = static_cast<MultiApp *>(context);
1527 : mooseAssert(multi_app, "Not set");
1528 :
1529 2500 : multi_app->backup();
1530 :
1531 2500 : dataStore(stream, static_cast<std::vector<std::unique_ptr<Backup>> &>(backups), nullptr);
1532 2500 : }
1533 :
1534 : void
1535 651 : dataLoad(std::istream & stream, SubAppBackups & backups, void * context)
1536 : {
1537 651 : MultiApp * multi_app = static_cast<MultiApp *>(context);
1538 : mooseAssert(multi_app, "Not set");
1539 :
1540 651 : dataLoad(stream, static_cast<std::vector<std::unique_ptr<Backup>> &>(backups), nullptr);
1541 :
1542 651 : multi_app->restore();
1543 651 : }
|