Serac  0.1
Serac is an implicit thermal strucural mechanics simulation code.
base_physics.cpp
1 // Copyright (c) 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 <cmath>
10 #include <algorithm>
11 #include <tuple>
12 
16 #include "serac/physics/mesh.hpp"
19 
20 namespace serac {
21 
22 BasePhysics::BasePhysics(std::string physics_name, std::shared_ptr<serac::Mesh> mesh, int cycle, double time,
23  bool checkpoint_to_disk)
24  : name_(physics_name),
25  mesh_(mesh),
26  comm_(mesh_->getComm()),
27  shape_displacement_(mesh_->newShapeDisplacement()),
28  shape_displacement_dual_(mesh_->newShapeDisplacementDual()),
29  bcs_(mesh_->mfemParMesh()),
30  checkpoint_to_disk_(checkpoint_to_disk)
31 {
32  std::tie(mpi_size_, mpi_rank_) = getMPIInfo(comm_);
33 
35 }
36 
37 double BasePhysics::time() const { return time_; }
38 
39 int BasePhysics::cycle() const { return cycle_; }
40 
41 double BasePhysics::maxTime() const { return max_time_; }
42 
43 int BasePhysics::maxCycle() const { return max_cycle_; }
44 
45 double BasePhysics::minTime() const { return min_time_; }
46 
47 int BasePhysics::minCycle() const { return min_cycle_; }
48 
49 const std::vector<double>& BasePhysics::timesteps() const { return timesteps_; }
50 
51 const serac::Mesh& BasePhysics::mesh() const { return *mesh_; }
52 
53 const mfem::ParMesh& BasePhysics::mfemParMesh() const { return mesh_->mfemParMesh(); }
54 
55 mfem::ParMesh& BasePhysics::mfemParMesh() { return mesh_->mfemParMesh(); }
56 
57 void BasePhysics::initializeBasePhysicsStates(int cycle, double time)
58 {
59  timesteps_.clear();
60 
61  time_ = time;
62  dt_ = 0.0;
63  max_time_ = time;
64  min_time_ = time;
65  cycle_ = cycle;
66  max_cycle_ = cycle;
67  min_cycle_ = cycle;
69 
71  for (auto& p : parameters_) {
72  *p.sensitivity = 0.0;
73  }
74 }
75 
76 void BasePhysics::setParameter(const size_t parameter_index, const FiniteElementState& parameter_state)
77 {
78  SLIC_ERROR_ROOT_IF(
79  parameter_index >= parameters_.size(),
80  axom::fmt::format("Parameter '{}' requested when only '{}' parameters exist in physics module '{}'",
81  parameter_index, parameters_.size(), name_));
82 
83  SLIC_ERROR_ROOT_IF(&parameter_state.mesh() != &mfemParMesh(),
84  axom::fmt::format("Mesh of parameter '{}' is not the same as the physics mesh", parameter_index));
85 
86  SLIC_ERROR_ROOT_IF(
87  parameter_state.space().GetTrueVSize() != parameters_[parameter_index].state->space().GetTrueVSize(),
88  axom::fmt::format(
89  "Physics module parameter '{}' has size '{}' while given state has size '{}'. The finite element "
90  "spaces are inconsistent.",
91  parameter_index, parameters_[parameter_index].state->space().GetTrueVSize(),
92  parameter_state.space().GetTrueVSize()));
93  *parameters_[parameter_index].state = parameter_state;
94 }
95 
97 
99 {
100  shape_displacement_ = shape_displacement;
101 }
102 
104 
106 {
107  SLIC_ERROR_ROOT(axom::fmt::format("Parameter sensitivities not enabled in physics module {}", name_));
108  return *parameters_[parameter_index].sensitivity;
109 }
110 
112 {
113  SLIC_ERROR_ROOT(axom::fmt::format("Shape sensitivities not enabled in physics module {}", name_));
115 }
116 
118 {
119  std::string output_name = name_.empty() ? "default" : name_;
120 
121  paraview_dc_ =
122  std::make_unique<mfem::ParaViewDataCollection>(output_name, const_cast<mfem::ParMesh*>(&mfemParMesh()));
123 
124  // Register finite element fields
125 
126  paraview_dc_->RegisterField(shapeDisplacement().name(), &shapeDisplacement().gridFunction());
127 
128  for (const FiniteElementState* state : states_) {
129  paraview_dc_->RegisterField(state->name(), &state->gridFunction());
130  }
131 
132  for (auto& parameter : parameters_) {
133  paraview_dc_->RegisterField(parameter.state->name(), &parameter.state->gridFunction());
134  }
135 
136  // Register dual fields. These don't have gridfunction views already, so create them
137 
138  const mfem::ParFiniteElementSpace& shape_sensitivity_space = shapeDisplacementSensitivity().space();
140  std::make_unique<mfem::ParGridFunction>(&const_cast<mfem::ParFiniteElementSpace&>(shape_sensitivity_space));
142 
143  for (const FiniteElementDual* dual : duals_) {
145  std::make_unique<mfem::ParGridFunction>(const_cast<mfem::ParFiniteElementSpace*>(&dual->space()));
146  paraview_dc_->RegisterField(dual->name(), paraview_dual_grid_functions_[dual->name()].get());
147  }
148 
149  // Identify maximum polynomial order in output fields in order to set detail level
150 
151  int max_order_in_fields = mfemParMesh().GetNodalFESpace()->GetMaxElementOrder();
152 
153  for (const auto& [_, field] : paraview_dc_->GetFieldMap()) {
154  max_order_in_fields = std::max(field->FESpace()->GetMaxElementOrder(), max_order_in_fields);
155  }
156 
157  // Set the options for the paraview output files
158  paraview_dc_->SetLevelsOfDetail(max_order_in_fields);
159  paraview_dc_->SetHighOrderOutput(true);
160  paraview_dc_->SetDataFormat(mfem::VTKFormat::BINARY);
161  paraview_dc_->SetCompression(true);
162 }
163 
164 void BasePhysics::UpdateParaviewDataCollection(const std::string& paraview_output_dir) const
165 {
166  for (const FiniteElementState* state : states_) {
167  state->gridFunction(); // update grid function values
168  }
169  for (const FiniteElementDual* dual : duals_) {
170  serac::FiniteElementDual* non_const_dual = const_cast<serac::FiniteElementDual*>(dual);
171  non_const_dual->linearForm().ParallelAssemble(paraview_dual_grid_functions_[dual->name()]->GetTrueVector());
172  paraview_dual_grid_functions_[dual->name()]->SetFromTrueVector();
173  }
174  for (auto& parameter : parameters_) {
175  parameter.state->gridFunction();
176  }
178  shapeDisplacementSensitivity().linearForm().ParallelAssemble(shape_sensitivity_grid_function_->GetTrueVector());
179  shape_sensitivity_grid_function_->SetFromTrueVector();
180 
181  // Set the current time, cycle, and requested paraview directory
182  paraview_dc_->SetCycle(cycle_);
183  paraview_dc_->SetTime(time_);
184  paraview_dc_->SetPrefixPath(paraview_output_dir);
185 }
186 
187 void BasePhysics::outputStateToDisk(std::optional<std::string> paraview_output_dir) const
188 {
189  // Update the states and duals in the state manager
190  for (auto& state : states_) {
192  }
193 
194  for (auto& dual : duals_) {
196  }
197 
198  for (auto& parameter : parameters_) {
200  StateManager::updateDual(*parameter.sensitivity);
201  }
202 
205 
206  // Save the restart/Sidre file
208 
209  // Optionally output a paraview datacollection for visualization
210  if (paraview_output_dir) {
211  // Check to see if the paraview data collection exists. If not, create it.
212  if (!paraview_dc_) {
214  }
215 
216  UpdateParaviewDataCollection(*paraview_output_dir);
217 
218  // Write the paraview file
219  paraview_dc_->Save();
220  }
221 }
222 
223 void BasePhysics::initializeSummary(axom::sidre::DataStore& datastore, double t_final, double dt) const
224 {
225  // Summary Sidre Structure
226  // Sidre root
227  // └── serac_summary
228  // ├── mpi_rank_count : int
229  // └── curves
230  // ├── t : Sidre::Array<axom::IndexType>
231  // ├── <FiniteElementState name>
232  // │ ├── l1norm : Sidre::Array<double>
233  // │ └── l2norm : Sidre::Array<double>
234  // ...
235  // └── <FiniteElementState name>
236  // ├── l1norm : Sidre::Array<double>
237  // └── l2norm : Sidre::Array<double>
238 
239  auto [count, rank] = getMPIInfo();
240  if (rank != 0) {
241  // Don't initialize except on root node
242  return;
243  }
244  const std::string summary_group_name = "serac_summary";
245  axom::sidre::Group* sidre_root = datastore.getRoot();
246  SLIC_ERROR_ROOT_IF(
247  sidre_root->hasGroup(summary_group_name),
248  axom::fmt::format("Sidre Group '{0}' cannot exist when initializeSummary is called", summary_group_name));
249  axom::sidre::Group* summary_group = sidre_root->createGroup(summary_group_name);
250 
251  // Write run info
252  summary_group->createViewScalar("mpi_rank_count", count);
253 
254  // Write curves info
255  axom::sidre::Group* curves_group = summary_group->createGroup("curves");
256 
257  // Calculate how many time steps which is the array size
258  axom::IndexType array_size = static_cast<axom::IndexType>(ceil(t_final / dt));
259 
260  // t: array of each time step value
261  axom::sidre::View* t_array_view = curves_group->createView("t");
262  axom::sidre::Array<double> ts(t_array_view, 0, array_size);
263 
264  for (const FiniteElementState* state : states_) {
265  // Group for this Finite Element State (Field)
266  axom::sidre::Group* state_group = curves_group->createGroup(state->name());
267 
268  // Create an array for each stat type to hold a value at each time step
269  for (std::string stat_name : {"l1norms", "l2norms", "linfnorms", "avgs", "mins", "maxs"}) {
270  axom::sidre::View* curr_array_view = state_group->createView(stat_name);
271  axom::sidre::Array<double> array(curr_array_view, 0, array_size);
272  }
273  }
274 }
275 
276 void BasePhysics::saveSummary(axom::sidre::DataStore& datastore, const double t) const
277 {
278  auto [_, rank] = getMPIInfo();
279 
280  // Find curves sidre group
281  axom::sidre::Group* curves_group = nullptr;
282  // Only save on root node
283  if (rank == 0) {
284  axom::sidre::Group* sidre_root = datastore.getRoot();
285  const std::string curves_group_name = "serac_summary/curves";
286  SLIC_ERROR_IF(!sidre_root->hasGroup(curves_group_name),
287  axom::fmt::format("Sidre Group '{0}' did not exist when saveCurves was called", curves_group_name));
288  curves_group = sidre_root->getGroup(curves_group_name);
289 
290  // Save time step
291  axom::sidre::Array<double> ts(curves_group->getView("t"));
292  ts.push_back(t);
293  }
294 
295  // For each Finite Element State (Field)
296  double l1norm_value, l2norm_value, linfnorm_value, avg_value, max_value, min_value;
297  for (const FiniteElementState* state : states_) {
298  // Calculate current stat value
299  // Note: These are collective operations.
300  l1norm_value = norm(*state, 1.0);
301  l2norm_value = norm(*state, 2.0);
302  linfnorm_value = norm(*state, mfem::infinity());
303  avg_value = avg(*state);
304  max_value = max(*state);
305  min_value = min(*state);
306 
307  // Only save on root node
308  if (rank == 0) {
309  // Group for this Finite Element State (Field)
310  axom::sidre::Group* state_group = curves_group->getGroup(state->name());
311 
312  // Save all current stat values in their respective sidre arrays
313  axom::sidre::View* l1norms_view = state_group->getView("l1norms");
314  axom::sidre::Array<double> l1norms(l1norms_view);
315  l1norms.push_back(l1norm_value);
316 
317  axom::sidre::View* l2norms_view = state_group->getView("l2norms");
318  axom::sidre::Array<double> l2norms(l2norms_view);
319  l2norms.push_back(l2norm_value);
320 
321  axom::sidre::View* linfnorms_view = state_group->getView("linfnorms");
322  axom::sidre::Array<double> linfnorms(linfnorms_view);
323  linfnorms.push_back(linfnorm_value);
324 
325  axom::sidre::View* avgs_view = state_group->getView("avgs");
326  axom::sidre::Array<double> avgs(avgs_view);
327  avgs.push_back(avg_value);
328 
329  axom::sidre::View* maxs_view = state_group->getView("maxs");
330  axom::sidre::Array<double> maxs(maxs_view);
331  maxs.push_back(max_value);
332 
333  axom::sidre::View* mins_view = state_group->getView("mins");
334  axom::sidre::Array<double> mins(mins_view);
335  mins.push_back(min_value);
336  }
337  }
338 }
339 
340 FiniteElementState BasePhysics::loadCheckpointedState(const std::string& state_name, int cycle)
341 {
342  if (checkpoint_to_disk_) {
343  // See if the requested cycle has been checkpointed previously
345  // If not, get the checkpoint from disk
348  }
349 
350  // Ensure that the state name exists in this physics module
351  SLIC_ERROR_ROOT_IF(
352  cached_checkpoint_states_.find(state_name) == cached_checkpoint_states_.end(),
353  axom::fmt::format("Requested state name {} does not exist in physics module {}.", state_name, name_));
354  return cached_checkpoint_states_.at(state_name);
355  }
356 
357  // Ensure that the state name exists in this physics module
358  SLIC_ERROR_ROOT_IF(
359  checkpoint_states_.find(state_name) == checkpoint_states_.end(),
360  axom::fmt::format("Requested state name {} does not exist in physics module {}.", state_name, name_));
361 
362  return checkpoint_states_.at(state_name)[static_cast<size_t>(cycle)];
363 }
364 
366 {
367  std::vector<FiniteElementState*> previous_states_ptrs;
368  for (const auto& state_name : stateNames()) {
369  previous_states_ptrs.emplace_back(const_cast<FiniteElementState*>(&state(state_name)));
370  }
371  StateManager::loadCheckpointedStates(cycle_to_load, previous_states_ptrs);
372 }
373 
374 std::unordered_map<std::string, FiniteElementState> BasePhysics::getCheckpointedStates(int cycle_to_load)
375 {
376  std::unordered_map<std::string, FiniteElementState> previous_states_map;
377 
378  if (checkpoint_to_disk_) {
379  loadCheckpointedStatesFromDisk(cycle_to_load);
380  for (const auto& state_name : stateNames()) {
381  previous_states_map.emplace(state_name, state(state_name));
382  }
383  return previous_states_map;
384  } else {
385  for (const auto& state_name : stateNames()) {
386  previous_states_map.emplace(state_name, checkpoint_states_.at(state_name)[static_cast<size_t>(cycle_to_load)]);
387  }
388  }
389 
390  return previous_states_map;
391 }
392 
394 {
395  SLIC_ERROR_ROOT_IF(cycle < 0, axom::fmt::format("Negative cycle number requested for physics module {}.", name_));
396  SLIC_ERROR_ROOT_IF(cycle > static_cast<int>(timesteps_.size()),
397  axom::fmt::format("Timestep for cycle {} requested, but physics module has only reached cycle {}.",
398  cycle, timesteps_.size()));
399  return cycle < static_cast<int>(timesteps_.size()) ? timesteps_[static_cast<size_t>(cycle)] : 0.0;
400 }
401 
402 namespace detail {
403 std::string addPrefix(const std::string& prefix, const std::string& target)
404 {
405  if (prefix.empty()) {
406  return target;
407  }
408  return prefix + "_" + target;
409 }
410 
411 std::string removePrefix(const std::string& prefix, const std::string& target)
412 {
413  std::string modified_target{target};
414  // Ensure the prefix isn't an empty string
415  if (!prefix.empty()) {
416  // Ensure the prefix is at the beginning of the string
417  auto index = modified_target.find(prefix + "_");
418  if (index == 0) {
419  // Remove the prefix
420  modified_target.erase(0, prefix.size() + 1);
421  }
422  }
423  return modified_target;
424 }
425 
426 } // namespace detail
427 
428 } // namespace serac
This file contains the interface used for retrieving information about how the driver is configured.
The base interface class for a generic PDE solver.
std::optional< int > cached_checkpoint_cycle_
An optional int for disk-based checkpointing containing the cycle number of the last retrieved checkp...
virtual FiniteElementState loadCheckpointedState(const std::string &state_name, int cycle)
Accessor for getting a single named finite element state primal solution from the physics modules at ...
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.
const mfem::ParMesh & mfemParMesh() const
Returns a reference to the mfem ParMesh object.
virtual void saveSummary(axom::sidre::DataStore &datastore, const double t) const
Saves the summary data to the Sidre Datastore.
double max_time_
The maximum time reached for the forward solver.
virtual std::vector< std::string > stateNames() const =0
Get a vector of the finite element state primal solution names.
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.
serac::FiniteElementState shape_displacement_
The shape displacement field.
virtual double time() const
Get the current forward-solution time.
std::string name() const
Return the name of the physics.
std::unordered_map< std::string, std::vector< serac::FiniteElementState > > checkpoint_states_
A map containing optionally in-memory checkpointed primal states for transient adjoint solvers.
virtual FiniteElementDual computeTimestepSensitivity(size_t parameter_index)
Compute the implicit sensitivity of the quantity of interest used in defining the adjoint load with r...
double min_time_
The time the forward solver was initialized to.
virtual const FiniteElementState & parameter(const std::string &parameter_name) const
Accessor for getting named finite element state parameter fields from the physics modules.
serac::FiniteElementDual shape_displacement_dual_
The shape displacement field sensitivity.
double time_
Current time for the forward pass.
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 dt_
Current time step.
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.
virtual void setShapeDisplacement(const FiniteElementState &shape_displacement)
Set the current shape displacement for the underlying mesh.
void loadCheckpointedStatesFromDisk(int cycle)
load checkpointed states from disk into states array
virtual const FiniteElementState & shapeDisplacement() const
Accessor for getting the shape displacement field from the physics modules.
std::shared_ptr< serac::Mesh > mesh_
The primary mesh.
std::unordered_map< std::string, FiniteElementState > getCheckpointedStates(int cycle)
Accessor for getting all of the primal solutions from the physics modules at a given checkpointed cyc...
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 std::vector< double > & timesteps() const
Get a vector of the timestep sizes (i.e. s) taken by the forward solver.
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::unique_ptr< mfem::ParaViewDataCollection > paraview_dc_
DataCollection pointer for optional paraview output.
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 const FiniteElementDual & dual(const std::string &dual_name) const
Accessor for getting named finite element state dual (reaction) solution from the physics modules.
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...
virtual 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.
const serac::Mesh & mesh() const
Returns a reference to the mesh object.
int mpi_size_
MPI size.
BasePhysics(std::string physics_name, std::shared_ptr< serac::Mesh > mesh, int cycle=0, double time=0.0, bool checkpoint_to_disk=false)
Empty constructor.
std::string name_
Name of the physics module.
virtual const FiniteElementDual & computeTimestepShapeSensitivity()
Compute the implicit sensitivity of the quantity of interest used in defining the adjoint load with r...
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.
const FiniteElementDual & shapeDisplacementSensitivity() const
Internally used accessor for getting the shape displacement sensitivity from the physics modules.
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...
mfem::ParLinearForm & linearForm() const
Construct a linear form from the finite element dual true vector.
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.
Helper class for constructing a mesh consistent with serac.
Definition: mesh.hpp:37
static void loadCheckpointedStates(int cycle_to_load, std::vector< FiniteElementState * > states_to_load)
loads the finite element states from a previously checkpointed cycle
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 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 ...
This file contains the declaration of structure that manages vectors derived from an MFEM finite elem...
This file contains the all the necessary functions and macros required for logging as well as a helpe...
Serac mesh class which assists in constructing the appropriate parallel mfem meshes and registering a...
Accelerator functionality.
Definition: serac.cpp:36
SERAC_HOST_DEVICE auto max(dual< gradient_type > a, double b)
Implementation of max for dual numbers.
Definition: dual.hpp:229
SERAC_HOST_DEVICE auto min(dual< gradient_type > a, double b)
Implementation of min for dual numbers.
Definition: dual.hpp:255
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)
Get MPI Info.
Definition: about.cpp:229
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
This file contains the declaration of the StateManager class.
Dual number struct (value plus gradient)
Definition: dual.hpp:28