2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2020,2021, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
35 /*! \libinternal \file
36 * \brief Provides the checkpoint data structure for the modular simulator
38 * \author Pascal Merz <pascal.merz@me.com>
39 * \ingroup module_mdtypes
42 #ifndef GMX_MODULARSIMULATOR_CHECKPOINTDATA_H
43 #define GMX_MODULARSIMULATOR_CHECKPOINTDATA_H
47 #include "gromacs/math/vectypes.h"
48 #include "gromacs/utility/arrayref.h"
49 #include "gromacs/utility/exceptions.h"
50 #include "gromacs/utility/keyvaluetreebuilder.h"
51 #include "gromacs/utility/stringutil.h"
58 * \brief The operations on CheckpointData
60 * This enum defines the two modes of operation on CheckpointData objects,
61 * reading and writing. This allows to template all access functions, which
62 * in turn enables clients to write a single function for read and write
63 * access, eliminating the risk of having read and write functions getting
66 * \ingroup module_modularsimulator
68 enum class CheckpointDataOperation
76 * \brief Get an ArrayRef whose const-ness is defined by the checkpointing operation
78 * \tparam operation Whether we are reading or writing
79 * \tparam T The type of values stored in the ArrayRef
80 * \param container The container the ArrayRef is referencing to
81 * \return The ArrayRef
85 * \ingroup module_modularsimulator
87 template<CheckpointDataOperation operation, typename T>
88 ArrayRef<std::conditional_t<operation == CheckpointDataOperation::Write || std::is_const<T>::value, const typename T::value_type, typename T::value_type>>
89 makeCheckpointArrayRef(T& container)
95 * \brief Get an ArrayRef to a C array whose const-ness is defined by the checkpointing operation
97 * \tparam operation Whether we are reading or writing
98 * \tparam T The type of values stored in the ArrayRef
99 * \param begin Pointer to the beginning of array.
100 * \param size Number of elements in array.
101 * \return The ArrayRef
105 * \ingroup module_modularsimulator
107 template<CheckpointDataOperation operation, typename T>
108 ArrayRef<std::conditional_t<operation == CheckpointDataOperation::Write || std::is_const<T>::value, const T, T>>
109 makeCheckpointArrayRefFromArray(T* begin, size_t size)
111 return ArrayRef<T>(begin, begin + size);
115 * \ingroup module_modularsimulator
116 * \brief Struct allowing to check if data is serializable through the KeyValueTree serializer
118 * This list of types is copied from ValueSerializer::initSerializers()
119 * Having this here allows us to catch errors at compile time
120 * instead of having cryptic runtime errors
123 struct IsSerializableType
125 static bool const value = std::is_same<T, std::string>::value || std::is_same<T, bool>::value
126 || std::is_same<T, int>::value || std::is_same<T, int64_t>::value
127 || std::is_same<T, float>::value || std::is_same<T, double>::value;
131 * \ingroup module_modularsimulator
132 * \brief Struct allowing to check if enum has a serializable underlying type
135 template<typename T, bool = std::is_enum<T>::value>
136 struct IsSerializableEnum
138 static bool const value = IsSerializableType<std::underlying_type_t<T>>::value;
141 struct IsSerializableEnum<T, false>
143 static bool const value = false;
148 * \ingroup module_modularsimulator
149 * \brief Data type hiding checkpoint implementation details
151 * This data type allows to separate the implementation details of the
152 * checkpoint writing / reading from the implementation of the checkpoint
153 * clients. Checkpoint clients interface via the methods of the CheckpointData
154 * object, and do not need knowledge of data types used to store the data.
156 * Templating allows checkpoint clients to have symmetric (templated)
157 * implementations for checkpoint reading and writing.
159 * CheckpointData objects are dispatched via [Write|Read]CheckpointDataHolder
160 * objects, which interact with the checkpoint reading from / writing to
164 template<CheckpointDataOperation operation>
165 class CheckpointData;
167 //! Convenience shortcut for reading checkpoint data.
168 using ReadCheckpointData = CheckpointData<CheckpointDataOperation::Read>;
169 //! Convenience shortcut for writing checkpoint data.
170 using WriteCheckpointData = CheckpointData<CheckpointDataOperation::Write>;
173 class CheckpointData<CheckpointDataOperation::Read>
176 /*! \brief Read or write a single value from / to checkpoint
178 * Allowed scalar types include std::string, bool, int, int64_t,
179 * float, double, or any enum with one of the previously mentioned
180 * scalar types as underlying type. Type compatibility is checked
183 * \tparam operation Whether we are reading or writing
184 * \tparam T The type of the value
185 * \param key The key to [read|write] the value [from|to]
186 * \param value The value to [read|write]
190 std::enable_if_t<IsSerializableType<T>::value, void> scalar(const std::string& key, T* value) const;
192 std::enable_if_t<IsSerializableEnum<T>::value, void> enumScalar(const std::string& key, T* value) const;
195 /*! \brief Read or write an ArrayRef from / to checkpoint
197 * Allowed types stored in the ArrayRef include std::string, bool, int,
198 * int64_t, float, double, and gmx::RVec. Type compatibility is checked
201 * \tparam operation Whether we are reading or writing
202 * \tparam T The type of values stored in the ArrayRef
203 * \param key The key to [read|write] the ArrayRef [from|to]
204 * \param values The ArrayRef to [read|write]
207 // Read ArrayRef of scalar
209 std::enable_if_t<IsSerializableType<T>::value, void> arrayRef(const std::string& key,
210 ArrayRef<T> values) const;
211 // Read ArrayRef of RVec
212 void arrayRef(const std::string& key, ArrayRef<RVec> values) const;
215 /*! \brief Read or write a tensor from / to checkpoint
217 * \tparam operation Whether we are reading or writing
218 * \param key The key to [read|write] the tensor [from|to]
219 * \param values The tensor to [read|write]
221 void tensor(const std::string& key, ::tensor values) const;
223 /*! \brief Return a subset of the current CheckpointData
225 * \tparam operation Whether we are reading or writing
226 * \param key The key to [read|write] the sub data [from|to]
227 * \return A CheckpointData object representing a subset of the current object
230 CheckpointData subCheckpointData(const std::string& key) const;
234 //! KV tree read from checkpoint
235 const KeyValueTreeObject* inputTree_ = nullptr;
237 //! Construct an input checkpoint data object
238 explicit CheckpointData(const KeyValueTreeObject& inputTree);
240 // Only holders should build
241 friend class ReadCheckpointDataHolder;
245 class CheckpointData<CheckpointDataOperation::Write>
248 //! \copydoc CheckpointData<CheckpointDataOperation::Read>::scalar
251 std::enable_if_t<IsSerializableType<T>::value, void> scalar(const std::string& key, const T* value);
253 std::enable_if_t<IsSerializableEnum<T>::value, void> enumScalar(const std::string& key, const T* value);
256 //! \copydoc CheckpointData<CheckpointDataOperation::Read>::arrayRef
258 // Write ArrayRef of scalar
260 std::enable_if_t<IsSerializableType<T>::value, void> arrayRef(const std::string& key,
261 ArrayRef<const T> values);
262 // Write ArrayRef of RVec
263 void arrayRef(const std::string& key, ArrayRef<const RVec> values);
266 //! \copydoc CheckpointData<CheckpointDataOperation::Read>::tensor
267 void tensor(const std::string& key, const ::tensor values);
269 //! \copydoc CheckpointData<CheckpointDataOperation::Read>::subCheckpointData
270 CheckpointData subCheckpointData(const std::string& key);
273 //! Builder for the tree to be written to checkpoint
274 std::optional<KeyValueTreeObjectBuilder> outputTreeBuilder_ = std::nullopt;
276 //! Construct an output checkpoint data object
277 explicit CheckpointData(KeyValueTreeObjectBuilder&& outputTreeBuilder);
279 // Only holders should build
280 friend class WriteCheckpointDataHolder;
283 /*! \brief Read a checkpoint version enum variable
285 * This reads the checkpoint version from file. The read version is returned.
287 * If the read version is more recent than the code version, this throws an error, since
288 * we cannot know what has changed in the meantime. Using newer checkpoint files with
289 * old code is not a functionality we can offer. Note, however, that since the checkpoint
290 * version is saved by module, older checkpoint files of all simulations that don't use
291 * that specific module can still be used.
293 * Allowing backwards compatibility of files (i.e., reading an older checkpoint file with
294 * a newer version of the code) is in the responsibility of the caller module. They can
295 * use the returned file checkpoint version to do that:
297 * const auto fileVersion = checkpointVersion(checkpointData, "version", c_currentVersion);
298 * if (fileVersion >= CheckpointVersion::AddedX)
300 * checkpointData->scalar("x", &x_));
303 * @tparam VersionEnum The type of the checkpoint version enum
304 * @param checkpointData A reading checkpoint data object
305 * @param key The key under which the version is saved - also used for error output
306 * @param programVersion The checkpoint version of the current code
307 * @return The checkpoint version read from file
309 template<typename VersionEnum>
310 VersionEnum checkpointVersion(const ReadCheckpointData* checkpointData,
311 const std::string& key,
312 const VersionEnum programVersion)
314 VersionEnum fileVersion;
315 checkpointData->enumScalar(key, &fileVersion);
316 if (fileVersion > programVersion)
319 formatString("The checkpoint file contains a %s that is more recent than the "
320 "current program version and is not backward compatible.",
326 /*! \brief Write the current code checkpoint version enum variable
328 * Write the current program checkpoint version to the checkpoint data object.
329 * Returns the written checkpoint version to mirror the signature of the reading version.
331 * @tparam VersionEnum The type of the checkpoint version enum
332 * @param checkpointData A writing checkpoint data object
333 * @param key The key under which the version is saved
334 * @param programVersion The checkpoint version of the current code
335 * @return The checkpoint version written to file
337 template<typename VersionEnum>
338 VersionEnum checkpointVersion(WriteCheckpointData* checkpointData,
339 const std::string& key,
340 const VersionEnum programVersion)
342 checkpointData->enumScalar(key, &programVersion);
343 return programVersion;
346 inline ReadCheckpointData::CheckpointData(const KeyValueTreeObject& inputTree) :
347 inputTree_(&inputTree)
351 inline WriteCheckpointData::CheckpointData(KeyValueTreeObjectBuilder&& outputTreeBuilder) :
352 outputTreeBuilder_(outputTreeBuilder)
356 * \brief Holder for read checkpoint data
358 * A ReadCheckpointDataHolder object is passed to the checkpoint reading
359 * functionality, and then passed into the SimulatorBuilder object. It
360 * holds the KV-tree read from file and dispatches CheckpointData objects
361 * to the checkpoint clients.
363 class ReadCheckpointDataHolder
366 //! Check whether a key exists
367 [[nodiscard]] bool keyExists(const std::string& key) const;
369 //! Return vector of existing keys
370 [[nodiscard]] std::vector<std::string> keys() const;
372 //! Deserialize serializer content into the CheckpointData object
373 void deserialize(ISerializer* serializer);
375 /*! \brief Return a subset of the current CheckpointData
377 * \param key The key to [read|write] the sub data [from|to]
378 * \return A CheckpointData object representing a subset of the current object
380 [[nodiscard]] ReadCheckpointData checkpointData(const std::string& key) const;
382 //! Write the contents of the Checkpoint to file
383 void dump(FILE* out) const;
386 //! KV-tree read from checkpoint
387 KeyValueTreeObject checkpointTree_;
391 * \brief Holder for write checkpoint data
393 * The WriteCheckpointDataHolder object holds the KV-tree builder and
394 * dispatches CheckpointData objects to the checkpoint clients to save
395 * their respective data. It is then passed to the checkpoint writing
398 class WriteCheckpointDataHolder
401 //! Serialize the content of the CheckpointData object
402 void serialize(ISerializer* serializer);
404 /*! \brief Return a subset of the current CheckpointData
406 * \param key The key to [read|write] the sub data [from|to]
407 * \return A CheckpointData object representing a subset of the current object
409 [[nodiscard]] WriteCheckpointData checkpointData(const std::string& key);
413 [[nodiscard]] bool empty() const;
417 KeyValueTreeBuilder outputTreeBuilder_;
418 //! Whether any checkpoint data object has been requested
419 bool hasCheckpointDataBeenRequested_ = false;
422 // Function definitions - here to avoid template-related linker problems
423 // doxygen doesn't like these...
426 std::enable_if_t<IsSerializableType<T>::value, void> ReadCheckpointData::scalar(const std::string& key,
429 GMX_RELEASE_ASSERT(inputTree_, "No input checkpoint data available.");
430 *value = (*inputTree_)[key].cast<T>();
434 std::enable_if_t<IsSerializableEnum<T>::value, void> ReadCheckpointData::enumScalar(const std::string& key,
437 GMX_RELEASE_ASSERT(inputTree_, "No input checkpoint data available.");
438 std::underlying_type_t<T> castValue;
439 castValue = (*inputTree_)[key].cast<std::underlying_type_t<T>>();
440 *value = static_cast<T>(castValue);
444 inline std::enable_if_t<IsSerializableType<T>::value, void>
445 WriteCheckpointData::scalar(const std::string& key, const T* value)
447 GMX_RELEASE_ASSERT(outputTreeBuilder_, "No output checkpoint data available.");
448 outputTreeBuilder_->addValue(key, *value);
452 inline std::enable_if_t<IsSerializableEnum<T>::value, void>
453 WriteCheckpointData::enumScalar(const std::string& key, const T* value)
455 GMX_RELEASE_ASSERT(outputTreeBuilder_, "No output checkpoint data available.");
456 auto castValue = static_cast<std::underlying_type_t<T>>(*value);
457 outputTreeBuilder_->addValue(key, castValue);
461 inline std::enable_if_t<IsSerializableType<T>::value, void>
462 ReadCheckpointData::arrayRef(const std::string& key, ArrayRef<T> values) const
464 GMX_RELEASE_ASSERT(inputTree_, "No input checkpoint data available.");
465 GMX_RELEASE_ASSERT(values.size() >= (*inputTree_)[key].asArray().values().size(),
466 "Read vector does not fit in passed ArrayRef.");
467 auto outputIt = values.begin();
468 auto inputIt = (*inputTree_)[key].asArray().values().begin();
469 auto outputEnd = values.end();
470 auto inputEnd = (*inputTree_)[key].asArray().values().end();
471 for (; outputIt != outputEnd && inputIt != inputEnd; outputIt++, inputIt++)
473 *outputIt = inputIt->cast<T>();
478 inline std::enable_if_t<IsSerializableType<T>::value, void>
479 WriteCheckpointData::arrayRef(const std::string& key, ArrayRef<const T> values)
481 GMX_RELEASE_ASSERT(outputTreeBuilder_, "No output checkpoint data available.");
482 auto builder = outputTreeBuilder_->addUniformArray<T>(key);
483 for (const auto& value : values)
485 builder.addValue(value);
489 inline void ReadCheckpointData::arrayRef(const std::string& key, ArrayRef<RVec> values) const
491 GMX_RELEASE_ASSERT(values.size() >= (*inputTree_)[key].asArray().values().size(),
492 "Read vector does not fit in passed ArrayRef.");
493 auto outputIt = values.begin();
494 auto inputIt = (*inputTree_)[key].asArray().values().begin();
495 auto outputEnd = values.end();
496 auto inputEnd = (*inputTree_)[key].asArray().values().end();
497 for (; outputIt != outputEnd && inputIt != inputEnd; outputIt++, inputIt++)
499 auto storedRVec = inputIt->asObject()["RVec"].asArray().values();
500 *outputIt = { storedRVec[XX].cast<real>(), storedRVec[YY].cast<real>(), storedRVec[ZZ].cast<real>() };
504 inline void WriteCheckpointData::arrayRef(const std::string& key, ArrayRef<const RVec> values)
506 auto builder = outputTreeBuilder_->addObjectArray(key);
507 for (const auto& value : values)
509 auto subbuilder = builder.addObject();
510 subbuilder.addUniformArray("RVec", { value[XX], value[YY], value[ZZ] });
514 inline void ReadCheckpointData::tensor(const std::string& key, ::tensor values) const
516 auto array = (*inputTree_)[key].asArray().values();
517 values[XX][XX] = array[0].cast<real>();
518 values[XX][YY] = array[1].cast<real>();
519 values[XX][ZZ] = array[2].cast<real>();
520 values[YY][XX] = array[3].cast<real>();
521 values[YY][YY] = array[4].cast<real>();
522 values[YY][ZZ] = array[5].cast<real>();
523 values[ZZ][XX] = array[6].cast<real>();
524 values[ZZ][YY] = array[7].cast<real>();
525 values[ZZ][ZZ] = array[8].cast<real>();
528 inline void WriteCheckpointData::tensor(const std::string& key, const ::tensor values)
530 auto builder = outputTreeBuilder_->addUniformArray<real>(key);
531 builder.addValue(values[XX][XX]);
532 builder.addValue(values[XX][YY]);
533 builder.addValue(values[XX][ZZ]);
534 builder.addValue(values[YY][XX]);
535 builder.addValue(values[YY][YY]);
536 builder.addValue(values[YY][ZZ]);
537 builder.addValue(values[ZZ][XX]);
538 builder.addValue(values[ZZ][YY]);
539 builder.addValue(values[ZZ][ZZ]);
542 inline ReadCheckpointData ReadCheckpointData::subCheckpointData(const std::string& key) const
544 return CheckpointData((*inputTree_)[key].asObject());
547 inline WriteCheckpointData WriteCheckpointData::subCheckpointData(const std::string& key)
549 return CheckpointData(outputTreeBuilder_->addObject(key));
556 #endif // GMX_MODULARSIMULATOR_CHECKPOINTDATA_H