Serac  0.1
Serac is an implicit thermal strucural mechanics simulation code.
base_physics.cpp
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 
8 
9 #include <fstream>
10 
11 #include "axom/fmt.hpp"
12 
18 
19 namespace serac {
20 
21 BasePhysics::BasePhysics(std::string physics_name, std::string mesh_tag, int cycle, double time,
22  bool checkpoint_to_disk)
23  : name_(physics_name),
24  mesh_tag_(mesh_tag),
25  mesh_(StateManager::mesh(mesh_tag_)),
26  comm_(mesh_.GetComm()),
27  shape_displacement_(StateManager::shapeDisplacement(mesh_tag_)),
28  bcs_(mesh_),
29  checkpoint_to_disk_(checkpoint_to_disk)
30 {
31  std::tie(mpi_size_, mpi_rank_) = getMPIInfo(comm_);
32 
33  if (mesh_.Dimension() == 2) {
35  std::make_unique<FiniteElementDual>(mesh_, H1<SHAPE_ORDER, 2>{}, name_ + "_shape_displacement_sensitivity");
36  } else if (mesh_.Dimension() == 3) {
38  std::make_unique<FiniteElementDual>(mesh_, H1<SHAPE_ORDER, 3>{}, name_ + "_shape_displacement_sensitivity");
39  } else {
40  SLIC_ERROR_ROOT(axom::fmt::format("Mesh of dimension {} given, only dimensions 2 or 3 are available in Serac.",
41  mesh_.Dimension()));
42  }
44 
46 }
47 
48 double BasePhysics::time() const { return time_; }
49 
50 int BasePhysics::cycle() const { return cycle_; }
51 
52 double BasePhysics::maxTime() const { return max_time_; }
53 
54 int BasePhysics::maxCycle() const { return max_cycle_; }
55 
56 double BasePhysics::minTime() const { return min_time_; }
57 
58 int BasePhysics::minCycle() const { return min_cycle_; }
59 
60 std::vector<double> BasePhysics::timesteps() const { return timesteps_; }
61 
62 void BasePhysics::initializeBasePhysicsStates(int cycle, double time)
63 {
64  timesteps_.clear();
65 
66  time_ = time;
67  max_time_ = time;
68  min_time_ = time;
69  cycle_ = cycle;
70  max_cycle_ = cycle;
71  min_cycle_ = cycle;
73 
75 
76  for (auto& p : parameters_) {
77  *p.sensitivity = 0.0;
78  }
79 }
80 
81 void BasePhysics::setParameter(const size_t parameter_index, const FiniteElementState& parameter_state)
82 {
83  SLIC_ERROR_ROOT_IF(
84  parameter_index >= parameters_.size(),
85  axom::fmt::format("Parameter '{}' requested when only '{}' parameters exist in physics module '{}'",
86  parameter_index, parameters_.size(), name_));
87 
88  SLIC_ERROR_ROOT_IF(&parameter_state.mesh() != &mesh_,
89  axom::fmt::format("Mesh of parameter '{}' is not the same as the physics mesh", parameter_index));
90 
91  SLIC_ERROR_ROOT_IF(
92  parameter_state.space().GetTrueVSize() != parameters_[parameter_index].state->space().GetTrueVSize(),
93  axom::fmt::format(
94  "Physics module parameter '{}' has size '{}' while given state has size '{}'. The finite element "
95  "spaces are inconsistent.",
96  parameter_index, parameters_[parameter_index].state->space().GetTrueVSize(),
97  parameter_state.space().GetTrueVSize()));
98  *parameters_[parameter_index].state = parameter_state;
99 }
100 
102 {
103  SLIC_ERROR_ROOT_IF(&shape_displacement.mesh() != &mesh_,
104  axom::fmt::format("Mesh of shape displacement is not the same as the physics mesh"));
105 
106  SLIC_ERROR_ROOT_IF(
107  shape_displacement.space().GetTrueVSize() != shape_displacement_.space().GetTrueVSize(),
108  axom::fmt::format(
109  "Physics module shape displacement has size '{}' while given state has size '{}'. The finite element "
110  "spaces are inconsistent.",
111  shape_displacement_.space().GetTrueVSize(), shape_displacement.space().GetTrueVSize()));
112  shape_displacement_ = shape_displacement;
113 }
114 
116 {
117  std::string output_name = name_;
118  if (output_name == "") {
119  output_name = "default";
120  }
121 
122  paraview_dc_ =
123  std::make_unique<mfem::ParaViewDataCollection>(output_name, const_cast<mfem::ParMesh*>(&states_.front()->mesh()));
124  int max_order_in_fields = 0;
125 
126  // Find the maximum polynomial order in the physics module's states
127  for (const FiniteElementState* state : states_) {
128  paraview_dc_->RegisterField(state->name(), &state->gridFunction());
129  max_order_in_fields = std::max(max_order_in_fields, state->space().GetOrder(0));
130  }
131 
132  for (const FiniteElementDual* dual : duals_) {
134  std::make_unique<mfem::ParGridFunction>(const_cast<mfem::ParFiniteElementSpace*>(&dual->space()));
135  max_order_in_fields = std::max(max_order_in_fields, dual->space().GetOrder(0));
136  paraview_dc_->RegisterField(dual->name(), paraview_dual_grid_functions_[dual->name()].get());
137  }
138 
139  for (auto& parameter : parameters_) {
140  paraview_dc_->RegisterField(parameter.state->name(), &parameter.state->gridFunction());
141  max_order_in_fields = std::max(max_order_in_fields, parameter.state->space().GetOrder(0));
142  }
143 
145  max_order_in_fields = std::max(max_order_in_fields, shape_displacement_.space().GetOrder(0));
146 
147  shape_sensitivity_grid_function_ = std::make_unique<mfem::ParGridFunction>(&shape_displacement_sensitivity_->space());
148  shape_displacement_sensitivity_->space().GetRestrictionMatrix()->MultTranspose(*shape_displacement_sensitivity_,
150  max_order_in_fields = std::max(max_order_in_fields, shape_displacement_sensitivity_->space().GetOrder(0));
152 
153  // Set the options for the paraview output files
154  paraview_dc_->SetLevelsOfDetail(max_order_in_fields);
155  paraview_dc_->SetHighOrderOutput(true);
156  paraview_dc_->SetDataFormat(mfem::VTKFormat::BINARY);
157  paraview_dc_->SetCompression(true);
158 }
159 
160 void BasePhysics::UpdateParaviewDataCollection(const std::string& paraview_output_dir) const
161 {
162  for (const FiniteElementState* state : states_) {
163  state->gridFunction(); // update grid function values
164  }
165  for (const FiniteElementDual* dual : duals_) {
166  // These are really const calls, but MFEM doesn't label them as such
167  serac::FiniteElementDual* non_const_dual = const_cast<serac::FiniteElementDual*>(dual);
168  non_const_dual->space().GetRestrictionMatrix()->MultTranspose(*dual, *paraview_dual_grid_functions_[dual->name()]);
169  }
170  for (auto& parameter : parameters_) {
171  parameter.state->gridFunction();
172  }
174  shape_displacement_sensitivity_->space().GetRestrictionMatrix()->MultTranspose(*shape_displacement_sensitivity_,
176 
177  // Set the current time, cycle, and requested paraview directory
178  paraview_dc_->SetCycle(cycle_);
179  paraview_dc_->SetTime(time_);
180  paraview_dc_->SetPrefixPath(paraview_output_dir);
181 }
182 
183 void BasePhysics::outputStateToDisk(std::optional<std::string> paraview_output_dir) const
184 {
185  // Update the states and duals in the state manager
186  for (auto& state : states_) {
188  }
189 
190  for (auto& dual : duals_) {
192  }
193 
194  for (auto& parameter : parameters_) {
196  StateManager::updateDual(*parameter.sensitivity);
197  }
198 
201 
202  // Save the restart/Sidre file
204 
205  // Optionally output a paraview datacollection for visualization
206  if (paraview_output_dir) {
207  // Check to see if the paraview data collection exists. If not, create it.
208  if (!paraview_dc_) {
210  }
211 
212  UpdateParaviewDataCollection(*paraview_output_dir);
213 
214  // Write the paraview file
215  paraview_dc_->Save();
216  }
217 }
218 
219 void BasePhysics::initializeSummary(axom::sidre::DataStore& datastore, double t_final, double dt) const
220 {
221  // Summary Sidre Structure
222  // Sidre root
223  // └── serac_summary
224  // ├── mpi_rank_count : int
225  // └── curves
226  // ├── t : Sidre::Array<axom::IndexType>
227  // ├── <FiniteElementState name>
228  // │ ├── l1norm : Sidre::Array<double>
229  // │ └── l2norm : Sidre::Array<double>
230  // ...
231  // └── <FiniteElementState name>
232  // ├── l1norm : Sidre::Array<double>
233  // └── l2norm : Sidre::Array<double>
234 
235  auto [count, rank] = getMPIInfo();
236  if (rank != 0) {
237  // Don't initialize except on root node
238  return;
239  }
240  const std::string summary_group_name = "serac_summary";
241  axom::sidre::Group* sidre_root = datastore.getRoot();
242  SLIC_ERROR_ROOT_IF(
243  sidre_root->hasGroup(summary_group_name),
244  axom::fmt::format("Sidre Group '{0}' cannot exist when initializeSummary is called", summary_group_name));
245  axom::sidre::Group* summary_group = sidre_root->createGroup(summary_group_name);
246 
247  // Write run info
248  summary_group->createViewScalar("mpi_rank_count", count);
249 
250  // Write curves info
251  axom::sidre::Group* curves_group = summary_group->createGroup("curves");
252 
253  // Calculate how many time steps which is the array size
254  axom::IndexType array_size = static_cast<axom::IndexType>(ceil(t_final / dt));
255 
256  // t: array of each time step value
257  axom::sidre::View* t_array_view = curves_group->createView("t");
258  axom::sidre::Array<double> ts(t_array_view, 0, array_size);
259 
260  for (const FiniteElementState* state : states_) {
261  // Group for this Finite Element State (Field)
262  axom::sidre::Group* state_group = curves_group->createGroup(state->name());
263 
264  // Create an array for each stat type to hold a value at each time step
265  for (std::string stat_name : {"l1norms", "l2norms", "linfnorms", "avgs", "mins", "maxs"}) {
266  axom::sidre::View* curr_array_view = state_group->createView(stat_name);
267  axom::sidre::Array<double> array(curr_array_view, 0, array_size);
268  }
269  }
270 }
271 
272 void BasePhysics::saveSummary(axom::sidre::DataStore& datastore, const double t) const
273 {
274  auto [_, rank] = getMPIInfo();
275 
276  // Find curves sidre group
277  axom::sidre::Group* curves_group = nullptr;
278  // Only save on root node
279  if (rank == 0) {
280  axom::sidre::Group* sidre_root = datastore.getRoot();
281  const std::string curves_group_name = "serac_summary/curves";
282  SLIC_ERROR_IF(!sidre_root->hasGroup(curves_group_name),
283  axom::fmt::format("Sidre Group '{0}' did not exist when saveCurves was called", curves_group_name));
284  curves_group = sidre_root->getGroup(curves_group_name);
285 
286  // Save time step
287  axom::sidre::Array<double> ts(curves_group->getView("t"));
288  ts.push_back(t);
289  }
290 
291  // For each Finite Element State (Field)
292  double l1norm_value, l2norm_value, linfnorm_value, avg_value, max_value, min_value;
293  for (const FiniteElementState* state : states_) {
294  // Calculate current stat value
295  // Note: These are collective operations.
296  l1norm_value = norm(*state, 1.0);
297  l2norm_value = norm(*state, 2.0);
298  linfnorm_value = norm(*state, mfem::infinity());
299  avg_value = avg(*state);
300  max_value = max(*state);
301  min_value = min(*state);
302 
303  // Only save on root node
304  if (rank == 0) {
305  // Group for this Finite Element State (Field)
306  axom::sidre::Group* state_group = curves_group->getGroup(state->name());
307 
308  // Save all current stat values in their respective sidre arrays
309  axom::sidre::View* l1norms_view = state_group->getView("l1norms");
310  axom::sidre::Array<double> l1norms(l1norms_view);
311  l1norms.push_back(l1norm_value);
312 
313  axom::sidre::View* l2norms_view = state_group->getView("l2norms");
314  axom::sidre::Array<double> l2norms(l2norms_view);
315  l2norms.push_back(l2norm_value);
316 
317  axom::sidre::View* linfnorms_view = state_group->getView("linfnorms");
318  axom::sidre::Array<double> linfnorms(linfnorms_view);
319  linfnorms.push_back(linfnorm_value);
320 
321  axom::sidre::View* avgs_view = state_group->getView("avgs");
322  axom::sidre::Array<double> avgs(avgs_view);
323  avgs.push_back(avg_value);
324 
325  axom::sidre::View* maxs_view = state_group->getView("maxs");
326  axom::sidre::Array<double> maxs(maxs_view);
327  maxs.push_back(max_value);
328 
329  axom::sidre::View* mins_view = state_group->getView("mins");
330  axom::sidre::Array<double> mins(mins_view);
331  mins.push_back(min_value);
332  }
333  }
334 }
335 
336 FiniteElementState BasePhysics::loadCheckpointedState(const std::string& state_name, int cycle) const
337 {
338  if (checkpoint_to_disk_) {
339  // See if the requested cycle has been checkpointed previously
341  // If not, get the checkpoint from disk
344  }
345 
346  // Ensure that the state name exists in this physics module
347  SLIC_ERROR_ROOT_IF(
348  cached_checkpoint_states_.find(state_name) == cached_checkpoint_states_.end(),
349  axom::fmt::format("Requested state name {} does not exist in physics module {}.", state_name, name_));
350  return cached_checkpoint_states_.at(state_name);
351  }
352 
353  // Ensure that the state name exists in this physics module
354  SLIC_ERROR_ROOT_IF(
355  checkpoint_states_.find(state_name) == checkpoint_states_.end(),
356  axom::fmt::format("Requested state name {} does not exist in physics module {}.", state_name, name_));
357 
358  return checkpoint_states_.at(state_name)[static_cast<size_t>(cycle)];
359 }
360 
361 std::unordered_map<std::string, FiniteElementState> BasePhysics::getCheckpointedStates(int /*cycle*/) const
362 {
363  SLIC_ERROR_ROOT(axom::fmt::format(
364  "loadCheckpointedState and getCheckpointedStates not implemented for physics module {}.", name_));
365  std::unordered_map<std::string, FiniteElementState> empty_container;
366  return empty_container;
367 }
368 
370 {
371  SLIC_ERROR_ROOT_IF(cycle < 0, axom::fmt::format("Negative cycle number requested for physics module {}.", name_));
372  SLIC_ERROR_ROOT_IF(cycle > max_cycle_,
373  axom::fmt::format("Timestep for cycle {} requested, but physics module has only reached cycle {}.",
374  cycle, max_cycle_));
375  return timesteps_[static_cast<size_t>(cycle)];
376 }
377 
378 namespace detail {
379 std::string addPrefix(const std::string& prefix, const std::string& target)
380 {
381  if (prefix.empty()) {
382  return target;
383  }
384  return prefix + "_" + target;
385 }
386 
387 std::string removePrefix(const std::string& prefix, const std::string& target)
388 {
389  std::string modified_target{target};
390  // Ensure the prefix isn't an empty string
391  if (!prefix.empty()) {
392  // Ensure the prefix is at the beginning of the string
393  auto index = modified_target.find(prefix + "_");
394  if (index == 0) {
395  // Remove the prefix
396  modified_target.erase(0, prefix.size() + 1);
397  }
398  }
399  return modified_target;
400 }
401 
402 } // namespace detail
403 
404 } // namespace serac
The base interface class for a generic PDE solver.
virtual std::vector< double > timesteps() const
Get a vector of the timestep sizes (i.e. s) taken by the forward solver.
std::optional< int > cached_checkpoint_cycle_
An optional int for disk-based checkpointing containing the cycle number of the last retrieved checkp...
mfem::ParMesh & mesh_
The primary mesh.
int mpi_rank_
MPI rank.
std::unordered_map< std::string, serac::FiniteElementState > cached_checkpoint_states_
A container relating a checkpointed cycle and the associated finite element state fields.
std::vector< const serac::FiniteElementDual * > duals_
List of finite element duals associated with this physics module.
virtual void initializeSummary(axom::sidre::DataStore &datastore, const double t_final, const double dt) const
Initializes the Sidre structure for simulation summary data.
virtual void saveSummary(axom::sidre::DataStore &datastore, const double t) const
Saves the summary data to the Sidre Datastore.
std::unique_ptr< FiniteElementDual > shape_displacement_sensitivity_
Sensitivity with respect to the shape displacement field.
double max_time_
The maximum time reached for the forward solver.
int cycle_
Current cycle (forward pass time iteration count)
std::vector< const serac::FiniteElementState * > states_
List of finite element primal states associated with this physics module.
virtual double time() const
Get the current forward-solution time.
std::unordered_map< std::string, std::vector< serac::FiniteElementState > > checkpoint_states_
A map containing optionally in-memory checkpointed primal states for transient adjoint solvers.
double min_time_
The time the forward solver was initialized to.
double time_
Current time for the forward pass.
FiniteElementState & shape_displacement_
The parameter info associated with the shape displacement field.
int min_cycle_
The cycle the forward solver was initialized to.
bool checkpoint_to_disk_
A flag denoting whether to save the state to disk or memory as needed for dynamic adjoint solves.
double ode_time_point_
The value of time at which the ODE solver wants to evaluate the residual.
int max_cycle_
The maximum cycle (forward pass iteration count) reached by the forward solver.
virtual int cycle() const
Get the current forward-solution cycle iteration number.
void setShapeDisplacement(const FiniteElementState &shape_displacement)
Set the current shape displacement for the underlying mesh.
const FiniteElementState & parameter(const std::string &parameter_name) const
Accessor for getting named finite element state parameter fields from the physics modules.
void UpdateParaviewDataCollection(const std::string &paraview_output_dir) const
Update the paraview states, duals, parameters, and metadata (cycle, time) in preparation for output.
virtual const FiniteElementState & state(const std::string &state_name) const =0
Accessor for getting named finite element state primal solution from the physics modules.
virtual double minTime() const
Get the initial time used by the forward solver.
std::string mesh_tag_
ID of the corresponding MFEMSidreDataCollection (denoting a mesh)
std::unique_ptr< mfem::ParaViewDataCollection > paraview_dc_
DataCollection pointer for optional paraview output.
FiniteElementState loadCheckpointedState(const std::string &state_name, int cycle) const
Accessor for getting a single named finite element state primal solution from the physics modules at ...
std::unique_ptr< mfem::ParGridFunction > shape_sensitivity_grid_function_
A optional view of the shape sensitivity in grid function form for paraview output.
virtual double getCheckpointedTimestep(int cycle) const
Get a timestep increment which has been previously checkpointed at the give cycle.
virtual void outputStateToDisk(std::optional< std::string > paraview_output_dir={}) const
Output the current state of the PDE fields in Sidre format and optionally in Paraview format if parav...
void setParameter(const size_t parameter_index, const FiniteElementState &parameter_state)
Deep copy a parameter field into the internally-owned parameter used for simulations.
std::vector< ParameterInfo > parameters_
A vector of the parameters associated with this physics module.
virtual std::unordered_map< std::string, FiniteElementState > getCheckpointedStates(int cycle) const
Accessor for getting all of the primal solutions from the physics modules at a given checkpointed cyc...
int mpi_size_
MPI size.
std::string name_
Name of the physics module.
virtual int minCycle() const
Get the initial cycle (timestep iteration number) used by the forward solver.
void CreateParaviewDataCollection() const
Create a paraview data collection for the physics package if requested.
virtual double maxTime() const
Get the maximum time reached by the forward solver.
std::unordered_map< std::string, std::unique_ptr< mfem::ParGridFunction > > paraview_dual_grid_functions_
A optional map of the dual names and duals in grid function form for paraview output.
void initializeBasePhysicsStates(int cycle, double time)
Protected, non-virtual method to reset physics states to zero. This does not reset design parameters ...
MPI_Comm comm_
The MPI communicator.
std::vector< double > timesteps_
A vector of the timestep sizes (i.e. ) taken by the forward solver.
BasePhysics(std::string physics_name, std::string mesh_tag, int cycle=0, double time=0.0, bool checkpoint_to_disk=false)
Empty constructor.
virtual int maxCycle() const
The maximum cycle (timestep iteration number) reached by the forward solver.
Class for encapsulating the dual vector space of a finite element space (i.e. the space of linear for...
Class for encapsulating the critical MFEM components of a primal finite element field.
mfem::ParGridFunction & gridFunction() const
Construct a grid function from the finite element state true vector.
mfem::ParFiniteElementSpace & space()
Returns a non-owning reference to the internal FESpace.
std::string name() const
Returns the name of the FEState (field)
mfem::ParMesh & mesh()
Returns a non-owning reference to the internal mesh object.
Manages the lifetimes of FEState objects such that restarts are abstracted from physics modules.
static void updateState(const FiniteElementState &state)
Updates the StateManager-owned grid function using the values from a given FiniteElementState.
static void updateDual(const FiniteElementDual &dual)
Updates the StateManager-owned grid function using the values from a given FiniteElementDual.
static void storeDual(FiniteElementDual &dual)
Store a pre-constructed finite element dual in the state manager.
static void save(const double t, const int cycle, const std::string &mesh_tag)
Updates the Conduit Blueprint state in the datastore and saves to a file.
This file contains the declaration of structure that manages the MFEM objects that make up the state ...
A function intended to be used as part of a driver to initialize common libraries.
This file contains the all the necessary functions and macros required for logging as well as a helpe...
Accelerator functionality.
Definition: serac.cpp:38
SERAC_HOST_DEVICE auto max(dual< gradient_type > a, double b)
Implementation of max for dual numbers.
Definition: dual.hpp:230
SERAC_HOST_DEVICE auto min(dual< gradient_type > a, double b)
Implementation of min for dual numbers.
Definition: dual.hpp:256
double avg(const FiniteElementVector &fe_vector)
Find the average value of a finite element vector across all dofs.
std::pair< int, int > getMPIInfo(MPI_Comm comm)
Returns the number of processes and rank for an MPI communicator.
Definition: initialize.cpp:26
constexpr SERAC_HOST_DEVICE auto norm(const isotropic_tensor< T, m, m > &I)
compute the Frobenius norm (sqrt(tr(dot(transpose(I), I)))) of an isotropic tensor
dual(double, T) -> dual< T >
class template argument deduction guide for type dual.
This file contains the declaration of the StateManager class.
H1 elements of order p.
Dual number struct (value plus gradient)
Definition: dual.hpp:29
Helper functions for exiting Serac cleanly.