Move gmxapicompat TPR tools to libgmxapi.
[gromacs.git] / src / api / cpp / tpr.cpp
blob5caf67d97e254ad0d86d13aadfe32d22b3024442
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2019, 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.
36 /*! \file
37 * \brief Helper code for TPR file access.
39 * \author M. Eric Irrgang <ericirrgang@gmail.com>
40 * \ingroup gmxapi_compat
43 #include "gmxapi/compat/tpr.h"
45 #include <cassert>
47 #include <map>
48 #include <memory>
49 #include <string>
50 #include <vector>
52 #include "gromacs/mdtypes/inputrec.h"
53 #include "gromacs/topology/topology.h"
54 #include "gromacs/mdtypes/state.h"
55 #include "gromacs/fileio/oenv.h"
56 #include "gromacs/fileio/tpxio.h"
57 #include "gromacs/options/timeunitmanager.h"
58 #include "gromacs/utility/cstringutil.h"
59 #include "gromacs/utility/programcontext.h"
61 #include "gmxapi/compat/exceptions.h"
62 #include "gmxapi/compat/mdparams.h"
64 namespace gmxapicompat
67 class TprContents
69 public:
70 explicit TprContents(const std::string &infile) :
71 irInstance_ {std::make_unique<t_inputrec>()},
72 mtop_ {std::make_unique<gmx_mtop_t>()},
73 state_ {std::make_unique<t_state>()}
75 read_tpx_state(infile.c_str(), irInstance_.get(), state_.get(), mtop_.get());
77 ~TprContents() = default;
78 TprContents(TprContents &&source) noexcept = default;
79 TprContents &operator=(TprContents &&) noexcept = default;
81 /*!
82 * \brief Get a reference to the input record in the TPR file.
84 * Note that this implementation allows different objects to share ownership
85 * of the TprFile and does not provide access restrictions to prevent multiple
86 * code blocks writing to the input record. This should be resolved with a
87 * combination of managed access controlled handles and through better
88 * management of the data structures in the TPR file. I.e. the t_inputrec is
89 * not copyable, moveable, nor default constructable (at least, to produce a
90 * valid record), and it does not necessarily make sense to map the library
91 * data structure to the file data structure (except that we don't have another
92 * way of constructing a complete and valid input record).
94 * \todo We can't play fast and loose with the irInstance for long...
96 * \return
98 t_inputrec &inputRecord() const
100 assert(irInstance_);
101 return *irInstance_;
104 gmx_mtop_t &molecularTopology() const
106 assert(mtop_);
107 return *mtop_;
110 t_state &state() const
112 assert(state_);
113 return *state_;
115 private:
116 // These types are not moveable in GROMACS 2019, so we use unique_ptr as a
117 // moveable wrapper to let TprContents be moveable.
118 std::unique_ptr<t_inputrec> irInstance_;
119 std::unique_ptr<gmx_mtop_t> mtop_;
120 std::unique_ptr<t_state> state_;
124 // Note: This mapping is incomplete. Hopefully we can replace it before more mapping is necessary.
125 // TODO: (#2993) Replace with GROMACS library header resources when available.
126 std::map<std::string, GmxapiType> simulationParameterTypeMap()
128 return {
130 "integrator", GmxapiType::STRING
133 "tinit", GmxapiType::FLOAT64
136 "dt", GmxapiType::FLOAT64
139 "nsteps", GmxapiType::INT64
142 "init-step", GmxapiType::INT64
145 "simulation-part", GmxapiType::INT64
148 "comm-mode", GmxapiType::STRING
151 "nstcomm", GmxapiType::INT64
154 "comm-grps", GmxapiType::NDARRAY
155 }, // Note: we do not have processing for this yet.
157 "bd-fric", GmxapiType::FLOAT64
160 "ld-seed", GmxapiType::INT64
163 "emtol", GmxapiType::FLOAT64
166 "emstep", GmxapiType::FLOAT64
169 "niter", GmxapiType::INT64
172 "fcstep", GmxapiType::FLOAT64
175 "nstcgsteep", GmxapiType::INT64
178 "nbfgscorr", GmxapiType::INT64
181 "rtpi", GmxapiType::FLOAT64
184 "nstxout", GmxapiType::INT64
187 "nstvout", GmxapiType::INT64
190 "nstfout", GmxapiType::INT64
193 "nstlog", GmxapiType::INT64
196 "nstcalcenergy", GmxapiType::INT64
199 "nstenergy", GmxapiType::INT64
202 "nstxout-compressed", GmxapiType::INT64
205 "compressed-x-precision", GmxapiType::FLOAT64
208 "cutoff-scheme", GmxapiType::STRING
211 "nstlist", GmxapiType::INT64
214 "ns-type", GmxapiType::STRING
217 "pbc", GmxapiType::STRING
220 "periodic-molecules", GmxapiType::BOOL
222 // TBD
228 * TODO: Visitor for predetermined known types.
230 * Development sequence:
231 * 1. map pointers
232 * 2. map setters ()
233 * 3. template the Visitor setter for compile-time extensibility of type and to prune incompatible types.
234 * 4. switch to Variant type for handling (setter templated on caller input)
235 * 5. switch to Variant type for input as well? (Variant in public API?)
238 std::map<std::string, bool t_inputrec::*> boolParams()
240 return {
242 "periodic-molecules", &t_inputrec::bPeriodicMols
244 // ...
248 std::map<std::string, int t_inputrec::*> int32Params()
250 return {
252 "simulation-part", &t_inputrec::simulation_part
255 "nstcomm", &t_inputrec::nstcomm
258 "niter", &t_inputrec::niter
261 "nstcgsteep", &t_inputrec::nstcgsteep
264 "nbfgscorr", &t_inputrec::nbfgscorr
267 "nstxout", &t_inputrec::nstxout
270 "nstvout", &t_inputrec::nstvout
273 "nstfout", &t_inputrec::nstfout
276 "nstlog", &t_inputrec::nstlog
279 "nstcalcenergy", &t_inputrec::nstcalcenergy
282 "nstenergy", &t_inputrec::nstenergy
285 "nstxout-compressed", &t_inputrec::nstxout_compressed
288 "nstlist", &t_inputrec::nstlist
290 // ...
294 namespace
297 // Provide a helper to disambiguate `real` typed inputrec values.
299 // Primary template returns empty map.
300 template<typename RealT>
301 std::map<std::string, RealT t_inputrec::*> compatibleRealParams()
303 return {};
306 // Specialize for the compile-time typedef of `real` to get the inputrec parameters
307 // compatible with the template parameter whose type is not known until compile time.
308 template<>
309 std::map<std::string, real t_inputrec::*> compatibleRealParams()
311 return {
313 "bd-fric", &t_inputrec::bd_fric
316 "emtol", &t_inputrec::em_tol
319 "emstep", &t_inputrec::em_stepsize
322 "fcstep", &t_inputrec::fc_stepsize
325 "rtpi", &t_inputrec::rtpi
328 "compressed-x-precision", &t_inputrec::x_compression_precision
330 // ...
337 std::map<std::string, float t_inputrec::*> float32Params()
339 return compatibleRealParams<float>();
342 std::map<std::string, double t_inputrec::*> float64Params()
344 static const std::map<std::string, double t_inputrec::*> explicitDoubles = {
346 "dt", &t_inputrec::delta_t
349 "tinit", &t_inputrec::init_t
351 // ...
355 // Initialize map to be returned with any compile-time-only doubles.
356 auto fullMap = compatibleRealParams<double>();
358 // Get the explicitly `double` parameters.
359 for (const auto &item : explicitDoubles)
361 fullMap.emplace(item);
364 return fullMap;
367 std::map<std::string, int64_t t_inputrec::*> int64Params()
369 return {
371 "nsteps", &t_inputrec::nsteps
374 "init-step", &t_inputrec::init_step
377 "ld-seed", &t_inputrec::ld_seed
379 // ...
385 * \brief Static mapping of parameter names to gmxapi types for GROMACS 2019.
387 * \param name MDP entry name.
388 * \return enumeration value for known parameters.
390 * \throws gmxapi_compat::ValueError for parameters with no mapping.
392 GmxapiType mdParamToType(const std::string &name)
394 const auto staticMap = simulationParameterTypeMap();
395 auto entry = staticMap.find(name);
396 if (entry == staticMap.end())
398 throw ValueError("Named parameter has unknown type mapping.");
400 return entry->second;
405 * \brief Handle / manager for GROMACS molecular computation input parameters.
407 * Interface should be consistent with MDP file entries, but data maps to TPR
408 * file interface. For type safety and simplicity, we don't have generic operator
409 * accessors. Instead, we have templated accessors that throw exceptions when
410 * there is trouble.
412 * When MDP input is entirely stored in a key-value tree, this class can be a
413 * simple adapter or wrapper. Until then, we need a manually maintained mapping
414 * of MDP entries to TPR data.
416 * Alternatively, we could update the infrastructure used by list_tpx to provide
417 * more generic output, but our efforts may be better spent in updating the
418 * infrastructure for the key-value tree input system.
420 class GmxMdParamsImpl final
422 public:
424 * \brief Create an initialized but empty parameters structure.
426 * Parameter keys are set at construction, but all values are empty. This
427 * allows the caller to check for valid parameter names or their types,
428 * while allowing the consuming code to know which parameters were explicitly
429 * set by the caller.
431 * To load values from a TPR file, see getMdParams().
433 GmxMdParamsImpl();
435 explicit GmxMdParamsImpl(std::shared_ptr<TprContents> tprContents);
438 * \brief Get the current list of keys.
440 * \return
442 std::vector<std::string> keys() const
444 std::vector<std::string> keyList;
445 for (auto && entry : int64Params_)
447 keyList.emplace_back(entry.first);
449 for (auto && entry : intParams_)
451 keyList.emplace_back(entry.first);
453 for (auto && entry : floatParams_)
455 keyList.emplace_back(entry.first);
457 for (auto && entry : float64Params_)
459 keyList.emplace_back(entry.first);
461 return keyList;
464 template<typename T> T extract(const std::string & /* key */) const
466 auto value = T();
467 // should be an APIError
468 throw TypeError("unhandled type");
471 void set(const std::string &key, const int64_t &value)
473 if (int64Params_.find(key) != int64Params_.end())
475 int64Params_[key] = std::make_pair(value, true);
477 if (source_)
479 auto memberPointer = int64Params().at(key);
480 source_->inputRecord().*memberPointer = value;
483 else if (intParams_.find(key) != intParams_.end())
485 // TODO: check whether value is too large?
486 intParams_[key] = std::make_pair(static_cast<int>(value), true);
488 if (source_)
490 auto memberPointer = int32Params().at(key);
491 source_->inputRecord().*memberPointer = value;
495 else
497 throw KeyError("Named parameter is incompatible with integer type value.");
501 void set(const std::string &key, const double &value)
503 if (float64Params_.find(key) != float64Params_.end())
505 float64Params_[key] = std::make_pair(value, true);
507 if (source_)
509 auto memberPointer = float64Params().at(key);
510 source_->inputRecord().*memberPointer = value;
514 else if (floatParams_.find(key) != floatParams_.end())
516 // TODO: check whether value is too large?
517 floatParams_[key] = std::make_pair(static_cast<float>(value), true);
519 if (source_)
521 auto memberPointer = float32Params().at(key);
522 source_->inputRecord().*memberPointer = value;
526 else
528 throw KeyError("Named parameter is incompatible with floating point type value.");
532 TprReadHandle getSource() const
534 // Note: might return a null handle. Need to decide what that means and how to address it.
535 return TprReadHandle(source_);
538 private:
540 // Hold the settable parameters and whether or not they have been set.
541 // TODO: update to gmxapi named types?
542 std::map < std::string, std::pair < int64_t, bool>> int64Params_;
543 std::map < std::string, std::pair < int, bool>> intParams_;
544 std::map < std::string, std::pair < float, bool>> floatParams_;
545 std::map < std::string, std::pair < double, bool>> float64Params_;
547 /*! \brief Shared ownership of a pack of TPR data.
549 * This is a non-normative way to retain access to gmxapi resources.
550 * \todo Subscribe to a Context-managed resource.
552 std::shared_ptr<TprContents> source_;
555 void setParam(gmxapicompat::GmxMdParams *params, const std::string &name, double value)
557 assert(params != nullptr);
558 assert(params->params_ != nullptr);
559 params->params_->set(name, value);
562 void setParam(gmxapicompat::GmxMdParams *params, const std::string &name, int64_t value)
564 assert(params != nullptr);
565 assert(params->params_ != nullptr);
566 params->params_->set(name, value);
569 template<typename ParamsContainerT, typename Mapping>
570 static void setParam(ParamsContainerT* params, const TprContents &source, const Mapping &map)
572 for (const auto &definition : map)
574 const auto &key = definition.first;
575 auto memberPointer = definition.second;
576 auto &irInstance = source.inputRecord();
577 auto fileValue = irInstance.*memberPointer;
578 (*params)[key] = std::make_pair(fileValue, true);
583 * \brief A GmxMdParams implementation that depends on TPR files.
585 * \param tprContents
587 GmxMdParamsImpl::GmxMdParamsImpl(std::shared_ptr<gmxapicompat::TprContents> tprContents) :
588 source_ {std::move(tprContents)}
590 if (source_)
592 setParam(&int64Params_, *source_, int64Params());
593 setParam(&intParams_, *source_, int32Params());
594 setParam(&float64Params_, *source_, float32Params());
595 setParam(&float64Params_, *source_, float64Params());
599 GmxMdParamsImpl::GmxMdParamsImpl() :
600 GmxMdParamsImpl(nullptr)
603 template<>
604 int GmxMdParamsImpl::extract<int>(const std::string &key) const
606 const auto &params = intParams_;
607 const auto &entry = params.find(key);
608 if (entry == params.cend())
610 throw KeyError("Parameter of the requested name and type not defined.");
612 else if (!entry->second.second)
614 // TODO: handle invalid and unset parameters differently.
615 throw KeyError("Parameter of the requested name not set.");
617 else
619 return entry->second.first;
623 template<>
624 int64_t GmxMdParamsImpl::extract<int64_t>(const std::string &key) const
626 const auto &params = int64Params_;
627 const auto &entry = params.find(key);
628 if (entry == params.cend())
630 throw KeyError("Parameter of the requested name and type not defined.");
632 else if (!entry->second.second)
634 // TODO: handle invalid and unset parameters differently.
635 throw KeyError("Parameter of the requested name not set.");
637 else
639 return entry->second.first;
642 template<>
643 float GmxMdParamsImpl::extract<float>(const std::string &key) const
645 const auto &params = floatParams_;
646 const auto &entry = params.find(key);
647 if (entry == params.cend())
649 throw KeyError("Parameter of the requested name and type not defined.");
651 else if (!entry->second.second)
653 // TODO: handle invalid and unset parameters differently.
654 throw KeyError("Parameter of the requested name not set.");
656 else
658 return entry->second.first;
661 template<>
662 double GmxMdParamsImpl::extract<double>(const std::string &key) const
664 const auto &params = float64Params_;
665 const auto &entry = params.find(key);
666 if (entry == params.cend())
668 throw KeyError("Parameter of the requested name and type not defined.");
670 else if (!entry->second.second)
672 // TODO: handle invalid and unset parameters differently.
673 throw KeyError("Parameter of the requested name not set.");
675 else
677 return entry->second.first;
682 int extractParam(const GmxMdParams &params, const std::string &name, int)
684 assert(params.params_);
685 return params.params_->extract<int>(name);
688 int64_t extractParam(const GmxMdParams &params, const std::string &name, int64_t)
690 assert(params.params_);
691 int64_t value {};
692 // Allow fetching both known integer types.
695 value = params.params_->extract<int>(name);
697 catch (const KeyError &error)
699 // If not found as a regular int, check for int64.
702 value = params.params_->extract<int64_t >(name);
704 catch (const KeyError &error64)
706 throw KeyError("Parameter of the requested name not set.");
709 // Any other exceptions propagate out.
710 return value;
713 float extractParam(const GmxMdParams &params, const std::string &name, float)
715 assert(params.params_);
716 return params.params_->extract<float>(name);
719 double extractParam(const GmxMdParams &params, const std::string &name, double)
721 assert(params.params_);
722 double value {};
723 // Allow fetching both single and double precision.
726 value = params.params_->extract<double>(name);
728 catch (const KeyError &errorDouble)
730 // If not found as a double precision value, check for single-precision.
733 value = params.params_->extract<float>(name);
735 catch (const KeyError &errorFloat)
737 throw KeyError("Parameter of the requested name not set.");
740 // Any other exceptions propagate out.
741 return value;
744 std::vector<std::string> keys(const GmxMdParams &params)
746 return params.params_->keys();
750 TprReadHandle readTprFile(const std::string &filename)
752 auto tprfile = gmxapicompat::TprContents(filename);
753 auto handle = gmxapicompat::TprReadHandle(std::move(tprfile));
754 return handle;
757 GmxMdParams getMdParams(const TprReadHandle &handle)
759 auto tprfile = handle.get();
760 // TODO: convert to exception / decide whether null handles are allowed.
761 assert(tprfile);
762 GmxMdParams params;
763 params.params_ = std::make_unique<GmxMdParamsImpl>(tprfile);
764 return params;
767 TopologySource getTopologySource(const TprReadHandle &handle)
769 TopologySource source;
770 source.tprFile_ = handle.get();
771 return source;
774 SimulationState getSimulationState(const TprReadHandle &handle)
776 SimulationState source;
777 source.tprFile_ = handle.get();
778 return source;
781 StructureSource getStructureSource(const TprReadHandle &handle)
783 StructureSource source;
784 source.tprFile_ = handle.get();
785 return source;
788 TprReadHandle::TprReadHandle(std::shared_ptr<TprContents> tprFile) :
789 tprContents_ {std::move(tprFile)}
793 TprReadHandle getSourceFileHandle(const GmxMdParams &params)
795 return params.params_->getSource();
798 void writeTprFile(const std::string &filename,
799 const GmxMdParams &params,
800 const StructureSource &structure,
801 const SimulationState &state,
802 const TopologySource &topology)
804 assert(params.params_);
805 // The only way we can check for consistent input right now is to make sure
806 // it all comes from the same file.
807 if (structure.tprFile_.get() != state.tprFile_.get() ||
808 state.tprFile_.get() != topology.tprFile_.get() ||
809 topology.tprFile_.get() != params.params_->getSource().get().get() ||
810 params.params_->getSource().get().get() != structure.tprFile_.get()
813 throw ValueError("writeTprFile does not yet know how to reconcile data from different TPR file sources.");
816 const auto tprFileHandle = params.params_->getSource();
817 const auto tprFile = tprFileHandle.get();
818 assert(tprFile);
819 const auto &inputRecord = tprFile->inputRecord();
820 const auto &writeState = tprFile->state();
821 const auto &writeTopology = tprFile->molecularTopology();
822 write_tpx_state(filename.c_str(), &inputRecord, &writeState, &writeTopology);
826 TprReadHandle::TprReadHandle(TprContents &&tprFile) :
827 TprReadHandle {std::make_shared<TprContents>(std::move(tprFile))}
831 std::shared_ptr<TprContents> TprReadHandle::get() const
833 return tprContents_;
836 // defaulted here to delay definition until after member types are defined.
837 TprReadHandle::~TprReadHandle() = default;
839 GmxMdParams::~GmxMdParams() = default;
841 GmxMdParams::GmxMdParams() :
842 params_ {std::make_unique<GmxMdParamsImpl>()}
845 GmxMdParams::GmxMdParams(GmxMdParams &&) noexcept = default;
847 GmxMdParams &GmxMdParams::operator=(GmxMdParams &&) noexcept = default;
849 // maybe this should return a handle to the new file?
850 bool copy_tprfile(const gmxapicompat::TprReadHandle &input, std::string outFile)
852 if (!input.get())
854 return false;
856 gmxapicompat::writeTprFile(outFile,
857 gmxapicompat::getMdParams(input),
858 gmxapicompat::getStructureSource(input),
859 gmxapicompat::getSimulationState(input),
860 gmxapicompat::getTopologySource(input));
861 return true;
864 bool rewrite_tprfile(std::string inFile, std::string outFile, double endTime)
866 bool success = false;
868 const char * top_fn = inFile.c_str();
870 t_inputrec irInstance;
871 gmx_mtop_t mtop;
872 t_state state;
873 read_tpx_state(top_fn, &irInstance, &state, &mtop);
875 /* set program name, command line, and default values for output options */
876 gmx_output_env_t *oenv;
877 gmx::TimeUnit timeUnit = gmx::TimeUnit_Default;
878 bool bView {
879 false
880 }; // argument that says we don't want to view graphs.
881 int xvgFormat {
884 output_env_init(&oenv, gmx::getProgramContext(),
885 static_cast<time_unit_t>(timeUnit + 1), bView, // NOLINT(misc-misplaced-widening-cast)
886 static_cast<xvg_format_t>(xvgFormat + 1), 0);
888 double run_t = irInstance.init_step*irInstance.delta_t + irInstance.init_t;
890 irInstance.nsteps = static_cast<int64_t>((endTime - run_t) / irInstance.delta_t + 0.5);
892 write_tpx_state(outFile.c_str(), &irInstance, &state, &mtop);
894 success = true;
895 return success;
898 } // end namespace gmxapicompat