Split simulationWork.useGpuBufferOps into separate x and f flags
[alexxy/gromacs.git] / src / gromacs / mdtypes / checkpointdata.h
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
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.
8  *
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.
13  *
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.
18  *
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.
23  *
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.
31  *
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.
34  */
35 /*! \libinternal \file
36  * \brief Provides the checkpoint data structure for the modular simulator
37  *
38  * \author Pascal Merz <pascal.merz@me.com>
39  * \ingroup module_mdtypes
40  */
41
42 #ifndef GMX_MODULARSIMULATOR_CHECKPOINTDATA_H
43 #define GMX_MODULARSIMULATOR_CHECKPOINTDATA_H
44
45 #include <optional>
46
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"
52
53 namespace gmx
54 {
55 class ISerializer;
56
57 /*! \libinternal
58  * \brief The operations on CheckpointData
59  *
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
64  * out of sync.
65  *
66  * \ingroup module_modularsimulator
67  */
68 enum class CheckpointDataOperation
69 {
70     Read,
71     Write,
72     Count
73 };
74
75 /*! \internal
76  * \brief Get an ArrayRef whose const-ness is defined by the checkpointing operation
77  *
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
82  *
83  * \see ArrayRef
84  *
85  * \ingroup module_modularsimulator
86  */
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)
90 {
91     return container;
92 }
93
94 /*! \internal
95  * \brief Get an ArrayRef to a C array whose const-ness is defined by the checkpointing operation
96  *
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
102  *
103  * \see ArrayRef
104  *
105  * \ingroup module_modularsimulator
106  */
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)
110 {
111     return ArrayRef<T>(begin, begin + size);
112 }
113
114 /*! \internal
115  * \ingroup module_modularsimulator
116  * \brief Struct allowing to check if data is serializable through the KeyValueTree serializer
117  *
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
121  */
122 template<typename T>
123 struct IsSerializableType
124 {
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;
128 };
129
130 /*! \internal
131  * \ingroup module_modularsimulator
132  * \brief Struct allowing to check if enum has a serializable underlying type
133  */
134 //! {
135 template<typename T, bool = std::is_enum<T>::value>
136 struct IsSerializableEnum
137 {
138     static bool const value = IsSerializableType<std::underlying_type_t<T>>::value;
139 };
140 template<typename T>
141 struct IsSerializableEnum<T, false>
142 {
143     static bool const value = false;
144 };
145 //! }
146
147 /*! \libinternal
148  * \ingroup module_modularsimulator
149  * \brief Data type hiding checkpoint implementation details
150  *
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.
155  *
156  * Templating allows checkpoint clients to have symmetric (templated)
157  * implementations for checkpoint reading and writing.
158  *
159  * CheckpointData objects are dispatched via [Write|Read]CheckpointDataHolder
160  * objects, which interact with the checkpoint reading from / writing to
161  * file.
162  */
163
164 template<CheckpointDataOperation operation>
165 class CheckpointData;
166
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>;
171
172 template<>
173 class CheckpointData<CheckpointDataOperation::Read>
174 {
175 public:
176     /*! \brief Read or write a single value from / to checkpoint
177      *
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
181      * at compile time.
182      *
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]
187      */
188     //! {
189     template<typename T>
190     std::enable_if_t<IsSerializableType<T>::value, void> scalar(const std::string& key, T* value) const;
191     template<typename T>
192     std::enable_if_t<IsSerializableEnum<T>::value, void> enumScalar(const std::string& key, T* value) const;
193     //! }
194
195     /*! \brief Read or write an ArrayRef from / to checkpoint
196      *
197      * Allowed types stored in the ArrayRef include std::string, bool, int,
198      * int64_t, float, double, and gmx::RVec. Type compatibility is checked
199      * at compile time.
200      *
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]
205      */
206     //! {
207     // Read ArrayRef of scalar
208     template<typename T>
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;
213     //! }
214
215     /*! \brief Read or write a tensor from / to checkpoint
216      *
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]
220      */
221     void tensor(const std::string& key, ::tensor values) const;
222
223     /*! \brief Return a subset of the current CheckpointData
224      *
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
228      */
229     //!{
230     CheckpointData subCheckpointData(const std::string& key) const;
231     //!}
232
233 private:
234     //! KV tree read from checkpoint
235     const KeyValueTreeObject* inputTree_ = nullptr;
236
237     //! Construct an input checkpoint data object
238     explicit CheckpointData(const KeyValueTreeObject& inputTree);
239
240     // Only holders should build
241     friend class ReadCheckpointDataHolder;
242 };
243
244 template<>
245 class CheckpointData<CheckpointDataOperation::Write>
246 {
247 public:
248     //! \copydoc CheckpointData<CheckpointDataOperation::Read>::scalar
249     //! {
250     template<typename T>
251     std::enable_if_t<IsSerializableType<T>::value, void> scalar(const std::string& key, const T* value);
252     template<typename T>
253     std::enable_if_t<IsSerializableEnum<T>::value, void> enumScalar(const std::string& key, const T* value);
254     //! }
255
256     //! \copydoc CheckpointData<CheckpointDataOperation::Read>::arrayRef
257     //! {
258     // Write ArrayRef of scalar
259     template<typename T>
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);
264     //! }
265
266     //! \copydoc CheckpointData<CheckpointDataOperation::Read>::tensor
267     void tensor(const std::string& key, const ::tensor values);
268
269     //! \copydoc CheckpointData<CheckpointDataOperation::Read>::subCheckpointData
270     CheckpointData subCheckpointData(const std::string& key);
271
272 private:
273     //! Builder for the tree to be written to checkpoint
274     std::optional<KeyValueTreeObjectBuilder> outputTreeBuilder_ = std::nullopt;
275
276     //! Construct an output checkpoint data object
277     explicit CheckpointData(KeyValueTreeObjectBuilder&& outputTreeBuilder);
278
279     // Only holders should build
280     friend class WriteCheckpointDataHolder;
281 };
282
283 /*! \brief Read a checkpoint version enum variable
284  *
285  * This reads the checkpoint version from file. The read version is returned.
286  *
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.
292  *
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:
296  *
297  *     const auto fileVersion = checkpointVersion(checkpointData, "version", c_currentVersion);
298  *     if (fileVersion >= CheckpointVersion::AddedX)
299  *     {
300  *         checkpointData->scalar("x", &x_));
301  *     }
302  *
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
308  */
309 template<typename VersionEnum>
310 VersionEnum checkpointVersion(const ReadCheckpointData* checkpointData,
311                               const std::string&        key,
312                               const VersionEnum         programVersion)
313 {
314     VersionEnum fileVersion;
315     checkpointData->enumScalar(key, &fileVersion);
316     if (fileVersion > programVersion)
317     {
318         throw FileIOError(
319                 formatString("The checkpoint file contains a %s that is more recent than the "
320                              "current program version and is not backward compatible.",
321                              key.c_str()));
322     }
323     return fileVersion;
324 }
325
326 /*! \brief Write the current code checkpoint version enum variable
327  *
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.
330  *
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
336  */
337 template<typename VersionEnum>
338 VersionEnum checkpointVersion(WriteCheckpointData* checkpointData,
339                               const std::string&   key,
340                               const VersionEnum    programVersion)
341 {
342     checkpointData->enumScalar(key, &programVersion);
343     return programVersion;
344 }
345
346 inline ReadCheckpointData::CheckpointData(const KeyValueTreeObject& inputTree) :
347     inputTree_(&inputTree)
348 {
349 }
350
351 inline WriteCheckpointData::CheckpointData(KeyValueTreeObjectBuilder&& outputTreeBuilder) :
352     outputTreeBuilder_(outputTreeBuilder)
353 {
354 }
355 /*! \libinternal
356  * \brief Holder for read checkpoint data
357  *
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.
362  */
363 class ReadCheckpointDataHolder
364 {
365 public:
366     //! Check whether a key exists
367     [[nodiscard]] bool keyExists(const std::string& key) const;
368
369     //! Return vector of existing keys
370     [[nodiscard]] std::vector<std::string> keys() const;
371
372     //! Deserialize serializer content into the CheckpointData object
373     void deserialize(ISerializer* serializer);
374
375     /*! \brief Return a subset of the current CheckpointData
376      *
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
379      */
380     [[nodiscard]] ReadCheckpointData checkpointData(const std::string& key) const;
381
382     //! Write the contents of the Checkpoint to file
383     void dump(FILE* out) const;
384
385 private:
386     //! KV-tree read from checkpoint
387     KeyValueTreeObject checkpointTree_;
388 };
389
390 /*! \libinternal
391  * \brief Holder for write checkpoint data
392  *
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
396  * functionality.
397  */
398 class WriteCheckpointDataHolder
399 {
400 public:
401     //! Serialize the content of the CheckpointData object
402     void serialize(ISerializer* serializer);
403
404     /*! \brief Return a subset of the current CheckpointData
405      *
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
408      */
409     [[nodiscard]] WriteCheckpointData checkpointData(const std::string& key);
410
411     /*! \brief
412      */
413     [[nodiscard]] bool empty() const;
414
415 private:
416     //! KV-tree builder
417     KeyValueTreeBuilder outputTreeBuilder_;
418     //! Whether any checkpoint data object has been requested
419     bool hasCheckpointDataBeenRequested_ = false;
420 };
421
422 // Function definitions - here to avoid template-related linker problems
423 // doxygen doesn't like these...
424 //! \cond
425 template<typename T>
426 std::enable_if_t<IsSerializableType<T>::value, void> ReadCheckpointData::scalar(const std::string& key,
427                                                                                 T* value) const
428 {
429     GMX_RELEASE_ASSERT(inputTree_, "No input checkpoint data available.");
430     *value = (*inputTree_)[key].cast<T>();
431 }
432
433 template<typename T>
434 std::enable_if_t<IsSerializableEnum<T>::value, void> ReadCheckpointData::enumScalar(const std::string& key,
435                                                                                     T* value) const
436 {
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);
441 }
442
443 template<typename T>
444 inline std::enable_if_t<IsSerializableType<T>::value, void>
445 WriteCheckpointData::scalar(const std::string& key, const T* value)
446 {
447     GMX_RELEASE_ASSERT(outputTreeBuilder_, "No output checkpoint data available.");
448     outputTreeBuilder_->addValue(key, *value);
449 }
450
451 template<typename T>
452 inline std::enable_if_t<IsSerializableEnum<T>::value, void>
453 WriteCheckpointData::enumScalar(const std::string& key, const T* value)
454 {
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);
458 }
459
460 template<typename T>
461 inline std::enable_if_t<IsSerializableType<T>::value, void>
462 ReadCheckpointData::arrayRef(const std::string& key, ArrayRef<T> values) const
463 {
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++)
472     {
473         *outputIt = inputIt->cast<T>();
474     }
475 }
476
477 template<typename T>
478 inline std::enable_if_t<IsSerializableType<T>::value, void>
479 WriteCheckpointData::arrayRef(const std::string& key, ArrayRef<const T> values)
480 {
481     GMX_RELEASE_ASSERT(outputTreeBuilder_, "No output checkpoint data available.");
482     auto builder = outputTreeBuilder_->addUniformArray<T>(key);
483     for (const auto& value : values)
484     {
485         builder.addValue(value);
486     }
487 }
488
489 inline void ReadCheckpointData::arrayRef(const std::string& key, ArrayRef<RVec> values) const
490 {
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++)
498     {
499         auto storedRVec = inputIt->asObject()["RVec"].asArray().values();
500         *outputIt = { storedRVec[XX].cast<real>(), storedRVec[YY].cast<real>(), storedRVec[ZZ].cast<real>() };
501     }
502 }
503
504 inline void WriteCheckpointData::arrayRef(const std::string& key, ArrayRef<const RVec> values)
505 {
506     auto builder = outputTreeBuilder_->addObjectArray(key);
507     for (const auto& value : values)
508     {
509         auto subbuilder = builder.addObject();
510         subbuilder.addUniformArray("RVec", { value[XX], value[YY], value[ZZ] });
511     }
512 }
513
514 inline void ReadCheckpointData::tensor(const std::string& key, ::tensor values) const
515 {
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>();
526 }
527
528 inline void WriteCheckpointData::tensor(const std::string& key, const ::tensor values)
529 {
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]);
540 }
541
542 inline ReadCheckpointData ReadCheckpointData::subCheckpointData(const std::string& key) const
543 {
544     return CheckpointData((*inputTree_)[key].asObject());
545 }
546
547 inline WriteCheckpointData WriteCheckpointData::subCheckpointData(const std::string& key)
548 {
549     return CheckpointData(outputTreeBuilder_->addObject(key));
550 }
551
552 //! \endcond
553
554 } // namespace gmx
555
556 #endif // GMX_MODULARSIMULATOR_CHECKPOINTDATA_H