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  axom::fmt::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  axom::fmt::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  axom::fmt::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),
146  axom::fmt::format("StateManager already given a field named '{0}'", name));
147 
148  // Create a new grid function with unallocated data. This will be managed by sidre.
149  grid_function = new mfem::ParGridFunction(&state.space(), static_cast<double*>(nullptr));
150  datacoll.RegisterField(name, grid_function);
151  state.fillGridFunction(*grid_function);
152  }
153  named_states_[name] = grid_function;
154 }
155 
156 FiniteElementState StateManager::newState(const mfem::ParFiniteElementSpace& space, const std::string& state_name)
157 {
158  std::string mesh_tag = collectionID(space.GetParMesh());
159 
160  SLIC_ERROR_ROOT_IF(!ds_, "Smith's data store was not initialized - call StateManager::initialize first");
161  SLIC_ERROR_ROOT_IF(!hasMesh(mesh_tag), axom::fmt::format("Mesh tag '{}' not found in the data store", mesh_tag));
162  SLIC_ERROR_ROOT_IF(hasState(state_name),
163  axom::fmt::format("StateManager already contains a state named '{}'", state_name));
164  auto state = FiniteElementState(space, state_name);
165  storeState(state);
166  return state;
167 }
168 
170 {
171  SLIC_ERROR_ROOT_IF(!ds_, "Smith's data store was not initialized - call StateManager::initialize first");
172  auto mesh_tag = collectionID(&dual.mesh());
173  SLIC_ERROR_ROOT_IF(hasDual(dual.name()),
174  axom::fmt::format("StateManager already contains a state named '{}'", dual.name()));
175  auto& datacoll = datacolls_.at(mesh_tag);
176  const std::string name = dual.name();
177  mfem::ParGridFunction* grid_function;
178  if (is_restart_) {
179  grid_function = datacoll.GetParField(name);
180  std::unique_ptr<mfem::HypreParVector> true_dofs(grid_function->GetTrueDofs());
181  dual = *true_dofs;
182  } else {
183  SLIC_ERROR_ROOT_IF(datacoll.HasField(name),
184  axom::fmt::format("StateManager already given a field named '{0}'", name));
185 
186  // Create a new grid function with unallocated data. This will be managed by sidre.
187  grid_function = new mfem::ParGridFunction(&dual.space(), static_cast<double*>(nullptr));
188  datacoll.RegisterField(name, grid_function);
189  grid_function->SetFromTrueDofs(dual);
190  }
191  named_duals_[name] = grid_function;
192 }
193 
194 FiniteElementDual StateManager::newDual(const mfem::ParFiniteElementSpace& space, const std::string& dual_name)
195 {
196  std::string mesh_tag = collectionID(space.GetParMesh());
197 
198  SLIC_ERROR_ROOT_IF(!ds_, "Smith's data store was not initialized - call StateManager::initialize first");
199  SLIC_ERROR_ROOT_IF(!hasMesh(mesh_tag), axom::fmt::format("Mesh tag '{}' not found in the data store", mesh_tag));
200  SLIC_ERROR_ROOT_IF(hasDual(dual_name),
201  axom::fmt::format("StateManager already contains a dual named '{}'", dual_name));
202  auto dual = FiniteElementDual(space, dual_name);
203  storeDual(dual);
204  return dual;
205 }
206 
207 void StateManager::save(const double t, const int cycle, const std::string& mesh_tag)
208 {
210  SLIC_ERROR_ROOT_IF(!ds_, "Smith's data store was not initialized - call StateManager::initialize first");
211  SLIC_ERROR_ROOT_IF(!hasMesh(mesh_tag), axom::fmt::format("Mesh tag '{}' not found in the data store", mesh_tag));
212  auto& datacoll = datacolls_.at(mesh_tag);
213  std::string file_path = axom::utilities::filesystem::joinPath(datacoll.GetPrefixPath(), datacoll.GetCollectionName());
214  SLIC_INFO_ROOT(
215  axom::fmt::format("Saving data collection at time: '{}' and cycle: '{}' to path: '{}'", t, cycle, file_path));
216 
217  datacoll.SetTime(t);
218  datacoll.SetCycle(cycle);
219  datacoll.Save();
220 }
221 
222 mfem::ParMesh& StateManager::setMesh(std::unique_ptr<mfem::ParMesh> pmesh, const std::string& mesh_tag)
223 {
224  checkMesh(*pmesh);
225 
226  // Sidre will destruct the nodal grid function instead of the mesh
227  pmesh->SetNodesOwner(false);
228 
229  newDataCollection(mesh_tag);
230  auto& datacoll = datacolls_.at(mesh_tag);
231  datacoll.SetMesh(pmesh.release());
232  datacoll.SetOwnData(true);
233 
234  auto& new_pmesh = mesh(mesh_tag);
235 
236  // We must construct the shape fields here as the mesh did not exist during the newDataCollection call
237  // for the non-restart case
238  // BT: Consider storing shape fields on the mesh class and making them on mesh construction.
239  // The setMesh() call wouldn't mutate the mesh at all, just store it as name implies.
240  constructShapeFields(mesh_tag);
241 
242  return new_pmesh;
243 }
244 
245 void StateManager::constructShapeFields(const std::string& mesh_tag)
246 {
247  // Construct the shape displacement field associated with this mesh
248  auto& new_mesh = mesh(mesh_tag);
249 
250  int dim = new_mesh.Dimension();
251 
252  if (dim == 2) {
253  shape_displacements_[mesh_tag] =
254  std::make_unique<FiniteElementState>(new_mesh, SHAPE_DIM_2, mesh_tag + "_shape_displacement");
255  } else if (dim == 3) {
256  shape_displacements_[mesh_tag] =
257  std::make_unique<FiniteElementState>(new_mesh, SHAPE_DIM_3, mesh_tag + "_shape_displacement");
258  } else {
259  SLIC_ERROR_ROOT(axom::fmt::format("Mesh of dimension {} given, only dimensions 2 or 3 are available in Smith.",
260  new_mesh.Dimension()));
261  }
262 
263  storeState(*shape_displacements_[mesh_tag]);
264 
265  *shape_displacements_[mesh_tag] = 0.0;
266 
267  if (dim == 2) {
268  shape_displacement_duals_[mesh_tag] =
269  std::make_unique<FiniteElementDual>(new_mesh, SHAPE_DIM_2, mesh_tag + "_shape_displacement_dual");
270  } else {
271  shape_displacement_duals_[mesh_tag] =
272  std::make_unique<FiniteElementDual>(new_mesh, SHAPE_DIM_3, mesh_tag + "_shape_displacement_dual");
273  }
274 
275  storeDual(*shape_displacement_duals_[mesh_tag]);
276 }
277 
278 mfem::ParMesh& StateManager::mesh(const std::string& mesh_tag)
279 {
280  SLIC_ERROR_ROOT_IF(!hasMesh(mesh_tag), axom::fmt::format("Mesh tag \"{}\" not found in the data store", mesh_tag));
281  auto mesh = datacolls_.at(mesh_tag).GetMesh();
282  SLIC_ERROR_ROOT_IF(!mesh, "The datacollection does not contain a mesh object");
283  return static_cast<mfem::ParMesh&>(*mesh);
284 }
285 
286 std::string StateManager::collectionID(const mfem::ParMesh* pmesh)
287 {
288  for (auto& [name, datacoll] : datacolls_) {
289  if (datacoll.GetMesh() == pmesh) {
290  return name;
291  }
292  }
293  SLIC_ERROR_ROOT("The mesh has not been registered with StateManager");
294  return {};
295 }
296 
297 int StateManager::cycle(std::string mesh_tag)
298 {
299  SLIC_ERROR_ROOT_IF(!hasMesh(mesh_tag), axom::fmt::format("Mesh tag \"{}\" not found in the data store", mesh_tag));
300  return datacolls_.at(mesh_tag).GetCycle();
301 }
302 
303 double StateManager::time(std::string mesh_tag)
304 {
305  SLIC_ERROR_ROOT_IF(!hasMesh(mesh_tag), axom::fmt::format("Mesh tag \"{}\" not found in the data store", mesh_tag));
306  return datacolls_.at(mesh_tag).GetTime();
307 }
308 
309 void checkMesh(const mfem::ParMesh& pmesh, bool is_restart)
310 {
311  const mfem::GridFunction* nodes = pmesh.GetNodes();
312 
313  SLIC_ERROR_ROOT_IF(!nodes,
314  "The mesh must have a grid function for the nodes defined. Call the EnsureNodes() method on "
315  "your mesh before setting it with the state manager.");
316 
317  if (!is_restart) {
318  SLIC_ERROR_ROOT_IF(!pmesh.OwnsNodes(),
319  "The mesh must own its node grid function, as ownership will be passed to the state manager.");
320  }
321 
322  SLIC_WARNING_ROOT_IF(nodes->FESpace()->FEColl()->GetContType() == mfem::FiniteElementCollection::DISCONTINUOUS,
323  "Periodic mesh detected! This will only work on translational periodic surfaces for vector H1 "
324  "fields and has not been thoroughly tested. Proceed at your own risk.");
325 
326  const std::string ordering_string = smith::ordering == mfem::Ordering::byNODES ? "byNODES" : "byVDIM";
327  SLIC_ERROR_ROOT_IF(nodes->FESpace()->GetOrdering() != smith::ordering,
328  "The dof ordering of the mesh coordinates grid function must be the same as the global setting "
329  "in smith::ordering. The Smith ordering is currently " +
330  ordering_string);
331 
332  // Mesh must have face restriction operators, as they are used by Functional
333  SLIC_ERROR_ROOT_IF(!pmesh.have_face_nbr_data,
334  "The mesh must have face neighbor data defined. Smith mesh building tools should ensure this "
335  "automatically. If you built your mesh with native MFEM tools, make sure to call the "
336  "ExchangeFaceNbrData() before setting it with the state manager.");
337 }
338 
339 } // 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
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