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