Serac  0.1
Serac is an implicit thermal strucural mechanics simulation code.
serac.cpp
Go to the documentation of this file.
1 // Copyright (c) 2019-2024, Lawrence Livermore National Security, LLC and
2 // other Serac Project Developers. See the top-level LICENSE file for
3 // details.
4 //
5 // SPDX-License-Identifier: (BSD-3-Clause)
6 
15 #include <fstream>
16 #include <iostream>
17 #include <memory>
18 #include <string>
19 
20 #include "axom/core.hpp"
21 #include "mfem.hpp"
22 
36 #include "serac/serac_config.hpp"
37 
38 namespace serac {
39 
45 void defineInputFileSchema(axom::inlet::Inlet& inlet)
46 {
47  // Simulation time parameters
48  inlet.addDouble("t_final", "Final time for simulation.").defaultValue(1.0);
49  inlet.addDouble("dt", "Time step.").defaultValue(0.25);
50 
51  // The mesh options
52  auto& mesh_table = inlet.addStruct("main_mesh", "The main mesh for the problem");
54 
55  // The solid mechanics options
56  auto& solid_solver_table = inlet.addStruct("solid", "Finite deformation solid mechanics module");
58 
59  // The heat transfer options
60  auto& thermal_solver_table = inlet.addStruct("heat_transfer", "Heat transfer module");
62 
63  // The thermal solid options
64  auto& thermal_solid_solver_table = inlet.addStruct("thermal_solid", "Thermal solid module");
65  ThermomechanicsInputOptions::defineInputFileSchema(thermal_solid_solver_table);
66 
67  // Verify the input file
68  if (!inlet.verify()) {
69  SLIC_ERROR_ROOT("Input file failed to verify.");
70  }
71 }
72 
73 } // namespace serac
74 
89 std::unique_ptr<serac::BasePhysics> createPhysics(
90  int dim, int order, std::optional<serac::SolidMechanicsInputOptions> solid_mechanics_options,
91  std::optional<serac::HeatTransferInputOptions> heat_transfer_options,
92  std::optional<serac::ThermomechanicsInputOptions> thermomechanics_options, std::string mesh_tag, int cycle,
93  double t)
94 {
95  std::unique_ptr<serac::BasePhysics> main_physics;
96  if (thermomechanics_options) {
97  if (order == 1) {
98  if (dim == 2) {
99  main_physics =
100  std::make_unique<serac::Thermomechanics<1, 2>>(*thermomechanics_options, "serac", mesh_tag, cycle, t);
101  } else if (dim == 3) {
102  main_physics =
103  std::make_unique<serac::Thermomechanics<1, 3>>(*thermomechanics_options, "serac", mesh_tag, cycle, t);
104  }
105  } else if (order == 2) {
106  if (dim == 2) {
107  main_physics =
108  std::make_unique<serac::Thermomechanics<2, 2>>(*thermomechanics_options, "serac", mesh_tag, cycle, t);
109  } else if (dim == 3) {
110  main_physics =
111  std::make_unique<serac::Thermomechanics<2, 3>>(*thermomechanics_options, "serac", mesh_tag, cycle, t);
112  }
113  } else if (order == 3) {
114  if (dim == 2) {
115  main_physics =
116  std::make_unique<serac::Thermomechanics<3, 2>>(*thermomechanics_options, "serac", mesh_tag, cycle, t);
117  } else if (dim == 3) {
118  main_physics =
119  std::make_unique<serac::Thermomechanics<3, 3>>(*thermomechanics_options, "serac", mesh_tag, cycle, t);
120  }
121  }
122  } else if (solid_mechanics_options && heat_transfer_options) {
123  if (order == 1) {
124  if (dim == 2) {
125  main_physics = std::make_unique<serac::Thermomechanics<1, 2>>(*heat_transfer_options, *solid_mechanics_options,
126  "serac", mesh_tag, cycle, t);
127  } else if (dim == 3) {
128  main_physics = std::make_unique<serac::Thermomechanics<1, 3>>(*heat_transfer_options, *solid_mechanics_options,
129  "serac", mesh_tag, cycle, t);
130  }
131  } else if (order == 2) {
132  if (dim == 2) {
133  main_physics = std::make_unique<serac::Thermomechanics<2, 2>>(*heat_transfer_options, *solid_mechanics_options,
134  "serac", mesh_tag, cycle, t);
135  } else if (dim == 3) {
136  main_physics = std::make_unique<serac::Thermomechanics<2, 3>>(*heat_transfer_options, *solid_mechanics_options,
137  "serac", mesh_tag, cycle, t);
138  }
139  } else if (order == 3) {
140  if (dim == 2) {
141  main_physics = std::make_unique<serac::Thermomechanics<3, 2>>(*heat_transfer_options, *solid_mechanics_options,
142  "serac", mesh_tag, cycle, t);
143  } else if (dim == 3) {
144  main_physics = std::make_unique<serac::Thermomechanics<3, 3>>(*heat_transfer_options, *solid_mechanics_options,
145  "serac", mesh_tag, cycle, t);
146  }
147  }
148  } else if (solid_mechanics_options) {
149  if (order == 1) {
150  if (dim == 2) {
151  main_physics =
152  std::make_unique<serac::SolidMechanics<1, 2>>(*solid_mechanics_options, "serac", mesh_tag, cycle, t);
153  } else if (dim == 3) {
154  main_physics =
155  std::make_unique<serac::SolidMechanics<1, 3>>(*solid_mechanics_options, "serac", mesh_tag, cycle, t);
156  }
157  } else if (order == 2) {
158  if (dim == 2) {
159  main_physics =
160  std::make_unique<serac::SolidMechanics<2, 2>>(*solid_mechanics_options, "serac", mesh_tag, cycle, t);
161  } else if (dim == 3) {
162  main_physics =
163  std::make_unique<serac::SolidMechanics<2, 3>>(*solid_mechanics_options, "serac", mesh_tag, cycle, t);
164  }
165  } else if (order == 3) {
166  if (dim == 2) {
167  main_physics =
168  std::make_unique<serac::SolidMechanics<3, 2>>(*solid_mechanics_options, "serac", mesh_tag, cycle, t);
169  } else if (dim == 3) {
170  main_physics =
171  std::make_unique<serac::SolidMechanics<3, 3>>(*solid_mechanics_options, "serac", mesh_tag, cycle, t);
172  }
173  }
174  } else if (heat_transfer_options) {
175  if (order == 1) {
176  if (dim == 2) {
177  main_physics = std::make_unique<serac::HeatTransfer<1, 2>>(*heat_transfer_options, "serac", mesh_tag, cycle, t);
178  } else if (dim == 3) {
179  main_physics = std::make_unique<serac::HeatTransfer<1, 3>>(*heat_transfer_options, "serac", mesh_tag, cycle, t);
180  }
181  } else if (order == 2) {
182  if (dim == 2) {
183  main_physics = std::make_unique<serac::HeatTransfer<2, 2>>(*heat_transfer_options, "serac", mesh_tag, cycle, t);
184  } else if (dim == 3) {
185  main_physics = std::make_unique<serac::HeatTransfer<2, 3>>(*heat_transfer_options, "serac", mesh_tag, cycle, t);
186  }
187  } else if (order == 3) {
188  if (dim == 2) {
189  main_physics = std::make_unique<serac::HeatTransfer<3, 2>>(*heat_transfer_options, "serac", mesh_tag, cycle, t);
190  } else if (dim == 3) {
191  main_physics = std::make_unique<serac::HeatTransfer<3, 3>>(*heat_transfer_options, "serac", mesh_tag, cycle, t);
192  }
193  }
194  } else {
195  SLIC_ERROR_ROOT("Neither solid, heat_transfer, nor thermal_solid blocks specified in the input file.");
196  }
197  return main_physics;
198 }
199 
209 int getOrder(std::optional<serac::SolidMechanicsInputOptions> solid_mechanics_options,
210  std::optional<serac::HeatTransferInputOptions> heat_transfer_options,
211  std::optional<serac::ThermomechanicsInputOptions> thermomechanics_options)
212 {
213  int order = 0;
214  if (thermomechanics_options) {
215  order = thermomechanics_options->solid_options.order;
216  int thermal_order = thermomechanics_options->thermal_options.order;
217  SLIC_ERROR_ROOT_IF(
218  order != thermal_order,
219  axom::fmt::format("Solid order '{0}' and thermal order '{1}'' do not match.", order, thermal_order));
220  } else if (solid_mechanics_options && heat_transfer_options) {
221  order = solid_mechanics_options->order;
222  int thermal_order = heat_transfer_options->order;
223  SLIC_ERROR_ROOT_IF(
224  order != thermal_order,
225  axom::fmt::format("Solid order '{0}' and thermal order '{1}'' do not match.", order, thermal_order));
226  } else if (solid_mechanics_options) {
227  order = solid_mechanics_options->order;
228  } else if (heat_transfer_options) {
229  order = heat_transfer_options->order;
230  } else {
231  SLIC_ERROR_ROOT("Neither solid, heat_transfer, nor thermal_solid blocks specified in the input file.");
232  }
233  SLIC_ERROR_ROOT_IF(order < 1 || order > 3,
234  axom::fmt::format("Invalid solver order '{0}' given. Valid values are 1, 2, or 3.", order));
235  return order;
236 }
237 
246 int main(int argc, char* argv[])
247 {
248  serac::initialize(argc, argv);
249 
250  // TODO: Remove this after input file functionality is restored.
251  const std::string warning_message = R"PREFIX(******************************
252 This Serac driver does not
253 currently work due to missing
254 input file functionality.
255 ******************************)PREFIX";
256  SLIC_INFO(warning_message);
257 
258  // Handle Command line
259  std::unordered_map<std::string, std::string> cli_opts =
260  serac::cli::defineAndParse(argc, argv, "Serac: a high order nonlinear thermomechanical simulation code");
261 
262  // Optionally, print about info and quit
263  // TODO: add option for just version and a longer for about?
264  bool print_version = cli_opts.find("version") != cli_opts.end();
265  if (print_version) {
266  SLIC_INFO(serac::about());
268  }
269 
270  // Output helpful run information
272  serac::cli::printGiven(cli_opts);
273 
274  // Read input file
275  std::string input_file_path = "";
276  auto search = cli_opts.find("input-file");
277  if (search != cli_opts.end()) {
278  input_file_path = search->second;
279  }
280 
281  // Output directory used for all files written to the file system.
282  // Example of outputted files:
283  //
284  // * Inlet docs + input file value file
285  // * StateManager state files
286  // * Summary file
287  std::string output_directory = "";
288  search = cli_opts.find("output-directory");
289  if (search != cli_opts.end()) {
290  output_directory = search->second;
291  }
292  axom::utilities::filesystem::makeDirsForPath(output_directory);
293 
294  search = cli_opts.find("paraview-directory");
295 
296  std::optional<std::string> paraview_output_dir = {};
297  if (search != cli_opts.end()) {
298  paraview_output_dir = search->second;
299  axom::utilities::filesystem::makeDirsForPath(*paraview_output_dir);
300  }
301 
302  // Check if a restart was requested
303  std::optional<int> restart_cycle;
304  if (auto cycle = cli_opts.find("restart-cycle"); cycle != cli_opts.end()) {
305  restart_cycle = std::stoi(cycle->second);
306  }
307 
308  // Create DataStore
309  axom::sidre::DataStore datastore;
310 
311  // Intialize MFEMSidreDataCollection
312  serac::StateManager::initialize(datastore, output_directory);
313 
314  // Initialize Inlet and read input file
315  auto inlet = serac::input::initialize(datastore, input_file_path);
317 
318  // Optionally, create input file documentation and quit
319  bool create_input_file_docs = cli_opts.find("create-input-file-docs") != cli_opts.end();
320  if (create_input_file_docs) {
321  std::string input_docs_path = axom::utilities::filesystem::joinPath(output_directory, "serac_input.rst");
322  inlet.write(axom::inlet::SphinxWriter(input_docs_path));
324  }
325 
326  // Optionally, print unused entries in input file and quit
327  if (cli_opts.find("print-unused") != cli_opts.end()) {
328  const std::vector<std::string> all_unexpected_names = inlet.unexpectedNames();
329  if (all_unexpected_names.size() != 0) {
330  SLIC_INFO("Printing unused entries in input file:");
331  for (auto& x : all_unexpected_names) {
332  SLIC_INFO(" " << x);
333  }
334  } else {
335  SLIC_INFO("No unused entries in input file.");
336  }
338  }
339 
340  // Save input values to file
341  std::string input_values_path = axom::utilities::filesystem::joinPath(output_directory, "serac_input_values.json");
342  datastore.getRoot()->getGroup("input_file")->save(input_values_path, "json");
343 
344  // Initialize/set the time information
345  double t = 0;
346  double t_final = inlet["t_final"];
347  double dt = inlet["dt"];
348  int cycle = 0;
349 
350  std::string mesh_tag{"mesh}"};
351 
352  // Not restarting, so we need to create the mesh and register it with the StateManager
353  if (!restart_cycle) {
354  // Build the mesh
355  auto mesh_options = inlet["main_mesh"].get<serac::mesh::InputOptions>();
356  if (const auto file_opts = std::get_if<serac::mesh::FileInputOptions>(&mesh_options.extra_options)) {
357  file_opts->absolute_mesh_file_name =
358  serac::input::findMeshFilePath(file_opts->relative_mesh_file_name, input_file_path);
359  }
360  auto mesh = serac::mesh::buildParallelMesh(mesh_options);
361  serac::StateManager::setMesh(std::move(mesh), mesh_tag);
362  } else {
363  // If restart_cycle is non-empty, then this is a restart run and the data will be loaded here
364  t = serac::StateManager::load(*restart_cycle, mesh_tag);
365  cycle = *restart_cycle;
366  }
367 
368  // Create nullable containers for the solid and thermal input file options
369  std::optional<serac::SolidMechanicsInputOptions> solid_mechanics_options;
370  std::optional<serac::HeatTransferInputOptions> heat_transfer_options;
371  std::optional<serac::ThermomechanicsInputOptions> thermomechanics_options;
372 
373  // If the blocks exist, read the appropriate input file options
374  if (inlet.isUserProvided("solid")) {
375  solid_mechanics_options = inlet["solid"].get<serac::SolidMechanicsInputOptions>();
376  }
377  if (inlet.isUserProvided("heat_transfer")) {
378  heat_transfer_options = inlet["heat_transfer"].get<serac::HeatTransferInputOptions>();
379  }
380  if (inlet.isUserProvided("thermal_solid")) {
381  thermomechanics_options = inlet["thermal_solid"].get<serac::ThermomechanicsInputOptions>();
382  }
383 
384  // Get dimension and order of problem
385  int dim = serac::StateManager::mesh(mesh_tag).Dimension();
386  SLIC_ERROR_ROOT_IF(dim < 2 || dim > 3,
387  axom::fmt::format("Invalid mesh dimension '{0}' provided. Valid values are 2 or 3.", dim));
388  int order = getOrder(solid_mechanics_options, heat_transfer_options, thermomechanics_options);
389 
390  // Create the physics object
391  auto main_physics = createPhysics(dim, order, solid_mechanics_options, heat_transfer_options, thermomechanics_options,
392  mesh_tag, cycle, t);
393 
394  // Complete the solver setup
395  main_physics->completeSetup();
396 
397  main_physics->initializeSummary(datastore, t_final, dt);
398 
399  // Enter the time step loop.
400  bool last_step = false;
401  while (!last_step) {
402  // Flush all messages held by the logger
403  serac::logger::flush();
404 
405  // Compute the real timestep. This may be less than dt for the last timestep.
406  double dt_real = std::min(dt, t_final - t);
407 
408  // Compute current time
409  t = t + dt_real;
410 
411  // Print the timestep information
412  SLIC_INFO_ROOT("step " << cycle << ", t = " << t);
413 
414  // Solve the physics module appropriately
415  main_physics->advanceTimestep(dt_real);
416 
417  // Output a visualization file
418  main_physics->outputStateToDisk(paraview_output_dir);
419 
420  // Save curve data to Sidre datastore to be output later
421  main_physics->saveSummary(datastore, t);
422 
423  // Determine if this is the last timestep
424  last_step = (t >= t_final - 1e-8 * dt);
425 
426  // Increment cycle
427  cycle++;
428  }
429 
430  // Output summary file (basic run info and curve data)
431  serac::output::outputSummary(datastore, output_directory);
432 
434 }
This file contains the interface used for retrieving information about how the driver is configured.
static void initialize(axom::sidre::DataStore &ds, const std::string &output_directory)
Initializes the StateManager with a sidre DataStore (into which state will be written/read)
static double load(const int cycle_to_load, const std::string &mesh_tag)
Loads an existing DataCollection.
static mfem::ParMesh & mesh(const std::string &mesh_tag)
Returns a non-owning reference to mesh held by StateManager.
static mfem::ParMesh & setMesh(std::unique_ptr< mfem::ParMesh > pmesh, const std::string &mesh_tag)
Gives ownership of mesh to StateManager.
This file contains the all the necessary functions and macros required for interacting with the comma...
This file contains the declaration of an equation solver wrapper.
An object containing the solver for a heat transfer PDE.
A function intended to be used as part of a driver to initialize common libraries.
This file contains the all the necessary functions for reading input files.
This file contains the all the necessary functions and macros required for logging as well as a helpe...
This file contains helper functions for importing and managing various mesh objects.
std::unordered_map< std::string, std::string > defineAndParse(int argc, char *argv[], std::string app_description)
Defines command line options and parses the found values.
Definition: cli.cpp:19
void printGiven(std::unordered_map< std::string, std::string > &cli_opts)
Prints all given command line options to the screen.
Definition: cli.cpp:104
axom::inlet::Inlet initialize(axom::sidre::DataStore &datastore, const std::string &input_file_path, const Language language, const std::string &sidre_path)
Initializes Inlet with the given datastore and input file.
Definition: input.cpp:20
std::string findMeshFilePath(const std::string &mesh_path, const std::string &input_file_path)
Returns the absolute path of the given mesh either relative to CWD or the input file.
Definition: input.cpp:46
std::unique_ptr< mfem::ParMesh > buildParallelMesh(const InputOptions &options, const MPI_Comm comm)
Constructs an MFEM parallel mesh from a set of input options.
Definition: mesh_utils.cpp:423
void outputSummary(const axom::sidre::DataStore &datastore, const std::string &output_directory, const FileFormat file_format)
Outputs simulation summary data from the datastore to the given file only on rank 0.
Definition: output.cpp:33
Accelerator functionality.
Definition: serac.cpp:38
void exitGracefully(bool error)
Exits the program gracefully after cleaning up necessary tasks.
Definition: terminator.cpp:44
SERAC_HOST_DEVICE auto min(dual< gradient_type > a, double b)
Implementation of min for dual numbers.
Definition: dual.hpp:256
std::pair< int, int > initialize(int argc, char *argv[], MPI_Comm comm)
Initializes MPI, signal handling, and logging.
Definition: initialize.cpp:40
std::string about()
Returns a string about the configuration of Serac.
Definition: about.cpp:53
void defineInputFileSchema(axom::inlet::Inlet &inlet)
Define the input file structure for the driver code.
Definition: serac.cpp:45
void printRunInfo()
Outputs basic run information to the screen.
Definition: about.cpp:192
This file contains the all the necessary functions for outputting simulation data.
int main(int argc, char *argv[])
The main serac driver code.
Definition: serac.cpp:246
int getOrder(std::optional< serac::SolidMechanicsInputOptions > solid_mechanics_options, std::optional< serac::HeatTransferInputOptions > heat_transfer_options, std::optional< serac::ThermomechanicsInputOptions > thermomechanics_options)
Return and check correctness of the order of discretization.
Definition: serac.cpp:209
std::unique_ptr< serac::BasePhysics > createPhysics(int dim, int order, std::optional< serac::SolidMechanicsInputOptions > solid_mechanics_options, std::optional< serac::HeatTransferInputOptions > heat_transfer_options, std::optional< serac::ThermomechanicsInputOptions > thermomechanics_options, std::string mesh_tag, int cycle, double t)
Constructs the appropriate physics object using the input file options.
Definition: serac.cpp:89
An object containing the solver for total Lagrangian finite deformation solid mechanics.
This file contains the declaration of the StateManager class.
Stores all information held in the input file that is used to configure the solver.
static void defineInputFileSchema(axom::inlet::Container &container)
Input file parameters specific to this class.
Stores all information held in the input file that is used to configure the solver.
static void defineInputFileSchema(axom::inlet::Container &container)
Input file parameters specific to this class.
Stores all information held in the input file that is used to configure the thermal structural solver...
static void defineInputFileSchema(axom::inlet::Container &container)
Input file parameters specific to this class.
Container for the mesh input options.
Definition: mesh_utils.hpp:27
static void defineInputFileSchema(axom::inlet::Container &container)
Input file parameters for mesh generation.
Definition: mesh_utils.cpp:394
Helper functions for exiting Serac cleanly.
An object containing an operator-split thermal structural solver.