Smith  0.1
Smith is an implicit thermal structural mechanics simulation code.
state_manager.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 <tuple>
10 #include <utility>
11 
12 #include "mpi.h"
13 #include "axom/core.hpp"
14 
15 #include "smith/smith_config.hpp"
18 
19 namespace smith {
20 
21 // Initialize StateManager's static members - these will be fully initialized in StateManager::initialize
22 std::unordered_map<std::string, axom::sidre::MFEMSidreDataCollection> StateManager::datacolls_;
23 std::unordered_map<std::string, std::unique_ptr<FiniteElementState>> StateManager::shape_displacements_;
24 std::unordered_map<std::string, std::unique_ptr<FiniteElementDual>> StateManager::shape_displacement_duals_;
25 bool StateManager::is_restart_ = false;
26 axom::sidre::DataStore* StateManager::ds_ = nullptr;
27 std::string StateManager::output_dir_ = "";
28 std::unordered_map<std::string, mfem::ParGridFunction*> StateManager::named_states_;
29 std::unordered_map<std::string, mfem::ParGridFunction*> StateManager::named_duals_;
30 
31 double StateManager::newDataCollection(const std::string& mesh_tag, const std::optional<int> cycle_to_load)
32 {
33  SLIC_ERROR_ROOT_IF(!ds_, "Cannot construct a DataCollection without a DataStore");
34  std::string coll_name = getCollectionName(mesh_tag);
35 
36  auto global_grp = ds_->getRoot()->createGroup(coll_name + "_global");
37  auto bp_index_grp = global_grp->createGroup("blueprint_index/" + coll_name);
38  auto domain_grp = ds_->getRoot()->createGroup(coll_name);
39 
40  // Needs to be configured to own the mesh data so all mesh data is saved to datastore/output file
41  constexpr bool owns_mesh_data = true;
42  auto [iter, _] = datacolls_.emplace(std::piecewise_construct, std::forward_as_tuple(mesh_tag),
43  std::forward_as_tuple(coll_name, bp_index_grp, domain_grp, owns_mesh_data));
44  auto& datacoll = iter->second;
45  datacoll.SetComm(MPI_COMM_WORLD);
46 
47  datacoll.SetPrefixPath(output_dir_);
48 
49  if (cycle_to_load) {
50  // NOTE: Load invalidates previous Sidre pointers
51  datacoll.Load(*cycle_to_load);
52  datacoll.SetGroupPointers(ds_->getRoot()->getGroup(coll_name + "_global/blueprint_index/" + coll_name),
53  ds_->getRoot()->getGroup(coll_name));
54  SLIC_ERROR_ROOT_IF(datacoll.GetBPGroup()->getNumGroups() == 0,
55  "Loaded datastore is empty, was the datastore created on a "
56  "different number of nodes?");
57 
58  datacoll.UpdateStateFromDS();
59  datacoll.UpdateMeshAndFieldsFromDS();
60 
61  // TODO: This should not be necessary, figure out why on restart this information is not being restored
62  // Generate the face neighbor information in the mesh. This is needed by the face restriction
63  // operators used by Functional
64  mesh(mesh_tag).ExchangeFaceNbrData();
65 
66  checkMesh(mesh(mesh_tag), is_restart_);
67 
68  // Construct and store the shape displacement fields and sensitivities associated with this mesh
69  constructShapeFields(mesh_tag);
70 
71  } else {
72  datacoll.SetCycle(0); // Iteration counter
73  datacoll.SetTime(0.0); // Simulation time
74  }
75 
76  return datacoll.GetTime();
77 }
78 
79 void StateManager::loadCheckpointedStates(int cycle_to_load, std::vector<FiniteElementState*> states_to_load)
80 {
82  const mfem::ParMesh* meshPtr = &(*states_to_load.begin())->mesh();
83  std::string mesh_tag = collectionID(meshPtr);
84 
85  std::string coll_name = getCollectionName(mesh_tag);
86 
87  axom::sidre::MFEMSidreDataCollection previous_datacoll(coll_name);
88 
89  previous_datacoll.SetComm(meshPtr->GetComm());
90  previous_datacoll.SetPrefixPath(output_dir_);
91  previous_datacoll.Load(cycle_to_load);
92 
93  for (auto state : states_to_load) {
94  meshPtr = &state->mesh();
95  SLIC_ERROR_ROOT_IF(collectionID(meshPtr) != mesh_tag,
96  "Loading FiniteElementStates from two different meshes at one time is not allowed.");
97  mfem::ParGridFunction* datacoll_owned_grid_function = previous_datacoll.GetParField(state->name());
98 
99  state->setFromGridFunction(*datacoll_owned_grid_function);
100  }
101 }
102 
103 void StateManager::initialize(axom::sidre::DataStore& ds, const std::string& output_directory)
104 {
105  // If the global object has already been initialized, clear it out
106  if (ds_) {
107  reset();
108  }
109  ds_ = &ds;
110  output_dir_ = output_directory;
111  if (output_directory.empty()) {
112  SLIC_ERROR_ROOT(
113  "DataCollection output directory cannot be empty - this will result in problems if executables are run in "
114  "parallel");
115  }
116 }
117 
119 {
120  SLIC_ERROR_ROOT_IF(shape_displacement_duals_.count(mesh_tag) == 0,
121  std::format("No shape displacement dual exists on mesh named '{}'", mesh_tag));
122  return *shape_displacement_duals_[mesh_tag];
123 }
124 
126 {
127  SLIC_ERROR_ROOT_IF(shape_displacements_.count(mesh_tag) == 0,
128  std::format("No shape displacement exists on mesh named '{}'", mesh_tag));
129  return *shape_displacements_[mesh_tag];
130 }
131 
133 {
134  SLIC_ERROR_ROOT_IF(!ds_, "Smith's data store was not initialized - call StateManager::initialize first");
135  auto mesh_tag = collectionID(&state.mesh());
136  SLIC_ERROR_ROOT_IF(hasState(state.name()),
137  std::format("StateManager already contains a state named '{}'", state.name()));
138  auto& datacoll = datacolls_.at(mesh_tag);
139  const std::string name = state.name();
140  mfem::ParGridFunction* grid_function;
141  if (is_restart_) {
142  grid_function = datacoll.GetParField(name);
143  state.setFromGridFunction(*grid_function);
144  } else {
145  SLIC_ERROR_ROOT_IF(datacoll.HasField(name), std::format("StateManager already given a field named '{0}'", name));
146 
147  // Create a new grid function with unallocated data. This will be managed by sidre.
148  grid_function = new mfem::ParGridFunction(&state.space(), static_cast<double*>(nullptr));
149  datacoll.RegisterField(name, grid_function);
150  state.fillGridFunction(*grid_function);
151  }
152  named_states_[name] = grid_function;
153 }
154 
155 FiniteElementState StateManager::newState(const mfem::ParFiniteElementSpace& space, const std::string& state_name)
156 {
157  std::string mesh_tag = collectionID(space.GetParMesh());
158 
159  SLIC_ERROR_ROOT_IF(!ds_, "Smith's data store was not initialized - call StateManager::initialize first");
160  SLIC_ERROR_ROOT_IF(!hasMesh(mesh_tag), std::format("Mesh tag '{}' not found in the data store", mesh_tag));
161  SLIC_ERROR_ROOT_IF(hasState(state_name), std::format("StateManager already contains a state named '{}'", state_name));
162  auto state = FiniteElementState(space, state_name);
163  storeState(state);
164  return state;
165 }
166 
168 {
169  SLIC_ERROR_ROOT_IF(!ds_, "Smith's data store was not initialized - call StateManager::initialize first");
170  auto mesh_tag = collectionID(&dual.mesh());
171  SLIC_ERROR_ROOT_IF(hasDual(dual.name()),
172  std::format("StateManager already contains a state named '{}'", dual.name()));
173  auto& datacoll = datacolls_.at(mesh_tag);
174  const std::string name = dual.name();
175  mfem::ParGridFunction* grid_function;
176  if (is_restart_) {
177  grid_function = datacoll.GetParField(name);
178  std::unique_ptr<mfem::HypreParVector> true_dofs(grid_function->GetTrueDofs());
179  dual = *true_dofs;
180  } else {
181  SLIC_ERROR_ROOT_IF(datacoll.HasField(name), std::format("StateManager already given a field named '{0}'", name));
182 
183  // Create a new grid function with unallocated data. This will be managed by sidre.
184  grid_function = new mfem::ParGridFunction(&dual.space(), static_cast<double*>(nullptr));
185  datacoll.RegisterField(name, grid_function);
186  grid_function->SetFromTrueDofs(dual);
187  }
188  named_duals_[name] = grid_function;
189 }
190 
191 FiniteElementDual StateManager::newDual(const mfem::ParFiniteElementSpace& space, const std::string& dual_name)
192 {
193  std::string mesh_tag = collectionID(space.GetParMesh());
194 
195  SLIC_ERROR_ROOT_IF(!ds_, "Smith's data store was not initialized - call StateManager::initialize first");
196  SLIC_ERROR_ROOT_IF(!hasMesh(mesh_tag), std::format("Mesh tag '{}' not found in the data store", mesh_tag));
197  SLIC_ERROR_ROOT_IF(hasDual(dual_name), std::format("StateManager already contains a dual named '{}'", dual_name));
198  auto dual = FiniteElementDual(space, dual_name);
199  storeDual(dual);
200  return dual;
201 }
202 
203 void StateManager::save(const double t, const int cycle, const std::string& mesh_tag)
204 {
206  SLIC_ERROR_ROOT_IF(!ds_, "Smith's data store was not initialized - call StateManager::initialize first");
207  SLIC_ERROR_ROOT_IF(!hasMesh(mesh_tag), std::format("Mesh tag '{}' not found in the data store", mesh_tag));
208 
209  // copy data to host (if needed; HostRead() does nothing if host data is up-to-date)
210  for (auto& state : named_states_) {
211  state.second->HostRead();
212  }
213  for (auto& dual : named_duals_) {
214  dual.second->HostRead();
215  }
216 
217  auto& datacoll = datacolls_.at(mesh_tag);
218  std::string file_path = axom::utilities::filesystem::joinPath(datacoll.GetPrefixPath(), datacoll.GetCollectionName());
219  SLIC_INFO_ROOT(
220  std::format("Saving data collection at time: '{}' and cycle: '{}' to path: '{}'", t, cycle, file_path));
221 
222  datacoll.SetTime(t);
223  datacoll.SetCycle(cycle);
224  datacoll.Save();
225 }
226 
227 mfem::ParMesh& StateManager::setMesh(std::unique_ptr<mfem::ParMesh> pmesh, const std::string& mesh_tag)
228 {
229  checkMesh(*pmesh);
230 
231  // Sidre will destruct the nodal grid function instead of the mesh
232  pmesh->SetNodesOwner(false);
233 
234  newDataCollection(mesh_tag);
235  auto& datacoll = datacolls_.at(mesh_tag);
236  datacoll.SetMesh(pmesh.release());
237  datacoll.SetOwnData(true);
238 
239  auto& new_pmesh = mesh(mesh_tag);
240 
241  // We must construct the shape fields here as the mesh did not exist during the newDataCollection call
242  // for the non-restart case
243  // BT: Consider storing shape fields on the mesh class and making them on mesh construction.
244  // The setMesh() call wouldn't mutate the mesh at all, just store it as name implies.
245  constructShapeFields(mesh_tag);
246 
247  return new_pmesh;
248 }
249 
250 void StateManager::constructShapeFields(const std::string& mesh_tag)
251 {
252  // Construct the shape displacement field associated with this mesh
253  auto& new_mesh = mesh(mesh_tag);
254 
255  int dim = new_mesh.Dimension();
256 
257  if (dim == 2) {
258  shape_displacements_[mesh_tag] =
259  std::make_unique<FiniteElementState>(new_mesh, SHAPE_DIM_2, mesh_tag + "_shape_displacement");
260  } else if (dim == 3) {
261  shape_displacements_[mesh_tag] =
262  std::make_unique<FiniteElementState>(new_mesh, SHAPE_DIM_3, mesh_tag + "_shape_displacement");
263  } else {
264  SLIC_ERROR_ROOT(std::format("Mesh of dimension {} given, only dimensions 2 or 3 are available in Smith.",
265  new_mesh.Dimension()));
266  }
267 
268  storeState(*shape_displacements_[mesh_tag]);
269 
270  *shape_displacements_[mesh_tag] = 0.0;
271 
272  if (dim == 2) {
273  shape_displacement_duals_[mesh_tag] =
274  std::make_unique<FiniteElementDual>(new_mesh, SHAPE_DIM_2, mesh_tag + "_shape_displacement_dual");
275  } else {
276  shape_displacement_duals_[mesh_tag] =
277  std::make_unique<FiniteElementDual>(new_mesh, SHAPE_DIM_3, mesh_tag + "_shape_displacement_dual");
278  }
279 
280  storeDual(*shape_displacement_duals_[mesh_tag]);
281 }
282 
283 mfem::ParMesh& StateManager::mesh(const std::string& mesh_tag)
284 {
285  SLIC_ERROR_ROOT_IF(!hasMesh(mesh_tag), std::format("Mesh tag \"{}\" not found in the data store", mesh_tag));
286  auto mesh = datacolls_.at(mesh_tag).GetMesh();
287  SLIC_ERROR_ROOT_IF(!mesh, "The datacollection does not contain a mesh object");
288  return static_cast<mfem::ParMesh&>(*mesh);
289 }
290 
291 std::string StateManager::collectionID(const mfem::ParMesh* pmesh)
292 {
293  for (auto& [name, datacoll] : datacolls_) {
294  if (datacoll.GetMesh() == pmesh) {
295  return name;
296  }
297  }
298  SLIC_ERROR_ROOT("The mesh has not been registered with StateManager");
299  return {};
300 }
301 
302 int StateManager::cycle(std::string mesh_tag)
303 {
304  SLIC_ERROR_ROOT_IF(!hasMesh(mesh_tag), std::format("Mesh tag \"{}\" not found in the data store", mesh_tag));
305  return datacolls_.at(mesh_tag).GetCycle();
306 }
307 
308 double StateManager::time(std::string mesh_tag)
309 {
310  SLIC_ERROR_ROOT_IF(!hasMesh(mesh_tag), std::format("Mesh tag \"{}\" not found in the data store", mesh_tag));
311  return datacolls_.at(mesh_tag).GetTime();
312 }
313 
314 void checkMesh(const mfem::ParMesh& pmesh, bool is_restart)
315 {
316  const mfem::GridFunction* nodes = pmesh.GetNodes();
317 
318  SLIC_ERROR_ROOT_IF(!nodes,
319  "The mesh must have a grid function for the nodes defined. Call the EnsureNodes() method on "
320  "your mesh before setting it with the state manager.");
321 
322  if (!is_restart) {
323  SLIC_ERROR_ROOT_IF(!pmesh.OwnsNodes(),
324  "The mesh must own its node grid function, as ownership will be passed to the state manager.");
325  }
326 
327  SLIC_WARNING_ROOT_IF(nodes->FESpace()->FEColl()->GetContType() == mfem::FiniteElementCollection::DISCONTINUOUS,
328  "Periodic mesh detected! This will only work on translational periodic surfaces for vector H1 "
329  "fields and has not been thoroughly tested. Proceed at your own risk.");
330 
331  const std::string ordering_string = smith::ordering == mfem::Ordering::byNODES ? "byNODES" : "byVDIM";
332  SLIC_ERROR_ROOT_IF(nodes->FESpace()->GetOrdering() != smith::ordering,
333  "The dof ordering of the mesh coordinates grid function must be the same as the global setting "
334  "in smith::ordering. The Smith ordering is currently " +
335  ordering_string);
336 
337  // Mesh must have face restriction operators, as they are used by Functional
338  SLIC_ERROR_ROOT_IF(!pmesh.have_face_nbr_data,
339  "The mesh must have face neighbor data defined. Smith mesh building tools should ensure this "
340  "automatically. If you built your mesh with native MFEM tools, make sure to call the "
341  "ExchangeFaceNbrData() before setting it with the state manager.");
342 }
343 
344 } // namespace smith
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.
void fillGridFunction(mfem::ParGridFunction &grid_function) const
Fill a user-provided grid function based on the underlying true vector.
void setFromGridFunction(const mfem::ParGridFunction &grid_function)
Initialize the true vector in the FiniteElementState based on an input grid function.
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.
static void storeState(FiniteElementState &state)
Store a pre-constructed finite element state in the state manager.
static FiniteElementState & shapeDisplacement(const std::string &mesh_tag)
Get the shape displacement finite element state.
static std::string collectionID(const mfem::ParMesh *pmesh)
Returns the datacollection ID for a given mesh.
static FiniteElementDual newDual(FunctionSpace space, const std::string &dual_name, const std::string &mesh_tag)
Factory method for creating a new FEDual object.
static void reset()
Resets the underlying global datacollection object.
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 int cycle(std::string mesh_tag)
Get the current cycle (iteration number) from the underlying datacollection.
static bool hasMesh(const std::string &mesh_tag)
Checks if StateManager has a mesh with the given mesh_tag.
static mfem::ParMesh & setMesh(std::unique_ptr< mfem::ParMesh > pmesh, const std::string &mesh_tag)
Gives ownership of mesh to StateManager.
static bool hasDual(const std::string &name)
Checks if StateManager has a dual with the given name.
static FiniteElementDual & shapeDisplacementDual(const std::string &mesh_tag)
Get the shape displacement finite element dual.
static bool hasState(const std::string &name)
Checks if StateManager has a state with the given name.
static double time(std::string mesh_tag)
Get the current simulation time from the underlying datacollection.
static FiniteElementState newState(FunctionSpace space, const std::string &state_name, const std::string &mesh_tag)
Factory method for creating a new FEState object.
static mfem::ParMesh & mesh(const std::string &mesh_tag)
Returns a non-owning reference to mesh held by StateManager.
static void storeDual(FiniteElementDual &dual)
Store a pre-constructed finite element dual in the state manager.
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 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 vectors derived from an MFEM finite elem...
Accelerator functionality.
Definition: smith.cpp:36
constexpr H1< SHAPE_ORDER, 3 > SHAPE_DIM_3
Function space for shape displacement on dimension 2 meshes.
Definition: field_types.hpp:27
dual(double, T) -> dual< T >
class template argument deduction guide for type dual.
void checkMesh(const mfem::ParMesh &pmesh, bool is_restart)
Check that a mesh satisfies our required properties.
constexpr H1< SHAPE_ORDER, 2 > SHAPE_DIM_2
Function space for shape displacement on dimension 2 meshes.
Definition: field_types.hpp:24
mfem::ParFiniteElementSpace & space(FieldState field)
Get the space from the primal field of a field states.
Various helper functions and macros for profiling using Caliper.
#define SMITH_MARK_FUNCTION
Definition: profiling.hpp:90
This file contains the declaration of the StateManager class.
Dual number struct (value plus gradient)
Definition: dual.hpp:28