Line data Source code
1 : //* This file is part of the MOOSE framework
2 : //* https://mooseframework.inl.gov
3 : //*
4 : //* All rights reserved, see COPYRIGHT for full restrictions
5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 : //*
7 : //* Licensed under LGPL 2.1, please see LICENSE for details
8 : //* https://www.gnu.org/licenses/lgpl-2.1.html
9 :
10 : #include "Exodus.h"
11 :
12 : // Moose includes
13 : #include "DisplacedProblem.h"
14 : #include "ExodusFormatter.h"
15 : #include "FEProblem.h"
16 : #include "FileMesh.h"
17 : #include "MooseApp.h"
18 : #include "MooseVariableScalar.h"
19 : #include "LockFile.h"
20 :
21 : #include "libmesh/exodusII_io.h"
22 : #include "libmesh/libmesh_config.h" // LIBMESH_HAVE_HDF5
23 :
24 : using namespace libMesh;
25 :
26 : registerMooseObject("MooseApp", Exodus);
27 :
28 : InputParameters
29 86020 : Exodus::validParams()
30 : {
31 : // Get the base class parameters
32 86020 : InputParameters params = SampledOutput::validParams();
33 : params +=
34 86020 : AdvancedOutput::enableOutputTypes("nodal elemental scalar postprocessor reporter input");
35 :
36 : // Enable sequential file output (do not set default, the use_displace criteria relies on
37 : // isParamValid, see Constructor)
38 86020 : params.addParam<bool>("sequence",
39 : "Enable/disable sequential file output (enabled by default "
40 : "when 'use_displace = true', otherwise defaults to false");
41 :
42 : // Select problem dimension for mesh output
43 86020 : params.addDeprecatedParam<bool>("use_problem_dimension",
44 : "Use the problem dimension to the mesh output. "
45 : "Set to false when outputting lower dimensional "
46 : "meshes embedded in a higher dimensional space.",
47 : "Use 'output_dimension = problem_dimension' instead.");
48 :
49 86020 : MooseEnum output_dimension("default 1 2 3 problem_dimension", "default");
50 :
51 86020 : params.addParam<MooseEnum>(
52 : "output_dimension", output_dimension, "The dimension of the output file");
53 :
54 86020 : params.addParamNamesToGroup("output_dimension", "Advanced");
55 :
56 : // Set the default padding to 3
57 86020 : params.set<unsigned int>("padding") = 3;
58 :
59 : // Add description for the Exodus class
60 86020 : params.addClassDescription("Object for output data in the Exodus format");
61 :
62 : // Flag for overwriting at each timestep
63 258060 : params.addParam<bool>("overwrite",
64 172040 : false,
65 : "When true the latest timestep will overwrite the "
66 : "existing file, so only a single timestep exists.");
67 :
68 : // Set outputting of the input to be on by default
69 86020 : params.set<ExecFlagEnum>("execute_input_on") = EXEC_INITIAL;
70 :
71 : // Flag for outputting discontinuous data to Exodus
72 258060 : params.addParam<bool>(
73 172040 : "discontinuous", false, "Enables discontinuous output format for Exodus files.");
74 :
75 : // Flag for outputting added side elements (for side-discontinuous data) to Exodus
76 258060 : params.addParam<bool>(
77 172040 : "side_discontinuous", false, "Enables adding side-discontinuous output in Exodus files.");
78 :
79 : // Flag for outputting Exodus data in HDF5 format (when libMesh is
80 : // configured with HDF5 support). libMesh wants to do so by default
81 : // (for backwards compatibility with libMesh HDF5 users), but we
82 : // want to avoid this by default (for backwards compatibility with
83 : // most Moose users and to avoid generating regression test gold
84 : // files that non-HDF5 Moose builds can't read)
85 86020 : params.addParam<bool>("write_hdf5", false, "Enables HDF5 output format for Exodus files.");
86 :
87 : // Need a layer of geometric ghosting for mesh serialization
88 86020 : params.addRelationshipManager("ElementPointNeighborLayers",
89 : Moose::RelationshipManagerType::GEOMETRIC);
90 :
91 : // Return the InputParameters
92 172040 : return params;
93 86020 : }
94 :
95 34725 : Exodus::Exodus(const InputParameters & parameters)
96 : : SampledOutput(parameters),
97 34713 : _exodus_initialized(false),
98 34713 : _exodus_mesh_changed(declareRestartableData<bool>("exodus_mesh_changed", true)),
99 69396 : _sequence(isParamValid("sequence") ? getParam<bool>("sequence")
100 34683 : : _use_displaced ? true
101 : : false),
102 34713 : _exodus_num(declareRestartableData<unsigned int>("exodus_num", 0)),
103 34713 : _recovering(_app.isRecovering()),
104 34713 : _overwrite(getParam<bool>("overwrite")),
105 34713 : _output_dimension(getParam<MooseEnum>("output_dimension").getEnum<OutputDimension>()),
106 34713 : _discontinuous(getParam<bool>("discontinuous")),
107 34713 : _side_discontinuous(getParam<bool>("side_discontinuous")),
108 104151 : _write_hdf5(getParam<bool>("write_hdf5"))
109 : {
110 34713 : if (isParamValid("use_problem_dimension"))
111 : {
112 0 : auto use_problem_dimension = getParam<bool>("use_problem_dimension");
113 :
114 0 : if (use_problem_dimension)
115 0 : _output_dimension = OutputDimension::PROBLEM_DIMENSION;
116 : else
117 0 : _output_dimension = OutputDimension::DEFAULT;
118 : }
119 : // If user sets 'discontinuous = true' and 'elemental_as_nodal = false', issue an error that these
120 : // are incompatible states
121 34713 : if (_discontinuous && parameters.isParamSetByUser("elemental_as_nodal") && !_elemental_as_nodal)
122 0 : mooseError(name(),
123 : ": Invalid parameters. 'elemental_as_nodal' set to false while 'discontinuous' set "
124 : "to true.");
125 : // At this point, if we have discontinuous ouput, we know the user hasn't explicitly set
126 : // 'elemental_as_nodal = false', so we can safely default it to true
127 34713 : if (_discontinuous)
128 36 : _elemental_as_nodal = true;
129 34713 : }
130 :
131 : void
132 0 : Exodus::setOutputDimension(unsigned int /*dim*/)
133 : {
134 0 : mooseDeprecated(
135 : "This method is no longer needed. We can determine output dimension programmatically");
136 0 : }
137 :
138 : void
139 34250 : Exodus::initialSetup()
140 : {
141 : // Call base class setup method
142 34250 : SampledOutput::initialSetup();
143 :
144 : // The libMesh::ExodusII_IO will fail when it is closed if the object is created but
145 : // nothing is written to the file. This checks that at least something will be written.
146 34246 : if (!hasOutput())
147 0 : mooseError("The current settings result in nothing being output to the Exodus file.");
148 :
149 : // Test that some sort of variable output exists (case when all variables are disabled but input
150 : // output is still enabled
151 34294 : if (!hasNodalVariableOutput() && !hasElementalVariableOutput() && !hasPostprocessorOutput() &&
152 48 : !hasScalarOutput())
153 4 : mooseError("The current settings results in only the input file and no variables being output "
154 : "to the Exodus file, this is not supported.");
155 34242 : }
156 :
157 : void
158 4742 : Exodus::meshChanged()
159 : {
160 : // Maintain Sampled::meshChanged() functionality
161 4742 : SampledOutput::meshChanged();
162 :
163 : // Indicate to the Exodus object that the mesh has changed
164 4742 : _exodus_mesh_changed = true;
165 4742 : }
166 :
167 : void
168 0 : Exodus::sequence(bool state)
169 : {
170 0 : _sequence = state;
171 0 : }
172 :
173 : void
174 157257 : Exodus::outputSetup()
175 : {
176 157257 : if (_exodus_io_ptr)
177 : {
178 : // Do nothing if the ExodusII_IO objects exists, but has not been initialized
179 124614 : if (!_exodus_initialized)
180 121243 : return;
181 :
182 : // Do nothing if the output is using oversampling. In this case the mesh that is being output
183 : // has not been changed, so there is no need to create a new ExodusII_IO object
184 119272 : if (_use_sampled_output)
185 282 : return;
186 :
187 : // Do nothing if the mesh has not changed and sequential output is not desired
188 118990 : if (!_exodus_mesh_changed && !_sequence)
189 115619 : return;
190 : }
191 :
192 76394 : auto serialize = [this](auto & moose_mesh)
193 : {
194 38197 : auto & lm_mesh = moose_mesh.getMesh();
195 : // Exodus is serial output so that we have to gather everything to "zero".
196 38197 : lm_mesh.gather_to_zero();
197 : // This makes the face information out-of-date on process 0 for distributed meshes, e.g.
198 : // elements will have neighbors that they didn't previously have
199 38197 : if ((this->processor_id() == 0) && !lm_mesh.is_replicated())
200 3791 : moose_mesh.markFiniteVolumeInfoDirty();
201 74211 : };
202 36014 : serialize(_problem_ptr->mesh());
203 :
204 : // We need to do the same thing for displaced mesh to make them consistent.
205 : // In general, it is a good idea to make the reference mesh and the displaced mesh
206 : // consistent since some operations or calculations are already based on this assumption.
207 : // For example,
208 : // FlagElementsThread::onElement(const Elem * elem)
209 : // if (_displaced_problem)
210 : // _displaced_problem->mesh().elemPtr(elem->id())->set_refinement_flag((Elem::RefinementState)marker_value);
211 : // Here we assume that the displaced mesh and the reference mesh are identical except
212 : // coordinations.
213 36014 : if (_problem_ptr->getDisplacedProblem())
214 2183 : serialize(_problem_ptr->getDisplacedProblem()->mesh());
215 :
216 : // Create the ExodusII_IO object
217 36014 : _exodus_io_ptr = std::make_unique<ExodusII_IO>(_es_ptr->get_mesh());
218 36014 : _exodus_initialized = false;
219 :
220 36014 : if (_write_hdf5)
221 : {
222 : #ifndef LIBMESH_HAVE_HDF5
223 : mooseError("Moose input requested HDF Exodus output, but libMesh was built without HDF5.");
224 : #endif
225 :
226 : // This is redundant unless the libMesh default changes
227 0 : _exodus_io_ptr->set_hdf5_writing(true);
228 : }
229 : else
230 : {
231 36014 : _exodus_io_ptr->set_hdf5_writing(false);
232 : }
233 :
234 36014 : if (_side_discontinuous)
235 22 : _exodus_io_ptr->write_added_sides(true);
236 :
237 : // Increment file number and set appending status, append if all the following conditions are met:
238 : // (1) If the application is recovering (not restarting)
239 : // (2) The mesh has NOT changed
240 : // (3) An existing Exodus file exists for appending (_exodus_num > 0)
241 : // (4) Sequential output is NOT desired
242 : // (5) Exodus is NOT being output only on FINAL
243 36789 : if (_recovering && !_exodus_mesh_changed && _exodus_num > 0 && !_sequence &&
244 775 : (getExecuteOnEnum().size() != 1 || !getExecuteOnEnum().contains(EXEC_FINAL)))
245 : {
246 : // Set the recovering flag to false so that this special case is not triggered again
247 775 : _recovering = false;
248 :
249 : // Set the append flag to true b/c on recover the file is being appended
250 775 : _exodus_io_ptr->append(true);
251 : }
252 : else
253 : {
254 : // Disable file appending and reset exodus file number count
255 35239 : _exodus_io_ptr->append(false);
256 :
257 : // Customize file output
258 35239 : customizeFileOutput();
259 : }
260 :
261 36014 : setOutputDimensionInExodusWriter(*_exodus_io_ptr, *_mesh_ptr, _output_dimension);
262 : }
263 :
264 : void
265 35239 : Exodus::customizeFileOutput()
266 : {
267 35239 : if (_exodus_mesh_changed || _sequence)
268 34428 : _file_num++;
269 :
270 35239 : _exodus_num = 1;
271 35239 : }
272 :
273 : void
274 39020 : Exodus::setOutputDimensionInExodusWriter(ExodusII_IO & exodus_io,
275 : const MooseMesh & mesh,
276 : OutputDimension output_dimension)
277 : {
278 39020 : switch (output_dimension)
279 : {
280 39009 : case OutputDimension::DEFAULT:
281 : // If the mesh_dimension is 1, we need to write out as 3D.
282 : //
283 : // This works around an issue in Paraview where 1D meshes cannot
284 : // not be visualized correctly. Otherwise, write out based on the effectiveSpatialDimension.
285 39009 : if (mesh.getMesh().mesh_dimension() == 1)
286 4391 : exodus_io.write_as_dimension(3);
287 : else
288 34618 : exodus_io.write_as_dimension(static_cast<int>(mesh.effectiveSpatialDimension()));
289 39009 : break;
290 :
291 11 : case OutputDimension::ONE:
292 : case OutputDimension::TWO:
293 : case OutputDimension::THREE:
294 11 : exodus_io.write_as_dimension(static_cast<int>(output_dimension));
295 11 : break;
296 :
297 0 : case OutputDimension::PROBLEM_DIMENSION:
298 0 : exodus_io.use_mesh_dimension_instead_of_spatial_dimension(true);
299 0 : break;
300 :
301 0 : default:
302 0 : ::mooseError("Unknown output_dimension in Exodus writer");
303 : }
304 39020 : }
305 :
306 : void
307 143082 : Exodus::outputNodalVariables()
308 : {
309 : // Set the output variable to the nodal variables
310 143082 : std::vector<std::string> nodal(getNodalVariableOutput().begin(), getNodalVariableOutput().end());
311 143082 : _exodus_io_ptr->set_output_variables(nodal);
312 :
313 : // Check if the mesh is contiguously numbered, because exodus output will renumber to force that
314 143082 : const auto & mesh = _problem_ptr->mesh().getMesh();
315 : const bool mesh_contiguous_numbering =
316 143082 : (mesh.n_nodes() == mesh.max_node_id()) && (mesh.n_elem() == mesh.max_elem_id());
317 :
318 : // Write the data via libMesh::ExodusII_IO
319 143082 : if (_discontinuous)
320 110 : _exodus_io_ptr->write_timestep_discontinuous(
321 110 : filename(), *_es_ptr, _exodus_num, getOutputTime() + _app.getGlobalTimeOffset());
322 : else
323 286054 : _exodus_io_ptr->write_timestep(
324 286054 : filename(), *_es_ptr, _exodus_num, getOutputTime() + _app.getGlobalTimeOffset());
325 :
326 143082 : if (!_overwrite)
327 139728 : _exodus_num++;
328 :
329 143082 : if (!mesh_contiguous_numbering)
330 35 : handleExodusIOMeshRenumbering();
331 :
332 : // This satisfies the initialization of the ExodusII_IO object
333 143082 : _exodus_initialized = true;
334 143082 : }
335 :
336 : void
337 33721 : Exodus::outputElementalVariables()
338 : {
339 : // Make sure the the file is ready for writing of elemental data
340 33721 : if (!_exodus_initialized || !hasNodalVariableOutput())
341 8010 : outputEmptyTimestep();
342 :
343 : // Write the elemental data
344 33721 : std::vector<std::string> elemental(getElementalVariableOutput().begin(),
345 67442 : getElementalVariableOutput().end());
346 33721 : _exodus_io_ptr->set_output_variables(elemental);
347 33721 : _exodus_io_ptr->write_element_data(*_es_ptr);
348 33721 : }
349 :
350 : void
351 33499 : Exodus::outputPostprocessors()
352 : {
353 : // List of desired postprocessor outputs
354 33499 : const std::set<std::string> & pps = getPostprocessorOutput();
355 :
356 : // Append the postprocessor data to the global name value parameters; scalar outputs
357 : // also append these member variables
358 94229 : for (const auto & name : pps)
359 : {
360 60730 : _global_names.push_back(name);
361 60730 : _global_values.push_back(_problem_ptr->getPostprocessorValueByName(name));
362 : }
363 33499 : }
364 :
365 : void
366 457 : Exodus::outputReporters()
367 : {
368 2012 : for (const auto & combined_name : getReporterOutput())
369 : {
370 1555 : ReporterName r_name(combined_name);
371 1772 : if (_reporter_data.hasReporterValue<Real>(r_name) &&
372 1772 : !hasPostprocessorByName(r_name.getObjectName()))
373 : {
374 217 : const Real & value = _reporter_data.getReporterValue<Real>(r_name);
375 217 : _global_names.push_back(r_name.getValueName());
376 217 : _global_values.push_back(value);
377 : }
378 1555 : }
379 457 : }
380 :
381 : void
382 3888 : Exodus::outputScalarVariables()
383 : {
384 : // List of desired scalar outputs
385 3888 : const std::set<std::string> & out = getScalarOutput();
386 :
387 : // Append the scalar to the global output lists
388 10745 : for (const auto & out_name : out)
389 : {
390 : // Make sure scalar values are in sync with the solution vector
391 : // and are visible on this processor. See TableOutput.C for
392 : // TableOutput::outputScalarVariables() explanatory comments
393 :
394 6857 : MooseVariableScalar & scalar_var = _problem_ptr->getScalarVariable(0, out_name);
395 6857 : scalar_var.reinit();
396 6857 : VariableValue value(scalar_var.sln());
397 :
398 6857 : const std::vector<dof_id_type> & dof_indices = scalar_var.dofIndices();
399 6857 : const unsigned int n = dof_indices.size();
400 6857 : value.resize(n);
401 :
402 6857 : const DofMap & dof_map = scalar_var.sys().dofMap();
403 14774 : for (unsigned int i = 0; i != n; ++i)
404 : {
405 7917 : const processor_id_type pid = dof_map.dof_owner(dof_indices[i]);
406 7917 : this->comm().broadcast(value[i], pid);
407 : }
408 :
409 : // If the scalar has a single component, output the name directly
410 6857 : if (n == 1)
411 : {
412 6296 : _global_names.push_back(out_name);
413 6296 : _global_values.push_back(value[0]);
414 : }
415 :
416 : // If the scalar as many components add indices to the end of the name
417 : else
418 : {
419 2182 : for (unsigned int i = 0; i < n; ++i)
420 : {
421 1621 : std::ostringstream os;
422 1621 : os << out_name << "_" << i;
423 1621 : _global_names.push_back(os.str());
424 1621 : _global_values.push_back(value[i]);
425 1621 : }
426 : }
427 6857 : }
428 3888 : }
429 :
430 : void
431 31628 : Exodus::outputInput()
432 : {
433 : // Format the input file
434 31628 : ExodusFormatter syntax_formatter;
435 31628 : syntax_formatter.printInputFile(_app.actionWarehouse());
436 31628 : syntax_formatter.format();
437 :
438 : // Store the information
439 31628 : _input_record = syntax_formatter.getInputFileRecord();
440 31628 : }
441 :
442 : void
443 157257 : Exodus::output()
444 : {
445 : // Prepare the ExodusII_IO object
446 157257 : outputSetup();
447 157257 : LockFile lf(filename(), processor_id() == 0);
448 :
449 : // Adjust the position of the output
450 157257 : if (_app.hasOutputPosition())
451 5833 : _exodus_io_ptr->set_coordinate_offset(_app.getOutputPosition());
452 :
453 : // Clear the global variables (postprocessors and scalars)
454 157257 : _global_names.clear();
455 157257 : _global_values.clear();
456 :
457 : // Call the individual output methods
458 157257 : AdvancedOutput::output();
459 :
460 : // Write the global variables (populated by the output methods)
461 157257 : if (!_global_values.empty())
462 : {
463 34549 : if (!_exodus_initialized)
464 42 : outputEmptyTimestep();
465 34549 : _exodus_io_ptr->write_global_data(_global_values, _global_names);
466 : }
467 :
468 : // Write the input file record if it exists and the output file is initialized
469 157257 : if (!_input_record.empty() && _exodus_initialized)
470 : {
471 31543 : _exodus_io_ptr->write_information_records(_input_record);
472 31543 : _input_record.clear();
473 : }
474 :
475 : // Reset the mesh changed flag
476 157257 : _exodus_mesh_changed = false;
477 :
478 : // It is possible to have an empty file created with the following scenario. By default the
479 : // 'execute_on_input' flag is setup to run on INITIAL. If the 'execute_on' is set to FINAL
480 : // but the simulation stops early (e.g., --test-checkpoint-half-transient) the Exodus file is
481 : // created but there is no data in it, because of the initial call to write the input data seems
482 : // to create the file but doesn't actually write the data into the solution/mesh is also supplied
483 : // to the IO object. Then if --recover is used this empty file fails to open for appending.
484 : //
485 : // The code below will delete any empty files that exist. Another solution is to set the
486 : // 'execute_on_input' flag to NONE.
487 157257 : std::string current = filename();
488 273643 : if (processor_id() == 0 && MooseUtils::checkFileReadable(current, false, false) &&
489 116386 : (MooseUtils::fileSize(current) == 0))
490 : {
491 3310 : int err = std::remove(current.c_str());
492 3310 : if (err != 0)
493 0 : mooseError("MOOSE failed to remove the empty file ", current);
494 : }
495 157257 : }
496 :
497 : std::string
498 499950 : Exodus::filename()
499 : {
500 : // Append the .e extension on the base file name
501 499950 : std::ostringstream output;
502 499950 : output << _file_base + ".e";
503 :
504 : // Add the -s00x extension to the file
505 499950 : if (_file_num > 1)
506 15250 : output << "-s" << std::setw(_padding) << std::setprecision(0) << std::setfill('0') << std::right
507 15250 : << _file_num;
508 :
509 999900 : return output.str();
510 499950 : }
511 :
512 : void
513 8052 : Exodus::outputEmptyTimestep()
514 : {
515 : // Check if the mesh is contiguously numbered, because exodus output will renumber to force that
516 8052 : const auto & mesh = _problem_ptr->mesh().getMesh();
517 : const bool mesh_contiguous_numbering =
518 8052 : (mesh.n_nodes() == mesh.max_node_id()) && (mesh.n_elem() == mesh.max_elem_id());
519 :
520 : // Write a timestep with no variables
521 8052 : _exodus_io_ptr->set_output_variables(std::vector<std::string>());
522 16104 : _exodus_io_ptr->write_timestep(
523 16104 : filename(), *_es_ptr, _exodus_num, getOutputTime() + _app.getGlobalTimeOffset());
524 :
525 8052 : if (!_overwrite)
526 8008 : _exodus_num++;
527 :
528 8052 : if (!mesh_contiguous_numbering)
529 22 : handleExodusIOMeshRenumbering();
530 8052 : _exodus_initialized = true;
531 8052 : }
532 :
533 : void
534 2463 : Exodus::clear()
535 : {
536 2463 : _exodus_io_ptr.reset();
537 2463 : }
538 :
539 : void
540 57 : Exodus::handleExodusIOMeshRenumbering()
541 : {
542 : // We renumbered our mesh, so we need the other mesh to do the same
543 57 : if (auto * const disp_problem = _problem_ptr->getDisplacedProblem().get(); disp_problem)
544 : {
545 11 : auto & disp_eq = disp_problem->es();
546 11 : auto & other_mesh = &disp_eq == _es_ptr ? _problem_ptr->mesh().getMesh() : disp_eq.get_mesh();
547 : mooseAssert(
548 : !other_mesh.allow_renumbering(),
549 : "The only way we shouldn't have contiguous numbering is if we've disabled renumbering");
550 11 : other_mesh.allow_renumbering(true);
551 11 : other_mesh.renumber_nodes_and_elements();
552 : // Copying over the comment in MeshOutput::write_equation_systems
553 : // Not sure what good going back to false will do here, the
554 : // renumbering horses have already left the barn...
555 11 : other_mesh.allow_renumbering(false);
556 : }
557 :
558 : // Objects that depend on element/node ids are no longer valid
559 57 : _problem_ptr->meshChanged(
560 : /*intermediate_change=*/false, /*contract_mesh=*/false, /*clean_refinement_flags=*/false);
561 57 : }
|