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 : #ifdef HAVE_GPERFTOOLS
11 : #include "gperftools/profiler.h"
12 : #include "gperftools/heap-profiler.h"
13 : #endif
14 :
15 : // MOOSE includes
16 : #include "MooseRevision.h"
17 : #include "AppFactory.h"
18 : #include "DisplacedProblem.h"
19 : #include "NonlinearSystemBase.h"
20 : #include "AuxiliarySystem.h"
21 : #include "MooseSyntax.h"
22 : #include "MooseInit.h"
23 : #include "Executioner.h"
24 : #include "Executor.h"
25 : #include "PetscSupport.h"
26 : #include "Conversion.h"
27 : #include "CommandLine.h"
28 : #include "InfixIterator.h"
29 : #include "MultiApp.h"
30 : #include "MooseUtils.h"
31 : #include "MooseObjectAction.h"
32 : #include "InputParameterWarehouse.h"
33 : #include "SystemInfo.h"
34 : #include "MooseMesh.h"
35 : #include "FileOutput.h"
36 : #include "ConsoleUtils.h"
37 : #include "JsonSyntaxTree.h"
38 : #include "JsonInputFileFormatter.h"
39 : #include "RelationshipManager.h"
40 : #include "ProxyRelationshipManager.h"
41 : #include "Registry.h"
42 : #include "SerializerGuard.h"
43 : #include "PerfGraphInterface.h" // For TIME_SECTION
44 : #include "SolutionInvalidInterface.h"
45 : #include "Attributes.h"
46 : #include "MooseApp.h"
47 : #include "CommonOutputAction.h"
48 : #include "CastUniquePointer.h"
49 : #include "NullExecutor.h"
50 : #include "ExecFlagRegistry.h"
51 : #include "SolutionInvalidity.h"
52 : #include "MooseServer.h"
53 : #include "RestartableDataWriter.h"
54 : #include "StringInputStream.h"
55 : #include "MooseMain.h"
56 : #include "FEProblemBase.h"
57 : #include "Parser.h"
58 : #include "CSGBase.h"
59 : #include "Capabilities.h"
60 :
61 : // Regular expression includes
62 : #include "pcrecpp.h"
63 :
64 : #include "libmesh/exodusII_io.h"
65 : #include "libmesh/mesh_refinement.h"
66 : #include "libmesh/string_to_enum.h"
67 : #include "libmesh/checkpoint_io.h"
68 : #include "libmesh/mesh_base.h"
69 : #include "libmesh/petsc_solver_exception.h"
70 :
71 : // System include for dynamic library methods
72 : #ifdef LIBMESH_HAVE_DLOPEN
73 : #include <dlfcn.h>
74 : #include <sys/utsname.h> // utsname
75 : #endif
76 :
77 : #if __has_include(<torch/xpu.h>)
78 : #include <torch/xpu.h>
79 : #define MOOSE_HAVE_XPU 1
80 : #endif
81 :
82 : // C++ includes
83 : #include <numeric> // std::accumulate
84 : #include <fstream>
85 : #include <sys/types.h>
86 : #include <unistd.h>
87 : #include <cstdlib> // for system()
88 : #include <chrono>
89 : #include <thread>
90 : #include <filesystem>
91 :
92 : using namespace libMesh;
93 :
94 : void
95 188815 : MooseApp::addAppParam(InputParameters & params)
96 : {
97 944075 : params.addCommandLineParam<std::string>(
98 : "app_to_run", "--app <type>", "Specify the application type to run (case-sensitive)");
99 188815 : }
100 :
101 : void
102 121843 : MooseApp::addInputParam(InputParameters & params)
103 : {
104 609215 : params.addCommandLineParam<std::vector<std::string>>(
105 : "input_file", "-i <input file(s)>", "Specify input file(s); multiple files are merged");
106 121843 : }
107 :
108 : InputParameters
109 66972 : MooseApp::validParams()
110 : {
111 66972 : InputParameters params = MooseBase::validParams();
112 :
113 66972 : MooseApp::addAppParam(params);
114 66972 : MooseApp::addInputParam(params);
115 :
116 401832 : params.addCommandLineParam<bool>("display_version", "-v --version", "Print application version");
117 :
118 535776 : params.addOptionalValuedCommandLineParam<std::string>(
119 : "mesh_only",
120 : "--mesh-only <optional path>",
121 : "",
122 : "Build and output the mesh only (Default: \"<input_file_name>_in.e\")");
123 535776 : params.addOptionalValuedCommandLineParam<std::string>(
124 : "csg_only",
125 : "--csg-only <optional path>",
126 : "",
127 : "Setup and output the input mesh in CSG format only (Default: "
128 : "\"<input_file_name>_out_csg.json\")");
129 401832 : params.addCommandLineParam<bool>(
130 : "show_input", "--show-input", "Shows the parsed input file before running the simulation");
131 133944 : params.setGlobalCommandLineParam("show_input");
132 401832 : params.addCommandLineParam<bool>(
133 : "show_outputs", "--show-outputs", "Shows the output execution time information");
134 133944 : params.setGlobalCommandLineParam("show_outputs");
135 401832 : params.addCommandLineParam<bool>(
136 : "show_controls", "--show-controls", "Shows the Control logic available and executed");
137 133944 : params.setGlobalCommandLineParam("show_controls");
138 :
139 401832 : params.addCommandLineParam<bool>(
140 : "no_color", "--no-color", "Disable coloring of all Console outputs");
141 133944 : params.setGlobalCommandLineParam("no_color");
142 :
143 267888 : MooseEnum colors("auto on off", "on");
144 401832 : params.addCommandLineParam<MooseEnum>(
145 : "color", "--color <auto,on,off=on>", colors, "Whether to use color in console output");
146 133944 : params.setGlobalCommandLineParam("color");
147 :
148 401832 : params.addCommandLineParam<bool>("help", "-h --help", "Displays CLI usage statement");
149 401832 : params.addCommandLineParam<bool>(
150 : "minimal",
151 : "--minimal",
152 : "Ignore input file and build a minimal application with Transient executioner");
153 :
154 401832 : params.addCommandLineParam<bool>(
155 : "language_server",
156 : "--language-server",
157 : "Starts a process to communicate with development tools using the language server protocol");
158 :
159 401832 : params.addCommandLineParam<bool>("dump", "--dump", "Shows a dump of available input file syntax");
160 401832 : params.addCommandLineParam<std::string>(
161 : "dump_search",
162 : "--dump-search <search>",
163 : "Shows a dump of available input syntax matching a search");
164 401832 : params.addCommandLineParam<bool>("registry", "--registry", "Lists all known objects and actions");
165 401832 : params.addCommandLineParam<bool>(
166 : "registry_hit", "--registry-hit", "Lists all known objects and actions in hit format");
167 401832 : params.addCommandLineParam<bool>(
168 : "use_executor", "--executor", "Use the new Executor system instead of Executioners");
169 :
170 401832 : params.addCommandLineParam<bool>(
171 : "show_type", "--show-type", "Return the name of the application object");
172 401832 : params.addCommandLineParam<bool>("yaml", "--yaml", "Dumps all input file syntax in YAML format");
173 401832 : params.addCommandLineParam<std::string>(
174 : "yaml_search", "--yaml-search", "Dumps input file syntax matching a search in YAML format");
175 401832 : params.addCommandLineParam<bool>("json", "--json", "Dumps all input file syntax in JSON format");
176 401832 : params.addCommandLineParam<std::string>(
177 : "json_search", "--json-search", "Dumps input file syntax matching a search in JSON format");
178 401832 : params.addCommandLineParam<bool>(
179 : "syntax", "--syntax", "Dumps the associated Action syntax paths ONLY");
180 401832 : params.addCommandLineParam<bool>(
181 : "show_docs", "--docs", "Print url/path to the documentation website");
182 401832 : params.addCommandLineParam<bool>(
183 : "show_capabilities", "--show-capabilities", "Dumps the capability registry in JSON format.");
184 401832 : params.addCommandLineParam<std::string>(
185 : "required_capabilities",
186 : "--required-capabilities",
187 : "A list of conditions that is checked against the registered capabilities (see "
188 : "--show-capabilities). The executable will terminate early if the conditions are not met.");
189 401832 : params.addCommandLineParam<std::string>(
190 : "testharness_capabilities",
191 : "--testharness-capabilities",
192 : "Path to JSON from the TestHarness that contains capabilities to be appended.");
193 :
194 401832 : params.addCommandLineParam<std::string>(
195 : "check_capabilities",
196 : "--check-capabilities",
197 : "A list of conditions that is checked against the registered capabilities. Will exit based "
198 : "on whether or not the capaiblities are fulfilled. Does not check dynamically loaded apps.");
199 401832 : params.addCommandLineParam<bool>("check_input",
200 : "--check-input",
201 : "Check the input file (i.e. requires -i <filename>) and quit");
202 133944 : params.setGlobalCommandLineParam("check_input");
203 401832 : params.addCommandLineParam<bool>(
204 : "show_inputs",
205 : "--show-copyable-inputs",
206 : "Shows the directories able to be copied into a user-writable location");
207 :
208 401832 : params.addCommandLineParam<std::string>(
209 : "copy_inputs",
210 : "--copy-inputs <dir>",
211 : "Copies installed inputs (e.g. tests, examples, etc.) to a directory <appname>_<dir>");
212 : // TODO: Should this remain a bool? It can't be a regular argument because it contains
213 : // values that have dashes in it, so it'll get treated as another arg
214 535776 : params.addOptionalValuedCommandLineParam<std::string>(
215 : "run",
216 : "--run <test harness args>",
217 : "",
218 : "Runs the inputs in the current directory copied to a "
219 : "user-writable location by \"--copy-inputs\"");
220 :
221 401832 : params.addCommandLineParam<bool>(
222 : "list_constructed_objects",
223 : "--list-constructed-objects",
224 : "List all moose object type names constructed by the master app factory");
225 :
226 401832 : params.addCommandLineParam<unsigned int>(
227 : "n_threads", "--n-threads=<n>", "Runs the specified number of threads per process");
228 : // This probably shouldn't be global, but the implications of removing this are currently
229 : // unknown and we need to manage it with libmesh better
230 133944 : params.setGlobalCommandLineParam("n_threads");
231 :
232 401832 : params.addCommandLineParam<bool>("allow_unused",
233 : "-w --allow-unused",
234 : "Warn about unused input file options instead of erroring");
235 133944 : params.setGlobalCommandLineParam("allow_unused");
236 401832 : params.addCommandLineParam<bool>(
237 : "error_unused", "-e --error-unused", "Error when encountering unused input file options");
238 133944 : params.setGlobalCommandLineParam("error_unused");
239 401832 : params.addCommandLineParam<bool>(
240 : "error_override",
241 : "-o --error-override",
242 : "Error when encountering overridden or parameters supplied multiple times");
243 133944 : params.setGlobalCommandLineParam("error_override");
244 401832 : params.addCommandLineParam<bool>(
245 : "error_deprecated", "--error-deprecated", "Turn deprecated code messages into Errors");
246 133944 : params.setGlobalCommandLineParam("error_deprecated");
247 :
248 401832 : params.addCommandLineParam<bool>("distributed_mesh",
249 : "--distributed-mesh",
250 : "Forces the use of a distributed finite element mesh");
251 : // Would prefer that this parameter isn't global, but we rely on it too much
252 : // in tests to be able to go back on that decision now
253 133944 : params.setGlobalCommandLineParam("distributed_mesh");
254 :
255 401832 : params.addCommandLineParam<std::string>(
256 : "split_mesh",
257 : "--split-mesh <splits>",
258 : "Comma-separated list of numbers of chunks to split the mesh into");
259 :
260 : // TODO: remove the logic now that this is global
261 401832 : params.addCommandLineParam<std::string>(
262 : "split_file", "--split-file <filename>", "Name of split mesh file(s) to write/read");
263 :
264 401832 : params.addCommandLineParam<bool>("use_split", "--use-split", "Use split distributed mesh files");
265 :
266 401832 : params.addCommandLineParam<unsigned int>(
267 : "refinements", "-r <num refinements>", "Specify additional initial uniform mesh refinements");
268 :
269 535776 : params.addOptionalValuedCommandLineParam<std::string>(
270 : "recover",
271 : "--recover <optional file base>",
272 : "",
273 : "Continue the calculation. Without <file base>, the most recent recovery file will be used");
274 133944 : params.setGlobalCommandLineParam("recover");
275 401832 : params.addCommandLineParam<bool>(
276 : "force_restart",
277 : "--force-restart",
278 : "Forcefully load checkpoints despite possible incompatibilities");
279 133944 : params.setGlobalCommandLineParam("force_restart");
280 :
281 334860 : params.addCommandLineParam<bool>("suppress_header",
282 : "--suppress-header",
283 133944 : false,
284 : "Disables the output of the application header.");
285 133944 : params.setGlobalCommandLineParam("suppress_header");
286 :
287 401832 : params.addCommandLineParam<bool>(
288 : "test_checkpoint_half_transient",
289 : "--test-checkpoint-half-transient",
290 : "Run half of a transient with checkpoints enabled; used by the TestHarness");
291 133944 : params.setGlobalCommandLineParam("test_checkpoint_half_transient");
292 :
293 401832 : params.addCommandLineParam<bool>("test_restep",
294 : "--test-restep",
295 : "Test re-running the middle timestep; used by the TestHarness");
296 :
297 401832 : params.addCommandLineParam<bool>(
298 : "trap_fpe",
299 : "--trap-fpe",
300 : "Enable floating point exception handling in critical sections of code"
301 : #ifdef DEBUG
302 : " (automatic due to debug build)"
303 : #endif
304 : );
305 133944 : params.setGlobalCommandLineParam("trap_fpe");
306 :
307 401832 : params.addCommandLineParam<bool>(
308 : "no_trap_fpe",
309 : "--no-trap-fpe",
310 : "Disable floating point exception handling in critical sections of code"
311 : #ifndef DEBUG
312 : " (unused due to non-debug build)"
313 : #endif
314 : );
315 :
316 133944 : params.setGlobalCommandLineParam("no_trap_fpe");
317 :
318 401832 : params.addCommandLineParam<bool>(
319 : "no_gdb_backtrace", "--no-gdb-backtrace", "Disables gdb backtraces.");
320 133944 : params.setGlobalCommandLineParam("no_gdb_backtrace");
321 :
322 401832 : params.addCommandLineParam<bool>("error", "--error", "Turn all warnings into errors");
323 133944 : params.setGlobalCommandLineParam("error");
324 :
325 401832 : params.addCommandLineParam<bool>("timing",
326 : "-t --timing",
327 : "Enable all performance logging for timing; disables screen "
328 : "output of performance logs for all Console objects");
329 133944 : params.setGlobalCommandLineParam("timing");
330 401832 : params.addCommandLineParam<bool>(
331 : "no_timing", "--no-timing", "Disabled performance logging; overrides -t or --timing");
332 133944 : params.setGlobalCommandLineParam("no_timing");
333 :
334 401832 : params.addCommandLineParam<bool>(
335 : "allow_test_objects", "--allow-test-objects", "Register test objects and syntax");
336 133944 : params.setGlobalCommandLineParam("allow_test_objects");
337 :
338 : // Options ignored by MOOSE but picked up by libMesh, these are here so that they are displayed in
339 : // the application help
340 401832 : params.addCommandLineParam<bool>(
341 : "keep_cout",
342 : "--keep-cout",
343 : "Keep standard output from all processors when running in parallel");
344 133944 : params.setGlobalCommandLineParam("keep_cout");
345 401832 : params.addCommandLineParam<bool>(
346 : "redirect_stdout",
347 : "--redirect-stdout",
348 : "Keep standard output from all processors when running in parallel");
349 133944 : params.setGlobalCommandLineParam("redirect_stdout");
350 :
351 535776 : params.addCommandLineParam<std::string>(
352 : "timpi_sync",
353 : "--timpi-sync <type=nbx>",
354 : "nbx",
355 : "Changes the sync type used in spare parallel communitations within TIMPI");
356 133944 : params.setGlobalCommandLineParam("timpi_sync");
357 :
358 : // Options for debugging
359 401832 : params.addCommandLineParam<std::string>("start_in_debugger",
360 : "--start-in-debugger <debugger>",
361 : "Start the application and attach a debugger; this will "
362 : "launch xterm windows using <debugger>");
363 :
364 401832 : params.addCommandLineParam<unsigned int>(
365 : "stop_for_debugger",
366 : "--stop-for-debugger <seconds>",
367 : "Pauses the application during startup for <seconds> to allow for connection of debuggers");
368 :
369 401832 : params.addCommandLineParam<bool>(
370 : "perf_graph_live_all", "--perf-graph-live-all", "Forces printing of ALL progress messages");
371 133944 : params.setGlobalCommandLineParam("perf_graph_live_all");
372 :
373 401832 : params.addCommandLineParam<bool>(
374 : "disable_perf_graph_live", "--disable-perf-graph-live", "Disables PerfGraph live printing");
375 133944 : params.setGlobalCommandLineParam("disable_perf_graph_live");
376 :
377 200916 : params.addParam<bool>(
378 133944 : "automatic_automatic_scaling", false, "Whether to turn on automatic scaling by default");
379 :
380 267888 : const MooseEnum compute_device_type("cpu cuda mps hip ceed-cpu ceed-cuda ceed-hip xpu", "cpu");
381 401832 : params.addCommandLineParam<MooseEnum>(
382 : "compute_device",
383 : "--compute-device",
384 : compute_device_type,
385 : "The device type we want to run accelerated (libtorch, MFEM) computations on.");
386 :
387 : #ifdef HAVE_GPERFTOOLS
388 : params.addCommandLineParam<std::string>(
389 : "gperf_profiler_on",
390 : "--gperf-profiler-on <ranks>",
391 : "To generate profiling report only on comma-separated list of MPI ranks");
392 : #endif
393 :
394 334860 : params.addCommandLineParam<bool>(
395 : "show_data_params",
396 : "--show-data-params",
397 133944 : false,
398 : "Show found paths for all DataFileName parameters in the header");
399 334860 : params.addCommandLineParam<bool>("show_data_paths",
400 : "--show-data-paths",
401 133944 : false,
402 : "Show registered data paths for searching in the header");
403 :
404 133944 : params.addPrivateParam<std::shared_ptr<CommandLine>>("_command_line");
405 133944 : params.addPrivateParam<std::shared_ptr<Parallel::Communicator>>("_comm");
406 133944 : params.addPrivateParam<unsigned int>("_multiapp_level");
407 66972 : params.addPrivateParam<unsigned int>("_multiapp_number");
408 200916 : params.addPrivateParam<bool>("_use_master_mesh", false);
409 133944 : params.addPrivateParam<const MooseMesh *>("_master_mesh");
410 66972 : params.addPrivateParam<const MooseMesh *>("_master_displaced_mesh");
411 200916 : params.addPrivateParam<std::unique_ptr<Backup> *>("_initial_backup", nullptr);
412 133944 : params.addPrivateParam<std::shared_ptr<Parser>>("_parser");
413 : #ifdef MOOSE_MFEM_ENABLED
414 100374 : params.addPrivateParam<std::shared_ptr<mfem::Device>>("_mfem_device");
415 100374 : params.addPrivateParam<std::set<std::string>>("_mfem_devices");
416 : #endif
417 :
418 200916 : params.addParam<bool>(
419 : "use_legacy_material_output",
420 133944 : true,
421 : "Set false to allow material properties to be output on INITIAL, not just TIMESTEP_END.");
422 200916 : params.addParam<bool>(
423 : "use_legacy_initial_residual_evaluation_behavior",
424 133944 : true,
425 : "The legacy behavior performs an often times redundant residual evaluation before the "
426 : "solution modifying objects are executed prior to the initial (0th nonlinear iteration) "
427 : "residual evaluation. The new behavior skips that redundant residual evaluation unless the "
428 : "parameter Executioner/use_pre_SMO_residual is set to true.");
429 :
430 133944 : params.addParam<bool>(
431 : MeshGeneratorSystem::allow_data_driven_param,
432 66972 : false,
433 : "Set true to enable data-driven mesh generation, which is an experimental feature");
434 :
435 334860 : params.addCommandLineParam<bool>(
436 : "parse_neml2_only",
437 : "--parse-neml2-only",
438 : "Executes the [NEML2] block to parse the input file and terminate.");
439 :
440 66972 : MooseApp::addAppParam(params);
441 :
442 66972 : params.registerBase("Application");
443 :
444 133944 : return params;
445 66972 : }
446 :
447 66995 : MooseApp::MooseApp(const InputParameters & parameters)
448 : : PerfGraphInterface(*this, "MooseApp"),
449 66995 : ParallelObject(*parameters.get<std::shared_ptr<Parallel::Communicator>>(
450 66995 : "_comm")), // Can't call getParam() before pars is set
451 : // The use of AppFactory::getAppParams() is atrocious. However, a long time ago
452 : // we decided to copy construct parameters in each derived application...
453 : // which means that the "parameters" we get if someone derives from MooseApp are
454 : // actually a copy of the ones built by the factory. Because we have unique
455 : // application names, this allows us to reference (using _pars and MooseBase)
456 : // the actual const parameters that the AppFactory made for this application
457 133990 : MooseBase(*this, AppFactory::instance().getAppParams(parameters)),
458 133990 : _comm(getParam<std::shared_ptr<Parallel::Communicator>>("_comm")),
459 66995 : _file_base_set_by_user(false),
460 66995 : _output_position_set(false),
461 66995 : _start_time_set(false),
462 66995 : _start_time(0.0),
463 66995 : _global_time_offset(0.0),
464 66995 : _input_parameter_warehouse(std::make_unique<InputParameterWarehouse>()),
465 66995 : _action_factory(*this),
466 66995 : _action_warehouse(*this, _syntax, _action_factory),
467 66995 : _output_warehouse(*this),
468 267980 : _parser(getCheckedPointerParam<std::shared_ptr<Parser>>("_parser")),
469 267980 : _command_line(getCheckedPointerParam<std::shared_ptr<CommandLine>>("_command_line")),
470 66995 : _builder(*this, _action_warehouse, *_parser),
471 133990 : _restartable_data(libMesh::n_threads()),
472 66995 : _perf_graph(createRecoverablePerfGraph()),
473 66995 : _solution_invalidity(createRecoverableSolutionInvalidity()),
474 66995 : _rank_map(*_comm, _perf_graph),
475 133990 : _use_executor(getParam<bool>("use_executor")),
476 66995 : _null_executor(NULL),
477 66995 : _use_nonlinear(true),
478 66995 : _use_eigen_value(false),
479 66995 : _enable_unused_check(ERROR_UNUSED),
480 66995 : _factory(*this),
481 66995 : _error_overridden(false),
482 133990 : _early_exit_param(""),
483 66995 : _ready_to_exit(false),
484 66995 : _exit_code(0),
485 66995 : _initial_from_file(false),
486 66995 : _distributed_mesh_on_command_line(false),
487 66995 : _recover(false),
488 66995 : _restart(false),
489 66995 : _split_mesh(false),
490 133990 : _use_split(getParam<bool>("use_split")),
491 133990 : _force_restart(getParam<bool>("force_restart")),
492 : #ifdef DEBUG
493 : _trap_fpe(true),
494 : #else
495 66995 : _trap_fpe(false),
496 : #endif
497 66995 : _test_checkpoint_half_transient(parameters.get<bool>("test_checkpoint_half_transient")),
498 66995 : _test_restep(parameters.get<bool>("test_restep")),
499 133990 : _check_input(getParam<bool>("check_input")),
500 158178 : _multiapp_level(isParamValid("_multiapp_level") ? getParam<unsigned int>("_multiapp_level")
501 : : 0),
502 158178 : _multiapp_number(isParamValid("_multiapp_number") ? getParam<unsigned int>("_multiapp_number")
503 : : 0),
504 133990 : _use_master_mesh(getParam<bool>("_use_master_mesh")),
505 158178 : _master_mesh(isParamValid("_master_mesh") ? getParam<const MooseMesh *>("_master_mesh")
506 : : nullptr),
507 133990 : _master_displaced_mesh(isParamValid("_master_displaced_mesh")
508 66995 : ? getParam<const MooseMesh *>("_master_displaced_mesh")
509 : : nullptr),
510 66995 : _mesh_generator_system(*this),
511 66995 : _chain_control_system(*this),
512 66995 : _rd_reader(*this, _restartable_data, forceRestart()),
513 66995 : _execute_flags(moose::internal::ExecFlagRegistry::getExecFlagRegistry().getFlags()),
514 66995 : _output_buffer_cache(nullptr),
515 133990 : _automatic_automatic_scaling(getParam<bool>("automatic_automatic_scaling")),
516 318031 : _initial_backup(getParam<std::unique_ptr<Backup> *>("_initial_backup"))
517 : #ifdef MOOSE_LIBTORCH_ENABLED
518 : ,
519 5096 : _libtorch_device(determineLibtorchDeviceType(getParam<MooseEnum>("compute_device")))
520 : #endif
521 : #ifdef MOOSE_MFEM_ENABLED
522 : ,
523 169299 : _mfem_device(isParamValid("_mfem_device")
524 50211 : ? getParam<std::shared_ptr<mfem::Device>>("_mfem_device")
525 : : nullptr),
526 119088 : _mfem_devices(isParamValid("_mfem_devices") ? getParam<std::set<std::string>>("_mfem_devices")
527 602532 : : std::set<std::string>{})
528 : #endif
529 : {
530 66995 : if (¶meters != &_pars)
531 : {
532 4 : const std::string bad_params = "(InputParameters parameters)";
533 2 : const std::string good_params = "(const InputParameters & parameters)";
534 2 : const std::string source_constructor = type() + "::" + type();
535 2 : mooseDoOnce(
536 : mooseDeprecatedNoTrace(type(),
537 : " copy-constructs its input parameters.\n\n",
538 : "This is deprecated and will not be allowed in the future.\n\n",
539 : "In ",
540 : type(),
541 : ".C, change:\n ",
542 : source_constructor,
543 : bad_params,
544 : " -> ",
545 : source_constructor,
546 : good_params,
547 : "\n\n",
548 : "In ",
549 : type(),
550 : ".h, change:\n ",
551 : type(),
552 : bad_params,
553 : "; -> ",
554 : type(),
555 : good_params,
556 : ";"));
557 6 : }
558 :
559 : mooseAssert(_command_line->hasParsed(), "Command line has not parsed");
560 : mooseAssert(_parser->queryRoot(), "Parser has not parsed");
561 :
562 : // Set the TIMPI sync type via --timpi-sync
563 133986 : const auto & timpi_sync = getParam<std::string>("timpi_sync");
564 66993 : const_cast<Parallel::Communicator &>(comm()).sync_type(timpi_sync);
565 :
566 : #ifdef HAVE_GPERFTOOLS
567 : if (isUltimateMaster())
568 : {
569 : bool has_cpu_profiling = false;
570 : bool has_heap_profiling = false;
571 : static std::string cpu_profile_file;
572 : static std::string heap_profile_file;
573 :
574 : // For CPU profiling, users need to have environment 'MOOSE_PROFILE_BASE'
575 : if (std::getenv("MOOSE_PROFILE_BASE"))
576 : {
577 : has_cpu_profiling = true;
578 : cpu_profile_file =
579 : std::getenv("MOOSE_PROFILE_BASE") + std::to_string(_comm->rank()) + ".prof";
580 : // create directory if needed
581 : auto name = MooseUtils::splitFileName(cpu_profile_file);
582 : if (!name.first.empty())
583 : {
584 : if (processor_id() == 0)
585 : MooseUtils::makedirs(name.first.c_str());
586 : _comm->barrier();
587 : }
588 : }
589 :
590 : // For Heap profiling, users need to have 'MOOSE_HEAP_BASE'
591 : if (std::getenv("MOOSE_HEAP_BASE"))
592 : {
593 : has_heap_profiling = true;
594 : heap_profile_file = std::getenv("MOOSE_HEAP_BASE") + std::to_string(_comm->rank());
595 : // create directory if needed
596 : auto name = MooseUtils::splitFileName(heap_profile_file);
597 : if (!name.first.empty())
598 : {
599 : if (processor_id() == 0)
600 : MooseUtils::makedirs(name.first.c_str());
601 : _comm->barrier();
602 : }
603 : }
604 :
605 : // turn on profiling only on selected ranks
606 : if (isParamSetByUser("gperf_profiler_on"))
607 : {
608 : auto rankstr = getParam<std::string>("gperf_profiler_on");
609 : std::vector<processor_id_type> ranks;
610 : bool success = MooseUtils::tokenizeAndConvert(rankstr, ranks, ", ");
611 : if (!success)
612 : mooseError("Invalid argument for --gperf-profiler-on: '", rankstr, "'");
613 : for (auto & rank : ranks)
614 : {
615 : if (rank >= _comm->size())
616 : mooseError("Invalid argument for --gperf-profiler-on: ",
617 : rank,
618 : " is greater than or equal to ",
619 : _comm->size());
620 : if (rank == _comm->rank())
621 : {
622 : _cpu_profiling = has_cpu_profiling;
623 : _heap_profiling = has_heap_profiling;
624 : }
625 : }
626 : }
627 : else
628 : {
629 : _cpu_profiling = has_cpu_profiling;
630 : _heap_profiling = has_heap_profiling;
631 : }
632 :
633 : if (_cpu_profiling)
634 : if (!ProfilerStart(cpu_profile_file.c_str()))
635 : mooseError("CPU profiler is not started properly");
636 :
637 : if (_heap_profiling)
638 : {
639 : HeapProfilerStart(heap_profile_file.c_str());
640 : if (!IsHeapProfilerRunning())
641 : mooseError("Heap profiler is not started properly");
642 : }
643 : }
644 : #else
645 66993 : if (std::getenv("MOOSE_PROFILE_BASE") || std::getenv("MOOSE_HEAP_BASE"))
646 0 : mooseError("gperftool is not available for CPU or heap profiling");
647 : #endif
648 :
649 : // If this will be a language server then turn off output until that starts
650 334965 : if (isParamValid("language_server") && getParam<bool>("language_server"))
651 0 : _output_buffer_cache = Moose::out.rdbuf(nullptr);
652 :
653 66993 : Registry::addKnownLabel(_type);
654 66993 : Moose::registerAll(_factory, _action_factory, _syntax);
655 :
656 66993 : _the_warehouse = std::make_unique<TheWarehouse>();
657 133986 : _the_warehouse->registerAttribute<AttribMatrixTags>("matrix_tags", 0);
658 133986 : _the_warehouse->registerAttribute<AttribVectorTags>("vector_tags", 0);
659 133986 : _the_warehouse->registerAttribute<AttribExecOns>("exec_ons", 0);
660 133986 : _the_warehouse->registerAttribute<AttribSubdomains>("subdomains", 0);
661 133986 : _the_warehouse->registerAttribute<AttribBoundaries>("boundaries", 0);
662 133986 : _the_warehouse->registerAttribute<AttribThread>("thread", 0);
663 133986 : _the_warehouse->registerAttribute<AttribExecutionOrderGroup>("execution_order_group", 0);
664 133986 : _the_warehouse->registerAttribute<AttribPreIC>("pre_ic", 0);
665 133986 : _the_warehouse->registerAttribute<AttribPreAux>("pre_aux");
666 133986 : _the_warehouse->registerAttribute<AttribPostAux>("post_aux");
667 133986 : _the_warehouse->registerAttribute<AttribName>("name", "dummy");
668 133986 : _the_warehouse->registerAttribute<AttribSystem>("system", "dummy");
669 133986 : _the_warehouse->registerAttribute<AttribVar>("variable", -1);
670 133986 : _the_warehouse->registerAttribute<AttribInterfaces>("interfaces", 0);
671 133986 : _the_warehouse->registerAttribute<AttribSysNum>("sys_num", libMesh::invalid_uint);
672 133986 : _the_warehouse->registerAttribute<AttribResidualObject>("residual_object");
673 133986 : _the_warehouse->registerAttribute<AttribSorted>("sorted");
674 133986 : _the_warehouse->registerAttribute<AttribDisplaced>("displaced", -1);
675 :
676 66993 : _perf_graph.enableLivePrint();
677 :
678 67087 : if (_check_input && isParamSetByUser("recover"))
679 0 : mooseError("Cannot run --check-input with --recover. Recover files might not exist");
680 :
681 200979 : if (isParamSetByUser("start_in_debugger") && isUltimateMaster())
682 : {
683 0 : auto command = getParam<std::string>("start_in_debugger");
684 :
685 0 : Moose::out << "Starting in debugger using: " << command << std::endl;
686 :
687 0 : auto hostname = MooseUtils::hostname();
688 :
689 0 : std::stringstream command_stream;
690 :
691 : // This will start XTerm and print out some info first... then run the debugger
692 0 : command_stream << "xterm -e \"echo 'Rank: " << processor_id() << " Hostname: " << hostname
693 0 : << " PID: " << getpid() << "'; echo ''; ";
694 :
695 : // Figure out how to run the debugger
696 0 : if (command.find("lldb") != std::string::npos || command.find("gdb") != std::string::npos)
697 0 : command_stream << command << " -p " << getpid();
698 : else
699 0 : mooseError("Unknown debugger: ",
700 : command,
701 : "\nIf this is truly what you meant then contact moose-users to have a discussion "
702 : "about adding your debugger.");
703 :
704 : // Finish up the command
705 0 : command_stream << "\"" << " & ";
706 0 : std::string command_string = command_stream.str();
707 0 : Moose::out << "Running: " << command_string << std::endl;
708 :
709 0 : int ret = std::system(command_string.c_str());
710 0 : libmesh_ignore(ret);
711 :
712 : // Sleep to allow time for the debugger to attach
713 0 : std::this_thread::sleep_for(std::chrono::seconds(10));
714 0 : }
715 :
716 200979 : if (isParamSetByUser("stop_for_debugger") && isUltimateMaster())
717 : {
718 27 : Moose::out << "\nStopping for " << getParam<unsigned int>("stop_for_debugger")
719 9 : << " seconds to allow attachment from a debugger.\n";
720 :
721 9 : Moose::out << "\nAll of the processes you can connect to:\n";
722 9 : Moose::out << "rank - hostname - pid\n";
723 :
724 9 : auto hostname = MooseUtils::hostname();
725 :
726 : {
727 : // The 'false' turns off the serialization warning
728 9 : SerializerGuard sg(_communicator, false); // Guarantees that the processors print in order
729 9 : Moose::err << processor_id() << " - " << hostname << " - " << getpid() << "\n";
730 9 : }
731 :
732 9 : Moose::out << "\nWaiting...\n" << std::endl;
733 :
734 : // Sleep to allow time for the debugger to attach
735 18 : std::this_thread::sleep_for(std::chrono::seconds(getParam<unsigned int>("stop_for_debugger")));
736 9 : }
737 :
738 66993 : if (_master_mesh && isUltimateMaster())
739 0 : mooseError("Mesh can be passed in only for sub-apps");
740 :
741 66993 : if (_master_displaced_mesh && !_master_mesh)
742 0 : mooseError("_master_mesh should have been set when _master_displaced_mesh is set");
743 :
744 : #ifdef MOOSE_MFEM_ENABLED
745 50209 : if (_mfem_device)
746 : {
747 : mooseAssert(!isUltimateMaster(),
748 : "The MFEM device should only be auto-set for sub-applications");
749 : mooseAssert(!_mfem_devices.empty(),
750 : "If we are a sub-application and we have an MFEM device object, then we must know "
751 : "its configuration string");
752 : }
753 : #endif
754 :
755 : // Data specifically associated with the mesh (meta-data) that will read from the restart
756 : // file early during the simulation setup so that they are available to Actions and other objects
757 : // that need them during the setup process. Most of the restartable data isn't made available
758 : // until all objects have been created and all Actions have been executed (i.e. initialSetup).
759 66993 : registerRestartableDataMapName(MooseApp::MESH_META_DATA, MooseApp::MESH_META_DATA_SUFFIX);
760 :
761 66993 : if (_pars.have_parameter<bool>("use_legacy_dirichlet_bc"))
762 0 : mooseDeprecated("The parameter 'use_legacy_dirichlet_bc' is no longer valid.\n\n",
763 : "All Dirichlet boundary conditions are preset by default.\n\n",
764 : "Remove said parameter in ",
765 0 : name(),
766 : " to remove this deprecation warning.");
767 :
768 66993 : if (_test_restep && _test_checkpoint_half_transient)
769 0 : mooseError("Cannot use --test-restep and --test-checkpoint-half-transient together");
770 :
771 66993 : Moose::out << std::flush;
772 :
773 : #ifdef MOOSE_KOKKOS_ENABLED
774 : #ifdef MOOSE_ENABLE_KOKKOS_GPU
775 2176 : queryKokkosGPUs();
776 : #endif
777 : #endif
778 67077 : }
779 :
780 : std::optional<MooseEnum>
781 1361 : MooseApp::getComputeDevice() const
782 : {
783 4083 : if (isParamSetByUser("compute_device"))
784 2559 : return getParam<MooseEnum>("compute_device");
785 508 : return {};
786 : }
787 :
788 440118 : MooseApp::~MooseApp()
789 : {
790 : #ifdef HAVE_GPERFTOOLS
791 : // CPU profiling stop
792 : if (_cpu_profiling)
793 : ProfilerStop();
794 : // Heap profiling stop
795 : if (_heap_profiling)
796 : HeapProfilerStop();
797 : #endif
798 62874 : _action_warehouse.clear();
799 62874 : _the_warehouse.reset();
800 62874 : _executioner.reset();
801 :
802 : // Don't wait for implicit destruction of input parameter storage
803 62874 : _input_parameter_warehouse.reset();
804 :
805 : // This is dirty, but I don't know what else to do. Obviously, others
806 : // have had similar problems if you look above. In specific, the
807 : // dlclose below on macs is destructing some data that does not
808 : // belong to it in garbage collection. So... don't even give
809 : // dlclose an option
810 62874 : _restartable_data.clear();
811 :
812 : // Remove this app's parameters from the AppFactory. This allows
813 : // for creating an app with this name again in the same execution,
814 : // which needs to be done when resetting applications in MultiApp
815 62874 : AppFactory::instance().clearAppParams(parameters(), {});
816 :
817 : #ifdef LIBMESH_HAVE_DLOPEN
818 : // Close any open dynamic libraries
819 62882 : for (const auto & lib_pair : _lib_handles)
820 8 : dlclose(lib_pair.second.library_handle);
821 : #endif
822 :
823 : #ifdef MOOSE_KOKKOS_ENABLED
824 47451 : deallocateKokkosMemoryPool();
825 : #endif
826 62874 : }
827 :
828 : std::string
829 0 : MooseApp::getFrameworkVersion() const
830 : {
831 0 : return MOOSE_VERSION;
832 : }
833 :
834 : std::string
835 24 : MooseApp::getVersion() const
836 : {
837 48 : return MOOSE_VERSION;
838 : }
839 :
840 : std::string
841 24 : MooseApp::getPrintableVersion() const
842 : {
843 24 : return getPrintableName() + " Version: " + getVersion();
844 : }
845 :
846 : void
847 66495 : MooseApp::setupOptions()
848 : {
849 332475 : TIME_SECTION("setupOptions", 5, "Setting Up Options");
850 :
851 : // Print the header, this is as early as possible
852 66603 : if (header().length() && !getParam<bool>("suppress_header"))
853 27 : _console << header() << std::endl;
854 :
855 199485 : if (getParam<bool>("error_unused"))
856 309 : setCheckUnusedFlag(true);
857 198558 : else if (getParam<bool>("allow_unused"))
858 113 : setCheckUnusedFlag(false);
859 :
860 199485 : if (getParam<bool>("error_override"))
861 64576 : setErrorOverridden();
862 :
863 132990 : _distributed_mesh_on_command_line = getParam<bool>("distributed_mesh");
864 :
865 199485 : if (getParam<bool>("trap_fpe"))
866 : {
867 0 : _trap_fpe = true;
868 0 : _perf_graph.setActive(false);
869 0 : if (getParam<bool>("no_trap_fpe"))
870 0 : mooseError("Cannot use both \"--trap-fpe\" and \"--no-trap-fpe\" flags.");
871 : }
872 199485 : else if (getParam<bool>("no_trap_fpe"))
873 107 : _trap_fpe = false;
874 :
875 : // Turn all warnings in MOOSE to errors (almost see next logic block)
876 132990 : Moose::_warnings_are_errors = getParam<bool>("error");
877 :
878 : // Deprecated messages can be toggled to errors independently from everything else.
879 132990 : Moose::_deprecated_is_error = getParam<bool>("error_deprecated");
880 :
881 66495 : if (isUltimateMaster()) // makes sure coloring isn't reset incorrectly in multi-app settings
882 : {
883 : // Set from command line
884 108808 : auto color = getParam<MooseEnum>("color");
885 163212 : if (!isParamSetByUser("color"))
886 : {
887 : // Set from deprecated --no-color
888 161949 : if (getParam<bool>("no_color"))
889 0 : color = "off";
890 : // Set from environment
891 : else
892 : {
893 53983 : char * c_color = std::getenv("MOOSE_COLOR");
894 53983 : if (c_color)
895 0 : color.assign(std::string(c_color), "While assigning environment variable MOOSE_COLOR");
896 : }
897 : }
898 :
899 54404 : if (color == "auto")
900 0 : Moose::setColorConsole(true);
901 54404 : else if (color == "on")
902 53983 : Moose::setColorConsole(true, true);
903 421 : else if (color == "off")
904 421 : Moose::setColorConsole(false);
905 : else
906 : mooseAssert(false, "Should not hit");
907 :
908 : // After setting color so that non-yellow deprecated is honored
909 163212 : if (getParam<bool>("no_color"))
910 0 : mooseDeprecated("The --no-color flag is deprecated. Use '--color off' instead.");
911 54404 : }
912 :
913 : // If there's no threading model active, but the user asked for
914 : // --n-threads > 1 on the command line, throw a mooseError. This is
915 : // intended to prevent situations where the user has potentially
916 : // built MOOSE incorrectly (neither TBB nor pthreads found) and is
917 : // asking for multiple threads, not knowing that there will never be
918 : // any threads launched.
919 : #if !LIBMESH_USING_THREADS
920 : if (libMesh::command_line_value("--n-threads", 1) > 1)
921 : mooseError("You specified --n-threads > 1, but there is no threading model active!");
922 : #endif
923 :
924 : // Capability checking
925 : {
926 : // Augment capabilities from the TestHarness
927 66495 : std::optional<std::set<std::string>> ignore_capabilities;
928 199485 : if (isParamValid("testharness_capabilities"))
929 : {
930 1179 : if (!isParamValid("required_capabilities"))
931 2 : mooseError(
932 : "--testharness-capabilities: Should not be specified without --required-capabilities");
933 :
934 : const auto file_path = std::filesystem::absolute(
935 782 : std::filesystem::path(getParam<std::string>("testharness_capabilities")));
936 :
937 391 : std::ifstream file(file_path);
938 391 : if (!file)
939 2 : mooseError("--testharness-capabilities: Could not open ", file_path);
940 :
941 389 : nlohmann::json root;
942 : try
943 : {
944 389 : file >> root;
945 387 : if (const auto it = root.find("capabilities"); it != root.end())
946 383 : Moose::internal::Capabilities::getCapabilities({}).augment(*it, {});
947 385 : if (const auto it = root.find("ignore_capabilities"); it != root.end())
948 6 : ignore_capabilities = it->get<std::set<std::string>>();
949 : }
950 4 : catch (const std::exception & e)
951 : {
952 8 : mooseError(
953 4 : "--testharness-capabilities: Failed to load capabilities ", file_path, ":\n", e.what());
954 4 : }
955 401 : }
956 :
957 199461 : if (isParamValid("required_capabilities"))
958 : {
959 : using Moose::internal::CapabilityRegistry;
960 :
961 15158 : const auto & required_capabilities = getParam<std::string>("required_capabilities");
962 :
963 7579 : CapabilityRegistry::CheckOptions options;
964 : // Allowed to be unknown
965 7579 : options.certain = false;
966 : // Add ignored capabilities, if any
967 7579 : if (ignore_capabilities)
968 6 : options.ignore_capabilities = *ignore_capabilities;
969 :
970 7579 : CapabilityRegistry::CheckResult result;
971 : try
972 : {
973 15158 : result = Moose::internal::Capabilities::getCapabilities({}).check(required_capabilities,
974 7566 : options);
975 : }
976 13 : catch (const std::exception & e)
977 : {
978 13 : mooseError("--required-capablities: ", e.what());
979 4 : }
980 :
981 7566 : if (result.state < CapabilityRegistry::CheckState::UNKNOWN)
982 : {
983 55 : mooseInfo("Required capabilities '", required_capabilities, "' not fulfilled.");
984 55 : _ready_to_exit = true;
985 : // we use code 77 as "skip" in the Testharness
986 55 : _exit_code = 77;
987 55 : return;
988 : }
989 7511 : if (result.state == CapabilityRegistry::CheckState::UNKNOWN)
990 3 : mooseError("Required capabilities '",
991 : required_capabilities,
992 : "' are not specific enough. A comparison test is performed on an undefined "
993 : "capability. Disambiguate this requirement by adding an existence/non-existence "
994 : "requirement. Example: 'unknown<1.2.3' should become 'unknown & unknown<1.2.3' "
995 : "or '!unknown | unknown<1.2.3'");
996 7626 : }
997 66483 : }
998 :
999 : // Build a minimal running application, ignoring the input file.
1000 199248 : if (getParam<bool>("minimal"))
1001 8 : createMinimalApp();
1002 :
1003 199224 : else if (getParam<bool>("display_version"))
1004 : {
1005 24 : Moose::out << getPrintableVersion() << std::endl;
1006 24 : _early_exit_param = "--version";
1007 24 : _ready_to_exit = true;
1008 24 : return;
1009 : }
1010 199152 : else if (getParam<bool>("help"))
1011 : {
1012 11 : _command_line->printUsage();
1013 11 : _early_exit_param = "--help";
1014 11 : _ready_to_exit = true;
1015 : }
1016 331859 : else if (getParam<bool>("dump") || isParamSetByUser("dump_search"))
1017 : {
1018 : const std::string search =
1019 36 : isParamSetByUser("dump_search") ? getParam<std::string>("dump_search") : "";
1020 :
1021 9 : JsonSyntaxTree tree(search);
1022 :
1023 : {
1024 45 : TIME_SECTION("dump", 1, "Building Syntax Tree");
1025 9 : _builder.buildJsonSyntaxTree(tree);
1026 9 : }
1027 :
1028 : // Check if second arg is valid or not
1029 9 : if ((tree.getRoot()).is_object())
1030 : {
1031 : // Turn off live printing so that it doesn't mess with the dump
1032 6 : _perf_graph.disableLivePrint();
1033 :
1034 6 : JsonInputFileFormatter formatter;
1035 6 : Moose::out << "\n### START DUMP DATA ###\n"
1036 6 : << formatter.toString(tree.getRoot()) << "\n### END DUMP DATA ###" << std::endl;
1037 6 : _early_exit_param = "--dump";
1038 6 : _ready_to_exit = true;
1039 6 : }
1040 : else
1041 3 : mooseError("Search parameter '", search, "' was not found in the registered syntax.");
1042 6 : }
1043 199092 : else if (getParam<bool>("registry"))
1044 : {
1045 9 : _perf_graph.disableLivePrint();
1046 :
1047 9 : Moose::out << "Label\tType\tName\tClass\tFile\n";
1048 :
1049 9 : auto & objmap = Registry::allObjects();
1050 27 : for (auto & entry : objmap)
1051 14985 : for (auto & obj : entry.second)
1052 29934 : Moose::out << entry.first << "\tobject\t" << obj->name() << "\t" << obj->_classname << "\t"
1053 14967 : << obj->_file << "\n";
1054 :
1055 9 : auto & actmap = Registry::allActions();
1056 27 : for (auto & entry : actmap)
1057 : {
1058 2013 : for (auto & act : entry.second)
1059 1995 : Moose::out << entry.first << "\taction\t" << act->_name << "\t" << act->_classname << "\t"
1060 1995 : << act->_file << "\n";
1061 : }
1062 9 : _early_exit_param = "--registry";
1063 9 : _ready_to_exit = true;
1064 : }
1065 199065 : else if (getParam<bool>("registry_hit"))
1066 : {
1067 9 : _perf_graph.disableLivePrint();
1068 :
1069 9 : Moose::out << "### START REGISTRY DATA ###\n";
1070 :
1071 9 : hit::Section root("");
1072 18 : auto sec = new hit::Section("registry");
1073 9 : root.addChild(sec);
1074 18 : auto objsec = new hit::Section("objects");
1075 9 : sec->addChild(objsec);
1076 :
1077 9 : auto & objmap = Registry::allObjects();
1078 27 : for (auto & entry : objmap)
1079 14985 : for (auto & obj : entry.second)
1080 : {
1081 29934 : auto ent = new hit::Section("entry");
1082 14967 : objsec->addChild(ent);
1083 29934 : ent->addChild(new hit::Field("label", hit::Field::Kind::String, entry.first));
1084 59868 : ent->addChild(new hit::Field("type", hit::Field::Kind::String, "object"));
1085 44901 : ent->addChild(new hit::Field("name", hit::Field::Kind::String, obj->name()));
1086 29934 : ent->addChild(new hit::Field("class", hit::Field::Kind::String, obj->_classname));
1087 44901 : ent->addChild(new hit::Field("file", hit::Field::Kind::String, obj->_file));
1088 : }
1089 :
1090 18 : auto actsec = new hit::Section("actions");
1091 9 : sec->addChild(actsec);
1092 9 : auto & actmap = Registry::allActions();
1093 27 : for (auto & entry : actmap)
1094 2013 : for (auto & act : entry.second)
1095 : {
1096 3990 : auto ent = new hit::Section("entry");
1097 1995 : actsec->addChild(ent);
1098 3990 : ent->addChild(new hit::Field("label", hit::Field::Kind::String, entry.first));
1099 7980 : ent->addChild(new hit::Field("type", hit::Field::Kind::String, "action"));
1100 3990 : ent->addChild(new hit::Field("task", hit::Field::Kind::String, act->_name));
1101 3990 : ent->addChild(new hit::Field("class", hit::Field::Kind::String, act->_classname));
1102 5985 : ent->addChild(new hit::Field("file", hit::Field::Kind::String, act->_file));
1103 : }
1104 :
1105 9 : Moose::out << root.render();
1106 :
1107 9 : Moose::out << "\n### END REGISTRY DATA ###\n";
1108 9 : _early_exit_param = "--registry_hit";
1109 9 : _ready_to_exit = true;
1110 9 : }
1111 331712 : else if (getParam<bool>("yaml") || isParamSetByUser("yaml_search"))
1112 : {
1113 : const std::string search =
1114 72 : isParamSetByUser("yaml_search") ? getParam<std::string>("yaml_search") : "";
1115 18 : _perf_graph.disableLivePrint();
1116 :
1117 18 : _builder.initSyntaxFormatter(Moose::Builder::YAML, true);
1118 18 : _builder.buildFullTree(search);
1119 :
1120 18 : _early_exit_param = "--yaml";
1121 18 : _ready_to_exit = true;
1122 18 : }
1123 331616 : else if (getParam<bool>("json") || isParamSetByUser("json_search"))
1124 : {
1125 : const std::string search =
1126 60 : isParamSetByUser("json_search") ? getParam<std::string>("json_search") : "";
1127 15 : _perf_graph.disableLivePrint();
1128 :
1129 15 : JsonSyntaxTree tree(search);
1130 15 : _builder.buildJsonSyntaxTree(tree);
1131 :
1132 75 : outputMachineReadableData(
1133 30 : "json", "**START JSON DATA**\n", "\n**END JSON DATA**", tree.getRoot().dump(2));
1134 15 : _early_exit_param = "--json";
1135 15 : _ready_to_exit = true;
1136 15 : }
1137 198939 : else if (getParam<bool>("syntax"))
1138 : {
1139 0 : _perf_graph.disableLivePrint();
1140 :
1141 0 : std::multimap<std::string, Syntax::ActionInfo> syntax = _syntax.getAssociatedActions();
1142 0 : std::stringstream ss;
1143 0 : for (const auto & it : syntax)
1144 0 : ss << it.first << "\n";
1145 0 : outputMachineReadableData("syntax", "**START SYNTAX DATA**\n", "**END SYNTAX DATA**", ss.str());
1146 0 : _early_exit_param = "--syntax";
1147 0 : _ready_to_exit = true;
1148 0 : }
1149 198939 : else if (getParam<bool>("show_type"))
1150 : {
1151 9 : _perf_graph.disableLivePrint();
1152 :
1153 9 : Moose::out << "MooseApp Type: " << type() << std::endl;
1154 9 : _early_exit_param = "--show-type";
1155 9 : _ready_to_exit = true;
1156 : }
1157 198912 : else if (getParam<bool>("show_capabilities"))
1158 : {
1159 32 : _perf_graph.disableLivePrint();
1160 160 : outputMachineReadableData("show_capabilities",
1161 : "**START JSON DATA**\n",
1162 : "\n**END JSON DATA**",
1163 64 : Moose::internal::Capabilities::getCapabilities({}).dump());
1164 32 : _ready_to_exit = true;
1165 : }
1166 198816 : else if (isParamValid("check_capabilities"))
1167 : {
1168 : using Moose::internal::CapabilityRegistry;
1169 :
1170 144 : _perf_graph.disableLivePrint();
1171 288 : const auto & capabilities = getParam<std::string>("check_capabilities");
1172 :
1173 144 : CapabilityRegistry::CheckResult result;
1174 : try
1175 : {
1176 148 : result = Moose::internal::Capabilities::getCapabilities({}).check(capabilities);
1177 : }
1178 2 : catch (const std::exception & e)
1179 : {
1180 2 : mooseError("--check-capablities: ", e.what());
1181 2 : }
1182 :
1183 142 : const bool pass = result.state == CapabilityRegistry::CheckState::CERTAIN_PASS;
1184 142 : _console << "Capabilities '" << capabilities << "' are " << (pass ? "" : "not ") << "fulfilled."
1185 142 : << std::endl;
1186 142 : _ready_to_exit = true;
1187 142 : if (!pass)
1188 2 : _exit_code = 77;
1189 142 : return;
1190 144 : }
1191 66128 : else if (!getInputFileNames().empty())
1192 : {
1193 198348 : if (isParamSetByUser("recover"))
1194 : {
1195 : // We need to set the flag manually here since the recover parameter is a string type (takes
1196 : // an optional filename)
1197 4260 : _recover = true;
1198 8520 : const auto & recover = getParam<std::string>("recover");
1199 4260 : if (recover.size())
1200 39 : _restart_recover_base = recover;
1201 : }
1202 :
1203 66116 : _builder.build();
1204 :
1205 : // Lambda to check for mutually exclusive parameters
1206 : auto isExclusiveParamSetByUser =
1207 255066 : [this](const std::vector<std::string> & group, const std::string & param)
1208 : {
1209 255066 : auto is_set = isParamSetByUser(param);
1210 255066 : if (is_set)
1211 21820 : for (const auto & p : group)
1212 17456 : if (p != param && isParamSetByUser(p))
1213 0 : mooseError("Parameters '" + p + "' and '" + param +
1214 : "' are mutually exclusive. Please choose only one of them.");
1215 255066 : return is_set;
1216 65946 : };
1217 :
1218 : // The following parameters set the final task and so are mutually exclusive.
1219 : const std::vector<std::string> final_task_params = {
1220 197838 : "csg_only", "mesh_only", "split_mesh", "parse_neml2_only"};
1221 131892 : if (isExclusiveParamSetByUser(final_task_params, "csg_only"))
1222 : {
1223 : // Error checking on incompatible command line options
1224 79 : if (_distributed_mesh_on_command_line)
1225 3 : mooseError("--csg-only cannot be used in conjunction with --distributed-mesh");
1226 152 : const bool has_mesh_split = isParamSetByUser("split_file") || _use_split;
1227 76 : if (has_mesh_split)
1228 3 : mooseError("--csg-only is not compatible with any mesh splitting options");
1229 219 : if (isParamSetByUser("refinements"))
1230 3 : mooseError("--csg-only cannot be used in conjunction with -r refinements option");
1231 70 : if (!isUltimateMaster())
1232 0 : mooseError("--csg-only option cannot be used as a Subapp");
1233 70 : if (_recover)
1234 3 : mooseError("--csg-only option cannot be used in recovery mode");
1235 :
1236 134 : _syntax.registerTaskName("execute_csg_generators", true);
1237 268 : _syntax.addDependency("execute_csg_generators", "execute_mesh_generators");
1238 268 : _syntax.addDependency("recover_meta_data", "execute_csg_generators");
1239 :
1240 134 : _syntax.registerTaskName("csg_only", true);
1241 268 : _syntax.addDependency("csg_only", "recover_meta_data");
1242 268 : _syntax.addDependency("set_mesh_base", "csg_only");
1243 201 : _action_warehouse.setFinalTask("csg_only");
1244 : }
1245 131734 : else if (isExclusiveParamSetByUser(final_task_params, "mesh_only"))
1246 : {
1247 : // If we are looking to just check the input, there is no need to
1248 : // call MeshOnlyAction and generate a mesh
1249 4196 : if (_check_input)
1250 33 : _action_warehouse.setFinalTask("setup_mesh_complete");
1251 : else
1252 : {
1253 8370 : _syntax.registerTaskName("mesh_only", true);
1254 16740 : _syntax.addDependency("mesh_only", "setup_mesh_complete");
1255 16740 : _syntax.addDependency("determine_system_type", "mesh_only");
1256 12555 : _action_warehouse.setFinalTask("mesh_only");
1257 : }
1258 : }
1259 123342 : else if (isExclusiveParamSetByUser(final_task_params, "split_mesh"))
1260 : {
1261 89 : _split_mesh = true;
1262 178 : _syntax.registerTaskName("split_mesh", true);
1263 356 : _syntax.addDependency("split_mesh", "setup_mesh_complete");
1264 356 : _syntax.addDependency("determine_system_type", "split_mesh");
1265 267 : _action_warehouse.setFinalTask("split_mesh");
1266 : }
1267 123164 : else if (isExclusiveParamSetByUser(final_task_params, "parse_neml2_only"))
1268 : {
1269 0 : _syntax.registerTaskName("parse_neml2");
1270 0 : _syntax.addDependency("determine_system_type", "parse_neml2");
1271 0 : _action_warehouse.setFinalTask("parse_neml2");
1272 : }
1273 65934 : _action_warehouse.build();
1274 :
1275 : // Setup the AppFileBase for use by the Outputs or other systems that need output file info
1276 : {
1277 : // Extract the CommonOutputAction
1278 65934 : const auto common_actions = _action_warehouse.getActions<CommonOutputAction>();
1279 : mooseAssert(common_actions.size() <= 1, "Should not be more than one CommonOutputAction");
1280 65934 : const Action * common = common_actions.empty() ? nullptr : *common_actions.begin();
1281 :
1282 : // If file_base is set in CommonOutputAction through parsing input, obtain the file_base
1283 197802 : if (common && common->isParamValid("file_base"))
1284 : {
1285 29474 : _output_file_base = common->getParam<std::string>("file_base");
1286 14737 : _file_base_set_by_user = true;
1287 : }
1288 51197 : else if (isUltimateMaster())
1289 : {
1290 : // if this app is a master, we use the first input file name as the default file base.
1291 : // use proximate here because the input file is an absolute path
1292 39142 : const auto & base = getLastInputFileName();
1293 39142 : size_t pos = base.find_last_of('.');
1294 39142 : _output_file_base = base.substr(0, pos);
1295 : // Note: we did not append "_out" in the file base here because we do not want to
1296 : // have it in between the input file name and the object name for Output/*
1297 : // syntax.
1298 : }
1299 : // default file base for multiapps is set by MultiApp
1300 65934 : }
1301 65934 : }
1302 : // No input file provided but we have other arguments (so don't just show print usage)
1303 36 : else if (!isParamSetByUser("input_file") && _command_line->getArguments().size() > 2)
1304 : {
1305 : mooseAssert(getInputFileNames().empty(), "Should be empty");
1306 :
1307 6 : if (_check_input)
1308 3 : mooseError("You specified --check-input, but did not provide an input file. Add -i "
1309 : "<inputfile> to your command line.");
1310 :
1311 3 : mooseError("No input files specified. Add -i <inputfile> to your command line.");
1312 : }
1313 30 : else if (isParamValid("language_server") && getParam<bool>("language_server"))
1314 : {
1315 0 : _perf_graph.disableLivePrint();
1316 :
1317 : // Reset output to the buffer what was cached before it was turned it off
1318 0 : if (!Moose::out.rdbuf() && _output_buffer_cache)
1319 0 : Moose::out.rdbuf(_output_buffer_cache);
1320 :
1321 : // Start a language server that communicates using an iostream connection
1322 0 : MooseServer moose_server(*this);
1323 :
1324 0 : moose_server.run();
1325 :
1326 0 : _early_exit_param = "--language-server";
1327 0 : _ready_to_exit = true;
1328 0 : }
1329 :
1330 : else /* The catch-all case for bad options or missing options, etc. */
1331 : {
1332 6 : _command_line->printUsage();
1333 6 : _early_exit_param = "bad or missing";
1334 6 : _ready_to_exit = true;
1335 6 : _exit_code = 1;
1336 : }
1337 :
1338 66057 : Moose::out << std::flush;
1339 66294 : }
1340 :
1341 : const std::vector<std::string> &
1342 154483 : MooseApp::getInputFileNames() const
1343 : {
1344 : mooseAssert(_parser, "Parser is not set");
1345 154483 : return _parser->getInputFileNames();
1346 : }
1347 :
1348 : const std::string &
1349 39142 : MooseApp::getLastInputFileName() const
1350 : {
1351 : mooseAssert(_parser, "Parser is not set");
1352 39142 : return _parser->getLastInputFileName();
1353 : }
1354 :
1355 : std::string
1356 149188 : MooseApp::getOutputFileBase(bool for_non_moose_build_output) const
1357 : {
1358 149188 : if (_file_base_set_by_user || for_non_moose_build_output || _multiapp_level)
1359 48956 : return _output_file_base;
1360 : else
1361 100232 : return _output_file_base + "_out";
1362 : }
1363 :
1364 : void
1365 12055 : MooseApp::setOutputFileBase(const std::string & output_file_base)
1366 : {
1367 12055 : _output_file_base = output_file_base;
1368 :
1369 : // Reset the file base in the outputs
1370 12055 : _output_warehouse.resetFileBase();
1371 :
1372 : // Reset the file base in multiapps (if they have been constructed yet)
1373 12055 : if (getExecutioner())
1374 0 : for (auto & multi_app : feProblem().getMultiAppWarehouse().getObjects())
1375 0 : multi_app->setAppOutputFileBase();
1376 :
1377 12055 : _file_base_set_by_user = true;
1378 12055 : }
1379 :
1380 : void
1381 66272 : MooseApp::runInputFile()
1382 : {
1383 198816 : TIME_SECTION("runInputFile", 3);
1384 :
1385 : // If early exit param has been set, then just return
1386 66272 : if (_ready_to_exit)
1387 336 : return;
1388 :
1389 65936 : _action_warehouse.executeAllActions();
1390 :
1391 190896 : if (isParamSetByUser("csg_only"))
1392 : {
1393 64 : _early_exit_param = "--csg-only";
1394 64 : _ready_to_exit = true;
1395 : }
1396 190704 : else if (isParamSetByUser("mesh_only"))
1397 : {
1398 3746 : _early_exit_param = "--mesh-only";
1399 3746 : _ready_to_exit = true;
1400 : }
1401 179466 : else if (isParamSetByUser("split_mesh"))
1402 : {
1403 89 : _early_exit_param = "--split-mesh";
1404 89 : _ready_to_exit = true;
1405 : }
1406 179199 : else if (isParamSetByUser("parse_neml2_only"))
1407 : {
1408 0 : _early_exit_param = "--parse-neml2-only";
1409 0 : _ready_to_exit = true;
1410 : }
1411 179199 : else if (getParam<bool>("list_constructed_objects"))
1412 : {
1413 : // TODO: ask multiapps for their constructed objects
1414 0 : _early_exit_param = "--list-constructed-objects";
1415 0 : _ready_to_exit = true;
1416 0 : std::stringstream ss;
1417 0 : for (const auto & obj : _factory.getConstructedObjects())
1418 0 : ss << obj << '\n';
1419 0 : outputMachineReadableData(
1420 0 : "list_constructed_objects", "**START OBJECT DATA**\n", "\n**END OBJECT DATA**", ss.str());
1421 0 : }
1422 63990 : }
1423 :
1424 : void
1425 58595 : MooseApp::errorCheck()
1426 : {
1427 58595 : bool warn = _enable_unused_check == WARN_UNUSED;
1428 58595 : bool err = _enable_unused_check == ERROR_UNUSED;
1429 :
1430 58595 : _builder.errorCheck(*_comm, warn, err);
1431 :
1432 : // Return early for mesh only mode, since we want error checking to run even though
1433 : // an executor is not created for this case
1434 175704 : if (isParamSetByUser("mesh_only"))
1435 8 : return;
1436 :
1437 58560 : if (!_executor.get() && !_executioner.get())
1438 : {
1439 0 : if (!_early_exit_param.empty())
1440 : {
1441 : mooseAssert(_check_input,
1442 : "Something went wrong, we should only get here if _check_input is true.");
1443 0 : mooseError(
1444 : "Incompatible command line arguments provided. --check-input cannot be called with ",
1445 0 : _early_exit_param,
1446 : ".");
1447 : }
1448 : // We should never get here
1449 0 : mooseError("The Executor is being called without being initialized. This is likely "
1450 : "caused by "
1451 : "incompatible command line arguments");
1452 : }
1453 :
1454 58560 : auto apps = feProblem().getMultiAppWarehouse().getObjects();
1455 66278 : for (auto app : apps)
1456 19133 : for (unsigned int i = 0; i < app->numLocalApps(); i++)
1457 19133 : app->localApp(i)->errorCheck();
1458 58557 : }
1459 :
1460 : void
1461 51887 : MooseApp::executeExecutioner()
1462 : {
1463 155661 : TIME_SECTION("executeExecutioner", 3);
1464 :
1465 : // If ready to exit has been set, then just return
1466 51887 : if (_ready_to_exit)
1467 4224 : return;
1468 :
1469 : // run the simulation
1470 47663 : if (_use_executor && _executor)
1471 : {
1472 18 : LibmeshPetscCall(Moose::PetscSupport::petscSetupOutput(_command_line.get()));
1473 18 : _executor->init();
1474 18 : errorCheck();
1475 18 : auto result = _executor->exec();
1476 18 : if (!result.convergedAll())
1477 0 : mooseError(result.str());
1478 18 : }
1479 47645 : else if (_executioner)
1480 : {
1481 47645 : LibmeshPetscCall(Moose::PetscSupport::petscSetupOutput(_command_line.get()));
1482 47645 : _executioner->init();
1483 47133 : errorCheck();
1484 47111 : _executioner->execute();
1485 46767 : if (!_executioner->lastSolveConverged())
1486 200 : setExitCode(1);
1487 : }
1488 : else
1489 0 : mooseError("No executioner was specified (go fix your input file)");
1490 51009 : }
1491 :
1492 : bool
1493 1501065 : MooseApp::isRecovering() const
1494 : {
1495 1501065 : return _recover;
1496 : }
1497 :
1498 : bool
1499 566184 : MooseApp::isRestarting() const
1500 : {
1501 566184 : return _restart;
1502 : }
1503 :
1504 : bool
1505 125326 : MooseApp::isSplitMesh() const
1506 : {
1507 125326 : return _split_mesh;
1508 : }
1509 :
1510 : bool
1511 0 : MooseApp::hasRestartRecoverFileBase() const
1512 : {
1513 0 : return !_restart_recover_base.empty();
1514 : }
1515 :
1516 : bool
1517 0 : MooseApp::hasRecoverFileBase() const
1518 : {
1519 0 : mooseDeprecated("MooseApp::hasRecoverFileBase is deprecated, use "
1520 : "MooseApp::hasRestartRecoverFileBase() instead.");
1521 0 : return !_restart_recover_base.empty();
1522 : }
1523 :
1524 : void
1525 1008249 : MooseApp::registerRestartableNameWithFilter(const std::string & name,
1526 : Moose::RESTARTABLE_FILTER filter)
1527 : {
1528 : using Moose::RESTARTABLE_FILTER;
1529 1008249 : switch (filter)
1530 : {
1531 1008249 : case RESTARTABLE_FILTER::RECOVERABLE:
1532 1008249 : _recoverable_data_names.insert(name);
1533 1008249 : break;
1534 0 : default:
1535 0 : mooseError("Unknown filter");
1536 : }
1537 1008249 : }
1538 :
1539 : std::vector<std::filesystem::path>
1540 12858 : MooseApp::backup(const std::filesystem::path & folder_base)
1541 : {
1542 64290 : TIME_SECTION("backup", 2, "Backing Up Application to File");
1543 :
1544 12858 : preBackup();
1545 :
1546 12858 : RestartableDataWriter writer(*this, _restartable_data);
1547 25713 : return writer.write(folder_base);
1548 12855 : }
1549 :
1550 : std::unique_ptr<Backup>
1551 32779 : MooseApp::backup()
1552 : {
1553 163895 : TIME_SECTION("backup", 2, "Backing Up Application");
1554 :
1555 32779 : RestartableDataWriter writer(*this, _restartable_data);
1556 :
1557 32779 : preBackup();
1558 :
1559 32779 : auto backup = std::make_unique<Backup>();
1560 32779 : writer.write(*backup->header, *backup->data);
1561 :
1562 65558 : return backup;
1563 32779 : }
1564 :
1565 : void
1566 3735 : MooseApp::restore(const std::filesystem::path & folder_base, const bool for_restart)
1567 : {
1568 18675 : TIME_SECTION("restore", 2, "Restoring Application from File");
1569 :
1570 3735 : const DataNames filter_names = for_restart ? getRecoverableData() : DataNames{};
1571 :
1572 3735 : _rd_reader.setInput(folder_base);
1573 3735 : _rd_reader.restore(filter_names);
1574 :
1575 3702 : postRestore(for_restart);
1576 3702 : }
1577 :
1578 : void
1579 9985 : MooseApp::restore(std::unique_ptr<Backup> backup, const bool for_restart)
1580 : {
1581 49925 : TIME_SECTION("restore", 2, "Restoring Application");
1582 :
1583 9985 : const DataNames filter_names = for_restart ? getRecoverableData() : DataNames{};
1584 :
1585 9985 : if (!backup)
1586 0 : mooseError("MooseApp::restore(): Provided backup is not initialized");
1587 :
1588 9985 : auto header = std::move(backup->header);
1589 : mooseAssert(header, "Header not available");
1590 :
1591 9985 : auto data = std::move(backup->data);
1592 : mooseAssert(data, "Data not available");
1593 :
1594 9985 : _rd_reader.setInput(std::move(header), std::move(data));
1595 9985 : _rd_reader.restore(filter_names);
1596 :
1597 9985 : postRestore(for_restart);
1598 9985 : }
1599 :
1600 : void
1601 1046 : MooseApp::restoreFromInitialBackup(const bool for_restart)
1602 : {
1603 : mooseAssert(hasInitialBackup(), "Missing initial backup");
1604 1046 : restore(std::move(*_initial_backup), for_restart);
1605 1046 : }
1606 :
1607 : std::unique_ptr<Backup>
1608 13687 : MooseApp::finalizeRestore()
1609 : {
1610 13687 : if (!_rd_reader.isRestoring())
1611 0 : mooseError("MooseApp::finalizeRestore(): Not currently restoring");
1612 :
1613 : // This gives us access to the underlying streams so that we can return it if needed
1614 13687 : auto input_streams = _rd_reader.clear();
1615 :
1616 13687 : std::unique_ptr<Backup> backup;
1617 :
1618 : // Give them back a backup if this restore started from a Backup, in which case
1619 : // the two streams in the Backup are formed into StringInputStreams
1620 13687 : if (auto header_string_input = dynamic_cast<StringInputStream *>(input_streams.header.get()))
1621 : {
1622 9985 : auto data_string_input = dynamic_cast<StringInputStream *>(input_streams.data.get());
1623 : mooseAssert(data_string_input, "Should also be a string input");
1624 :
1625 9985 : auto header_sstream = header_string_input->release();
1626 : mooseAssert(header_sstream, "Header not available");
1627 :
1628 9985 : auto data_sstream = data_string_input->release();
1629 : mooseAssert(data_sstream, "Data not available");
1630 :
1631 9985 : backup = std::make_unique<Backup>();
1632 9985 : backup->header = std::move(header_sstream);
1633 9985 : backup->data = std::move(data_sstream);
1634 9985 : }
1635 :
1636 27374 : return backup;
1637 13687 : }
1638 :
1639 : void
1640 422 : MooseApp::setCheckUnusedFlag(bool warn_is_error)
1641 : {
1642 422 : _enable_unused_check = warn_is_error ? ERROR_UNUSED : WARN_UNUSED;
1643 422 : }
1644 :
1645 : void
1646 0 : MooseApp::disableCheckUnusedFlag()
1647 : {
1648 0 : _enable_unused_check = OFF;
1649 0 : }
1650 :
1651 : FEProblemBase &
1652 9903353 : MooseApp::feProblem() const
1653 : {
1654 : mooseAssert(_executor.get() || _executioner.get(), "No executioner yet, calling too early!");
1655 9903353 : return _executor.get() ? _executor->feProblem() : _executioner->feProblem();
1656 : }
1657 :
1658 : void
1659 51 : MooseApp::addExecutor(const std::string & type,
1660 : const std::string & name,
1661 : const InputParameters & params)
1662 : {
1663 51 : std::shared_ptr<Executor> executor = _factory.create<Executor>(type, name, params);
1664 :
1665 51 : if (_executors.count(executor->name()) > 0)
1666 0 : mooseError("an executor with name '", executor->name(), "' already exists");
1667 51 : _executors[executor->name()] = executor;
1668 51 : }
1669 :
1670 : void
1671 57 : MooseApp::addExecutorParams(const std::string & type,
1672 : const std::string & name,
1673 : const InputParameters & params)
1674 : {
1675 57 : _executor_params[name] = std::make_pair(type, std::make_unique<InputParameters>(params));
1676 57 : }
1677 :
1678 : const Parser &
1679 1557014 : MooseApp::parser() const
1680 : {
1681 : mooseAssert(_parser, "Not set");
1682 1557014 : return *_parser;
1683 : }
1684 :
1685 : Parser &
1686 1557014 : MooseApp::parser()
1687 : {
1688 1557014 : return const_cast<Parser &>(std::as_const(*this).parser());
1689 : }
1690 :
1691 : void
1692 60 : MooseApp::recursivelyCreateExecutors(const std::string & current_executor_name,
1693 : std::list<std::string> & possible_roots,
1694 : std::list<std::string> & current_branch)
1695 : {
1696 : // Did we already make this one?
1697 60 : if (_executors.find(current_executor_name) != _executors.end())
1698 0 : return;
1699 :
1700 : // Is this one already on the current branch (i.e. there is a cycle)
1701 60 : if (std::find(current_branch.begin(), current_branch.end(), current_executor_name) !=
1702 120 : current_branch.end())
1703 : {
1704 3 : std::stringstream exec_names_string;
1705 :
1706 3 : auto branch_it = current_branch.begin();
1707 :
1708 3 : exec_names_string << *branch_it++;
1709 :
1710 6 : for (; branch_it != current_branch.end(); ++branch_it)
1711 3 : exec_names_string << ", " << *branch_it;
1712 :
1713 3 : exec_names_string << ", " << current_executor_name;
1714 :
1715 3 : mooseError("Executor cycle detected: ", exec_names_string.str());
1716 0 : }
1717 :
1718 57 : current_branch.push_back(current_executor_name);
1719 :
1720 : // Build the dependencies first
1721 57 : const auto & params = *_executor_params[current_executor_name].second;
1722 :
1723 2661 : for (const auto & param : params)
1724 : {
1725 2610 : if (params.have_parameter<ExecutorName>(param.first))
1726 : {
1727 111 : const auto & dependency_name = params.get<ExecutorName>(param.first);
1728 :
1729 111 : possible_roots.remove(dependency_name);
1730 :
1731 111 : if (!dependency_name.empty())
1732 33 : recursivelyCreateExecutors(dependency_name, possible_roots, current_branch);
1733 : }
1734 : }
1735 :
1736 : // Add this Executor
1737 51 : const auto & type = _executor_params[current_executor_name].first;
1738 51 : addExecutor(type, current_executor_name, params);
1739 :
1740 51 : current_branch.pop_back();
1741 : }
1742 :
1743 : void
1744 61082 : MooseApp::createExecutors()
1745 : {
1746 : // Do we have any?
1747 61082 : if (_executor_params.empty())
1748 61058 : return;
1749 :
1750 : // Holds the names of Executors that may be the root executor
1751 24 : std::list<std::string> possibly_root;
1752 :
1753 : // What is already built
1754 24 : std::map<std::string, bool> already_built;
1755 :
1756 : // The Executors that are currently candidates for being roots
1757 24 : std::list<std::string> possible_roots;
1758 :
1759 : // The current line of dependencies - used for finding cycles
1760 24 : std::list<std::string> current_branch;
1761 :
1762 : // Build the NullExecutor
1763 : {
1764 48 : auto params = _factory.getValidParams("NullExecutor");
1765 96 : _null_executor = _factory.create<NullExecutor>("NullExecutor", "_null_executor", params);
1766 24 : }
1767 :
1768 72 : for (const auto & params_entry : _executor_params)
1769 : {
1770 51 : const auto & name = params_entry.first;
1771 :
1772 : // Did we already make this one?
1773 51 : if (_executors.find(name) != _executors.end())
1774 24 : continue;
1775 :
1776 27 : possible_roots.emplace_back(name);
1777 :
1778 27 : recursivelyCreateExecutors(name, possible_roots, current_branch);
1779 : }
1780 :
1781 : // If there is more than one possible root - error
1782 21 : if (possible_roots.size() > 1)
1783 : {
1784 3 : auto root_string_it = possible_roots.begin();
1785 :
1786 3 : std::stringstream roots_string;
1787 :
1788 3 : roots_string << *root_string_it++;
1789 :
1790 6 : for (; root_string_it != possible_roots.end(); ++root_string_it)
1791 3 : roots_string << ", " << *root_string_it;
1792 :
1793 3 : mooseError("Multiple Executor roots found: ", roots_string.str());
1794 0 : }
1795 :
1796 : // Set the root executor
1797 18 : _executor = _executors[possible_roots.front()];
1798 18 : }
1799 :
1800 : Executor &
1801 24 : MooseApp::getExecutor(const std::string & name, bool fail_if_not_found)
1802 : {
1803 24 : auto it = _executors.find(name);
1804 :
1805 24 : if (it != _executors.end())
1806 24 : return *it->second;
1807 :
1808 0 : if (fail_if_not_found)
1809 0 : mooseError("Executor not found: ", name);
1810 :
1811 0 : return *_null_executor;
1812 : }
1813 :
1814 : Executioner *
1815 3900310 : MooseApp::getExecutioner() const
1816 : {
1817 3900310 : return _executioner.get() ? _executioner.get() : _executor.get();
1818 : }
1819 :
1820 : void
1821 64576 : MooseApp::setErrorOverridden()
1822 : {
1823 64576 : _error_overridden = true;
1824 64576 : }
1825 :
1826 : void
1827 54416 : MooseApp::run()
1828 : {
1829 163248 : TIME_SECTION("run", 3);
1830 163248 : if (getParam<bool>("show_docs"))
1831 : {
1832 0 : auto binname = appBinaryName();
1833 0 : if (binname == "")
1834 0 : mooseError("could not locate installed tests to run (unresolved binary/app name)");
1835 0 : auto docspath = MooseUtils::docsDir(binname);
1836 0 : if (docspath == "")
1837 0 : mooseError("no installed documentation found");
1838 :
1839 0 : auto docmsgfile = MooseUtils::pathjoin(docspath, "docmsg.txt");
1840 0 : std::string docmsg = "file://" + MooseUtils::realpath(docspath) + "/index.html";
1841 0 : if (MooseUtils::pathExists(docmsgfile) && MooseUtils::checkFileReadable(docmsgfile))
1842 : {
1843 0 : std::ifstream ifs(docmsgfile);
1844 : std::string content((std::istreambuf_iterator<char>(ifs)),
1845 0 : (std::istreambuf_iterator<char>()));
1846 0 : content.replace(content.find("$LOCAL_SITE_HOME"), content.length(), docmsg);
1847 0 : docmsg = content;
1848 0 : }
1849 :
1850 0 : Moose::out << docmsg << "\n";
1851 0 : _early_exit_param = "--docs";
1852 0 : _ready_to_exit = true;
1853 0 : return;
1854 0 : }
1855 :
1856 54416 : if (showInputs() || copyInputs() || runInputs())
1857 : {
1858 9 : _early_exit_param = "--show-input, --copy-inputs, or --run";
1859 9 : _ready_to_exit = true;
1860 9 : return;
1861 : }
1862 :
1863 : try
1864 : {
1865 272020 : TIME_SECTION("setup", 2, "Setting Up");
1866 54404 : setupOptions();
1867 54184 : runInputFile();
1868 51954 : }
1869 38 : catch (Parser::Error & err)
1870 : {
1871 : mooseAssert(_parser->getThrowOnError(), "Should be true");
1872 2 : throw;
1873 2 : }
1874 24 : catch (MooseRuntimeError & err)
1875 : {
1876 : mooseAssert(Moose::_throw_on_error, "Should be true");
1877 24 : throw;
1878 24 : }
1879 12 : catch (std::exception & err)
1880 : {
1881 12 : mooseError(err.what());
1882 0 : }
1883 :
1884 51916 : if (!_check_input)
1885 : {
1886 259435 : TIME_SECTION("execute", 2, "Executing");
1887 51887 : executeExecutioner();
1888 51009 : }
1889 : else
1890 : {
1891 29 : errorCheck();
1892 : // Output to stderr, so it is easier for peacock to get the result
1893 24 : Moose::err << "Syntax OK" << std::endl;
1894 : }
1895 51070 : }
1896 :
1897 : bool
1898 54416 : MooseApp::showInputs() const
1899 : {
1900 163248 : if (getParam<bool>("show_inputs"))
1901 : {
1902 6 : const auto show_inputs_syntax = _pars.getCommandLineMetadata("show_inputs").switches;
1903 3 : std::vector<std::string> dirs;
1904 3 : const auto installable_inputs = getInstallableInputs();
1905 :
1906 3 : if (installable_inputs == "")
1907 : {
1908 : Moose::out
1909 : << "Show inputs has not been overriden in this application.\nContact the developers of "
1910 0 : "this appication and request that they override \"MooseApp::getInstallableInputs\".\n";
1911 : }
1912 : else
1913 : {
1914 : mooseAssert(!show_inputs_syntax.empty(), "show_inputs sytnax should not be empty");
1915 :
1916 3 : MooseUtils::tokenize(installable_inputs, dirs, 1, " ");
1917 3 : Moose::out << "The following directories are installable into a user-writeable directory:\n\n"
1918 3 : << installable_inputs << '\n'
1919 3 : << "\nTo install one or more directories of inputs, execute the binary with the \""
1920 3 : << show_inputs_syntax[0] << "\" flag. e.g.:\n$ "
1921 6 : << _command_line->getExecutableName() << ' ' << show_inputs_syntax[0] << ' '
1922 3 : << dirs[0] << '\n';
1923 : }
1924 3 : return true;
1925 3 : }
1926 54413 : return false;
1927 : }
1928 :
1929 : std::string
1930 0 : MooseApp::getInstallableInputs() const
1931 : {
1932 0 : return "tests";
1933 : }
1934 :
1935 : bool
1936 54413 : MooseApp::copyInputs()
1937 : {
1938 163239 : if (isParamSetByUser("copy_inputs"))
1939 : {
1940 6 : if (comm().size() > 1)
1941 0 : mooseError("The --copy-inputs option should not be ran in parallel");
1942 :
1943 : // Get command line argument following --copy-inputs on command line
1944 12 : auto dir_to_copy = getParam<std::string>("copy_inputs");
1945 :
1946 6 : if (dir_to_copy.empty())
1947 0 : mooseError("Error retrieving directory to copy");
1948 6 : if (dir_to_copy.back() != '/')
1949 6 : dir_to_copy += '/';
1950 :
1951 : // This binary name is the actual binary. That is, if we called a symlink it'll
1952 : // be the name of what the symlink points to
1953 6 : auto binname = appBinaryName();
1954 6 : if (binname == "")
1955 0 : mooseError("could not locate installed tests to run (unresolved binary/app name)");
1956 :
1957 : auto src_dir = MooseUtils::installedInputsDir(
1958 : binname,
1959 : dir_to_copy,
1960 18 : "Rerun binary with " + _pars.getCommandLineMetadata("show_inputs").switches[0] +
1961 6 : " to get a list of installable directories.");
1962 :
1963 : // Use the command line here because if we have a symlink to another binary,
1964 : // we want to dump into a directory that is named after the symlink not the true binary
1965 6 : auto dst_dir = _command_line->getExecutableNameBase() + "/" + dir_to_copy;
1966 6 : auto cmdname = _command_line->getExecutableName();
1967 6 : if (cmdname.find_first_of("/") != std::string::npos)
1968 0 : cmdname = cmdname.substr(cmdname.find_first_of("/") + 1, std::string::npos);
1969 :
1970 6 : if (MooseUtils::pathExists(dst_dir))
1971 3 : mooseError(
1972 : "The directory \"./",
1973 : dst_dir,
1974 : "\" already exists.\nTo update/recopy the contents of this directory, rename (\"mv ",
1975 : dst_dir,
1976 : " new_dir_name\") or remove (\"rm -r ",
1977 : dst_dir,
1978 : "\") the existing directory.\nThen re-run \"",
1979 : cmdname,
1980 : " --copy-inputs ",
1981 : dir_to_copy,
1982 : "\".");
1983 :
1984 3 : std::string cmd = "mkdir -p " + dst_dir + "; rsync -av " + src_dir + " " + dst_dir;
1985 :
1986 15 : TIME_SECTION("copy_inputs", 2, "Copying Inputs");
1987 :
1988 : mooseAssert(comm().size() == 1, "Should be run in serial");
1989 3 : const auto return_value = system(cmd.c_str());
1990 3 : if (!WIFEXITED(return_value))
1991 0 : mooseError("Process exited unexpectedly");
1992 3 : setExitCode(WEXITSTATUS(return_value));
1993 3 : if (exitCode() == 0)
1994 3 : Moose::out << "Directory successfully copied into ./" << dst_dir << '\n';
1995 3 : return true;
1996 3 : }
1997 54407 : return false;
1998 : }
1999 :
2000 : bool
2001 54407 : MooseApp::runInputs()
2002 : {
2003 163221 : if (isParamSetByUser("run"))
2004 : {
2005 3 : if (comm().size() > 1)
2006 0 : mooseError("The --run option should not be ran in parallel");
2007 :
2008 : // Pass everything after --run on the cli to the TestHarness
2009 6 : const auto find_run_it = std::as_const(*_command_line).findCommandLineParam("run");
2010 3 : const auto & cl_entries = std::as_const(*_command_line).getEntries();
2011 : mooseAssert(find_run_it != cl_entries.end(), "Didn't find the option");
2012 3 : std::string test_args;
2013 6 : for (auto it = std::next(find_run_it); it != cl_entries.end(); ++it)
2014 6 : for (const auto & arg : it->raw_args)
2015 : {
2016 3 : test_args += " " + arg;
2017 3 : libMesh::add_command_line_name(arg);
2018 : }
2019 :
2020 3 : auto working_dir = MooseUtils::getCurrentWorkingDir();
2021 3 : if (MooseUtils::findTestRoot() == "")
2022 : {
2023 0 : auto bin_name = appBinaryName();
2024 0 : if (bin_name == "")
2025 0 : mooseError("Could not locate binary name relative to installed location");
2026 :
2027 0 : auto cmd_name = Moose::getExecutableName();
2028 0 : mooseError(
2029 : "Could not locate installed tests from the current working directory:",
2030 : working_dir,
2031 : ".\nMake sure you are executing this command from within a writable installed inputs ",
2032 : "directory.\nRun \"",
2033 : cmd_name,
2034 : " --copy-inputs <dir>\" to copy the contents of <dir> to a \"./",
2035 : bin_name,
2036 : "_<dir>\" directory.\nChange into that directory and try \"",
2037 : cmd_name,
2038 : " --run <dir>\" again.");
2039 0 : }
2040 :
2041 : // Set this application as the app name for the moose_test_runner script that we're running
2042 3 : setenv("MOOSE_TEST_RUNNER_APP_NAME", appBinaryName().c_str(), true);
2043 :
2044 3 : const std::string cmd = MooseUtils::runTestsExecutable() + test_args;
2045 3 : Moose::out << "Working Directory: " << working_dir << "\nRunning Command: " << cmd << std::endl;
2046 : mooseAssert(comm().size() == 1, "Should be run in serial");
2047 3 : const auto return_value = system(cmd.c_str());
2048 3 : if (!WIFEXITED(return_value))
2049 0 : mooseError("Process exited unexpectedly");
2050 3 : setExitCode(WEXITSTATUS(return_value));
2051 3 : return true;
2052 3 : }
2053 :
2054 54404 : return false;
2055 : }
2056 :
2057 : Moose::Capability &
2058 464928 : MooseApp::addCapabilityInternal(const std::string_view capability,
2059 : const Moose::Capability::Value & value,
2060 : const std::string_view doc)
2061 : {
2062 : try
2063 : {
2064 464928 : return Moose::internal::Capabilities::getCapabilities({}).add(capability, value, doc);
2065 : }
2066 6 : catch (const std::exception & e)
2067 : {
2068 6 : ::mooseError(e.what());
2069 6 : }
2070 : }
2071 :
2072 : void
2073 3429 : MooseApp::setOutputPosition(const Point & p)
2074 : {
2075 3429 : _output_position_set = true;
2076 3429 : _output_position = p;
2077 3429 : _output_warehouse.meshChanged();
2078 :
2079 3429 : if (_executioner.get())
2080 70 : _executioner->parentOutputPositionChanged();
2081 3429 : }
2082 :
2083 : std::list<std::string>
2084 3256 : MooseApp::getCheckpointDirectories() const
2085 : {
2086 : // Storage for the directory names
2087 3256 : std::list<std::string> checkpoint_dirs;
2088 :
2089 : // Add the directories added with Outputs/checkpoint=true input syntax
2090 3256 : checkpoint_dirs.push_back(getOutputFileBase() + "_cp");
2091 :
2092 : // Add the directories from any existing checkpoint output objects
2093 6512 : const auto & actions = _action_warehouse.getActionListByName("add_output");
2094 19721 : for (const auto & action : actions)
2095 : {
2096 : // Get the parameters from the MooseObjectAction
2097 16465 : MooseObjectAction * moose_object_action = dynamic_cast<MooseObjectAction *>(action);
2098 16465 : if (!moose_object_action)
2099 3350 : continue;
2100 :
2101 13115 : const InputParameters & params = moose_object_action->getObjectParams();
2102 39345 : if (moose_object_action->getParam<std::string>("type") == "Checkpoint")
2103 : {
2104 : // Unless file_base was explicitly set by user, we cannot rely on it, as it will be changed
2105 : // later
2106 : const std::string cp_dir =
2107 14 : _file_base_set_by_user ? params.get<std::string>("file_base")
2108 32 : : (getOutputFileBase(true) + "_" + moose_object_action->name());
2109 23 : checkpoint_dirs.push_back(cp_dir + "_cp");
2110 23 : }
2111 : }
2112 3256 : return checkpoint_dirs;
2113 0 : }
2114 :
2115 : std::list<std::string>
2116 3256 : MooseApp::getCheckpointFiles() const
2117 : {
2118 3256 : auto checkpoint_dirs = getCheckpointDirectories();
2119 6512 : return MooseUtils::getFilesInDirs(checkpoint_dirs, false);
2120 3256 : }
2121 :
2122 : void
2123 4000 : MooseApp::setStartTime(Real time)
2124 : {
2125 4000 : _start_time_set = true;
2126 4000 : _start_time = time;
2127 4000 : }
2128 :
2129 : std::string
2130 36 : MooseApp::getFileName(bool stripLeadingPath) const
2131 : {
2132 36 : return _builder.getPrimaryFileName(stripLeadingPath);
2133 : }
2134 :
2135 : OutputWarehouse &
2136 31266286 : MooseApp::getOutputWarehouse()
2137 : {
2138 31266286 : return _output_warehouse;
2139 : }
2140 :
2141 : const OutputWarehouse &
2142 88328 : MooseApp::getOutputWarehouse() const
2143 : {
2144 88328 : return _output_warehouse;
2145 : }
2146 :
2147 : std::string
2148 10 : MooseApp::appNameToLibName(const std::string & app_name) const
2149 : {
2150 10 : std::string library_name(app_name);
2151 :
2152 : // Strip off the App part (should always be the last 3 letters of the name)
2153 10 : size_t pos = library_name.find("App");
2154 10 : if (pos != library_name.length() - 3)
2155 0 : mooseError("Invalid application name: ", library_name);
2156 10 : library_name.erase(pos);
2157 :
2158 : // Now get rid of the camel case, prepend lib, and append the method and suffix
2159 40 : return std::string("lib") + MooseUtils::camelCaseToUnderscore(library_name) + '-' +
2160 20 : QUOTE(METHOD) + ".la";
2161 10 : }
2162 :
2163 : std::string
2164 0 : MooseApp::libNameToAppName(const std::string & library_name) const
2165 : {
2166 0 : std::string app_name(library_name);
2167 :
2168 : // Strip off the leading "lib" and trailing ".la"
2169 0 : if (pcrecpp::RE("lib(.+?)(?:-\\w+)?\\.la").Replace("\\1", &app_name) == 0)
2170 0 : mooseError("Invalid library name: ", app_name);
2171 :
2172 0 : return MooseUtils::underscoreToCamelCase(app_name, true);
2173 0 : }
2174 :
2175 : RestartableDataValue &
2176 4284623 : MooseApp::registerRestartableData(std::unique_ptr<RestartableDataValue> data,
2177 : THREAD_ID tid,
2178 : bool read_only,
2179 : const RestartableDataMapName & metaname)
2180 : {
2181 4284623 : if (!metaname.empty() && tid != 0)
2182 0 : mooseError(
2183 : "The meta data storage for '", metaname, "' is not threaded, so the tid must be zero.");
2184 :
2185 : mooseAssert(metaname.empty() ||
2186 : _restartable_meta_data.find(metaname) != _restartable_meta_data.end(),
2187 : "The desired meta data name does not exist: " + metaname);
2188 :
2189 : // Select the data store for saving this piece of restartable data (mesh or everything else)
2190 : auto & data_map =
2191 4284623 : metaname.empty() ? _restartable_data[tid] : _restartable_meta_data[metaname].first;
2192 :
2193 4284623 : RestartableDataValue * stored_data = data_map.findData(data->name());
2194 4284623 : if (stored_data)
2195 : {
2196 1573345 : if (data->typeId() != stored_data->typeId())
2197 0 : mooseError("Type mismatch found in RestartableData registration of '",
2198 0 : data->name(),
2199 : "'\n\n Stored type: ",
2200 0 : stored_data->type(),
2201 : "\n New type: ",
2202 0 : data->type());
2203 : }
2204 : else
2205 2711278 : stored_data = &data_map.addData(std::move(data));
2206 :
2207 4284623 : if (!read_only)
2208 2711272 : stored_data->setDeclared({});
2209 :
2210 4284623 : return *stored_data;
2211 : }
2212 :
2213 : RestartableDataValue &
2214 0 : MooseApp::registerRestartableData(const std::string & libmesh_dbg_var(name),
2215 : std::unique_ptr<RestartableDataValue> data,
2216 : THREAD_ID tid,
2217 : bool read_only,
2218 : const RestartableDataMapName & metaname)
2219 : {
2220 0 : mooseDeprecated("The use of MooseApp::registerRestartableData with a data name is "
2221 : "deprecated.\n\nUse the call without a name instead.");
2222 :
2223 : mooseAssert(name == data->name(), "Inconsistent name");
2224 0 : return registerRestartableData(std::move(data), tid, read_only, metaname);
2225 : }
2226 :
2227 : bool
2228 194296 : MooseApp::hasRestartableMetaData(const std::string & name,
2229 : const RestartableDataMapName & metaname) const
2230 : {
2231 194296 : auto it = _restartable_meta_data.find(metaname);
2232 194296 : if (it == _restartable_meta_data.end())
2233 0 : return false;
2234 194296 : return it->second.first.hasData(name);
2235 : }
2236 :
2237 : RestartableDataValue &
2238 1090 : MooseApp::getRestartableMetaData(const std::string & name,
2239 : const RestartableDataMapName & metaname,
2240 : THREAD_ID tid)
2241 : {
2242 1090 : if (tid != 0)
2243 0 : mooseError(
2244 : "The meta data storage for '", metaname, "' is not threaded, so the tid must be zero.");
2245 :
2246 : // Get metadata reference from RestartableDataMap and return a (non-const) reference to its value
2247 1090 : auto & restartable_data_map = getRestartableDataMap(metaname);
2248 1090 : RestartableDataValue * const data = restartable_data_map.findData(name);
2249 1090 : if (!data)
2250 0 : mooseError("Unable to find RestartableDataValue object with name " + name +
2251 : " in RestartableDataMap");
2252 :
2253 1090 : return *data;
2254 : }
2255 :
2256 : void
2257 7253 : MooseApp::possiblyLoadRestartableMetaData(const RestartableDataMapName & name,
2258 : const std::filesystem::path & folder_base)
2259 : {
2260 7253 : const auto & map_name = getRestartableDataMapName(name);
2261 7253 : const auto meta_data_folder_base = metaDataFolderBase(folder_base, map_name);
2262 7253 : if (RestartableDataReader::isAvailable(meta_data_folder_base))
2263 : {
2264 3573 : RestartableDataReader reader(*this, getRestartableDataMap(name), forceRestart());
2265 3573 : reader.setErrorOnLoadWithDifferentNumberOfProcessors(false);
2266 3573 : reader.setInput(meta_data_folder_base);
2267 3573 : reader.restore();
2268 3573 : }
2269 7253 : }
2270 :
2271 : void
2272 3292 : MooseApp::loadRestartableMetaData(const std::filesystem::path & folder_base)
2273 : {
2274 6584 : for (const auto & name_map_pair : _restartable_meta_data)
2275 3292 : possiblyLoadRestartableMetaData(name_map_pair.first, folder_base);
2276 3292 : }
2277 :
2278 : std::vector<std::filesystem::path>
2279 11324 : MooseApp::writeRestartableMetaData(const RestartableDataMapName & name,
2280 : const std::filesystem::path & folder_base)
2281 : {
2282 11324 : if (processor_id() != 0)
2283 0 : mooseError("MooseApp::writeRestartableMetaData(): Should only run on processor 0");
2284 :
2285 11324 : const auto & map_name = getRestartableDataMapName(name);
2286 11324 : const auto meta_data_folder_base = metaDataFolderBase(folder_base, map_name);
2287 :
2288 11324 : RestartableDataWriter writer(*this, getRestartableDataMap(name));
2289 22648 : return writer.write(meta_data_folder_base);
2290 11324 : }
2291 :
2292 : std::vector<std::filesystem::path>
2293 11261 : MooseApp::writeRestartableMetaData(const std::filesystem::path & folder_base)
2294 : {
2295 11261 : std::vector<std::filesystem::path> paths;
2296 :
2297 11261 : if (processor_id() == 0)
2298 22522 : for (const auto & name_map_pair : _restartable_meta_data)
2299 : {
2300 11261 : const auto map_paths = writeRestartableMetaData(name_map_pair.first, folder_base);
2301 11261 : paths.insert(paths.end(), map_paths.begin(), map_paths.end());
2302 11261 : }
2303 :
2304 11261 : return paths;
2305 0 : }
2306 :
2307 : void
2308 5 : MooseApp::dynamicAppRegistration(const std::string & app_name,
2309 : std::string library_path,
2310 : const std::string & library_name,
2311 : bool lib_load_deps)
2312 : {
2313 : #ifdef LIBMESH_HAVE_DLOPEN
2314 5 : Parameters params;
2315 10 : params.set<std::string>("app_name") = app_name;
2316 5 : params.set<RegistrationType>("reg_type") = APPLICATION;
2317 15 : params.set<std::string>("registration_method") = app_name + "__registerApps";
2318 5 : params.set<std::string>("library_path") = library_path;
2319 :
2320 : const auto effective_library_name =
2321 5 : library_name.empty() ? appNameToLibName(app_name) : library_name;
2322 5 : params.set<std::string>("library_name") = effective_library_name;
2323 10 : params.set<bool>("library_load_dependencies") = lib_load_deps;
2324 :
2325 5 : const auto paths = getLibrarySearchPaths(library_path);
2326 5 : std::ostringstream oss;
2327 :
2328 5 : auto successfully_loaded = false;
2329 5 : if (paths.empty())
2330 : oss << '"' << app_name << "\" is not a registered application name.\n"
2331 : << "No search paths were set. We made no attempts to locate the corresponding library "
2332 1 : "file.\n";
2333 : else
2334 : {
2335 4 : dynamicRegistration(params);
2336 :
2337 : // At this point the application should be registered so check it
2338 4 : if (!AppFactory::instance().isRegistered(app_name))
2339 : {
2340 : oss << '"' << app_name << "\" is not a registered application name.\n"
2341 : << "Unable to locate library archive for \"" << app_name
2342 : << "\".\nWe attempted to locate the library archive \"" << effective_library_name
2343 2 : << "\" in the following paths:\n\t";
2344 2 : std::copy(paths.begin(), paths.end(), infix_ostream_iterator<std::string>(oss, "\n\t"));
2345 : }
2346 : else
2347 2 : successfully_loaded = true;
2348 : }
2349 :
2350 5 : if (!successfully_loaded)
2351 : {
2352 : oss << "\nMake sure you have compiled the library and either set the \"library_path\" "
2353 3 : "variable in your input file or exported \"MOOSE_LIBRARY_PATH\".\n";
2354 :
2355 3 : mooseError(oss.str());
2356 : }
2357 :
2358 : #else
2359 : libmesh_ignore(app_name, library_path, library_name, lib_load_deps);
2360 : mooseError("Dynamic Loading is either not supported or was not detected by libMesh configure.");
2361 : #endif
2362 2 : }
2363 :
2364 : void
2365 9 : MooseApp::dynamicAllRegistration(const std::string & app_name,
2366 : Factory * factory,
2367 : ActionFactory * action_factory,
2368 : Syntax * syntax,
2369 : std::string library_path,
2370 : const std::string & library_name)
2371 : {
2372 : #ifdef LIBMESH_HAVE_DLOPEN
2373 9 : Parameters params;
2374 18 : params.set<std::string>("app_name") = app_name;
2375 9 : params.set<RegistrationType>("reg_type") = REGALL;
2376 27 : params.set<std::string>("registration_method") = app_name + "__registerAll";
2377 9 : params.set<std::string>("library_path") = library_path;
2378 18 : params.set<std::string>("library_name") =
2379 27 : library_name.empty() ? appNameToLibName(app_name) : library_name;
2380 :
2381 18 : params.set<Factory *>("factory") = factory;
2382 18 : params.set<Syntax *>("syntax") = syntax;
2383 27 : params.set<ActionFactory *>("action_factory") = action_factory;
2384 9 : params.set<bool>("library_load_dependencies") = false;
2385 :
2386 9 : dynamicRegistration(params);
2387 : #else
2388 : libmesh_ignore(app_name, factory, action_factory, syntax, library_path, library_name);
2389 : mooseError("Dynamic Loading is either not supported or was not detected by libMesh configure.");
2390 : #endif
2391 9 : }
2392 :
2393 : void
2394 13 : MooseApp::dynamicRegistration(const Parameters & params)
2395 : {
2396 13 : const auto paths = getLibrarySearchPaths(params.get<std::string>("library_path"));
2397 13 : const auto library_name = params.get<std::string>("library_name");
2398 :
2399 : // Attempt to dynamically load the library
2400 26 : for (const auto & path : paths)
2401 13 : if (MooseUtils::checkFileReadable(path + '/' + library_name, false, false))
2402 10 : loadLibraryAndDependencies(
2403 20 : path + '/' + library_name, params, params.get<bool>("library_load_dependencies"));
2404 13 : }
2405 :
2406 : void
2407 10 : MooseApp::loadLibraryAndDependencies(const std::string & library_filename,
2408 : const Parameters & params,
2409 : const bool load_dependencies)
2410 : {
2411 10 : std::string line;
2412 10 : std::string dl_lib_filename;
2413 :
2414 : // This RE looks for absolute path libtool filenames (i.e. begins with a slash and ends with a
2415 : // .la)
2416 10 : pcrecpp::RE re_deps("(/\\S*\\.la)");
2417 :
2418 10 : std::ifstream la_handle(library_filename.c_str());
2419 10 : if (la_handle.is_open())
2420 : {
2421 200 : while (std::getline(la_handle, line))
2422 : {
2423 : // Look for the system dependent dynamic library filename to open
2424 200 : if (line.find("dlname=") != std::string::npos)
2425 : // Magic numbers are computed from length of this string "dlname=' and line minus that
2426 : // string plus quotes"
2427 10 : dl_lib_filename = line.substr(8, line.size() - 9);
2428 :
2429 200 : if (line.find("dependency_libs=") != std::string::npos)
2430 : {
2431 10 : if (load_dependencies)
2432 : {
2433 0 : pcrecpp::StringPiece input(line);
2434 0 : pcrecpp::StringPiece depend_library;
2435 0 : while (re_deps.FindAndConsume(&input, &depend_library))
2436 : // Recurse here to load dependent libraries in depth-first order
2437 0 : loadLibraryAndDependencies(depend_library.as_string(), params, load_dependencies);
2438 : }
2439 :
2440 : // There's only one line in the .la file containing the dependency libs so break after
2441 : // finding it
2442 10 : break;
2443 : }
2444 : }
2445 10 : la_handle.close();
2446 : }
2447 :
2448 : // This should only occur if we have static linkage.
2449 10 : if (dl_lib_filename.empty())
2450 0 : return;
2451 :
2452 10 : const auto & [dir, file_name] = MooseUtils::splitFileName(library_filename);
2453 :
2454 : // Time to load the library, First see if we've already loaded this particular dynamic library
2455 : // 1) make sure we haven't already loaded this library
2456 : // AND 2) make sure we have a library name (we won't for static linkage)
2457 : // Note: Here was are going to assume uniqueness based on the filename alone. This has significant
2458 : // implications for applications that have "diamond" inheritance of libraries (usually
2459 : // modules). We will only load one of those libraries, versions be damned.
2460 10 : auto dyn_lib_it = _lib_handles.find(file_name);
2461 10 : if (dyn_lib_it == _lib_handles.end())
2462 : {
2463 : // Assemble the actual filename using the base path of the *.la file and the dl_lib_filename
2464 10 : const auto dl_lib_full_path = MooseUtils::pathjoin(dir, dl_lib_filename);
2465 :
2466 10 : MooseUtils::checkFileReadable(dl_lib_full_path, false, /*throw_on_unreadable=*/true);
2467 :
2468 : #ifdef LIBMESH_HAVE_DLOPEN
2469 10 : void * const lib_handle = dlopen(dl_lib_full_path.c_str(), RTLD_LAZY);
2470 : #else
2471 : void * const lib_handle = nullptr;
2472 : #endif
2473 :
2474 10 : if (!lib_handle)
2475 0 : mooseError("The library file \"",
2476 : dl_lib_full_path,
2477 : "\" exists and has proper permissions, but cannot by dynamically loaded.\nThis "
2478 : "generally means that the loader was unable to load one or more of the "
2479 : "dependencies listed in the supplied library (see otool or ldd).\n",
2480 0 : dlerror());
2481 :
2482 10 : DynamicLibraryInfo lib_info;
2483 10 : lib_info.library_handle = lib_handle;
2484 10 : lib_info.full_path = library_filename;
2485 :
2486 10 : auto insert_ret = _lib_handles.insert(std::make_pair(file_name, lib_info));
2487 : mooseAssert(insert_ret.second == true, "Error inserting into lib_handles map");
2488 :
2489 10 : dyn_lib_it = insert_ret.first;
2490 10 : }
2491 :
2492 : // Library has been loaded, check to see if we've called the requested registration method
2493 10 : const auto registration_method = params.get<std::string>("registration_method");
2494 10 : auto & entry_sym_from_curr_lib = dyn_lib_it->second.entry_symbols;
2495 :
2496 10 : if (entry_sym_from_curr_lib.find(registration_method) == entry_sym_from_curr_lib.end())
2497 : {
2498 : // get the pointer to the method in the library. The dlsym()
2499 : // function returns a null pointer if the symbol cannot be found,
2500 : // we also explicitly set the pointer to NULL if dlsym is not
2501 : // available.
2502 : #ifdef LIBMESH_HAVE_DLOPEN
2503 : void * registration_handle =
2504 10 : dlsym(dyn_lib_it->second.library_handle, registration_method.c_str());
2505 : #else
2506 : void * registration_handle = nullptr;
2507 : #endif
2508 :
2509 10 : if (registration_handle)
2510 : {
2511 10 : switch (params.get<RegistrationType>("reg_type"))
2512 : {
2513 2 : case APPLICATION:
2514 : {
2515 : using register_app_t = void (*)();
2516 2 : register_app_t * const reg_ptr = reinterpret_cast<register_app_t *>(®istration_handle);
2517 2 : (*reg_ptr)();
2518 2 : break;
2519 : }
2520 8 : case REGALL:
2521 : {
2522 : using register_app_t = void (*)(Factory *, ActionFactory *, Syntax *);
2523 8 : register_app_t * const reg_ptr = reinterpret_cast<register_app_t *>(®istration_handle);
2524 8 : (*reg_ptr)(params.get<Factory *>("factory"),
2525 8 : params.get<ActionFactory *>("action_factory"),
2526 8 : params.get<Syntax *>("syntax"));
2527 8 : break;
2528 : }
2529 0 : default:
2530 0 : mooseError("Unhandled RegistrationType");
2531 : }
2532 :
2533 10 : entry_sym_from_curr_lib.insert(registration_method);
2534 : }
2535 : else
2536 : {
2537 :
2538 : #if defined(DEBUG) && defined(LIBMESH_HAVE_DLOPEN)
2539 : // We found a dynamic library that doesn't have a dynamic
2540 : // registration method in it. This shouldn't be an error, so
2541 : // we'll just move on.
2542 : if (!registration_handle)
2543 : mooseWarning("Unable to find extern \"C\" method \"",
2544 : registration_method,
2545 : "\" in library: ",
2546 : dyn_lib_it->first,
2547 : ".\n",
2548 : "This doesn't necessarily indicate an error condition unless you believe that "
2549 : "the method should exist in that library.\n",
2550 : dlerror());
2551 : #endif
2552 : }
2553 : }
2554 10 : }
2555 :
2556 : std::set<std::string>
2557 15 : MooseApp::getLoadedLibraryPaths() const
2558 : {
2559 : // Return the paths but not the open file handles
2560 15 : std::set<std::string> paths;
2561 17 : for (const auto & it : _lib_handles)
2562 2 : paths.insert(it.first);
2563 :
2564 15 : return paths;
2565 0 : }
2566 :
2567 : std::set<std::string>
2568 18 : MooseApp::getLibrarySearchPaths(const std::string & library_path) const
2569 : {
2570 18 : std::set<std::string> paths;
2571 :
2572 18 : if (!library_path.empty())
2573 : {
2574 17 : std::vector<std::string> tmp_paths;
2575 17 : MooseUtils::tokenize(library_path, tmp_paths, 1, ":");
2576 :
2577 17 : paths.insert(tmp_paths.begin(), tmp_paths.end());
2578 17 : }
2579 :
2580 18 : char * moose_lib_path_env = std::getenv("MOOSE_LIBRARY_PATH");
2581 18 : if (moose_lib_path_env)
2582 : {
2583 0 : std::string moose_lib_path(moose_lib_path_env);
2584 0 : std::vector<std::string> tmp_paths;
2585 0 : MooseUtils::tokenize(moose_lib_path, tmp_paths, 1, ":");
2586 :
2587 0 : paths.insert(tmp_paths.begin(), tmp_paths.end());
2588 0 : }
2589 :
2590 18 : return paths;
2591 0 : }
2592 :
2593 : InputParameterWarehouse &
2594 7320195 : MooseApp::getInputParameterWarehouse()
2595 : {
2596 7320195 : return *_input_parameter_warehouse;
2597 : }
2598 :
2599 : std::string
2600 91 : MooseApp::header() const
2601 : {
2602 182 : return std::string("");
2603 : }
2604 :
2605 : void
2606 12538 : MooseApp::setRestart(bool value)
2607 : {
2608 12538 : _restart = value;
2609 12538 : }
2610 :
2611 : void
2612 12094 : MooseApp::setRecover(bool value)
2613 : {
2614 12094 : _recover = value;
2615 12094 : }
2616 :
2617 : void
2618 8 : MooseApp::createMinimalApp()
2619 : {
2620 40 : TIME_SECTION("createMinimalApp", 3, "Creating Minimal App");
2621 :
2622 : // SetupMeshAction
2623 : {
2624 : // Build the Action parameters
2625 24 : InputParameters action_params = _action_factory.getValidParams("SetupMeshAction");
2626 8 : action_params.set<std::string>("type") = "GeneratedMesh";
2627 :
2628 : // Create The Action
2629 : std::shared_ptr<MooseObjectAction> action = std::static_pointer_cast<MooseObjectAction>(
2630 32 : _action_factory.create("SetupMeshAction", "Mesh", action_params));
2631 :
2632 : // Set the object parameters
2633 8 : InputParameters & params = action->getObjectParams();
2634 32 : params.set<MooseEnum>("dim") = "1";
2635 8 : params.set<unsigned int>("nx") = 1;
2636 :
2637 : // Add Action to the warehouse
2638 8 : _action_warehouse.addActionBlock(action);
2639 8 : }
2640 :
2641 : // Executioner
2642 : {
2643 : // Build the Action parameters
2644 24 : InputParameters action_params = _action_factory.getValidParams("CreateExecutionerAction");
2645 8 : action_params.set<std::string>("type") = "Transient";
2646 :
2647 : // Create the action
2648 : std::shared_ptr<MooseObjectAction> action = std::static_pointer_cast<MooseObjectAction>(
2649 32 : _action_factory.create("CreateExecutionerAction", "Executioner", action_params));
2650 :
2651 : // Set the object parameters
2652 8 : InputParameters & params = action->getObjectParams();
2653 16 : params.set<unsigned int>("num_steps") = 1;
2654 8 : params.set<Real>("dt") = 1;
2655 :
2656 : // Add Action to the warehouse
2657 8 : _action_warehouse.addActionBlock(action);
2658 8 : }
2659 :
2660 : // Problem
2661 : {
2662 : // Build the Action parameters
2663 24 : InputParameters action_params = _action_factory.getValidParams("CreateProblemDefaultAction");
2664 8 : action_params.set<bool>("_solve") = false;
2665 :
2666 : // Create the action
2667 : std::shared_ptr<Action> action = std::static_pointer_cast<Action>(
2668 32 : _action_factory.create("CreateProblemDefaultAction", "Problem", action_params));
2669 :
2670 : // Add Action to the warehouse
2671 8 : _action_warehouse.addActionBlock(action);
2672 8 : }
2673 :
2674 : // Outputs
2675 : {
2676 : // Build the Action parameters
2677 24 : InputParameters action_params = _action_factory.getValidParams("CommonOutputAction");
2678 8 : action_params.set<bool>("console") = false;
2679 :
2680 : // Create action
2681 : std::shared_ptr<Action> action =
2682 32 : _action_factory.create("CommonOutputAction", "Outputs", action_params);
2683 :
2684 : // Add Action to the warehouse
2685 8 : _action_warehouse.addActionBlock(action);
2686 8 : }
2687 :
2688 8 : _action_warehouse.build();
2689 8 : }
2690 :
2691 : bool
2692 0 : MooseApp::hasRelationshipManager(const std::string & name) const
2693 : {
2694 0 : return std::find_if(_relationship_managers.begin(),
2695 : _relationship_managers.end(),
2696 0 : [&name](const std::shared_ptr<RelationshipManager> & rm)
2697 0 : { return rm->name() == name; }) != _relationship_managers.end();
2698 : }
2699 :
2700 : namespace
2701 : {
2702 : void
2703 474734 : donateForWhom(const RelationshipManager & donor, RelationshipManager & acceptor)
2704 : {
2705 474734 : auto & existing_for_whom = acceptor.forWhom();
2706 :
2707 : // Take all the for_whoms from the donor, and give them to the acceptor
2708 954310 : for (auto & fw : donor.forWhom())
2709 : {
2710 479576 : if (std::find(existing_for_whom.begin(), existing_for_whom.end(), fw) ==
2711 959152 : existing_for_whom.end())
2712 91446 : acceptor.addForWhom(fw);
2713 : }
2714 474734 : }
2715 : }
2716 :
2717 : bool
2718 589750 : MooseApp::addRelationshipManager(std::shared_ptr<RelationshipManager> new_rm)
2719 : {
2720 : // We prefer to always add geometric RMs. There is no hurt to add RMs for replicated mesh
2721 : // since MeshBase::delete_remote_elements{} is a no-op (empty) for replicated mesh.
2722 : // The motivation here is that MooseMesh::_use_distributed_mesh may not be properly set
2723 : // at the time we are adding geometric relationship managers. We deleted the following
2724 : // old logic to add all geometric RMs regardless of there is a distributed mesh or not.
2725 : // Otherwise, all geometric RMs will be improperly ignored for a distributed mesh generator.
2726 :
2727 : // if (!_action_warehouse.mesh()->isDistributedMesh() && !_split_mesh &&
2728 : // (relationship_manager->isType(Moose::RelationshipManagerType::GEOMETRIC) &&
2729 : // !(relationship_manager->isType(Moose::RelationshipManagerType::ALGEBRAIC) ||
2730 : // relationship_manager->isType(Moose::RelationshipManagerType::COUPLING))))
2731 : // return false;
2732 :
2733 589750 : bool add = true;
2734 :
2735 589750 : std::set<std::shared_ptr<RelationshipManager>> rms_to_erase;
2736 :
2737 981753 : for (const auto & existing_rm : _relationship_managers)
2738 : {
2739 862757 : if (*existing_rm >= *new_rm)
2740 : {
2741 470754 : add = false;
2742 470754 : donateForWhom(*new_rm, *existing_rm);
2743 470754 : break;
2744 : }
2745 : // The new rm did not provide less or the same amount/type of ghosting as the existing rm, but
2746 : // what about the other way around?
2747 392003 : else if (*new_rm >= *existing_rm)
2748 3980 : rms_to_erase.emplace(existing_rm);
2749 : }
2750 :
2751 589750 : if (add)
2752 : {
2753 118996 : _relationship_managers.emplace(new_rm);
2754 122976 : for (const auto & rm_to_erase : rms_to_erase)
2755 : {
2756 3980 : donateForWhom(*rm_to_erase, *new_rm);
2757 3980 : removeRelationshipManager(rm_to_erase);
2758 : }
2759 : }
2760 :
2761 : // Inform the caller whether the object was added or not
2762 589750 : return add;
2763 589750 : }
2764 :
2765 : const std::string &
2766 19467 : MooseApp::checkpointSuffix()
2767 : {
2768 36155 : static const std::string suffix = "-mesh.cpa.gz";
2769 19467 : return suffix;
2770 : }
2771 :
2772 : std::filesystem::path
2773 18577 : MooseApp::metaDataFolderBase(const std::filesystem::path & folder_base,
2774 : const std::string & map_suffix)
2775 : {
2776 37154 : return RestartableDataIO::restartableDataFolder(folder_base /
2777 55731 : std::filesystem::path("meta_data" + map_suffix));
2778 : }
2779 :
2780 : std::filesystem::path
2781 16593 : MooseApp::restartFolderBase(const std::filesystem::path & folder_base) const
2782 : {
2783 16593 : auto folder = folder_base;
2784 16593 : folder += "-restart-" + std::to_string(processor_id());
2785 33186 : return RestartableDataIO::restartableDataFolder(folder);
2786 16593 : }
2787 :
2788 : const hit::Node *
2789 2731866 : MooseApp::getCurrentActionHitNode() const
2790 : {
2791 2731866 : if (const auto action = _action_warehouse.getCurrentAction())
2792 1173293 : return action->parameters().getHitNode();
2793 1558573 : return nullptr;
2794 : }
2795 :
2796 : bool
2797 25 : MooseApp::hasRMClone(const RelationshipManager & template_rm, const MeshBase & mesh) const
2798 : {
2799 25 : auto it = _template_to_clones.find(&template_rm);
2800 : // C++ does short circuiting so we're safe here
2801 25 : return (it != _template_to_clones.end()) && (it->second.find(&mesh) != it->second.end());
2802 : }
2803 :
2804 : RelationshipManager &
2805 25 : MooseApp::getRMClone(const RelationshipManager & template_rm, const MeshBase & mesh) const
2806 : {
2807 25 : auto outer_it = _template_to_clones.find(&template_rm);
2808 25 : if (outer_it == _template_to_clones.end())
2809 0 : mooseError("The template rm does not exist in our _template_to_clones map");
2810 :
2811 25 : auto & mesh_to_clone_map = outer_it->second;
2812 25 : auto inner_it = mesh_to_clone_map.find(&mesh);
2813 25 : if (inner_it == mesh_to_clone_map.end())
2814 0 : mooseError("We should have the mesh key in our mesh");
2815 :
2816 50 : return *inner_it->second;
2817 : }
2818 :
2819 : void
2820 3980 : MooseApp::removeRelationshipManager(std::shared_ptr<RelationshipManager> rm)
2821 : {
2822 3980 : auto * const mesh = _action_warehouse.mesh().get();
2823 3980 : if (!mesh)
2824 0 : mooseError("The MooseMesh should exist");
2825 :
2826 3980 : const MeshBase * const undisp_lm_mesh = mesh->getMeshPtr();
2827 3980 : RelationshipManager * undisp_clone = nullptr;
2828 3980 : if (undisp_lm_mesh && hasRMClone(*rm, *undisp_lm_mesh))
2829 : {
2830 25 : undisp_clone = &getRMClone(*rm, *undisp_lm_mesh);
2831 25 : const_cast<MeshBase *>(undisp_lm_mesh)->remove_ghosting_functor(*undisp_clone);
2832 : }
2833 :
2834 3980 : auto & displaced_mesh = _action_warehouse.displacedMesh();
2835 3980 : MeshBase * const disp_lm_mesh = displaced_mesh ? &displaced_mesh->getMesh() : nullptr;
2836 3980 : RelationshipManager * disp_clone = nullptr;
2837 3980 : if (disp_lm_mesh && hasRMClone(*rm, *disp_lm_mesh))
2838 : {
2839 0 : disp_clone = &getRMClone(*rm, *disp_lm_mesh);
2840 0 : disp_lm_mesh->remove_ghosting_functor(*disp_clone);
2841 : }
2842 :
2843 3980 : if (_executioner)
2844 : {
2845 25 : auto & problem = feProblem();
2846 25 : if (undisp_clone)
2847 : {
2848 25 : problem.removeAlgebraicGhostingFunctor(*undisp_clone);
2849 25 : problem.removeCouplingGhostingFunctor(*undisp_clone);
2850 : }
2851 :
2852 25 : auto * dp = problem.getDisplacedProblem().get();
2853 25 : if (dp && disp_clone)
2854 0 : dp->removeAlgebraicGhostingFunctor(*disp_clone);
2855 : }
2856 :
2857 3980 : _factory.releaseSharedObjects(*rm);
2858 3980 : _relationship_managers.erase(rm);
2859 3980 : }
2860 :
2861 : RelationshipManager &
2862 144934 : MooseApp::createRMFromTemplateAndInit(const RelationshipManager & template_rm,
2863 : MooseMesh & moose_mesh,
2864 : MeshBase & mesh,
2865 : const DofMap * const dof_map)
2866 : {
2867 144934 : auto & mesh_to_clone = _template_to_clones[&template_rm];
2868 144934 : auto it = mesh_to_clone.find(&mesh);
2869 144934 : if (it != mesh_to_clone.end())
2870 : {
2871 : // We've already created a clone for this mesh
2872 28902 : auto & clone_rm = *it->second;
2873 28902 : if (!clone_rm.dofMap() && dof_map)
2874 : // We didn't have a DofMap before, but now we do, so we should re-init
2875 9068 : clone_rm.init(moose_mesh, mesh, dof_map);
2876 19834 : else if (clone_rm.dofMap() && dof_map && (clone_rm.dofMap() != dof_map))
2877 0 : mooseError("Attempting to create and initialize an existing clone with a different DofMap. "
2878 : "This should not happen.");
2879 :
2880 28902 : return clone_rm;
2881 : }
2882 :
2883 : // It's possible that this method is going to get called for multiple different MeshBase
2884 : // objects. If that happens, then we *cannot* risk having a MeshBase object with a ghosting
2885 : // functor that is init'd with another MeshBase object. So the safe thing to do is to make a
2886 : // different RM for every MeshBase object that gets called here. Then the
2887 : // RelationshipManagers stored here in MooseApp are serving as a template only
2888 116032 : auto pr = mesh_to_clone.emplace(
2889 232064 : std::make_pair(&const_cast<const MeshBase &>(mesh),
2890 232064 : dynamic_pointer_cast<RelationshipManager>(template_rm.clone())));
2891 : mooseAssert(pr.second, "An insertion should have happened");
2892 116032 : auto & clone_rm = *pr.first->second;
2893 116032 : clone_rm.init(moose_mesh, mesh, dof_map);
2894 116032 : return clone_rm;
2895 : }
2896 :
2897 : void
2898 70012 : MooseApp::attachRelationshipManagers(MeshBase & mesh, MooseMesh & moose_mesh)
2899 : {
2900 175889 : for (auto & rm : _relationship_managers)
2901 : {
2902 105877 : if (rm->isType(Moose::RelationshipManagerType::GEOMETRIC))
2903 : {
2904 66008 : if (rm->attachGeometricEarly())
2905 : {
2906 50142 : mesh.add_ghosting_functor(createRMFromTemplateAndInit(*rm, moose_mesh, mesh));
2907 50142 : _attached_relationship_managers[Moose::RelationshipManagerType::GEOMETRIC].insert(rm.get());
2908 : }
2909 : else
2910 : {
2911 : // If we have a geometric ghosting functor that can't be attached early, then we have to
2912 : // prevent the mesh from deleting remote elements
2913 15866 : moose_mesh.allowRemoteElementRemoval(false);
2914 :
2915 15866 : if (const MeshBase * const moose_mesh_base = moose_mesh.getMeshPtr())
2916 : {
2917 0 : if (moose_mesh_base != &mesh)
2918 0 : mooseError("The MooseMesh MeshBase and the MeshBase we're trying to attach "
2919 : "relationship managers to are different");
2920 : }
2921 : else
2922 : // The MeshBase isn't attached to the MooseMesh yet, so have to tell it not to remove
2923 : // remote elements independently
2924 15866 : mesh.allow_remote_element_removal(false);
2925 : }
2926 : }
2927 : }
2928 70012 : }
2929 :
2930 : void
2931 244766 : MooseApp::attachRelationshipManagers(Moose::RelationshipManagerType rm_type,
2932 : bool attach_geometric_rm_final)
2933 : {
2934 682403 : for (auto & rm : _relationship_managers)
2935 : {
2936 437637 : if (!rm->isType(rm_type))
2937 233195 : continue;
2938 :
2939 : // RM is already attached (this also handles the geometric early case)
2940 204442 : if (_attached_relationship_managers[rm_type].count(rm.get()))
2941 95307 : continue;
2942 :
2943 109135 : if (rm_type == Moose::RelationshipManagerType::GEOMETRIC)
2944 : {
2945 : // The problem is not built yet - so the ActionWarehouse currently owns the mesh
2946 31373 : MooseMesh * const mesh = _action_warehouse.mesh().get();
2947 :
2948 : // "attach_geometric_rm_final = true" inidicate that it is the last chance to attach
2949 : // geometric RMs. Therefore, we need to attach them.
2950 31373 : if (!rm->attachGeometricEarly() && !attach_geometric_rm_final)
2951 : // Will attach them later (during algebraic). But also, we need to tell the mesh that we
2952 : // shouldn't be deleting remote elements yet
2953 15830 : mesh->allowRemoteElementRemoval(false);
2954 : else
2955 : {
2956 15543 : MeshBase & undisp_mesh_base = mesh->getMesh();
2957 : const DofMap * const undisp_sys_dof_map =
2958 15543 : _executioner ? &feProblem().getSolverSystem(0).dofMap() : nullptr;
2959 15543 : undisp_mesh_base.add_ghosting_functor(
2960 15543 : createRMFromTemplateAndInit(*rm, *mesh, undisp_mesh_base, undisp_sys_dof_map));
2961 :
2962 : // In the final stage, if there is a displaced mesh, we need to
2963 : // clone ghosting functors for displacedMesh
2964 15543 : if (auto & disp_moose_mesh = _action_warehouse.displacedMesh();
2965 15543 : attach_geometric_rm_final && disp_moose_mesh)
2966 : {
2967 1487 : MeshBase & disp_mesh_base = _action_warehouse.displacedMesh()->getMesh();
2968 1487 : const DofMap * disp_sys_dof_map = nullptr;
2969 1487 : if (_executioner && feProblem().getDisplacedProblem())
2970 1487 : disp_sys_dof_map = &feProblem().getDisplacedProblem()->solverSys(0).dofMap();
2971 1487 : disp_mesh_base.add_ghosting_functor(
2972 1487 : createRMFromTemplateAndInit(*rm, *disp_moose_mesh, disp_mesh_base, disp_sys_dof_map));
2973 : }
2974 14056 : else if (_action_warehouse.displacedMesh())
2975 0 : mooseError("The displaced mesh should not yet exist at the time that we are attaching "
2976 : "early geometric relationship managers.");
2977 :
2978 : // Mark this RM as attached
2979 : mooseAssert(!_attached_relationship_managers[rm_type].count(rm.get()), "Already attached");
2980 15543 : _attached_relationship_managers[rm_type].insert(rm.get());
2981 : }
2982 : }
2983 : else // rm_type is algebraic or coupling
2984 : {
2985 77762 : if (!_executioner && !_executor)
2986 0 : mooseError("We must have an executioner by now or else we do not have to data to add "
2987 : "algebraic or coupling functors to in MooseApp::attachRelationshipManagers");
2988 :
2989 : // Now we've built the problem, so we can use it
2990 77762 : auto & problem = feProblem();
2991 77762 : auto & undisp_moose_mesh = problem.mesh();
2992 77762 : auto & undisp_sys = feProblem().getSolverSystem(0);
2993 77762 : auto & undisp_sys_dof_map = undisp_sys.dofMap();
2994 77762 : auto & undisp_mesh = undisp_moose_mesh.getMesh();
2995 :
2996 77762 : if (rm->useDisplacedMesh() && problem.getDisplacedProblem())
2997 : {
2998 4296 : if (rm_type == Moose::RelationshipManagerType::COUPLING)
2999 : // We actually need to add this to the FEProblemBase NonlinearSystemBase's DofMap
3000 : // because the DisplacedProblem "nonlinear" DisplacedSystem doesn't have any matrices
3001 : // for which to do coupling. It's actually horrifying to me that we are adding a
3002 : // coupling functor, that is going to determine its couplings based on a displaced
3003 : // MeshBase object, to a System associated with the undisplaced MeshBase object (there
3004 : // is only ever one EquationSystems object per MeshBase object and visa versa). So here
3005 : // I'm left with the choice of whether to pass in a MeshBase object that is *not* the
3006 : // MeshBase object that will actually determine the couplings or to pass in the MeshBase
3007 : // object that is inconsistent with the System DofMap that we are adding the coupling
3008 : // functor for! Let's err on the side of *libMesh* consistency and pass properly paired
3009 : // MeshBase-DofMap
3010 204 : problem.addCouplingGhostingFunctor(
3011 102 : createRMFromTemplateAndInit(*rm, undisp_moose_mesh, undisp_mesh, &undisp_sys_dof_map),
3012 : /*to_mesh = */ false);
3013 :
3014 4194 : else if (rm_type == Moose::RelationshipManagerType::ALGEBRAIC)
3015 : {
3016 4194 : auto & displaced_problem = *problem.getDisplacedProblem();
3017 4194 : auto & disp_moose_mesh = displaced_problem.mesh();
3018 4194 : auto & disp_mesh = disp_moose_mesh.getMesh();
3019 4194 : const DofMap * const disp_nl_dof_map = &displaced_problem.solverSys(0).dofMap();
3020 8388 : displaced_problem.addAlgebraicGhostingFunctor(
3021 4194 : createRMFromTemplateAndInit(*rm, disp_moose_mesh, disp_mesh, disp_nl_dof_map),
3022 : /*to_mesh = */ false);
3023 : }
3024 : }
3025 : else // undisplaced
3026 : {
3027 73466 : if (rm_type == Moose::RelationshipManagerType::COUPLING)
3028 88088 : problem.addCouplingGhostingFunctor(
3029 44044 : createRMFromTemplateAndInit(*rm, undisp_moose_mesh, undisp_mesh, &undisp_sys_dof_map),
3030 : /*to_mesh = */ false);
3031 :
3032 29422 : else if (rm_type == Moose::RelationshipManagerType::ALGEBRAIC)
3033 58844 : problem.addAlgebraicGhostingFunctor(
3034 29422 : createRMFromTemplateAndInit(*rm, undisp_moose_mesh, undisp_mesh, &undisp_sys_dof_map),
3035 : /*to_mesh = */ false);
3036 : }
3037 :
3038 : // Mark this RM as attached
3039 : mooseAssert(!_attached_relationship_managers[rm_type].count(rm.get()), "Already attached");
3040 77762 : _attached_relationship_managers[rm_type].insert(rm.get());
3041 : }
3042 : }
3043 244766 : }
3044 :
3045 : std::vector<std::pair<std::string, std::string>>
3046 91 : MooseApp::getRelationshipManagerInfo() const
3047 : {
3048 91 : std::vector<std::pair<std::string, std::string>> info_strings;
3049 91 : info_strings.reserve(_relationship_managers.size());
3050 :
3051 369 : for (const auto & rm : _relationship_managers)
3052 : {
3053 278 : std::stringstream oss;
3054 278 : oss << rm->getInfo();
3055 :
3056 278 : auto & for_whom = rm->forWhom();
3057 :
3058 278 : if (!for_whom.empty())
3059 : {
3060 278 : oss << " for ";
3061 :
3062 278 : std::copy(for_whom.begin(), for_whom.end(), infix_ostream_iterator<std::string>(oss, ", "));
3063 : }
3064 :
3065 278 : info_strings.emplace_back(std::make_pair(Moose::stringify(rm->getType()), oss.str()));
3066 278 : }
3067 :
3068 : // List the libMesh GhostingFunctors - Not that in libMesh all of the algebraic and coupling
3069 : // Ghosting Functors are also attached to the mesh. This should catch them all.
3070 91 : const auto & mesh = _action_warehouse.getMesh();
3071 91 : if (mesh)
3072 : {
3073 : // Let us use an ordered map to avoid stochastic console behaviors.
3074 : // I believe we won't have many RMs, and there is no performance issue.
3075 : // Deterministic behaviors are good for setting up regression tests
3076 91 : std::map<std::string, unsigned int> counts;
3077 :
3078 182 : for (auto & gf : as_range(mesh->getMesh().ghosting_functors_begin(),
3079 754 : mesh->getMesh().ghosting_functors_end()))
3080 : {
3081 481 : const auto * gf_ptr = dynamic_cast<const RelationshipManager *>(gf);
3082 481 : if (!gf_ptr)
3083 : // Count how many occurences of the same Ghosting Functor types we are encountering
3084 309 : counts[demangle(typeid(*gf).name())]++;
3085 : }
3086 :
3087 273 : for (const auto & pair : counts)
3088 182 : info_strings.emplace_back(std::make_pair(
3089 528 : "Default", pair.first + (pair.second > 1 ? " x " + std::to_string(pair.second) : "")));
3090 91 : }
3091 :
3092 : // List the libMesh GhostingFunctors - Not that in libMesh all of the algebraic and coupling
3093 : // Ghosting Functors are also attached to the mesh. This should catch them all.
3094 91 : const auto & d_mesh = _action_warehouse.getDisplacedMesh();
3095 91 : if (d_mesh)
3096 : {
3097 : // Let us use an ordered map to avoid stochastic console behaviors.
3098 : // I believe we won't have many RMs, and there is no performance issue.
3099 : // Deterministic behaviors are good for setting up regression tests
3100 9 : std::map<std::string, unsigned int> counts;
3101 :
3102 18 : for (auto & gf : as_range(d_mesh->getMesh().ghosting_functors_begin(),
3103 99 : d_mesh->getMesh().ghosting_functors_end()))
3104 : {
3105 72 : const auto * gf_ptr = dynamic_cast<const RelationshipManager *>(gf);
3106 72 : if (!gf_ptr)
3107 : // Count how many occurences of the same Ghosting Functor types we are encountering
3108 45 : counts[demangle(typeid(*gf).name())]++;
3109 : }
3110 :
3111 27 : for (const auto & pair : counts)
3112 18 : info_strings.emplace_back(
3113 36 : std::make_pair("Default",
3114 36 : pair.first + (pair.second > 1 ? " x " + std::to_string(pair.second) : "") +
3115 : " for DisplacedMesh"));
3116 9 : }
3117 :
3118 91 : return info_strings;
3119 0 : }
3120 :
3121 : void
3122 65144 : MooseApp::checkMetaDataIntegrity() const
3123 : {
3124 130288 : for (auto map_iter = _restartable_meta_data.begin(); map_iter != _restartable_meta_data.end();
3125 65144 : ++map_iter)
3126 : {
3127 65144 : const RestartableDataMapName & name = map_iter->first;
3128 65144 : const RestartableDataMap & meta_data = map_iter->second.first;
3129 :
3130 65144 : std::vector<std::string> not_declared;
3131 :
3132 251383 : for (const auto & data : meta_data)
3133 186239 : if (!data.declared())
3134 0 : not_declared.push_back(data.name());
3135 :
3136 65144 : if (!not_declared.empty())
3137 : {
3138 0 : std::ostringstream oss;
3139 0 : std::copy(
3140 : not_declared.begin(), not_declared.end(), infix_ostream_iterator<std::string>(oss, ", "));
3141 :
3142 0 : mooseError("The following '",
3143 : name,
3144 : "' meta-data properties were retrieved but never declared: ",
3145 0 : oss.str());
3146 0 : }
3147 65144 : }
3148 65144 : }
3149 :
3150 : const RestartableDataMapName MooseApp::MESH_META_DATA = "MeshMetaData";
3151 : const RestartableDataMapName MooseApp::MESH_META_DATA_SUFFIX = "mesh";
3152 :
3153 : RestartableDataMap &
3154 16014 : MooseApp::getRestartableDataMap(const RestartableDataMapName & name)
3155 : {
3156 16014 : auto iter = _restartable_meta_data.find(name);
3157 16014 : if (iter == _restartable_meta_data.end())
3158 3 : mooseError("Unable to find RestartableDataMap object for the supplied name '",
3159 : name,
3160 : "', did you call registerRestartableDataMapName in the application constructor?");
3161 32022 : return iter->second.first;
3162 : }
3163 :
3164 : bool
3165 0 : MooseApp::hasRestartableDataMap(const RestartableDataMapName & name) const
3166 : {
3167 0 : return _restartable_meta_data.count(name);
3168 : }
3169 :
3170 : void
3171 66993 : MooseApp::registerRestartableDataMapName(const RestartableDataMapName & name, std::string suffix)
3172 : {
3173 66993 : if (!suffix.empty())
3174 66993 : std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower);
3175 66993 : suffix.insert(0, "_");
3176 66993 : _restartable_meta_data.emplace(
3177 133986 : std::make_pair(name, std::make_pair(RestartableDataMap(), suffix)));
3178 66993 : }
3179 :
3180 : const std::string &
3181 18577 : MooseApp::getRestartableDataMapName(const RestartableDataMapName & name) const
3182 : {
3183 18577 : const auto it = _restartable_meta_data.find(name);
3184 18577 : if (it == _restartable_meta_data.end())
3185 0 : mooseError("MooseApp::getRestartableDataMapName: The name '", name, "' is not registered");
3186 37154 : return it->second.second;
3187 : }
3188 :
3189 : PerfGraph &
3190 66995 : MooseApp::createRecoverablePerfGraph()
3191 : {
3192 66995 : registerRestartableNameWithFilter("perf_graph", Moose::RESTARTABLE_FILTER::RECOVERABLE);
3193 :
3194 : auto perf_graph =
3195 : std::make_unique<RestartableData<PerfGraph>>("perf_graph",
3196 0 : this,
3197 133990 : type() + " (" + name() + ')',
3198 : *this,
3199 : getParam<bool>("perf_graph_live_all"),
3200 468965 : !getParam<bool>("disable_perf_graph_live"));
3201 :
3202 117206 : return dynamic_cast<RestartableData<PerfGraph> &>(
3203 133990 : registerRestartableData(std::move(perf_graph), 0, false))
3204 133990 : .set();
3205 66995 : }
3206 :
3207 : SolutionInvalidity &
3208 66995 : MooseApp::createRecoverableSolutionInvalidity()
3209 : {
3210 66995 : registerRestartableNameWithFilter("solution_invalidity", Moose::RESTARTABLE_FILTER::RECOVERABLE);
3211 :
3212 : auto solution_invalidity =
3213 66995 : std::make_unique<RestartableData<SolutionInvalidity>>("solution_invalidity", nullptr, *this);
3214 :
3215 117206 : return dynamic_cast<RestartableData<SolutionInvalidity> &>(
3216 133990 : registerRestartableData(std::move(solution_invalidity), 0, false))
3217 133990 : .set();
3218 66995 : }
3219 :
3220 : bool
3221 309688 : MooseApp::constructingMeshGenerators() const
3222 : {
3223 366794 : return _action_warehouse.getCurrentTaskName() == "create_added_mesh_generators" ||
3224 366794 : _mesh_generator_system.appendingMeshGenerators();
3225 : }
3226 :
3227 : #ifdef MOOSE_LIBTORCH_ENABLED
3228 : torch::DeviceType
3229 2230 : MooseApp::determineLibtorchDeviceType(const MooseEnum & device_enum) const
3230 : {
3231 2230 : const auto pname = "--compute-device";
3232 2230 : if (device_enum == "cuda")
3233 : {
3234 : #ifdef __linux__
3235 1562 : if (!torch::cuda::is_available())
3236 0 : mooseError(pname, "=cuda: CUDA support is not available in the linked libtorch library");
3237 1562 : return torch::kCUDA;
3238 : #else
3239 : mooseError(pname, "=cuda: CUDA is not supported on your platform");
3240 : #endif
3241 : }
3242 668 : else if (device_enum == "mps")
3243 : {
3244 : #ifdef __APPLE__
3245 : if (!torch::mps::is_available())
3246 : mooseError(pname, "=mps: MPS support is not available in the linked libtorch library");
3247 : return torch::kMPS;
3248 : #else
3249 0 : mooseError(pname, "=mps: MPS is not supported on your platform");
3250 : #endif
3251 : }
3252 668 : else if (device_enum == "xpu")
3253 : {
3254 : #ifdef MOOSE_HAVE_XPU
3255 0 : if (!torch::xpu::is_available())
3256 0 : mooseError(pname, "=xpu: XPU support is not available in the linked libtorch library");
3257 0 : return torch::kXPU;
3258 : #else
3259 : mooseError(pname, "=xpu: XPU is not supported in the current application");
3260 : #endif
3261 : }
3262 668 : else if (device_enum != "cpu")
3263 0 : mooseError("The device '",
3264 : device_enum,
3265 : "' is not currently supported by the MOOSE libtorch integration.");
3266 668 : return torch::kCPU;
3267 : }
3268 : #endif
3269 :
3270 : void
3271 47 : MooseApp::outputMachineReadableData(const std::string & param,
3272 : const std::string & start_marker,
3273 : const std::string & end_marker,
3274 : const std::string & data) const
3275 : {
3276 : // Bool parameter, just to screen
3277 47 : if (_pars.have_parameter<bool>(param))
3278 : {
3279 47 : Moose::out << start_marker << data << end_marker << std::endl;
3280 47 : return;
3281 : }
3282 :
3283 : // String parameter, to file
3284 0 : const auto & filename = getParam<std::string>(param);
3285 : // write to file
3286 0 : std::ofstream out(filename.c_str());
3287 0 : if (out.is_open())
3288 : {
3289 0 : std::ofstream out(filename.c_str());
3290 0 : out << data << std::flush;
3291 0 : out.close();
3292 0 : }
3293 : else
3294 0 : mooseError("Unable to open file `", filename, "` for writing ", param, " data to it.");
3295 0 : }
3296 :
3297 : Moose::Capability &
3298 66420 : MooseApp::addBoolCapability(const std::string_view capability,
3299 : const bool value,
3300 : const std::string_view doc)
3301 : {
3302 66420 : return addCapabilityInternal(capability, value, doc);
3303 : }
3304 :
3305 : Moose::Capability &
3306 132836 : MooseApp::addIntCapability(const std::string_view capability,
3307 : const int value,
3308 : const std::string_view doc)
3309 : {
3310 132836 : return addCapabilityInternal(capability, value, doc);
3311 : }
3312 :
3313 : Moose::Capability &
3314 265668 : MooseApp::addStringCapability(const std::string_view capability,
3315 : const std::string_view value,
3316 : const std::string_view doc)
3317 : {
3318 531338 : return addCapabilityInternal(capability, std::string(value), doc);
3319 : }
3320 :
3321 : Moose::Capability &
3322 6 : MooseApp::addCapability(const std::string_view capability,
3323 : const Moose::Capability::Value & value,
3324 : const std::string_view doc)
3325 : {
3326 :
3327 : // Warn deprecation on the first time this is added so that we
3328 : // don't get multiple warnings if the app is registered more
3329 : // than once
3330 18 : if (!Moose::internal::Capabilities::getCapabilities({}).query(std::string(capability)))
3331 4 : ::mooseDeprecated("MooseApp::addCapability() is deprecated (adding capability '",
3332 : capability,
3333 : "'); use one of MooseApp::add[Bool,Int,String]Capability instead.");
3334 :
3335 4 : return addCapabilityInternal(capability, value, doc);
3336 : }
3337 :
3338 : bool
3339 2 : MooseApp::isRelocated()
3340 : {
3341 2 : return Moose::internal::Capabilities::getCapabilities({}).isRelocated();
3342 : }
3343 :
3344 : bool
3345 2 : MooseApp::isInTree()
3346 : {
3347 2 : return Moose::internal::Capabilities::getCapabilities({}).isInTree();
3348 : }
3349 :
3350 : #ifdef MOOSE_MFEM_ENABLED
3351 : void
3352 1361 : MooseApp::setMFEMDevice(const std::string & device_string, Moose::PassKey<MFEMProblemSolve>)
3353 : {
3354 2722 : const auto string_vec = MooseUtils::split(device_string, ",");
3355 1361 : auto string_set = std::set<std::string>(string_vec.begin(), string_vec.end());
3356 1361 : if (!_mfem_device)
3357 : {
3358 1147 : _mfem_device = std::make_shared<mfem::Device>(device_string);
3359 1147 : _mfem_devices = std::move(string_set);
3360 1147 : _mfem_device->Print(Moose::out);
3361 : }
3362 214 : else if (!device_string.empty() && string_set != _mfem_devices)
3363 0 : mooseError("Attempted to configure with "
3364 : "MFEM devices '",
3365 0 : MooseUtils::join(string_set, " "),
3366 : "', but we have already "
3367 : "configured the MFEM device "
3368 : "object with the devices '",
3369 0 : MooseUtils::join(_mfem_devices, " "),
3370 : "'");
3371 1361 : }
3372 : #endif
|