From 646ea0f808f766f0031a430d899d6e1e73d91775 Mon Sep 17 00:00:00 2001 From: Paul Bauer Date: Tue, 9 Oct 2018 14:35:12 +0200 Subject: [PATCH] Add C++ coordinate file writing method. TrajectoryFileWriter also allows the chaining of modules derived from IOutputAdapter together to perform actions that modify the meta information contained in a t_trxframe before writing the information to disk. IOutputAdapter can be used to communicate what requirements a specific file writing method has on the meta information contained in t_trxframe, with modules being able to proclaim those and TrajectoryFileWriter checking them against the requested file types before accepting the addition of a module to the processing chain. The TrajectoryFileWriter object needs a builder method to construct itself that will be added in a child change. Change-Id: I1c29701b4ce788918cb6b3a47ca9277898a7bff4 --- src/gromacs/coordinateio.h | 37 ++-- src/gromacs/coordinateio/CMakeLists.txt | 3 +- src/gromacs/coordinateio/coordinatefile.cpp | 256 ++++++++++++++++++++++++++++ src/gromacs/coordinateio/coordinatefile.h | 191 +++++++++++++++++++++ 4 files changed, 468 insertions(+), 19 deletions(-) create mode 100644 src/gromacs/coordinateio/coordinatefile.cpp create mode 100644 src/gromacs/coordinateio/coordinatefile.h diff --git a/src/gromacs/coordinateio.h b/src/gromacs/coordinateio.h index be3016aa0c..365a42d81a 100644 --- a/src/gromacs/coordinateio.h +++ b/src/gromacs/coordinateio.h @@ -56,7 +56,7 @@ * * The main parts are the outputadapters implemented using the * IOutputAdapter interface to change information in a local (deep) copy of t_trxframes - * stored in the outputmanager. + * stored in the coordinatefile. * *

Outputadapter

* Each OutputAdapter module implements the same IOutputAdapter interface and @@ -66,7 +66,7 @@ * a new file to disk. * * - * The interaction between the OutputManager and the OutputAdapter modules derived from + * The interaction between the CoordinateFile and the OutputAdapter modules derived from * IOutputAdapter is shown in the diagram below. * * \msc @@ -74,8 +74,8 @@ hscale="2"; analysistool, - builder [ label="OutputManagerBuilder" ], - outputmanager [ label="OutputManager" ], + builder [ label="CoordinateFileBuilder" ], + coordinatefile [ label="CoordinateFile" ], container [ label="OutputAdapterStorage" ], outputadapters [ label="OutputAdapters" ]; @@ -85,18 +85,18 @@ outputadapters => builder [ label="Return or give error for wrong preconditions" ]; outputadapters => container [ label="Outputadapters are stored" ]; container => builder [ label="Gives error if storage conditions are violated" ]; - builder => outputmanager [ label="Constructs new manager according to specifications" ]; + builder => coordinatefile [ label="Constructs new manager according to specifications" ]; builder => container [ label="Requests storage object with registered outputadapters" ]; container => builder [ label="Gives ownership of stored outputadapters" ]; - builder box builder [ label="Tests preconditions of storage object and new outputmanager" ]; + builder box builder [ label="Tests preconditions of storage object and new coordinatefile" ]; builder => analysistool [ label="Raise error if preconditions don't match" ]; - builder => outputmanager [ label="Add storage object to new outputmanager" ]; - outputmanager => analysistool [ label="Returns finished outputmanager" ]; - builder box builder [ label="outputmanager created, can start to work on input data" ]; + builder => coordinatefile [ label="Add storage object to new coordinatefile" ]; + coordinatefile => analysistool [ label="Returns finished coordinatefile" ]; + builder box builder [ label="coordinatefile created, can start to work on input data" ]; * \endmsc * - * Once the OutputManager object and its registered modules are created, they can be used to + * Once the CoordinateFile object and its registered modules are created, they can be used to * iterate over input data to write new coordinate frames. * * \msc @@ -105,18 +105,18 @@ analysistool, analysisloop, - outputmanager [ label="OutputManager" ], + coordinatefile [ label="CoordinateFile" ], outputadapters [ label="OutputAdapters" ] , filewriting; - --- [ label="Setup of outputmanager complete, analysis phase begins" ]; + --- [ label="Setup of coordinatefile complete, analysis phase begins" ]; analysistool => analysisloop [ label="Starts iteration over frames" ]; - analysisloop => outputmanager [ label="Provides coordinates" ]; - outputmanager => outputadapters [ label="Provide coordinate frames for changing" ]; - outputadapters => outputmanager [ label="Return after changing data" ]; - outputmanager => filewriting [ label="Send new coordinates for writing" ]; - filewriting => outputmanager [ label="Continue after writing to disk" ]; - outputmanager => analysisloop [ label="Returns after writing" ]; + analysisloop => coordinatefile [ label="Provides coordinates" ]; + coordinatefile => outputadapters [ label="Provide coordinate frames for changing" ]; + outputadapters => coordinatefile [ label="Return after changing data" ]; + coordinatefile => filewriting [ label="Send new coordinates for writing" ]; + filewriting => coordinatefile [ label="Continue after writing to disk" ]; + coordinatefile => analysisloop [ label="Returns after writing" ]; analysisloop box analysisloop [ label="Iterates over frames" ]; --- [ label="Analysis complete, object is destructed and files are closed" ]; @@ -147,6 +147,7 @@ #ifndef GMX_COORDINATEIO_H #define GMX_COORDINATEIO_H +#include "gromacs/coordinateio/coordinatefile.h" #include "gromacs/coordinateio/enums.h" #include "gromacs/coordinateio/ioutputadapter.h" #include "gromacs/coordinateio/outputadaptercontainer.h" diff --git a/src/gromacs/coordinateio/CMakeLists.txt b/src/gromacs/coordinateio/CMakeLists.txt index 00b8762484..4f5d653bd5 100644 --- a/src/gromacs/coordinateio/CMakeLists.txt +++ b/src/gromacs/coordinateio/CMakeLists.txt @@ -37,10 +37,11 @@ file(GLOB COORDINATEIO_SOURCES *.cpp outputadapters/*.cpp) set(LIBGROMACS_SOURCES ${LIBGROMACS_SOURCES} ${COORDINATEIO_SOURCES} PARENT_SCOPE) gmx_install_headers( + coordinatefile.h + enums.h ioutputadapter.h outputadaptercontainer.h outputadapters.h - enums.h ) if (BUILD_TESTING) diff --git a/src/gromacs/coordinateio/coordinatefile.cpp b/src/gromacs/coordinateio/coordinatefile.cpp new file mode 100644 index 0000000000..7751de898d --- /dev/null +++ b/src/gromacs/coordinateio/coordinatefile.cpp @@ -0,0 +1,256 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2019, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +/*!\file + * \internal + * \brief + * Implements methods from coordinatefile.h + * + * \author Paul Bauer + * \ingroup module_coordinateio + */ + + +#include "gmxpre.h" + +#include "coordinatefile.h" + +#include + +#include "gromacs/math/vec.h" +#include "gromacs/trajectory/trajectoryframe.h" +#include "gromacs/utility/exceptions.h" + +namespace gmx +{ + +/*! \brief + * Create a deep copy of a t_trxframe \p input into \p copy + * + * When running the analysis tools and changing values with the + * outputadapters, a deep copy of the \p input coordinate frame has to be + * created first to ensure that the data is not changed if it is needed for other + * tools following with analysis later. Therefore, the data is passed + * to \p copy by performing a deep copy first. + * + * The method allocates new storage for coordinates of the x, v, and f arrays + * in the new coordinate frame. This means that those arrays need to be free'd + * after the frame has been processed and been written to disk. + * + * \param[in] input Reference input coordinate frame. + * \param[in,out] copy Pointer to new output frame that will receive the deep copy. + * \param[in] xvec Pointer to local coordinate storage vector. + * \param[in] vvec Pointer to local velocity storage vector. + * \param[in] fvec Pointer to local force storage vector. + * \param[in] indexvec Pointer to local index storage vector. + */ +static void deepCopy_t_trxframe(const t_trxframe &input, + t_trxframe *copy, + RVec *xvec, + RVec *vvec, + RVec *fvec, + int *indexvec) +{ + copy->not_ok = input.not_ok; + copy->bStep = input.bStep; + copy->bTime = input.bTime; + copy->bLambda = input.bLambda; + copy->bFepState = input.bFepState; + copy->bAtoms = input.bAtoms; + copy->bPrec = input.bPrec; + copy->bX = input.bX; + copy->bV = input.bV; + copy->bF = input.bF; + copy->bBox = input.bBox; + copy->bDouble = input.bDouble; + copy->natoms = input.natoms; + copy->step = input.step; + copy->time = input.time; + copy->lambda = input.lambda; + copy->fep_state = input.fep_state; + if (input.bAtoms) + { + copy->atoms = input.atoms; + } + copy->prec = input.prec; + if (copy->bX) + { + copy->x = as_rvec_array(xvec); + } + if (copy->bV) + { + copy->v = as_rvec_array(vvec); + } + if (copy->bF) + { + copy->f = as_rvec_array(fvec); + } + if (input.index) + { + copy->index = indexvec; + } + else + { + copy->index = nullptr; + } + for (int i = 0; i < copy->natoms; i++) + { + if (copy->bX) + { + copy_rvec(input.x[i], copy->x[i]); + } + if (copy->bV) + { + copy_rvec(input.v[i], copy->v[i]); + } + if (copy->bF) + { + copy_rvec(input.f[i], copy->f[i]); + } + if (input.index) + { + copy->index[i] = input.index[i]; + } + } + copy_mat(input.box, copy->box); + copy->bPBC = input.bPBC; + copy->ePBC = input.ePBC; +} + +/*! \brief + * Method to open TNG file. + * + * Only need extra method to open this kind of file as it may need access to + * a Selection \p sel, if it is valid. Otherwise atom indices will be taken + * from the topology \p mtop. + * + * \param[in] name Name of the output file. + * \param[in] sel Reference to selection. + * \param[in] mtop Pointer to topology, tested before that it is valid. + * \todo Those should be methods in a replacement for t_trxstatus instead. + */ +static t_trxstatus *openTNG(const std::string &name, const Selection &sel, const gmx_mtop_t *mtop) +{ + const char *filemode = "w"; + if (sel.isValid()) + { + GMX_ASSERT(sel.hasOnlyAtoms(), "Can only work with selections consisting out of atoms"); + return trjtools_gmx_prepare_tng_writing(name.c_str(), + filemode[0], + nullptr, //infile_, //how to get the input file here? + nullptr, + sel.atomCount(), + mtop, + sel.atomIndices(), + sel.name()); + } + else + { + return trjtools_gmx_prepare_tng_writing(name.c_str(), + filemode[0], + nullptr, //infile_, //how to get the input file here? + nullptr, + mtop->natoms, + mtop, + get_atom_index(mtop), + "System"); + } +} + +TrajectoryFileOpener::~TrajectoryFileOpener() +{ + close_trx(outputFile_); +} + +t_trxstatus *TrajectoryFileOpener::outputFile() +{ + if (outputFile_ == nullptr) + { + const char *filemode = "w"; + switch (filetype_) + { + case (efTNG): + outputFile_ = openTNG(outputFileName_, + sel_, + mtop_); + break; + case (efPDB): + case (efGRO): + case (efTRR): + case (efXTC): + case (efG96): + outputFile_ = open_trx(outputFileName_.c_str(), filemode); + break; + default: + GMX_THROW(InvalidInputError("Invalid file type")); + } + } + return outputFile_; +} + +void +TrajectoryFrameWriter::prepareAndWriteFrame(const int framenumber, const t_trxframe &input) +{ + if (!outputAdapters_.isEmpty()) + { + t_trxframe local; + clear_trxframe(&local, true); + localX_.resize(input.natoms); + localIndex_.resize(input.natoms); + if (input.bV) + { + localV_.resize(input.natoms); + } + if (input.bF) + { + localF_.resize(input.natoms); + } + deepCopy_t_trxframe(input, &local, localX_.data(), localV_.data(), + localF_.data(), localIndex_.data()); + for (const auto &outputAdapter : outputAdapters_.getAdapters()) + { + if (outputAdapter) + { + outputAdapter->processFrame(framenumber, &local); + } + } + write_trxframe(file_.outputFile(), &local, nullptr); + } + else + { + write_trxframe(file_.outputFile(), const_cast(&input), nullptr); + } +} + +} // namespace gmx diff --git a/src/gromacs/coordinateio/coordinatefile.h b/src/gromacs/coordinateio/coordinatefile.h new file mode 100644 index 0000000000..27ca0c3a87 --- /dev/null +++ b/src/gromacs/coordinateio/coordinatefile.h @@ -0,0 +1,191 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2019, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +/*! \file + * \brief + * CoordinateFile takes care of opening files and writing output to them. + * + * \author + * \inpublicapi + * \ingroup module_coordinateio + */ +#ifndef GMX_COORDINATEIO_COORDINATEFILE_H +#define GMX_COORDINATEIO_COORDINATEFILE_H + +#include +#include + +#include "gromacs/coordinateio/ioutputadapter.h" +#include "gromacs/coordinateio/outputadaptercontainer.h" +#include "gromacs/fileio/filetypes.h" +#include "gromacs/fileio/trxio.h" +#include "gromacs/selection/selection.h" +#include "gromacs/topology/mtop_util.h" +#include "gromacs/topology/topology.h" + +namespace gmx +{ + +/*!\brief + * Low level method to take care of only file opening and closing. + * + * \inpublicapi + * \ingroup module_coordinateio + * + */ +class TrajectoryFileOpener +{ + public: + /*! \brief + * Constructor, taking all arguments needed to open valid coordinate files of any type. + * + * \param[in] name Name of the file to create. + * \param[in] filetype Internal filetype to know which kind we are going to have. + * \param[in] sel Reference to selection of atoms to write. Needs to be valid for + * longer than the lifetime of the object created here. + * \param[in] mtop Topology used to create TNG output. Needs to be valid for longer + * than the object created here. + */ + TrajectoryFileOpener(const std::string &name, + int filetype, + const Selection &sel, + const gmx_mtop_t *mtop) : + outputFileName_(name), outputFile_(nullptr), filetype_(filetype), sel_(sel), mtop_(mtop) + {} + + /*! \brief + * Closes new trajectory file after finishing the writing to it. + */ + ~TrajectoryFileOpener(); + + /*! \brief + * Get access to initialized output file object. + * + * Performs lazy initialization if needed. + */ + t_trxstatus *outputFile(); + + private: + //! Name for the new coordinate file. + std::string outputFileName_; + + //! File pointer to the coordinate file being written. + t_trxstatus *outputFile_; + + //! Storage of file type for determing what kind of file will be written to disk. + int filetype_; + + /*! \brief + * Selection of atoms to write out. + * + * Currently, CoordinateFile expects that the lifetime of the selection is longer + * than that of itself, and that the selection remains unchanged during this lifetime. + * A better approach will be to pass the selection to it and expect it to + * manage the lifetime instead. + */ + const Selection &sel_; + + //! Pointer to topology information if available. + const gmx_mtop_t *mtop_; +}; + +/*!\brief + * Writes coordinate frames to a sink, e.g. a trajectory file. + * + * Writes all frames passed to the trajectory file handle it + * manages. It can use IOutputAdapter modules to transform the + * frame data before writing. If any transform modules are used, + * makes a deep copy of the frame contents. + * + * \inpublicapi + * \ingroup module_coordinateio + * + */ +class TrajectoryFrameWriter +{ + public: + /*! \brief + * Writes the input frame, after applying any IOutputAdapters. + * + * \param[in] framenumber Number of frame being currently processed. + * \param[in] input View of the constant t_trxframe object provided by the + * method that calls the output manager. + */ + void prepareAndWriteFrame(int framenumber, const t_trxframe &input); + + private: + /*! \brief + * Creates fully initialized object. + * + * \param[in] name Name of the file to create. + * \param[in] filetype Internal filetype to know which kind we are going to have. + * \param[in] sel Reference to selection of atoms to write. Needs to be valid for + * longer than the lifetime of the object created here. + * \param[in] mtop Topology used to create TNG output. Needs to be valid for longer + * than the object created here. + * \param[in] adapters Container of methods that can modify the information written + * to the new file. + */ + TrajectoryFrameWriter(const std::string &name, + int filetype, + const Selection &sel, + const gmx_mtop_t *mtop, + OutputAdapterContainer adapters) : + file_(name, filetype, sel, mtop), + outputAdapters_(std::move(adapters)) + { + } + + //! Underlying object for open/writing to file. + TrajectoryFileOpener file_; + + //! Storage for list of output adapters. + OutputAdapterContainer outputAdapters_; + + //! Local storage for modified positions. + std::vector localX_; + //! Local storage for modified velocities. + std::vector localV_; + //! Local storage for modified forces. + std::vector localF_; + //! Local storage for modified indices. + std::vector localIndex_; +}; + +//! Smart pointer to manage the TrajectoryFrameWriter object. +using TrajectoryFrameWriterPointer = std::unique_ptr; + +} // namespace gmx + +#endif -- 2.11.4.GIT