Smith  0.1
Smith is an implicit thermal structural mechanics simulation code.
base_physics.cpp
1 // Copyright (c) Lawrence Livermore National Security, LLC and
2 // other Smith 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 "smith/physics/mesh.hpp"
19 
20 namespace smith {
21 
22 BasePhysics::BasePhysics(std::string physics_name, std::shared_ptr<smith::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 smith::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(parameter_index >= parameters_.size(),
79  std::format("Parameter '{}' requested when only '{}' parameters exist in physics module '{}'",
80  parameter_index, parameters_.size(), name_));
81 
82  SLIC_ERROR_ROOT_IF(&parameter_state.mesh() != &mfemParMesh(),
83  std::format("Mesh of parameter '{}' is not the same as the physics mesh", parameter_index));
84 
85  SLIC_ERROR_ROOT_IF(
86  parameter_state.space().GetTrueVSize() != parameters_[parameter_index].state->space().GetTrueVSize(),
87  std::format("Physics module parameter '{}' has size '{}' while given state has size '{}'. The finite element "
88  "spaces are inconsistent.",
89  parameter_index, parameters_[parameter_index].state->space().GetTrueVSize(),
90  parameter_state.space().GetTrueVSize()));
91  *parameters_[parameter_index].state = parameter_state;
92 }
93 
95 
97 {
98  shape_displacement_ = shape_displacement;
99 }
100 
102 
104 {
105  SLIC_ERROR_ROOT(std::format("Parameter sensitivities not enabled in physics module {}", name_));
106  return *parameters_[parameter_index].sensitivity;
107 }
108 
110 {
111  SLIC_ERROR_ROOT(std::format("Shape sensitivities not enabled in physics module {}", name_));
113 }
114 
116 {
117  std::string output_name = name_.empty() ? "default" : name_;
118 
119  paraview_dc_ =
120  std::make_unique<mfem::ParaViewDataCollection>(output_name, const_cast<mfem::ParMesh*>(&mfemParMesh()));
121 
122  // Register finite element fields
123 
124  paraview_dc_->RegisterField(shapeDisplacement().name(), &shapeDisplacement().gridFunction());
125 
126  for (const FiniteElementState* state : states_) {
127  paraview_dc_->RegisterField(state->name(), &state->gridFunction());
128  }
129 
130  for (auto& parameter : parameters_) {
131  paraview_dc_->RegisterField(parameter.state->name(), &parameter.state->gridFunction());
132  }
133 
134  // Register dual fields. These don't have gridfunction views already, so create them
135 
136  const mfem::ParFiniteElementSpace& shape_sensitivity_space = shapeDisplacementSensitivity().space();
138  std::make_unique<mfem::ParGridFunction>(&const_cast<mfem::ParFiniteElementSpace&>(shape_sensitivity_space));
140 
141  for (const FiniteElementDual* dual : duals_) {
143  std::make_unique<mfem::ParGridFunction>(const_cast<mfem::ParFiniteElementSpace*>(&dual->space()));
144  paraview_dc_->RegisterField(dual->name(), paraview_dual_grid_functions_[dual->name()].get());
145  }
146 
147  // Identify maximum polynomial order in output fields in order to set detail level
148 
149  int max_order_in_fields = mfemParMesh().GetNodalFESpace()->GetMaxElementOrder();
150 
151  for (const auto& [_, field] : paraview_dc_->GetFieldMap()) {
152  max_order_in_fields = std::max(field->FESpace()->GetMaxElementOrder(), max_order_in_fields);
153  }
154 
155  // Set the options for the paraview output files
156  paraview_dc_->SetLevelsOfDetail(max_order_in_fields);
157  paraview_dc_->SetHighOrderOutput(true);
158  paraview_dc_->SetDataFormat(mfem::VTKFormat::BINARY);
159  paraview_dc_->SetCompression(true);
160 }
161 
162 void BasePhysics::UpdateParaviewDataCollection(const std::string& paraview_output_dir) const
163 {
164  for (const FiniteElementState* state : states_) {
165  state->gridFunction(); // update grid function values
166  }
167  for (const FiniteElementDual* dual : duals_) {
168  smith::FiniteElementDual* non_const_dual = const_cast<smith::FiniteElementDual*>(dual);
169  non_const_dual->linearForm().ParallelAssemble(paraview_dual_grid_functions_[dual->name()]->GetTrueVector());
170  paraview_dual_grid_functions_[dual->name()]->SetFromTrueVector();
171  }
172  for (auto& parameter : parameters_) {
173  parameter.state->gridFunction();
174  }
176  shapeDisplacementSensitivity().linearForm().ParallelAssemble(shape_sensitivity_grid_function_->GetTrueVector());
177  shape_sensitivity_grid_function_->SetFromTrueVector();
178 
179  // Set the current time, cycle, and requested paraview directory
180  paraview_dc_->SetCycle(cycle_);
181  paraview_dc_->SetTime(time_);
182  paraview_dc_->SetPrefixPath(paraview_output_dir);
183 }
184 
185 void BasePhysics::outputStateToDisk(std::optional<std::string> paraview_output_dir) const
186 {
187  // Update the states and duals in the state manager
188  for (auto& state : states_) {
190  }
191 
192  for (auto& dual : duals_) {
194  }
195 
196  for (auto& parameter : parameters_) {
198  StateManager::updateDual(*parameter.sensitivity);
199  }
200 
203 
204  // Save the restart/Sidre file
206 
207  // Optionally output a paraview datacollection for visualization
208  if (paraview_output_dir) {
209  // Check to see if the paraview data collection exists. If not, create it.
210  if (!paraview_dc_) {
212  }
213 
214  UpdateParaviewDataCollection(*paraview_output_dir);
215 
216  // Write the paraview file
217  paraview_dc_->Save();
218  }
219 }
220 
221 void BasePhysics::initializeSummary(axom::sidre::DataStore& datastore, double t_final, double dt) const
222 {
223  // Summary Sidre Structure
224  // Sidre root
225  // └── smith_summary
226  // ├── mpi_rank_count : int
227  // └── curves
228  // ├── t : Sidre::Array<axom::IndexType>
229  // ├── <FiniteElementState name>
230  // │ ├── l1norm : Sidre::Array<double>
231  // │ └── l2norm : Sidre::Array<double>
232  // ...
233  // └── <FiniteElementState name>
234  // ├── l1norm : Sidre::Array<double>
235  // └── l2norm : Sidre::Array<double>
236 
237  auto [count, rank] = getMPIInfo();
238  if (rank != 0) {
239  // Don't initialize except on root node
240  return;
241  }
242  const std::string summary_group_name = "smith_summary";
243  axom::sidre::Group* sidre_root = datastore.getRoot();
244  SLIC_ERROR_ROOT_IF(
245  sidre_root->hasGroup(summary_group_name),
246  std::format("Sidre Group '{0}' cannot exist when initializeSummary is called", summary_group_name));
247  axom::sidre::Group* summary_group = sidre_root->createGroup(summary_group_name);
248 
249  // Write run info
250  summary_group->createViewScalar("mpi_rank_count", count);
251 
252  // Write curves info
253  axom::sidre::Group* curves_group = summary_group->createGroup("curves");
254 
255  // Calculate how many time steps which is the array size
256  axom::IndexType array_size = static_cast<axom::IndexType>(ceil(t_final / dt));
257 
258  // t: array of each time step value
259  axom::sidre::View* t_array_view = curves_group->createView("t");
260  axom::sidre::Array<double> ts(t_array_view, 0, array_size);
261 
262  for (const FiniteElementState* state : states_) {
263  // Group for this Finite Element State (Field)
264  axom::sidre::Group* state_group = curves_group->createGroup(state->name());
265 
266  // Create an array for each stat type to hold a value at each time step
267  for (std::string stat_name : {"l1norms", "l2norms", "linfnorms", "avgs", "mins", "maxs"}) {
268  axom::sidre::View* curr_array_view = state_group->createView(stat_name);
269  axom::sidre::Array<double> array(curr_array_view, 0, array_size);
270  }
271  }
272 }
273 
274 void BasePhysics::saveSummary(axom::sidre::DataStore& datastore, const double t) const
275 {
276  auto [_, rank] = getMPIInfo();
277 
278  // Find curves sidre group
279  axom::sidre::Group* curves_group = nullptr;
280  // Only save on root node
281  if (rank == 0) {
282  axom::sidre::Group* sidre_root = datastore.getRoot();
283  const std::string curves_group_name = "smith_summary/curves";
284  SLIC_ERROR_IF(!sidre_root->hasGroup(curves_group_name),
285  std::format("Sidre Group '{0}' did not exist when saveCurves was called", curves_group_name));
286  curves_group = sidre_root->getGroup(curves_group_name);
287 
288  // Save time step
289  axom::sidre::Array<double> ts(curves_group->getView("t"));
290  ts.push_back(t);
291  }
292 
293  // For each Finite Element State (Field)
294  double l1norm_value, l2norm_value, linfnorm_value, avg_value, max_value, min_value;
295  for (const FiniteElementState* state : states_) {
296  // Calculate current stat value
297  // Note: These are collective operations.
298  l1norm_value = norm(*state, 1.0);
299  l2norm_value = norm(*state, 2.0);
300  linfnorm_value = norm(*state, mfem::infinity());
301  avg_value = avg(*state);
302  max_value = max(*state);
303  min_value = min(*state);
304 
305  // Only save on root node
306  if (rank == 0) {
307  // Group for this Finite Element State (Field)
308  axom::sidre::Group* state_group = curves_group->getGroup(state->name());
309 
310  // Save all current stat values in their respective sidre arrays
311  axom::sidre::View* l1norms_view = state_group->getView("l1norms");
312  axom::sidre::Array<double> l1norms(l1norms_view);
313  l1norms.push_back(l1norm_value);
314 
315  axom::sidre::View* l2norms_view = state_group->getView("l2norms");
316  axom::sidre::Array<double> l2norms(l2norms_view);
317  l2norms.push_back(l2norm_value);
318 
319  axom::sidre::View* linfnorms_view = state_group->getView("linfnorms");
320  axom::sidre::Array<double> linfnorms(linfnorms_view);
321  linfnorms.push_back(linfnorm_value);
322 
323  axom::sidre::View* avgs_view = state_group->getView("avgs");
324  axom::sidre::Array<double> avgs(avgs_view);
325  avgs.push_back(avg_value);
326 
327  axom::sidre::View* maxs_view = state_group->getView("maxs");
328  axom::sidre::Array<double> maxs(maxs_view);
329  maxs.push_back(max_value);
330 
331  axom::sidre::View* mins_view = state_group->getView("mins");
332  axom::sidre::Array<double> mins(mins_view);
333  mins.push_back(min_value);
334  }
335  }
336 }
337 
338 FiniteElementState BasePhysics::loadCheckpointedState(const std::string& state_name, int cycle)
339 {
340  if (checkpoint_to_disk_) {
341  // See if the requested cycle has been checkpointed previously
343  // If not, get the checkpoint from disk
346  }
347 
348  // Ensure that the state name exists in this physics module
349  SLIC_ERROR_ROOT_IF(cached_checkpoint_states_.find(state_name) == cached_checkpoint_states_.end(),
350  std::format("Requested state name {} does not exist in physics module {}.", state_name, name_));
351  return cached_checkpoint_states_.at(state_name);
352  }
353 
354  // Ensure that the state name exists in this physics module
355  SLIC_ERROR_ROOT_IF(checkpoint_states_.find(state_name) == checkpoint_states_.end(),
356  std::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 
362 {
363  std::vector<FiniteElementState*> previous_states_ptrs;
364  for (const auto& state_name : stateNames()) {
365  previous_states_ptrs.emplace_back(const_cast<FiniteElementState*>(&state(state_name)));
366  }
367  StateManager::loadCheckpointedStates(cycle_to_load, previous_states_ptrs);
368 }
369 
370 std::unordered_map<std::string, FiniteElementState> BasePhysics::getCheckpointedStates(int cycle_to_load)
371 {
372  std::unordered_map<std::string, FiniteElementState> previous_states_map;
373 
374  if (checkpoint_to_disk_) {
375  loadCheckpointedStatesFromDisk(cycle_to_load);
376  for (const auto& state_name : stateNames()) {
377  previous_states_map.emplace(state_name, state(state_name));
378  }
379  return previous_states_map;
380  } else {
381  for (const auto& state_name : stateNames()) {
382  previous_states_map.emplace(state_name, checkpoint_states_.at(state_name)[static_cast<size_t>(cycle_to_load)]);
383  }
384  }
385 
386  return previous_states_map;
387 }
388 
390 {
391  SLIC_ERROR_ROOT_IF(cycle < 0, std::format("Negative cycle number requested for physics module {}.", name_));
392  SLIC_ERROR_ROOT_IF(cycle > static_cast<int>(timesteps_.size()),
393  std::format("Timestep for cycle {} requested, but physics module has only reached cycle {}.",
394  cycle, timesteps_.size()));
395  return cycle < static_cast<int>(timesteps_.size()) ? timesteps_[static_cast<size_t>(cycle)] : 0.0;
396 }
397 
398 namespace detail {
399 std::string addPrefix(const std::string& prefix, const std::string& target)
400 {
401  if (prefix.empty()) {
402  return target;
403  }
404  return prefix + "_" + target;
405 }
406 
407 std::string removePrefix(const std::string& prefix, const std::string& target)
408 {
409  std::string modified_target{target};
410  // Ensure the prefix isn't an empty string
411  if (!prefix.empty()) {
412  // Ensure the prefix is at the beginning of the string
413  auto index = modified_target.find(prefix + "_");
414  if (index == 0) {
415  // Remove the prefix
416  modified_target.erase(0, prefix.size() + 1);
417  }
418  }
419  return modified_target;
420 }
421 
422 } // namespace detail
423 
424 } // namespace smith
This file contains the interface used for retrieving information about how the driver is configured.
The base interface class for a generic PDE solver.
virtual const FiniteElementState & parameter(const std::string &parameter_name) const
Accessor for getting named finite element state parameter fields from the physics modules.
std::string name_
Name of the physics module.
virtual int maxCycle() const
The maximum cycle (timestep iteration number) reached by the forward solver.
virtual const std::vector< double > & timesteps() const
Get a vector of the timestep sizes (i.e. s) taken by the forward solver.
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::shared_ptr< smith::Mesh > mesh_
The primary mesh.
void UpdateParaviewDataCollection(const std::string &paraview_output_dir) const
Update the paraview states, duals, parameters, and metadata (cycle, time) in preparation for output.
void CreateParaviewDataCollection() const
Create a paraview data collection for the physics package if requested.
std::vector< const smith::FiniteElementState * > states_
List of finite element primal states associated with this physics module.
double min_time_
The time the forward solver was initialized to.
int cycle_
Current cycle (forward pass time iteration count)
double ode_time_point_
The value of time at which the ODE solver wants to evaluate the residual.
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...
std::vector< const smith::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.
std::string name() const
Return the name of the physics.
std::vector< double > timesteps_
A vector of the timestep sizes (i.e. ) taken by the forward solver.
std::vector< ParameterInfo > parameters_
A vector of the parameters associated with this physics module.
virtual void setShapeDisplacement(const FiniteElementState &shape_displacement)
Set the current shape displacement for the underlying mesh.
virtual double time() const
Get the current forward-solution time.
std::optional< int > cached_checkpoint_cycle_
An optional int for disk-based checkpointing containing the cycle number of the last retrieved checkp...
int max_cycle_
The maximum cycle (forward pass iteration count) reached by the forward solver.
smith::FiniteElementDual shape_displacement_dual_
The shape displacement field sensitivity.
BasePhysics(std::string physics_name, std::shared_ptr< smith::Mesh > mesh, int cycle=0, double time=0.0, bool checkpoint_to_disk=false)
Empty constructor.
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...
MPI_Comm comm_
The MPI communicator.
virtual double maxTime() const
Get the maximum time reached by the forward solver.
std::unordered_map< std::string, std::vector< smith::FiniteElementState > > checkpoint_states_
A map containing optionally in-memory checkpointed primal states for transient adjoint solvers.
std::unique_ptr< mfem::ParaViewDataCollection > paraview_dc_
DataCollection pointer for optional paraview output.
virtual const FiniteElementState & shapeDisplacement() const
Accessor for getting the shape displacement field from the physics modules.
const smith::Mesh & mesh() const
Returns a reference to the mesh object.
double dt_
Current time step.
int mpi_rank_
MPI rank.
int mpi_size_
MPI size.
double time_
Current time for the forward pass.
virtual int cycle() const
Get the current forward-solution cycle iteration number.
virtual const FiniteElementDual & computeTimestepShapeSensitivity()
Compute the implicit sensitivity of the quantity of interest used in defining the adjoint load with r...
std::unique_ptr< mfem::ParGridFunction > shape_sensitivity_grid_function_
A optional view of the shape sensitivity in grid function form for paraview output.
std::unordered_map< std::string, smith::FiniteElementState > cached_checkpoint_states_
A container relating a checkpointed cycle and the associated finite element state fields.
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.
virtual int minCycle() const
Get the initial cycle (timestep iteration number) used by the forward solver.
virtual double getCheckpointedTimestep(int cycle) const
Get a timestep increment which has been previously checkpointed at the give cycle.
double max_time_
The maximum time reached for the forward solver.
virtual FiniteElementDual computeTimestepSensitivity(size_t parameter_index)
Compute the implicit sensitivity of the quantity of interest used in defining the adjoint load with r...
const FiniteElementDual & shapeDisplacementSensitivity() const
Internally used accessor for getting the shape displacement sensitivity from the physics modules.
virtual void saveSummary(axom::sidre::DataStore &datastore, const double t) const
Saves the summary data to the Sidre Datastore.
bool checkpoint_to_disk_
A flag denoting whether to save the state to disk or memory as needed for dynamic adjoint solves.
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 const FiniteElementState & state(const std::string &state_name) const =0
Accessor for getting named finite element state primal solution from the physics modules.
int min_cycle_
The cycle the forward solver was initialized to.
const mfem::ParMesh & mfemParMesh() const
Returns a reference to the mfem ParMesh object.
void initializeBasePhysicsStates(int cycle, double time)
Protected, non-virtual method to reset physics states to zero. This does not reset design parameters ...
void loadCheckpointedStatesFromDisk(int cycle)
load checkpointed states from disk into states array
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 ...
virtual double minTime() const
Get the initial time used by the forward solver.
smith::FiniteElementState shape_displacement_
The shape displacement field.
virtual std::vector< std::string > stateNames() const =0
Get a vector of the finite element state primal solution names.
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 Smith.
Definition: mesh.hpp:37
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.
static void updateState(const FiniteElementState &state)
Updates the StateManager-owned grid function using the values from a given FiniteElementState.
static void loadCheckpointedStates(int cycle_to_load, std::vector< FiniteElementState * > states_to_load)
loads the finite element states from a previously checkpointed cycle
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...
Smith mesh class which assists in constructing the appropriate parallel mfem meshes and registering a...
Accelerator functionality.
Definition: smith.cpp:36
std::pair< int, int > getMPIInfo(MPI_Comm comm)
Get MPI Info.
Definition: about.cpp:237
constexpr SMITH_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
double avg(const FiniteElementVector &fe_vector)
Find the average value of a finite element vector across all dofs.
SMITH_HOST_DEVICE auto max(dual< gradient_type > a, double b)
Implementation of max for dual numbers.
Definition: dual.hpp:229
SMITH_HOST_DEVICE auto min(dual< gradient_type > a, double b)
Implementation of min for dual numbers.
Definition: dual.hpp:255
This file contains the declaration of the StateManager class.
Dual number struct (value plus gradient)
Definition: dual.hpp:28