2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2020, 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 * \ingroup module_modularsimulator
96 * \brief Struct allowing to check if data is serializable through the KeyValueTree serializer
98 * This list of types is copied from ValueSerializer::initSerializers()
99 * Having this here allows us to catch errors at compile time
100 * instead of having cryptic runtime errors
103 struct IsSerializableType
105 static bool const value
= std::is_same
<T
, std::string
>::value
|| std::is_same
<T
, bool>::value
106 || std::is_same
<T
, int>::value
|| std::is_same
<T
, int64_t>::value
107 || std::is_same
<T
, float>::value
|| std::is_same
<T
, double>::value
;
111 * \ingroup module_modularsimulator
112 * \brief Struct allowing to check if enum has a serializable underlying type
115 template<typename T
, bool = std::is_enum
<T
>::value
>
116 struct IsSerializableEnum
118 static bool const value
= IsSerializableType
<std::underlying_type_t
<T
>>::value
;
121 struct IsSerializableEnum
<T
, false>
123 static bool const value
= false;
128 * \ingroup module_modularsimulator
129 * \brief Data type hiding checkpoint implementation details
131 * This data type allows to separate the implementation details of the
132 * checkpoint writing / reading from the implementation of the checkpoint
133 * clients. Checkpoint clients interface via the methods of the CheckpointData
134 * object, and do not need knowledge of data types used to store the data.
136 * Templating allows checkpoint clients to have symmetric (templated)
137 * implementations for checkpoint reading and writing.
139 * CheckpointData objects are dispatched via [Write|Read]CheckpointDataHolder
140 * objects, which interact with the checkpoint reading from / writing to
144 template<CheckpointDataOperation operation
>
145 class CheckpointData
;
147 //! Convenience shortcut for reading checkpoint data.
148 using ReadCheckpointData
= CheckpointData
<CheckpointDataOperation::Read
>;
149 //! Convenience shortcut for writing checkpoint data.
150 using WriteCheckpointData
= CheckpointData
<CheckpointDataOperation::Write
>;
153 class CheckpointData
<CheckpointDataOperation::Read
>
156 /*! \brief Read or write a single value from / to checkpoint
158 * Allowed scalar types include std::string, bool, int, int64_t,
159 * float, double, or any enum with one of the previously mentioned
160 * scalar types as underlying type. Type compatibility is checked
163 * \tparam operation Whether we are reading or writing
164 * \tparam T The type of the value
165 * \param key The key to [read|write] the value [from|to]
166 * \param value The value to [read|write]
170 std::enable_if_t
<IsSerializableType
<T
>::value
, void> scalar(const std::string
& key
, T
* value
) const;
172 std::enable_if_t
<IsSerializableEnum
<T
>::value
, void> enumScalar(const std::string
& key
, T
* value
) const;
175 /*! \brief Read or write an ArrayRef from / to checkpoint
177 * Allowed types stored in the ArrayRef include std::string, bool, int,
178 * int64_t, float, double, and gmx::RVec. Type compatibility is checked
181 * \tparam operation Whether we are reading or writing
182 * \tparam T The type of values stored in the ArrayRef
183 * \param key The key to [read|write] the ArrayRef [from|to]
184 * \param values The ArrayRef to [read|write]
187 // Read ArrayRef of scalar
189 std::enable_if_t
<IsSerializableType
<T
>::value
, void> arrayRef(const std::string
& key
,
190 ArrayRef
<T
> values
) const;
191 // Read ArrayRef of RVec
192 void arrayRef(const std::string
& key
, ArrayRef
<RVec
> values
) const;
195 /*! \brief Read or write a tensor from / to checkpoint
197 * \tparam operation Whether we are reading or writing
198 * \param key The key to [read|write] the tensor [from|to]
199 * \param values The tensor to [read|write]
201 void tensor(const std::string
& key
, ::tensor values
) const;
203 /*! \brief Return a subset of the current CheckpointData
205 * \tparam operation Whether we are reading or writing
206 * \param key The key to [read|write] the sub data [from|to]
207 * \return A CheckpointData object representing a subset of the current object
210 CheckpointData
subCheckpointData(const std::string
& key
) const;
214 //! KV tree read from checkpoint
215 const KeyValueTreeObject
* inputTree_
= nullptr;
217 //! Construct an input checkpoint data object
218 explicit CheckpointData(const KeyValueTreeObject
& inputTree
);
220 // Only holders should build
221 friend class ReadCheckpointDataHolder
;
225 class CheckpointData
<CheckpointDataOperation::Write
>
228 //! \copydoc CheckpointData<CheckpointDataOperation::Read>::scalar
231 std::enable_if_t
<IsSerializableType
<T
>::value
, void> scalar(const std::string
& key
, const T
* value
);
233 std::enable_if_t
<IsSerializableEnum
<T
>::value
, void> enumScalar(const std::string
& key
, const T
* value
);
236 //! \copydoc CheckpointData<CheckpointDataOperation::Read>::arrayRef
238 // Write ArrayRef of scalar
240 std::enable_if_t
<IsSerializableType
<T
>::value
, void> arrayRef(const std::string
& key
,
241 ArrayRef
<const T
> values
);
242 // Write ArrayRef of RVec
243 void arrayRef(const std::string
& key
, ArrayRef
<const RVec
> values
);
246 //! \copydoc CheckpointData<CheckpointDataOperation::Read>::tensor
247 void tensor(const std::string
& key
, const ::tensor values
);
249 //! \copydoc CheckpointData<CheckpointDataOperation::Read>::subCheckpointData
250 CheckpointData
subCheckpointData(const std::string
& key
);
253 //! Builder for the tree to be written to checkpoint
254 std::optional
<KeyValueTreeObjectBuilder
> outputTreeBuilder_
= std::nullopt
;
256 //! Construct an output checkpoint data object
257 explicit CheckpointData(KeyValueTreeObjectBuilder
&& outputTreeBuilder
);
259 // Only holders should build
260 friend class WriteCheckpointDataHolder
;
263 /*! \brief Read a checkpoint version enum variable
265 * This reads the checkpoint version from file. The read version is returned.
267 * If the read version is more recent than the code version, this throws an error, since
268 * we cannot know what has changed in the meantime. Using newer checkpoint files with
269 * old code is not a functionality we can offer. Note, however, that since the checkpoint
270 * version is saved by module, older checkpoint files of all simulations that don't use
271 * that specific module can still be used.
273 * Allowing backwards compatibility of files (i.e., reading an older checkpoint file with
274 * a newer version of the code) is in the responsibility of the caller module. They can
275 * use the returned file checkpoint version to do that:
277 * const auto fileVersion = checkpointVersion(checkpointData, "version", c_currentVersion);
278 * if (fileVersion >= CheckpointVersion::AddedX)
280 * checkpointData->scalar("x", &x_));
283 * @tparam VersionEnum The type of the checkpoint version enum
284 * @param checkpointData A reading checkpoint data object
285 * @param key The key under which the version is saved - also used for error output
286 * @param programVersion The checkpoint version of the current code
287 * @return The checkpoint version read from file
289 template<typename VersionEnum
>
290 VersionEnum
checkpointVersion(const ReadCheckpointData
* checkpointData
,
291 const std::string
& key
,
292 const VersionEnum programVersion
)
294 VersionEnum fileVersion
;
295 checkpointData
->enumScalar(key
, &fileVersion
);
296 if (fileVersion
> programVersion
)
299 formatString("The checkpoint file contains a %s that is more recent than the "
300 "current program version and is not backward compatible.",
306 /*! \brief Write the current code checkpoint version enum variable
308 * Write the current program checkpoint version to the checkpoint data object.
309 * Returns the written checkpoint version to mirror the signature of the reading version.
311 * @tparam VersionEnum The type of the checkpoint version enum
312 * @param checkpointData A writing checkpoint data object
313 * @param key The key under which the version is saved
314 * @param programVersion The checkpoint version of the current code
315 * @return The checkpoint version written to file
317 template<typename VersionEnum
>
318 VersionEnum
checkpointVersion(WriteCheckpointData
* checkpointData
,
319 const std::string
& key
,
320 const VersionEnum programVersion
)
322 checkpointData
->enumScalar(key
, &programVersion
);
323 return programVersion
;
328 * \brief Holder for read checkpoint data
330 * A ReadCheckpointDataHolder object is passed to the checkpoint reading
331 * functionality, and then passed into the SimulatorBuilder object. It
332 * holds the KV-tree read from file and dispatches CheckpointData objects
333 * to the checkpoint clients.
335 class ReadCheckpointDataHolder
338 //! Check whether a key exists
339 [[nodiscard
]] bool keyExists(const std::string
& key
) const;
341 //! Return vector of existing keys
342 [[nodiscard
]] std::vector
<std::string
> keys() const;
344 //! Deserialize serializer content into the CheckpointData object
345 void deserialize(ISerializer
* serializer
);
347 /*! \brief Return a subset of the current CheckpointData
349 * \param key The key to [read|write] the sub data [from|to]
350 * \return A CheckpointData object representing a subset of the current object
352 [[nodiscard
]] ReadCheckpointData
checkpointData(const std::string
& key
) const;
355 //! KV-tree read from checkpoint
356 KeyValueTreeObject checkpointTree_
;
360 * \brief Holder for write checkpoint data
362 * The WriteCheckpointDataHolder object holds the KV-tree builder and
363 * dispatches CheckpointData objects to the checkpoint clients to save
364 * their respective data. It is then passed to the checkpoint writing
367 class WriteCheckpointDataHolder
370 //! Serialize the content of the CheckpointData object
371 void serialize(ISerializer
* serializer
);
373 /*! \brief Return a subset of the current CheckpointData
375 * \param key The key to [read|write] the sub data [from|to]
376 * \return A CheckpointData object representing a subset of the current object
378 [[nodiscard
]] WriteCheckpointData
checkpointData(const std::string
& key
);
382 [[nodiscard
]] bool empty() const;
386 KeyValueTreeBuilder outputTreeBuilder_
;
387 //! Whether any checkpoint data object has been requested
388 bool hasCheckpointDataBeenRequested_
= false;
391 // Function definitions - here to avoid template-related linker problems
392 // doxygen doesn't like these...
395 std::enable_if_t
<IsSerializableType
<T
>::value
, void> ReadCheckpointData::scalar(const std::string
& key
,
398 GMX_RELEASE_ASSERT(inputTree_
, "No input checkpoint data available.");
399 *value
= (*inputTree_
)[key
].cast
<T
>();
403 std::enable_if_t
<IsSerializableEnum
<T
>::value
, void> ReadCheckpointData::enumScalar(const std::string
& key
,
406 GMX_RELEASE_ASSERT(inputTree_
, "No input checkpoint data available.");
407 std::underlying_type_t
<T
> castValue
;
408 castValue
= (*inputTree_
)[key
].cast
<std::underlying_type_t
<T
>>();
409 *value
= static_cast<T
>(castValue
);
413 inline std::enable_if_t
<IsSerializableType
<T
>::value
, void>
414 WriteCheckpointData::scalar(const std::string
& key
, const T
* value
)
416 GMX_RELEASE_ASSERT(outputTreeBuilder_
, "No output checkpoint data available.");
417 outputTreeBuilder_
->addValue(key
, *value
);
421 inline std::enable_if_t
<IsSerializableEnum
<T
>::value
, void>
422 WriteCheckpointData::enumScalar(const std::string
& key
, const T
* value
)
424 GMX_RELEASE_ASSERT(outputTreeBuilder_
, "No output checkpoint data available.");
425 auto castValue
= static_cast<std::underlying_type_t
<T
>>(*value
);
426 outputTreeBuilder_
->addValue(key
, castValue
);
430 inline std::enable_if_t
<IsSerializableType
<T
>::value
, void>
431 ReadCheckpointData::arrayRef(const std::string
& key
, ArrayRef
<T
> values
) const
433 GMX_RELEASE_ASSERT(inputTree_
, "No input checkpoint data available.");
434 GMX_RELEASE_ASSERT(values
.size() >= (*inputTree_
)[key
].asArray().values().size(),
435 "Read vector does not fit in passed ArrayRef.");
436 auto outputIt
= values
.begin();
437 auto inputIt
= (*inputTree_
)[key
].asArray().values().begin();
438 auto outputEnd
= values
.end();
439 auto inputEnd
= (*inputTree_
)[key
].asArray().values().end();
440 for (; outputIt
!= outputEnd
&& inputIt
!= inputEnd
; outputIt
++, inputIt
++)
442 *outputIt
= inputIt
->cast
<T
>();
447 inline std::enable_if_t
<IsSerializableType
<T
>::value
, void>
448 WriteCheckpointData::arrayRef(const std::string
& key
, ArrayRef
<const T
> values
)
450 GMX_RELEASE_ASSERT(outputTreeBuilder_
, "No output checkpoint data available.");
451 auto builder
= outputTreeBuilder_
->addUniformArray
<T
>(key
);
452 for (const auto& value
: values
)
454 builder
.addValue(value
);
458 inline void ReadCheckpointData::arrayRef(const std::string
& key
, ArrayRef
<RVec
> values
) const
460 GMX_RELEASE_ASSERT(values
.size() >= (*inputTree_
)[key
].asArray().values().size(),
461 "Read vector does not fit in passed ArrayRef.");
462 auto outputIt
= values
.begin();
463 auto inputIt
= (*inputTree_
)[key
].asArray().values().begin();
464 auto outputEnd
= values
.end();
465 auto inputEnd
= (*inputTree_
)[key
].asArray().values().end();
466 for (; outputIt
!= outputEnd
&& inputIt
!= inputEnd
; outputIt
++, inputIt
++)
468 auto storedRVec
= inputIt
->asObject()["RVec"].asArray().values();
469 *outputIt
= { storedRVec
[XX
].cast
<real
>(), storedRVec
[YY
].cast
<real
>(),
470 storedRVec
[ZZ
].cast
<real
>() };
474 inline void WriteCheckpointData::arrayRef(const std::string
& key
, ArrayRef
<const RVec
> values
)
476 auto builder
= outputTreeBuilder_
->addObjectArray(key
);
477 for (const auto& value
: values
)
479 auto subbuilder
= builder
.addObject();
480 subbuilder
.addUniformArray("RVec", { value
[XX
], value
[YY
], value
[ZZ
] });
484 inline void ReadCheckpointData::tensor(const std::string
& key
, ::tensor values
) const
486 auto array
= (*inputTree_
)[key
].asArray().values();
487 values
[XX
][XX
] = array
[0].cast
<real
>();
488 values
[XX
][YY
] = array
[1].cast
<real
>();
489 values
[XX
][ZZ
] = array
[2].cast
<real
>();
490 values
[YY
][XX
] = array
[3].cast
<real
>();
491 values
[YY
][YY
] = array
[4].cast
<real
>();
492 values
[YY
][ZZ
] = array
[5].cast
<real
>();
493 values
[ZZ
][XX
] = array
[6].cast
<real
>();
494 values
[ZZ
][YY
] = array
[7].cast
<real
>();
495 values
[ZZ
][ZZ
] = array
[8].cast
<real
>();
498 inline void WriteCheckpointData::tensor(const std::string
& key
, const ::tensor values
)
500 auto builder
= outputTreeBuilder_
->addUniformArray
<real
>(key
);
501 builder
.addValue(values
[XX
][XX
]);
502 builder
.addValue(values
[XX
][YY
]);
503 builder
.addValue(values
[XX
][ZZ
]);
504 builder
.addValue(values
[YY
][XX
]);
505 builder
.addValue(values
[YY
][YY
]);
506 builder
.addValue(values
[YY
][ZZ
]);
507 builder
.addValue(values
[ZZ
][XX
]);
508 builder
.addValue(values
[ZZ
][YY
]);
509 builder
.addValue(values
[ZZ
][ZZ
]);
512 inline ReadCheckpointData
ReadCheckpointData::subCheckpointData(const std::string
& key
) const
514 return CheckpointData((*inputTree_
)[key
].asObject());
517 inline WriteCheckpointData
WriteCheckpointData::subCheckpointData(const std::string
& key
)
519 return CheckpointData(outputTreeBuilder_
->addObject(key
));
522 inline ReadCheckpointData::CheckpointData(const KeyValueTreeObject
& inputTree
) :
523 inputTree_(&inputTree
)
527 inline WriteCheckpointData::CheckpointData(KeyValueTreeObjectBuilder
&& outputTreeBuilder
) :
528 outputTreeBuilder_(outputTreeBuilder
)
535 #endif // GMX_MODULARSIMULATOR_CHECKPOINTDATA_H