Allow TNG to TNG file conversion without time per frame info
[alexxy/gromacs.git] / src / gromacs / fileio / tngio.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2013,2014,2015,2018,2019,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 #include "gmxpre.h"
36
37 #include "tngio.h"
38
39 #include "config.h"
40
41 #include <cmath>
42
43 #include <algorithm>
44 #include <iterator>
45 #include <memory>
46 #include <numeric>
47 #include <vector>
48
49 #if GMX_USE_TNG
50 #    include "tng/tng_io.h"
51 #endif
52
53 #include "gromacs/math/units.h"
54 #include "gromacs/math/utilities.h"
55 #include "gromacs/mdtypes/inputrec.h"
56 #include "gromacs/topology/ifunc.h"
57 #include "gromacs/topology/topology.h"
58 #include "gromacs/trajectory/trajectoryframe.h"
59 #include "gromacs/utility/basedefinitions.h"
60 #include "gromacs/utility/baseversion.h"
61 #include "gromacs/utility/fatalerror.h"
62 #include "gromacs/utility/futil.h"
63 #include "gromacs/utility/gmxassert.h"
64 #include "gromacs/utility/programcontext.h"
65 #include "gromacs/utility/smalloc.h"
66 #include "gromacs/utility/sysinfo.h"
67 #include "gromacs/utility/unique_cptr.h"
68
69 #if !GMX_USE_TNG
70 using tng_trajectory_t = void*;
71 #endif
72
73 /*! \brief Gromacs Wrapper around tng datatype
74  *
75  * This could in principle hold any GROMACS-specific requirements not yet
76  * implemented in or not relevant to the TNG library itself. However, for now
77  * we only use it to handle some shortcomings we have discovered, where the TNG
78  * API itself is a bit fragile and can end up overwriting data if called several
79  * times with the same frame number.
80  * The logic to determine the time per step was also a bit fragile. This is not
81  * critical, but since we anyway need a wrapper for ensuring unique frame
82  * numbers, we can also use it to store the time of the first step and use that
83  * to derive a slightly better/safer estimate of the time per step.
84  *
85  * At some future point where we have a second-generation TNG API we should
86  * consider removing this again.
87  */
88 struct gmx_tng_trajectory
89 {
90     tng_trajectory_t tng;                  //!< Actual TNG handle (pointer)
91     bool             lastStepDataIsValid;  //!< True if lastStep has been set
92     std::int64_t     lastStep;             //!< Index/step used for last frame
93     bool             lastTimeDataIsValid;  //!< True if lastTime has been set
94     double           lastTime;             //!< Time of last frame (TNG unit is seconds)
95     bool             timePerFrameIsSet;    //!< True if we have set the time per frame
96     int              boxOutputInterval;    //!< Number of steps between the output of box size
97     int              lambdaOutputInterval; //!< Number of steps between the output of lambdas
98 };
99
100 #if GMX_USE_TNG
101 static const char* modeToVerb(char mode)
102 {
103     const char* p;
104     switch (mode)
105     {
106         case 'r': p = "reading"; break;
107         case 'w': p = "writing"; break;
108         case 'a': p = "appending"; break;
109         default: gmx_fatal(FARGS, "Invalid file opening mode %c", mode);
110     }
111     return p;
112 }
113 #endif
114
115 void gmx_tng_open(const char* filename, char mode, gmx_tng_trajectory_t* gmx_tng)
116 {
117 #if GMX_USE_TNG
118     /* First check whether we have to make a backup,
119      * only for writing, not for read or append.
120      */
121     if (mode == 'w')
122     {
123         make_backup(filename);
124     }
125
126     *gmx_tng                        = new gmx_tng_trajectory;
127     (*gmx_tng)->lastStepDataIsValid = false;
128     (*gmx_tng)->lastTimeDataIsValid = false;
129     (*gmx_tng)->timePerFrameIsSet   = false;
130     tng_trajectory_t* tng           = &(*gmx_tng)->tng;
131
132     /* tng must not be pointing at already allocated memory.
133      * Memory will be allocated by tng_util_trajectory_open() and must
134      * later on be freed by tng_util_trajectory_close(). */
135     if (TNG_SUCCESS != tng_util_trajectory_open(filename, mode, tng))
136     {
137         /* TNG does return more than one degree of error, but there is
138            no use case for GROMACS handling the non-fatal errors
139            gracefully. */
140         gmx_fatal(FARGS, "File I/O error while opening %s for %s", filename, modeToVerb(mode));
141     }
142
143     if (mode == 'w' || mode == 'a')
144     {
145         char hostname[256];
146         gmx_gethostname(hostname, 256);
147         if (mode == 'w')
148         {
149             tng_first_computer_name_set(*tng, hostname);
150         }
151         else
152         {
153             tng_last_computer_name_set(*tng, hostname);
154         }
155
156         char        programInfo[256];
157         const char* precisionString = "";
158 #    if GMX_DOUBLE
159         precisionString = " (double precision)";
160 #    endif
161         sprintf(programInfo, "%.100s %.128s%.24s", gmx::getProgramContext().displayName(), gmx_version(), precisionString);
162         if (mode == 'w')
163         {
164             tng_first_program_name_set(*tng, programInfo);
165         }
166         else
167         {
168             tng_last_program_name_set(*tng, programInfo);
169         }
170
171         char username[256];
172         if (!gmx_getusername(username, 256))
173         {
174             if (mode == 'w')
175             {
176                 tng_first_user_name_set(*tng, username);
177             }
178             else
179             {
180                 tng_last_user_name_set(*tng, username);
181                 tng_file_headers_write(*tng, TNG_USE_HASH);
182             }
183         }
184     }
185 #else
186     gmx_file("GROMACS was compiled without TNG support, cannot handle this file type");
187     GMX_UNUSED_VALUE(filename);
188     GMX_UNUSED_VALUE(mode);
189     GMX_UNUSED_VALUE(gmx_tng);
190 #endif
191 }
192
193 void gmx_tng_close(gmx_tng_trajectory_t* gmx_tng)
194 {
195     /* We have to check that tng is set because
196      * tng_util_trajectory_close wants to return a NULL in it, and
197      * gives a fatal error if it is NULL. */
198 #if GMX_USE_TNG
199     if (gmx_tng == nullptr || *gmx_tng == nullptr)
200     {
201         return;
202     }
203     tng_trajectory_t* tng = &(*gmx_tng)->tng;
204
205     if (tng)
206     {
207         tng_util_trajectory_close(tng);
208     }
209     delete *gmx_tng;
210     *gmx_tng = nullptr;
211
212 #else
213     GMX_UNUSED_VALUE(gmx_tng);
214 #endif
215 }
216
217 #if GMX_USE_TNG
218 static void addTngMoleculeFromTopology(gmx_tng_trajectory_t gmx_tng,
219                                        const char*          moleculeName,
220                                        const t_atoms*       atoms,
221                                        int64_t              numMolecules,
222                                        tng_molecule_t*      tngMol)
223 {
224     tng_trajectory_t tng      = gmx_tng->tng;
225     tng_chain_t      tngChain = nullptr;
226     tng_residue_t    tngRes   = nullptr;
227
228     if (tng_molecule_add(tng, moleculeName, tngMol) != TNG_SUCCESS)
229     {
230         gmx_file("Cannot add molecule to TNG molecular system.");
231     }
232
233     for (int atomIndex = 0; atomIndex < atoms->nr; atomIndex++)
234     {
235         const t_atom* at = &atoms->atom[atomIndex];
236         /* FIXME: Currently the TNG API can only add atoms belonging to a
237          * residue and chain. Wait for TNG 2.0*/
238         if (atoms->nres > 0)
239         {
240             const t_resinfo* resInfo      = &atoms->resinfo[at->resind];
241             char             chainName[2] = { resInfo->chainid, 0 };
242             tng_atom_t       tngAtom      = nullptr;
243             t_atom*          prevAtom;
244
245             if (atomIndex > 0)
246             {
247                 prevAtom = &atoms->atom[atomIndex - 1];
248             }
249             else
250             {
251                 prevAtom = nullptr;
252             }
253
254             /* If this is the first atom or if the residue changed add the
255              * residue to the TNG molecular system. */
256             if (!prevAtom || resInfo != &atoms->resinfo[prevAtom->resind])
257             {
258                 /* If this is the first atom or if the chain changed add
259                  * the chain to the TNG molecular system. */
260                 if (!prevAtom || resInfo->chainid != atoms->resinfo[prevAtom->resind].chainid)
261                 {
262                     tng_molecule_chain_add(tng, *tngMol, chainName, &tngChain);
263                 }
264                 /* FIXME: When TNG supports both residue index and residue
265                  * number the latter should be used. Wait for TNG 2.0*/
266                 tng_chain_residue_add(tng, tngChain, *resInfo->name, &tngRes);
267             }
268             tng_residue_atom_add(
269                     tng, tngRes, *(atoms->atomname[atomIndex]), *(atoms->atomtype[atomIndex]), &tngAtom);
270         }
271     }
272     tng_molecule_cnt_set(tng, *tngMol, numMolecules);
273 }
274
275 void gmx_tng_add_mtop(gmx_tng_trajectory_t gmx_tng, const gmx_mtop_t* mtop)
276 {
277     int               i;
278     int               j;
279     std::vector<real> atomCharges;
280     std::vector<real> atomMasses;
281     tng_bond_t        tngBond;
282     char              datatype;
283
284     tng_trajectory_t tng = gmx_tng->tng;
285
286     if (!mtop)
287     {
288         /* No topology information available to add. */
289         return;
290     }
291
292 #    if GMX_DOUBLE
293     datatype = TNG_DOUBLE_DATA;
294 #    else
295     datatype                                               = TNG_FLOAT_DATA;
296 #    endif
297
298     atomCharges.reserve(mtop->natoms);
299     atomMasses.reserve(mtop->natoms);
300
301     for (const gmx_molblock_t& molBlock : mtop->molblock)
302     {
303         tng_molecule_t       tngMol  = nullptr;
304         const gmx_moltype_t* molType = &mtop->moltype[molBlock.type];
305
306         /* Add a molecule to the TNG trajectory with the same name as the
307          * current molecule. */
308         addTngMoleculeFromTopology(gmx_tng, *(molType->name), &molType->atoms, molBlock.nmol, &tngMol);
309
310         /* Bonds have to be deduced from interactions (constraints etc). Different
311          * interactions have different sets of parameters. */
312         /* Constraints are specified using two atoms */
313         for (i = 0; i < F_NRE; i++)
314         {
315             if (IS_CHEMBOND(i))
316             {
317                 const InteractionList& ilist = molType->ilist[i];
318                 j                            = 1;
319                 while (j < ilist.size())
320                 {
321                     tng_molecule_bond_add(tng, tngMol, ilist.iatoms[j], ilist.iatoms[j + 1], &tngBond);
322                     j += 3;
323                 }
324             }
325         }
326         /* Settle is described using three atoms */
327         const InteractionList& ilist = molType->ilist[F_SETTLE];
328         j                            = 1;
329         while (j < ilist.size())
330         {
331             tng_molecule_bond_add(tng, tngMol, ilist.iatoms[j], ilist.iatoms[j + 1], &tngBond);
332             tng_molecule_bond_add(tng, tngMol, ilist.iatoms[j], ilist.iatoms[j + 2], &tngBond);
333             j += 4;
334         }
335         /* First copy atom charges and masses, first atom by atom and then copy the memory for the molecule instances.
336          * FIXME: Atom B state data should also be written to TNG (v 2.0?) */
337         for (int atomCounter = 0; atomCounter < molType->atoms.nr; atomCounter++)
338         {
339             atomCharges.push_back(molType->atoms.atom[atomCounter].q);
340             atomMasses.push_back(molType->atoms.atom[atomCounter].m);
341         }
342         for (int molCounter = 1; molCounter < molBlock.nmol; molCounter++)
343         {
344             std::copy_n(atomCharges.end() - molType->atoms.nr,
345                         molType->atoms.nr,
346                         std::back_inserter(atomCharges));
347             std::copy_n(atomMasses.end() - molType->atoms.nr, molType->atoms.nr, std::back_inserter(atomMasses));
348         }
349     }
350     /* Write the TNG data blocks. */
351     tng_particle_data_block_add(tng,
352                                 TNG_TRAJ_PARTIAL_CHARGES,
353                                 "PARTIAL CHARGES",
354                                 datatype,
355                                 TNG_NON_TRAJECTORY_BLOCK,
356                                 1,
357                                 1,
358                                 1,
359                                 0,
360                                 mtop->natoms,
361                                 TNG_GZIP_COMPRESSION,
362                                 atomCharges.data());
363     tng_particle_data_block_add(tng,
364                                 TNG_TRAJ_MASSES,
365                                 "ATOM MASSES",
366                                 datatype,
367                                 TNG_NON_TRAJECTORY_BLOCK,
368                                 1,
369                                 1,
370                                 1,
371                                 0,
372                                 mtop->natoms,
373                                 TNG_GZIP_COMPRESSION,
374                                 atomMasses.data());
375 }
376
377 /*! \libinternal \brief Compute greatest common divisor of n1 and n2
378  * if they are positive.
379  *
380  * If only one of n1 and n2 is positive, then return it.
381  * If neither n1 or n2 is positive, then return -1. */
382 static int greatest_common_divisor_if_positive(int n1, int n2)
383 {
384     if (0 >= n1)
385     {
386         return (0 >= n2) ? -1 : n2;
387     }
388     if (0 >= n2)
389     {
390         return n1;
391     }
392
393     /* We have a non-trivial greatest common divisor to compute. */
394     return std::gcd(n1, n2);
395 }
396
397 /* By default try to write 100 frames (of actual output) in each frame set.
398  * This number is the number of outputs of the most frequently written data
399  * type per frame set.
400  * TODO for 5.1: Verify that 100 frames per frame set is efficient for most
401  * setups regarding compression efficiency and compression time. Make this
402  * a hidden command-line option? */
403 const int defaultFramesPerFrameSet = 100;
404
405 /*! \libinternal \brief  Set the number of frames per frame
406  * set according to output intervals.
407  * The default is that 100 frames are written of the data
408  * that is written most often. */
409 static void tng_set_frames_per_frame_set(gmx_tng_trajectory_t gmx_tng,
410                                          const gmx_bool       bUseLossyCompression,
411                                          const t_inputrec*    ir)
412 {
413     int              gcd = -1;
414     tng_trajectory_t tng = gmx_tng->tng;
415
416     /* Set the number of frames per frame set to contain at least
417      * defaultFramesPerFrameSet of the lowest common denominator of
418      * the writing interval of positions and velocities. */
419     /* FIXME after 5.0: consider nstenergy also? */
420     if (bUseLossyCompression)
421     {
422         gcd = ir->nstxout_compressed;
423     }
424     else
425     {
426         gcd = greatest_common_divisor_if_positive(ir->nstxout, ir->nstvout);
427         gcd = greatest_common_divisor_if_positive(gcd, ir->nstfout);
428     }
429     if (0 >= gcd)
430     {
431         return;
432     }
433
434     tng_num_frames_per_frame_set_set(tng, gcd * defaultFramesPerFrameSet);
435 }
436
437 /*! \libinternal \brief Set the data-writing intervals, and number of
438  * frames per frame set */
439 static void set_writing_intervals(gmx_tng_trajectory_t gmx_tng,
440                                   const gmx_bool       bUseLossyCompression,
441                                   const t_inputrec*    ir)
442 {
443     tng_trajectory_t tng = gmx_tng->tng;
444
445     /* Define pointers to specific writing functions depending on if we
446      * write float or double data */
447     typedef tng_function_status (*set_writing_interval_func_pointer)(
448             tng_trajectory_t, const int64_t, const int64_t, const int64_t, const char*, const char, const char);
449 #    if GMX_DOUBLE
450     set_writing_interval_func_pointer set_writing_interval = tng_util_generic_write_interval_double_set;
451 #    else
452     set_writing_interval_func_pointer set_writing_interval = tng_util_generic_write_interval_set;
453 #    endif
454     int  xout, vout, fout;
455     int  gcd = -1, lowest = -1;
456     char compression;
457
458     tng_set_frames_per_frame_set(gmx_tng, bUseLossyCompression, ir);
459
460     if (bUseLossyCompression)
461     {
462         xout = ir->nstxout_compressed;
463
464         /* If there is no uncompressed coordinate output write forces
465            and velocities to the compressed tng file. */
466         if (ir->nstxout)
467         {
468             vout = 0;
469             fout = 0;
470         }
471         else
472         {
473             vout = ir->nstvout;
474             fout = ir->nstfout;
475         }
476         compression = TNG_TNG_COMPRESSION;
477     }
478     else
479     {
480         xout        = ir->nstxout;
481         vout        = ir->nstvout;
482         fout        = ir->nstfout;
483         compression = TNG_GZIP_COMPRESSION;
484     }
485     if (xout)
486     {
487         set_writing_interval(
488                 tng, xout, 3, TNG_TRAJ_POSITIONS, "POSITIONS", TNG_PARTICLE_BLOCK_DATA, compression);
489         /* TODO: if/when we write energies to TNG also, reconsider how
490          * and when box information is written, because GROMACS
491          * behaviour pre-5.0 was to write the box with every
492          * trajectory frame and every energy frame, and probably
493          * people depend on this. */
494
495         gcd = greatest_common_divisor_if_positive(gcd, xout);
496         if (lowest < 0 || xout < lowest)
497         {
498             lowest = xout;
499         }
500     }
501     if (vout)
502     {
503         set_writing_interval(
504                 tng, vout, 3, TNG_TRAJ_VELOCITIES, "VELOCITIES", TNG_PARTICLE_BLOCK_DATA, compression);
505
506         gcd = greatest_common_divisor_if_positive(gcd, vout);
507         if (lowest < 0 || vout < lowest)
508         {
509             lowest = vout;
510         }
511     }
512     if (fout)
513     {
514         set_writing_interval(
515                 tng, fout, 3, TNG_TRAJ_FORCES, "FORCES", TNG_PARTICLE_BLOCK_DATA, TNG_GZIP_COMPRESSION);
516
517         gcd = greatest_common_divisor_if_positive(gcd, fout);
518         if (lowest < 0 || fout < lowest)
519         {
520             lowest = fout;
521         }
522     }
523     if (gcd > 0)
524     {
525         /* Lambdas and box shape written at an interval of the lowest common
526            denominator of other output */
527         set_writing_interval(
528                 tng, gcd, 1, TNG_GMX_LAMBDA, "LAMBDAS", TNG_NON_PARTICLE_BLOCK_DATA, TNG_GZIP_COMPRESSION);
529
530         set_writing_interval(
531                 tng, gcd, 9, TNG_TRAJ_BOX_SHAPE, "BOX SHAPE", TNG_NON_PARTICLE_BLOCK_DATA, TNG_GZIP_COMPRESSION);
532         gmx_tng->lambdaOutputInterval = gcd;
533         gmx_tng->boxOutputInterval    = gcd;
534         if (gcd < lowest / 10)
535         {
536             gmx_warning(
537                     "The lowest common denominator of trajectory output is "
538                     "every %d step(s), whereas the shortest output interval "
539                     "is every %d steps.",
540                     gcd,
541                     lowest);
542         }
543     }
544 }
545 #endif
546
547 void gmx_tng_prepare_md_writing(gmx_tng_trajectory_t gmx_tng, const gmx_mtop_t* mtop, const t_inputrec* ir)
548 {
549 #if GMX_USE_TNG
550     gmx_tng_add_mtop(gmx_tng, mtop);
551     set_writing_intervals(gmx_tng, FALSE, ir);
552     tng_time_per_frame_set(gmx_tng->tng, ir->delta_t * gmx::c_pico);
553     gmx_tng->timePerFrameIsSet = true;
554 #else
555     GMX_UNUSED_VALUE(gmx_tng);
556     GMX_UNUSED_VALUE(mtop);
557     GMX_UNUSED_VALUE(ir);
558 #endif
559 }
560
561 #if GMX_USE_TNG
562 /* Check if all atoms in the molecule system are selected
563  * by a selection group of type specified by gtype. */
564 static gmx_bool all_atoms_selected(const gmx_mtop_t* mtop, const SimulationAtomGroupType gtype)
565 {
566     /* Iterate through all atoms in the molecule system and
567      * check if they belong to a selection group of the
568      * requested type. */
569     int i = 0;
570     for (const gmx_molblock_t& molBlock : mtop->molblock)
571     {
572         const gmx_moltype_t& molType = mtop->moltype[molBlock.type];
573         const t_atoms&       atoms   = molType.atoms;
574
575         for (int j = 0; j < molBlock.nmol; j++)
576         {
577             for (int atomIndex = 0; atomIndex < atoms.nr; atomIndex++, i++)
578             {
579                 if (getGroupType(mtop->groups, gtype, i) != 0)
580                 {
581                     return FALSE;
582                 }
583             }
584         }
585     }
586     return TRUE;
587 }
588
589 /* Create TNG molecules which will represent each of the selection
590  * groups for writing. But do that only if there is actually a
591  * specified selection group and it is not the whole system.
592  * TODO: Currently the only selection that is taken into account
593  * is egcCompressedX, but other selections should be added when
594  * e.g. writing energies is implemented.
595  */
596 static void add_selection_groups(gmx_tng_trajectory_t gmx_tng, const gmx_mtop_t* mtop)
597 {
598     const t_atoms*   atoms;
599     const t_atom*    at;
600     const t_resinfo* resInfo;
601     int              nameIndex;
602     int              atom_offset = 0;
603     tng_molecule_t   mol, iterMol;
604     tng_chain_t      chain;
605     tng_residue_t    res;
606     tng_atom_t       atom;
607     tng_bond_t       tngBond;
608     int64_t          nMols;
609     char*            groupName;
610     tng_trajectory_t tng = gmx_tng->tng;
611
612     /* TODO: When the TNG molecules block is more flexible TNG selection
613      * groups should not need all atoms specified. It should be possible
614      * just to specify what molecules are selected (and/or which atoms in
615      * the molecule) and how many (if applicable). */
616
617     /* If no atoms are selected we do not need to create a
618      * TNG selection group molecule. */
619     if (mtop->groups.numberOfGroupNumbers(SimulationAtomGroupType::CompressedPositionOutput) == 0)
620     {
621         return;
622     }
623
624     /* If all atoms are selected we do not have to create a selection
625      * group molecule in the TNG molecule system. */
626     if (all_atoms_selected(mtop, SimulationAtomGroupType::CompressedPositionOutput))
627     {
628         return;
629     }
630
631     /* The name of the TNG molecule containing the selection group is the
632      * same as the name of the selection group. */
633     nameIndex = mtop->groups.groups[SimulationAtomGroupType::CompressedPositionOutput][0];
634     groupName = *mtop->groups.groupNames[nameIndex];
635
636     tng_molecule_alloc(tng, &mol);
637     tng_molecule_name_set(tng, mol, groupName);
638     tng_molecule_chain_add(tng, mol, "", &chain);
639     int i = 0;
640     for (const gmx_molblock_t& molBlock : mtop->molblock)
641     {
642         const gmx_moltype_t& molType = mtop->moltype[molBlock.type];
643
644         atoms = &molType.atoms;
645
646         for (int j = 0; j < molBlock.nmol; j++)
647         {
648             bool bAtomsAdded = FALSE;
649             for (int atomIndex = 0; atomIndex < atoms->nr; atomIndex++, i++)
650             {
651                 char* res_name;
652                 int   res_id;
653
654                 if (getGroupType(mtop->groups, SimulationAtomGroupType::CompressedPositionOutput, i) != 0)
655                 {
656                     continue;
657                 }
658                 at = &atoms->atom[atomIndex];
659                 if (atoms->nres > 0)
660                 {
661                     resInfo = &atoms->resinfo[at->resind];
662                     /* FIXME: When TNG supports both residue index and residue
663                      * number the latter should be used. */
664                     res_name = *resInfo->name;
665                     res_id   = at->resind + 1;
666                 }
667                 else
668                 {
669                     res_name = const_cast<char*>("");
670                     res_id   = 0;
671                 }
672                 if (tng_chain_residue_find(tng, chain, res_name, res_id, &res) != TNG_SUCCESS)
673                 {
674                     /* Since there is ONE chain for selection groups do not keep the
675                      * original residue IDs - otherwise there might be conflicts. */
676                     tng_chain_residue_add(tng, chain, res_name, &res);
677                 }
678                 tng_residue_atom_w_id_add(tng,
679                                           res,
680                                           *(atoms->atomname[atomIndex]),
681                                           *(atoms->atomtype[atomIndex]),
682                                           atom_offset + atomIndex,
683                                           &atom);
684                 bAtomsAdded = TRUE;
685             }
686             /* Add bonds. */
687             if (bAtomsAdded)
688             {
689                 for (int k = 0; k < F_NRE; k++)
690                 {
691                     if (IS_CHEMBOND(k))
692                     {
693                         const InteractionList& ilist = molType.ilist[k];
694                         for (int l = 1; l < ilist.size(); l += 3)
695                         {
696                             int atom1, atom2;
697                             atom1 = ilist.iatoms[l] + atom_offset;
698                             atom2 = ilist.iatoms[l + 1] + atom_offset;
699                             if (getGroupType(mtop->groups, SimulationAtomGroupType::CompressedPositionOutput, atom1)
700                                         == 0
701                                 && getGroupType(mtop->groups, SimulationAtomGroupType::CompressedPositionOutput, atom2)
702                                            == 0)
703                             {
704                                 tng_molecule_bond_add(
705                                         tng, mol, ilist.iatoms[l], ilist.iatoms[l + 1], &tngBond);
706                             }
707                         }
708                     }
709                 }
710                 /* Settle is described using three atoms */
711                 const InteractionList& ilist = molType.ilist[F_SETTLE];
712                 for (int l = 1; l < ilist.size(); l += 4)
713                 {
714                     int atom1, atom2, atom3;
715                     atom1 = ilist.iatoms[l] + atom_offset;
716                     atom2 = ilist.iatoms[l + 1] + atom_offset;
717                     atom3 = ilist.iatoms[l + 2] + atom_offset;
718                     if (getGroupType(mtop->groups, SimulationAtomGroupType::CompressedPositionOutput, atom1)
719                         == 0)
720                     {
721                         if (getGroupType(mtop->groups, SimulationAtomGroupType::CompressedPositionOutput, atom2)
722                             == 0)
723                         {
724                             tng_molecule_bond_add(tng, mol, atom1, atom2, &tngBond);
725                         }
726                         if (getGroupType(mtop->groups, SimulationAtomGroupType::CompressedPositionOutput, atom3)
727                             == 0)
728                         {
729                             tng_molecule_bond_add(tng, mol, atom1, atom3, &tngBond);
730                         }
731                     }
732                 }
733             }
734             atom_offset += atoms->nr;
735         }
736     }
737     tng_molecule_existing_add(tng, &mol);
738     tng_molecule_cnt_set(tng, mol, 1);
739     tng_num_molecule_types_get(tng, &nMols);
740     for (int64_t k = 0; k < nMols; k++)
741     {
742         tng_molecule_of_index_get(tng, k, &iterMol);
743         if (iterMol == mol)
744         {
745             continue;
746         }
747         tng_molecule_cnt_set(tng, iterMol, 0);
748     }
749 }
750 #endif
751
752 void gmx_tng_set_compression_precision(gmx_tng_trajectory_t gmx_tng, real prec)
753 {
754 #if GMX_USE_TNG
755     tng_compression_precision_set(gmx_tng->tng, prec);
756 #else
757     GMX_UNUSED_VALUE(gmx_tng);
758     GMX_UNUSED_VALUE(prec);
759 #endif
760 }
761
762 void gmx_tng_prepare_low_prec_writing(gmx_tng_trajectory_t gmx_tng, const gmx_mtop_t* mtop, const t_inputrec* ir)
763 {
764 #if GMX_USE_TNG
765     gmx_tng_add_mtop(gmx_tng, mtop);
766     add_selection_groups(gmx_tng, mtop);
767     set_writing_intervals(gmx_tng, TRUE, ir);
768     tng_time_per_frame_set(gmx_tng->tng, ir->delta_t * gmx::c_pico);
769     gmx_tng->timePerFrameIsSet = true;
770     gmx_tng_set_compression_precision(gmx_tng, ir->x_compression_precision);
771 #else
772     GMX_UNUSED_VALUE(gmx_tng);
773     GMX_UNUSED_VALUE(mtop);
774     GMX_UNUSED_VALUE(ir);
775 #endif
776 }
777
778 void gmx_fwrite_tng(gmx_tng_trajectory_t gmx_tng,
779                     const gmx_bool       bUseLossyCompression,
780                     int64_t              step,
781                     real                 elapsedPicoSeconds,
782                     real                 lambda,
783                     const rvec*          box,
784                     int                  nAtoms,
785                     const rvec*          x,
786                     const rvec*          v,
787                     const rvec*          f)
788 {
789 #if GMX_USE_TNG
790     typedef tng_function_status (*write_data_func_pointer)(tng_trajectory_t,
791                                                            const int64_t,
792                                                            const double,
793                                                            const real*,
794                                                            const int64_t,
795                                                            const int64_t,
796                                                            const char*,
797                                                            const char,
798                                                            const char);
799 #    if GMX_DOUBLE
800     static write_data_func_pointer write_data = tng_util_generic_with_time_double_write;
801 #    else
802     static write_data_func_pointer    write_data           = tng_util_generic_with_time_write;
803 #    endif
804     double  elapsedSeconds = elapsedPicoSeconds * gmx::c_pico;
805     int64_t nParticles;
806     char    compression;
807
808
809     if (!gmx_tng)
810     {
811         /* This function might get called when the type of the
812            compressed trajectory is actually XTC. So we exit and move
813            on. */
814         return;
815     }
816     tng_trajectory_t tng = gmx_tng->tng;
817
818     // While the GROMACS interface to this routine specifies 'step', TNG itself
819     // only uses 'frame index' internally, although it suggests that it's a good
820     // idea to let this represent the step rather than just frame numbers.
821     //
822     // However, the way the GROMACS interface works, there are cases where
823     // the step is simply not set, so all frames rather have step=0.
824     // If we call the lower-level TNG routines with the same frame index
825     // (which is set from the step) multiple times, things will go very
826     // wrong (overwritten data).
827     //
828     // To avoid this, we require the step value to always be larger than the
829     // previous value, and if this is not true we simply set it to a value
830     // one unit larger than the previous step.
831     //
832     // Note: We do allow the user to specify any crazy value the want for the
833     // first step. If it is "not set", GROMACS will have used the value 0.
834     if (gmx_tng->lastStepDataIsValid && step <= gmx_tng->lastStep)
835     {
836         step = gmx_tng->lastStep + 1;
837     }
838
839     // Now that we have fixed the potentially incorrect step, we can also set
840     // the time per frame if it was not already done, and we have
841     // step/time information from the last frame so it is possible to calculate it.
842     // Note that we do allow the time to be the same for all steps, or even
843     // decreasing. In that case we will simply say the time per step is 0
844     // or negative. A bit strange, but a correct representation of the data :-)
845     if (!gmx_tng->timePerFrameIsSet && gmx_tng->lastTimeDataIsValid && gmx_tng->lastStepDataIsValid)
846     {
847         double       deltaTime = elapsedSeconds - gmx_tng->lastTime;
848         std::int64_t deltaStep = step - gmx_tng->lastStep;
849         tng_time_per_frame_set(tng, deltaTime / deltaStep);
850         gmx_tng->timePerFrameIsSet = true;
851     }
852
853     tng_num_particles_get(tng, &nParticles);
854     if (nAtoms != static_cast<int>(nParticles))
855     {
856         tng_implicit_num_particles_set(tng, nAtoms);
857     }
858
859     if (bUseLossyCompression)
860     {
861         compression = TNG_TNG_COMPRESSION;
862     }
863     else
864     {
865         compression = TNG_GZIP_COMPRESSION;
866     }
867
868     /* The writing is done using write_data, which writes float or double
869      * depending on the GROMACS compilation. */
870     if (x)
871     {
872         GMX_ASSERT(box, "Need a non-NULL box if positions are written");
873
874         if (write_data(tng,
875                        step,
876                        elapsedSeconds,
877                        reinterpret_cast<const real*>(x),
878                        3,
879                        TNG_TRAJ_POSITIONS,
880                        "POSITIONS",
881                        TNG_PARTICLE_BLOCK_DATA,
882                        compression)
883             != TNG_SUCCESS)
884         {
885             gmx_file("Cannot write TNG trajectory frame; maybe you are out of disk space?");
886         }
887     }
888
889     if (v)
890     {
891         if (write_data(tng,
892                        step,
893                        elapsedSeconds,
894                        reinterpret_cast<const real*>(v),
895                        3,
896                        TNG_TRAJ_VELOCITIES,
897                        "VELOCITIES",
898                        TNG_PARTICLE_BLOCK_DATA,
899                        compression)
900             != TNG_SUCCESS)
901         {
902             gmx_file("Cannot write TNG trajectory frame; maybe you are out of disk space?");
903         }
904     }
905
906     if (f)
907     {
908         /* TNG-MF1 compression only compresses positions and velocities. Use lossless
909          * compression for forces regardless of output mode */
910         if (write_data(tng,
911                        step,
912                        elapsedSeconds,
913                        reinterpret_cast<const real*>(f),
914                        3,
915                        TNG_TRAJ_FORCES,
916                        "FORCES",
917                        TNG_PARTICLE_BLOCK_DATA,
918                        TNG_GZIP_COMPRESSION)
919             != TNG_SUCCESS)
920         {
921             gmx_file("Cannot write TNG trajectory frame; maybe you are out of disk space?");
922         }
923     }
924
925     if (box)
926     {
927         /* TNG-MF1 compression only compresses positions and velocities. Use lossless
928          * compression for box shape regardless of output mode */
929         if (write_data(tng,
930                        step,
931                        elapsedSeconds,
932                        reinterpret_cast<const real*>(box),
933                        9,
934                        TNG_TRAJ_BOX_SHAPE,
935                        "BOX SHAPE",
936                        TNG_NON_PARTICLE_BLOCK_DATA,
937                        TNG_GZIP_COMPRESSION)
938             != TNG_SUCCESS)
939         {
940             gmx_file("Cannot write TNG trajectory frame; maybe you are out of disk space?");
941         }
942     }
943
944     if (lambda >= 0)
945     {
946         /* TNG-MF1 compression only compresses positions and velocities. Use lossless
947          * compression for lambda regardless of output mode */
948         if (write_data(tng,
949                        step,
950                        elapsedSeconds,
951                        reinterpret_cast<const real*>(&lambda),
952                        1,
953                        TNG_GMX_LAMBDA,
954                        "LAMBDAS",
955                        TNG_NON_PARTICLE_BLOCK_DATA,
956                        TNG_GZIP_COMPRESSION)
957             != TNG_SUCCESS)
958         {
959             gmx_file("Cannot write TNG trajectory frame; maybe you are out of disk space?");
960         }
961     }
962
963     // Update the data in the wrapper
964
965     gmx_tng->lastStepDataIsValid = true;
966     gmx_tng->lastStep            = step;
967     gmx_tng->lastTimeDataIsValid = true;
968     gmx_tng->lastTime            = elapsedSeconds;
969 #else
970     GMX_UNUSED_VALUE(gmx_tng);
971     GMX_UNUSED_VALUE(bUseLossyCompression);
972     GMX_UNUSED_VALUE(step);
973     GMX_UNUSED_VALUE(elapsedPicoSeconds);
974     GMX_UNUSED_VALUE(lambda);
975     GMX_UNUSED_VALUE(box);
976     GMX_UNUSED_VALUE(nAtoms);
977     GMX_UNUSED_VALUE(x);
978     GMX_UNUSED_VALUE(v);
979     GMX_UNUSED_VALUE(f);
980 #endif
981 }
982
983 void fflush_tng(gmx_tng_trajectory_t gmx_tng)
984 {
985 #if GMX_USE_TNG
986     if (!gmx_tng)
987     {
988         return;
989     }
990     tng_frame_set_premature_write(gmx_tng->tng, TNG_USE_HASH);
991 #else
992     GMX_UNUSED_VALUE(gmx_tng);
993 #endif
994 }
995
996 float gmx_tng_get_time_of_final_frame(gmx_tng_trajectory_t gmx_tng)
997 {
998 #if GMX_USE_TNG
999     int64_t          nFrames;
1000     double           time;
1001     float            fTime;
1002     tng_trajectory_t tng = gmx_tng->tng;
1003
1004     tng_num_frames_get(tng, &nFrames);
1005     tng_util_time_of_frame_get(tng, nFrames - 1, &time);
1006
1007     fTime = time / gmx::c_pico;
1008     return fTime;
1009 #else
1010     GMX_UNUSED_VALUE(gmx_tng);
1011     return -1.0;
1012 #endif
1013 }
1014
1015 void gmx_prepare_tng_writing(const char*              filename,
1016                              char                     mode,
1017                              gmx_tng_trajectory_t*    gmx_tng_input,
1018                              gmx_tng_trajectory_t*    gmx_tng_output,
1019                              int                      nAtoms,
1020                              const gmx_mtop_t*        mtop,
1021                              gmx::ArrayRef<const int> index,
1022                              const char*              indexGroupName)
1023 {
1024 #if GMX_USE_TNG
1025     tng_trajectory_t* input = (gmx_tng_input && *gmx_tng_input) ? &(*gmx_tng_input)->tng : nullptr;
1026     /* FIXME after 5.0: Currently only standard block types are read */
1027     const int      defaultNumIds              = 5;
1028     static int64_t fallbackIds[defaultNumIds] = {
1029         TNG_TRAJ_BOX_SHAPE, TNG_TRAJ_POSITIONS, TNG_TRAJ_VELOCITIES, TNG_TRAJ_FORCES, TNG_GMX_LAMBDA
1030     };
1031     static char fallbackNames[defaultNumIds][32] = {
1032         "BOX SHAPE", "POSITIONS", "VELOCITIES", "FORCES", "LAMBDAS"
1033     };
1034
1035     typedef tng_function_status (*set_writing_interval_func_pointer)(
1036             tng_trajectory_t, const int64_t, const int64_t, const int64_t, const char*, const char, const char);
1037 #    if GMX_DOUBLE
1038     set_writing_interval_func_pointer set_writing_interval = tng_util_generic_write_interval_double_set;
1039 #    else
1040     set_writing_interval_func_pointer set_writing_interval = tng_util_generic_write_interval_set;
1041 #    endif
1042
1043     gmx_tng_open(filename, mode, gmx_tng_output);
1044     tng_trajectory_t* output = &(*gmx_tng_output)->tng;
1045
1046     /* Do we have an input file in TNG format? If so, then there's
1047        more data we can copy over, rather than having to improvise. */
1048     if (gmx_tng_input && *gmx_tng_input)
1049     {
1050         /* Set parameters (compression, time per frame, molecule
1051          * information, number of frames per frame set and writing
1052          * intervals of positions, box shape and lambdas) of the
1053          * output tng container based on their respective values int
1054          * the input tng container */
1055         double  time, compression_precision;
1056         int64_t n_frames_per_frame_set, interval = -1;
1057
1058         tng_compression_precision_get(*input, &compression_precision);
1059         tng_compression_precision_set(*output, compression_precision);
1060         // TODO make this configurable in a future version
1061         char compression_type = TNG_TNG_COMPRESSION;
1062
1063         tng_molecule_system_copy(*input, *output);
1064
1065         tng_time_per_frame_get(*input, &time);
1066         /* Only write the time per frame if it was written (and valid). E.g., single
1067          * frame files do not usually contain any time per frame information. */
1068         if (time >= 0)
1069         {
1070             (*gmx_tng_input)->timePerFrameIsSet = true;
1071             tng_time_per_frame_set(*output, time);
1072             // Since we have copied the value from the input TNG we should not change it again
1073             (*gmx_tng_output)->timePerFrameIsSet = true;
1074         }
1075
1076         tng_num_frames_per_frame_set_get(*input, &n_frames_per_frame_set);
1077         tng_num_frames_per_frame_set_set(*output, n_frames_per_frame_set);
1078
1079         for (int i = 0; i < defaultNumIds; i++)
1080         {
1081             if (tng_data_get_stride_length(*input, fallbackIds[i], -1, &interval) == TNG_SUCCESS)
1082             {
1083                 switch (fallbackIds[i])
1084                 {
1085                     case TNG_TRAJ_POSITIONS:
1086                     case TNG_TRAJ_VELOCITIES:
1087                         set_writing_interval(*output,
1088                                              interval,
1089                                              3,
1090                                              fallbackIds[i],
1091                                              fallbackNames[i],
1092                                              TNG_PARTICLE_BLOCK_DATA,
1093                                              compression_type);
1094                         break;
1095                     case TNG_TRAJ_FORCES:
1096                         set_writing_interval(*output,
1097                                              interval,
1098                                              3,
1099                                              fallbackIds[i],
1100                                              fallbackNames[i],
1101                                              TNG_PARTICLE_BLOCK_DATA,
1102                                              TNG_GZIP_COMPRESSION);
1103                         break;
1104                     case TNG_TRAJ_BOX_SHAPE:
1105                         set_writing_interval(*output,
1106                                              interval,
1107                                              9,
1108                                              fallbackIds[i],
1109                                              fallbackNames[i],
1110                                              TNG_NON_PARTICLE_BLOCK_DATA,
1111                                              TNG_GZIP_COMPRESSION);
1112                         (*gmx_tng_output)->boxOutputInterval = interval;
1113                         break;
1114                     case TNG_GMX_LAMBDA:
1115                         set_writing_interval(*output,
1116                                              interval,
1117                                              1,
1118                                              fallbackIds[i],
1119                                              fallbackNames[i],
1120                                              TNG_NON_PARTICLE_BLOCK_DATA,
1121                                              TNG_GZIP_COMPRESSION);
1122                         (*gmx_tng_output)->lambdaOutputInterval = interval;
1123                         break;
1124                     default: continue;
1125                 }
1126             }
1127         }
1128     }
1129     else
1130     {
1131         /* TODO after trjconv is modularized: fix this so the user can
1132            change precision when they are doing an operation where
1133            this makes sense, and not otherwise.
1134
1135            char compression = bUseLossyCompression ? TNG_TNG_COMPRESSION : TNG_GZIP_COMPRESSION;
1136            gmx_tng_set_compression_precision(*output, ndec2prec(nDecimalsOfPrecision));
1137          */
1138         gmx_tng_add_mtop(*gmx_tng_output, mtop);
1139         tng_num_frames_per_frame_set_set(*output, 1);
1140     }
1141
1142     if ((!index.empty()) && nAtoms > 0)
1143     {
1144         gmx_tng_setup_atom_subgroup(*gmx_tng_output, index, indexGroupName);
1145     }
1146
1147     /* If for some reason there are more requested atoms than there are atoms in the
1148      * molecular system create a number of implicit atoms (without atom data) to
1149      * compensate for that. */
1150     if (nAtoms >= 0)
1151     {
1152         tng_implicit_num_particles_set(*output, nAtoms);
1153     }
1154 #else
1155     GMX_UNUSED_VALUE(filename);
1156     GMX_UNUSED_VALUE(mode);
1157     GMX_UNUSED_VALUE(gmx_tng_input);
1158     GMX_UNUSED_VALUE(gmx_tng_output);
1159     GMX_UNUSED_VALUE(nAtoms);
1160     GMX_UNUSED_VALUE(mtop);
1161     GMX_UNUSED_VALUE(index);
1162     GMX_UNUSED_VALUE(indexGroupName);
1163 #endif
1164 }
1165
1166 void gmx_write_tng_from_trxframe(gmx_tng_trajectory_t gmx_tng_output, const t_trxframe* frame, int natoms)
1167 {
1168 #if GMX_USE_TNG
1169     if (natoms < 0)
1170     {
1171         natoms = frame->natoms;
1172     }
1173     gmx_fwrite_tng(
1174             gmx_tng_output, TRUE, frame->step, frame->time, 0, frame->box, natoms, frame->x, frame->v, frame->f);
1175 #else
1176     GMX_UNUSED_VALUE(gmx_tng_output);
1177     GMX_UNUSED_VALUE(frame);
1178     GMX_UNUSED_VALUE(natoms);
1179 #endif
1180 }
1181
1182 namespace
1183 {
1184
1185 #if GMX_USE_TNG
1186 void convert_array_to_real_array(void*       from,
1187                                  real*       to,
1188                                  const float fact,
1189                                  const int   nAtoms,
1190                                  const int   nValues,
1191                                  const char  datatype)
1192 {
1193     int i, j;
1194
1195     const bool useDouble = GMX_DOUBLE;
1196     switch (datatype)
1197     {
1198         case TNG_FLOAT_DATA:
1199             if (!useDouble)
1200             {
1201                 if (fact == 1)
1202                 {
1203                     memcpy(to, from, nValues * sizeof(real) * nAtoms);
1204                 }
1205                 else
1206                 {
1207                     for (i = 0; i < nAtoms; i++)
1208                     {
1209                         for (j = 0; j < nValues; j++)
1210                         {
1211                             to[i * nValues + j] = reinterpret_cast<float*>(from)[i * nValues + j] * fact;
1212                         }
1213                     }
1214                 }
1215             }
1216             else
1217             {
1218                 for (i = 0; i < nAtoms; i++)
1219                 {
1220                     for (j = 0; j < nValues; j++)
1221                     {
1222                         to[i * nValues + j] = reinterpret_cast<float*>(from)[i * nValues + j] * fact;
1223                     }
1224                 }
1225             }
1226             break;
1227         case TNG_INT_DATA:
1228             for (i = 0; i < nAtoms; i++)
1229             {
1230                 for (j = 0; j < nValues; j++)
1231                 {
1232                     to[i * nValues + j] = reinterpret_cast<int64_t*>(from)[i * nValues + j] * fact;
1233                 }
1234             }
1235             break;
1236         case TNG_DOUBLE_DATA:
1237             if (sizeof(real) == sizeof(double))
1238             {
1239                 if (fact == 1)
1240                 {
1241                     memcpy(to, from, nValues * sizeof(real) * nAtoms);
1242                 }
1243                 else
1244                 {
1245                     for (i = 0; i < nAtoms; i++)
1246                     {
1247                         for (j = 0; j < nValues; j++)
1248                         {
1249                             to[i * nValues + j] = reinterpret_cast<double*>(from)[i * nValues + j] * fact;
1250                         }
1251                     }
1252                 }
1253             }
1254             else
1255             {
1256                 for (i = 0; i < nAtoms; i++)
1257                 {
1258                     for (j = 0; j < nValues; j++)
1259                     {
1260                         to[i * nValues + j] = reinterpret_cast<double*>(from)[i * nValues + j] * fact;
1261                     }
1262                 }
1263             }
1264             break;
1265         default: gmx_incons("Illegal datatype when converting values to a real array!");
1266     }
1267 }
1268
1269 real getDistanceScaleFactor(gmx_tng_trajectory_t in)
1270 {
1271     int64_t exp = -1;
1272     real    distanceScaleFactor;
1273
1274     // TODO Hopefully, TNG 2.0 will do this kind of thing for us
1275     tng_distance_unit_exponential_get(in->tng, &exp);
1276
1277     // GROMACS expects distances in nm
1278     switch (exp)
1279     {
1280         case 9: distanceScaleFactor = gmx::c_nano / gmx::c_nano; break;
1281         case 10: distanceScaleFactor = gmx::c_nano / gmx::c_angstrom; break;
1282         default: distanceScaleFactor = pow(10.0, exp + 9.0);
1283     }
1284
1285     return distanceScaleFactor;
1286 }
1287 #endif
1288
1289 } // namespace
1290
1291 void gmx_tng_setup_atom_subgroup(gmx_tng_trajectory_t gmx_tng, gmx::ArrayRef<const int> ind, const char* name)
1292 {
1293 #if GMX_USE_TNG
1294     int64_t             nAtoms, cnt, nMols;
1295     tng_molecule_t      mol, iterMol;
1296     tng_chain_t         chain;
1297     tng_residue_t       res;
1298     tng_atom_t          atom;
1299     tng_function_status stat;
1300     tng_trajectory_t    tng = gmx_tng->tng;
1301
1302     tng_num_particles_get(tng, &nAtoms);
1303
1304     if (nAtoms == ind.ssize())
1305     {
1306         return;
1307     }
1308
1309     stat = tng_molecule_find(tng, name, -1, &mol);
1310     if (stat == TNG_SUCCESS)
1311     {
1312         tng_molecule_num_atoms_get(tng, mol, &nAtoms);
1313         tng_molecule_cnt_get(tng, mol, &cnt);
1314         if (nAtoms == ind.ssize())
1315         {
1316             stat = TNG_SUCCESS;
1317         }
1318         else
1319         {
1320             stat = TNG_FAILURE;
1321         }
1322     }
1323     if (stat == TNG_FAILURE)
1324     {
1325         /* The indexed atoms are added to one separate molecule. */
1326         tng_molecule_alloc(tng, &mol);
1327         tng_molecule_name_set(tng, mol, name);
1328         tng_molecule_chain_add(tng, mol, "", &chain);
1329
1330         for (gmx::index i = 0; i < ind.ssize(); i++)
1331         {
1332             char temp_name[256], temp_type[256];
1333
1334             /* Try to retrieve the residue name of the atom */
1335             stat = tng_residue_name_of_particle_nr_get(tng, ind[i], temp_name, 256);
1336             if (stat != TNG_SUCCESS)
1337             {
1338                 temp_name[0] = '\0';
1339             }
1340             /* Check if the molecule of the selection already contains this residue */
1341             if (tng_chain_residue_find(tng, chain, temp_name, -1, &res) != TNG_SUCCESS)
1342             {
1343                 tng_chain_residue_add(tng, chain, temp_name, &res);
1344             }
1345             /* Try to find the original name and type of the atom */
1346             stat = tng_atom_name_of_particle_nr_get(tng, ind[i], temp_name, 256);
1347             if (stat != TNG_SUCCESS)
1348             {
1349                 temp_name[0] = '\0';
1350             }
1351             stat = tng_atom_type_of_particle_nr_get(tng, ind[i], temp_type, 256);
1352             if (stat != TNG_SUCCESS)
1353             {
1354                 temp_type[0] = '\0';
1355             }
1356             tng_residue_atom_w_id_add(tng, res, temp_name, temp_type, ind[i], &atom);
1357         }
1358         tng_molecule_existing_add(tng, &mol);
1359     }
1360     /* Set the count of the molecule containing the selected atoms to 1 and all
1361      * other molecules to 0 */
1362     tng_molecule_cnt_set(tng, mol, 1);
1363     tng_num_molecule_types_get(tng, &nMols);
1364     for (int64_t k = 0; k < nMols; k++)
1365     {
1366         tng_molecule_of_index_get(tng, k, &iterMol);
1367         if (iterMol == mol)
1368         {
1369             continue;
1370         }
1371         tng_molecule_cnt_set(tng, iterMol, 0);
1372     }
1373 #else
1374     GMX_UNUSED_VALUE(gmx_tng);
1375     GMX_UNUSED_VALUE(ind);
1376     GMX_UNUSED_VALUE(name);
1377 #endif
1378 }
1379
1380 /* TODO: If/when TNG acquires the ability to copy data blocks without
1381  * uncompressing them, then this implemenation should be reconsidered.
1382  * Ideally, gmx trjconv -f a.tng -o b.tng -b 10 -e 20 would be fast
1383  * and lose no information. */
1384 gmx_bool gmx_read_next_tng_frame(gmx_tng_trajectory_t gmx_tng_input,
1385                                  t_trxframe*          fr,
1386                                  int64_t*             requestedIds,
1387                                  int                  numRequestedIds)
1388 {
1389 #if GMX_USE_TNG
1390     tng_trajectory_t    input = gmx_tng_input->tng;
1391     gmx_bool            bOK   = TRUE;
1392     tng_function_status stat;
1393     int64_t             numberOfAtoms = -1, frameNumber = -1;
1394     int64_t             nBlocks, blockId, *blockIds = nullptr, codecId;
1395     char                datatype  = -1;
1396     void*               values    = nullptr;
1397     double              frameTime = -1.0;
1398     int                 size, blockDependency;
1399     double              prec;
1400     const int           defaultNumIds                       = 5;
1401     static int64_t      fallbackRequestedIds[defaultNumIds] = {
1402         TNG_TRAJ_BOX_SHAPE, TNG_TRAJ_POSITIONS, TNG_TRAJ_VELOCITIES, TNG_TRAJ_FORCES, TNG_GMX_LAMBDA
1403     };
1404
1405
1406     fr->bStep   = FALSE;
1407     fr->bTime   = FALSE;
1408     fr->bLambda = FALSE;
1409     fr->bAtoms  = FALSE;
1410     fr->bPrec   = FALSE;
1411     fr->bX      = FALSE;
1412     fr->bV      = FALSE;
1413     fr->bF      = FALSE;
1414     fr->bBox    = FALSE;
1415
1416     /* If no specific IDs were requested read all block types that can
1417      * currently be interpreted */
1418     if (!requestedIds || numRequestedIds == 0)
1419     {
1420         numRequestedIds = defaultNumIds;
1421         requestedIds    = fallbackRequestedIds;
1422     }
1423
1424     stat = tng_num_particles_get(input, &numberOfAtoms);
1425     if (stat != TNG_SUCCESS)
1426     {
1427         gmx_file("Cannot determine number of atoms from TNG file.");
1428     }
1429     fr->natoms = numberOfAtoms;
1430
1431     bool nextFrameExists = gmx_get_tng_data_block_types_of_next_frame(
1432             gmx_tng_input, fr->step, numRequestedIds, requestedIds, &frameNumber, &nBlocks, &blockIds);
1433     gmx::unique_cptr<int64_t, gmx::free_wrapper> blockIdsGuard(blockIds);
1434     if (!nextFrameExists)
1435     {
1436         return FALSE;
1437     }
1438
1439     if (nBlocks == 0)
1440     {
1441         return FALSE;
1442     }
1443
1444     for (int64_t i = 0; i < nBlocks; i++)
1445     {
1446         blockId = blockIds[i];
1447         tng_data_block_dependency_get(input, blockId, &blockDependency);
1448         if (blockDependency & TNG_PARTICLE_DEPENDENT)
1449         {
1450             stat = tng_util_particle_data_next_frame_read(
1451                     input, blockId, &values, &datatype, &frameNumber, &frameTime);
1452         }
1453         else
1454         {
1455             stat = tng_util_non_particle_data_next_frame_read(
1456                     input, blockId, &values, &datatype, &frameNumber, &frameTime);
1457         }
1458         if (stat == TNG_CRITICAL)
1459         {
1460             gmx_file("Cannot read positions from TNG file.");
1461             return FALSE;
1462         }
1463         else if (stat == TNG_FAILURE)
1464         {
1465             continue;
1466         }
1467         switch (blockId)
1468         {
1469             case TNG_TRAJ_BOX_SHAPE:
1470                 switch (datatype)
1471                 {
1472                     case TNG_INT_DATA: size = sizeof(int64_t); break;
1473                     case TNG_FLOAT_DATA: size = sizeof(float); break;
1474                     case TNG_DOUBLE_DATA: size = sizeof(double); break;
1475                     default: gmx_incons("Illegal datatype of box shape values!");
1476                 }
1477                 for (int i = 0; i < DIM; i++)
1478                 {
1479                     convert_array_to_real_array(reinterpret_cast<char*>(values) + size * i * DIM,
1480                                                 reinterpret_cast<real*>(fr->box[i]),
1481                                                 getDistanceScaleFactor(gmx_tng_input),
1482                                                 1,
1483                                                 DIM,
1484                                                 datatype);
1485                 }
1486                 fr->bBox = TRUE;
1487                 break;
1488             case TNG_TRAJ_POSITIONS:
1489                 srenew(fr->x, fr->natoms);
1490                 convert_array_to_real_array(values,
1491                                             reinterpret_cast<real*>(fr->x),
1492                                             getDistanceScaleFactor(gmx_tng_input),
1493                                             fr->natoms,
1494                                             DIM,
1495                                             datatype);
1496                 fr->bX = TRUE;
1497                 tng_util_frame_current_compression_get(input, blockId, &codecId, &prec);
1498                 /* This must be updated if/when more lossy compression methods are added */
1499                 if (codecId == TNG_TNG_COMPRESSION)
1500                 {
1501                     fr->prec  = prec;
1502                     fr->bPrec = TRUE;
1503                 }
1504                 break;
1505             case TNG_TRAJ_VELOCITIES:
1506                 srenew(fr->v, fr->natoms);
1507                 convert_array_to_real_array(values,
1508                                             reinterpret_cast<real*>(fr->v),
1509                                             getDistanceScaleFactor(gmx_tng_input),
1510                                             fr->natoms,
1511                                             DIM,
1512                                             datatype);
1513                 fr->bV = TRUE;
1514                 tng_util_frame_current_compression_get(input, blockId, &codecId, &prec);
1515                 /* This must be updated if/when more lossy compression methods are added */
1516                 if (codecId == TNG_TNG_COMPRESSION)
1517                 {
1518                     fr->prec  = prec;
1519                     fr->bPrec = TRUE;
1520                 }
1521                 break;
1522             case TNG_TRAJ_FORCES:
1523                 srenew(fr->f, fr->natoms);
1524                 convert_array_to_real_array(values,
1525                                             reinterpret_cast<real*>(fr->f),
1526                                             getDistanceScaleFactor(gmx_tng_input),
1527                                             fr->natoms,
1528                                             DIM,
1529                                             datatype);
1530                 fr->bF = TRUE;
1531                 break;
1532             case TNG_GMX_LAMBDA:
1533                 switch (datatype)
1534                 {
1535                     case TNG_FLOAT_DATA: fr->lambda = *(reinterpret_cast<float*>(values)); break;
1536                     case TNG_DOUBLE_DATA: fr->lambda = *(reinterpret_cast<double*>(values)); break;
1537                     default: gmx_incons("Illegal datatype lambda value!");
1538                 }
1539                 fr->bLambda = TRUE;
1540                 break;
1541             default:
1542                 gmx_warning(
1543                         "Illegal block type! Currently GROMACS tools can only handle certain data "
1544                         "types. Skipping block.");
1545         }
1546         /* values does not have to be freed before reading next frame. It will
1547          * be reallocated if it is not NULL. */
1548     }
1549
1550     fr->step  = frameNumber;
1551     fr->bStep = TRUE;
1552
1553     // Convert the time to ps
1554     fr->time  = frameTime / gmx::c_pico;
1555     fr->bTime = (frameTime > 0);
1556
1557     // TODO This does not leak, but is not exception safe.
1558     /* values must be freed before leaving this function */
1559     sfree(values);
1560
1561     return bOK;
1562 #else
1563     GMX_UNUSED_VALUE(gmx_tng_input);
1564     GMX_UNUSED_VALUE(fr);
1565     GMX_UNUSED_VALUE(requestedIds);
1566     GMX_UNUSED_VALUE(numRequestedIds);
1567     return FALSE;
1568 #endif
1569 }
1570
1571 void gmx_print_tng_molecule_system(gmx_tng_trajectory_t gmx_tng_input, FILE* stream)
1572 {
1573 #if GMX_USE_TNG
1574     int64_t             nMolecules, nChains, nResidues, nAtoms, nFramesRead;
1575     int64_t             strideLength, nParticlesRead, nValuesPerFrameRead, *molCntList;
1576     tng_molecule_t      molecule;
1577     tng_chain_t         chain;
1578     tng_residue_t       residue;
1579     tng_atom_t          atom;
1580     tng_function_status stat;
1581     char                str[256];
1582     char                varNAtoms;
1583     char                datatype;
1584     void*               data = nullptr;
1585     std::vector<real>   atomCharges;
1586     std::vector<real>   atomMasses;
1587     tng_trajectory_t    input = gmx_tng_input->tng;
1588
1589     tng_num_molecule_types_get(input, &nMolecules);
1590     tng_molecule_cnt_list_get(input, &molCntList);
1591     /* Can the number of particles change in the trajectory or is it constant? */
1592     tng_num_particles_variable_get(input, &varNAtoms);
1593
1594     for (int64_t i = 0; i < nMolecules; i++)
1595     {
1596         tng_molecule_of_index_get(input, i, &molecule);
1597         tng_molecule_name_get(input, molecule, str, 256);
1598         if (varNAtoms == TNG_CONSTANT_N_ATOMS)
1599         {
1600             if (static_cast<int>(molCntList[i]) == 0)
1601             {
1602                 continue;
1603             }
1604             fprintf(stream, "Molecule: %s, count: %d\n", str, static_cast<int>(molCntList[i]));
1605         }
1606         else
1607         {
1608             fprintf(stream, "Molecule: %s\n", str);
1609         }
1610         tng_molecule_num_chains_get(input, molecule, &nChains);
1611         if (nChains > 0)
1612         {
1613             for (int64_t j = 0; j < nChains; j++)
1614             {
1615                 tng_molecule_chain_of_index_get(input, molecule, j, &chain);
1616                 tng_chain_name_get(input, chain, str, 256);
1617                 fprintf(stream, "\tChain: %s\n", str);
1618                 tng_chain_num_residues_get(input, chain, &nResidues);
1619                 for (int64_t k = 0; k < nResidues; k++)
1620                 {
1621                     tng_chain_residue_of_index_get(input, chain, k, &residue);
1622                     tng_residue_name_get(input, residue, str, 256);
1623                     fprintf(stream, "\t\tResidue: %s\n", str);
1624                     tng_residue_num_atoms_get(input, residue, &nAtoms);
1625                     for (int64_t l = 0; l < nAtoms; l++)
1626                     {
1627                         tng_residue_atom_of_index_get(input, residue, l, &atom);
1628                         tng_atom_name_get(input, atom, str, 256);
1629                         fprintf(stream, "\t\t\tAtom: %s", str);
1630                         tng_atom_type_get(input, atom, str, 256);
1631                         fprintf(stream, " (%s)\n", str);
1632                     }
1633                 }
1634             }
1635         }
1636         /* It is possible to have a molecule without chains, in which case
1637          * residues in the molecule can be iterated through without going
1638          * through chains. */
1639         else
1640         {
1641             tng_molecule_num_residues_get(input, molecule, &nResidues);
1642             if (nResidues > 0)
1643             {
1644                 for (int64_t k = 0; k < nResidues; k++)
1645                 {
1646                     tng_molecule_residue_of_index_get(input, molecule, k, &residue);
1647                     tng_residue_name_get(input, residue, str, 256);
1648                     fprintf(stream, "\t\tResidue: %s\n", str);
1649                     tng_residue_num_atoms_get(input, residue, &nAtoms);
1650                     for (int64_t l = 0; l < nAtoms; l++)
1651                     {
1652                         tng_residue_atom_of_index_get(input, residue, l, &atom);
1653                         tng_atom_name_get(input, atom, str, 256);
1654                         fprintf(stream, "\t\t\tAtom: %s", str);
1655                         tng_atom_type_get(input, atom, str, 256);
1656                         fprintf(stream, " (%s)\n", str);
1657                     }
1658                 }
1659             }
1660             else
1661             {
1662                 tng_molecule_num_atoms_get(input, molecule, &nAtoms);
1663                 for (int64_t l = 0; l < nAtoms; l++)
1664                 {
1665                     tng_molecule_atom_of_index_get(input, molecule, l, &atom);
1666                     tng_atom_name_get(input, atom, str, 256);
1667                     fprintf(stream, "\t\t\tAtom: %s", str);
1668                     tng_atom_type_get(input, atom, str, 256);
1669                     fprintf(stream, " (%s)\n", str);
1670                 }
1671             }
1672         }
1673     }
1674
1675     tng_num_particles_get(input, &nAtoms);
1676     stat = tng_particle_data_vector_get(input,
1677                                         TNG_TRAJ_PARTIAL_CHARGES,
1678                                         &data,
1679                                         &nFramesRead,
1680                                         &strideLength,
1681                                         &nParticlesRead,
1682                                         &nValuesPerFrameRead,
1683                                         &datatype);
1684     if (stat == TNG_SUCCESS)
1685     {
1686         atomCharges.resize(nAtoms);
1687         convert_array_to_real_array(data, atomCharges.data(), 1, nAtoms, 1, datatype);
1688
1689         fprintf(stream, "Atom Charges (%d):\n", int(nAtoms));
1690         for (int64_t i = 0; i < nAtoms; i += 10)
1691         {
1692             fprintf(stream, "Atom Charges [%8d-]=[", int(i));
1693             for (int64_t j = 0; (j < 10 && i + j < nAtoms); j++)
1694             {
1695                 fprintf(stream, " %12.5e", atomCharges[i + j]);
1696             }
1697             fprintf(stream, "]\n");
1698         }
1699     }
1700
1701     stat = tng_particle_data_vector_get(
1702             input, TNG_TRAJ_MASSES, &data, &nFramesRead, &strideLength, &nParticlesRead, &nValuesPerFrameRead, &datatype);
1703     if (stat == TNG_SUCCESS)
1704     {
1705         atomMasses.resize(nAtoms);
1706         convert_array_to_real_array(data, atomMasses.data(), 1, nAtoms, 1, datatype);
1707
1708         fprintf(stream, "Atom Masses (%d):\n", int(nAtoms));
1709         for (int64_t i = 0; i < nAtoms; i += 10)
1710         {
1711             fprintf(stream, "Atom Masses [%8d-]=[", int(i));
1712             for (int64_t j = 0; (j < 10 && i + j < nAtoms); j++)
1713             {
1714                 fprintf(stream, " %12.5e", atomMasses[i + j]);
1715             }
1716             fprintf(stream, "]\n");
1717         }
1718     }
1719
1720     sfree(data);
1721 #else
1722     GMX_UNUSED_VALUE(gmx_tng_input);
1723     GMX_UNUSED_VALUE(stream);
1724 #endif
1725 }
1726
1727 gmx_bool gmx_get_tng_data_block_types_of_next_frame(gmx_tng_trajectory_t gmx_tng_input,
1728                                                     int                  frame,
1729                                                     int                  nRequestedIds,
1730                                                     int64_t*             requestedIds,
1731                                                     int64_t*             nextFrame,
1732                                                     int64_t*             nBlocks,
1733                                                     int64_t**            blockIds)
1734 {
1735 #if GMX_USE_TNG
1736     tng_function_status stat;
1737     tng_trajectory_t    input = gmx_tng_input->tng;
1738
1739     stat = tng_util_trajectory_next_frame_present_data_blocks_find(
1740             input, frame, nRequestedIds, requestedIds, nextFrame, nBlocks, blockIds);
1741
1742     if (stat == TNG_CRITICAL)
1743     {
1744         gmx_file("Cannot read TNG file. Cannot find data blocks of next frame.");
1745     }
1746     else if (stat == TNG_FAILURE)
1747     {
1748         return FALSE;
1749     }
1750     return TRUE;
1751 #else
1752     GMX_UNUSED_VALUE(gmx_tng_input);
1753     GMX_UNUSED_VALUE(frame);
1754     GMX_UNUSED_VALUE(nRequestedIds);
1755     GMX_UNUSED_VALUE(requestedIds);
1756     GMX_UNUSED_VALUE(nextFrame);
1757     GMX_UNUSED_VALUE(nBlocks);
1758     GMX_UNUSED_VALUE(blockIds);
1759     return FALSE;
1760 #endif
1761 }
1762
1763 gmx_bool gmx_get_tng_data_next_frame_of_block_type(gmx_tng_trajectory_t gmx_tng_input,
1764                                                    int64_t              blockId,
1765                                                    real**               values,
1766                                                    int64_t*             frameNumber,
1767                                                    double*              frameTime,
1768                                                    int64_t*             nValuesPerFrame,
1769                                                    int64_t*             nAtoms,
1770                                                    real*                prec,
1771                                                    char*                name,
1772                                                    int                  maxLen,
1773                                                    gmx_bool*            bOK)
1774 {
1775 #if GMX_USE_TNG
1776     tng_function_status stat;
1777     char                datatype = -1;
1778     int64_t             codecId;
1779     int                 blockDependency;
1780     void*               data = nullptr;
1781     double              localPrec;
1782     tng_trajectory_t    input = gmx_tng_input->tng;
1783
1784     stat = tng_data_block_name_get(input, blockId, name, maxLen);
1785     if (stat != TNG_SUCCESS)
1786     {
1787         gmx_file("Cannot read next frame of TNG file");
1788     }
1789     stat = tng_data_block_dependency_get(input, blockId, &blockDependency);
1790     if (stat != TNG_SUCCESS)
1791     {
1792         gmx_file("Cannot read next frame of TNG file");
1793     }
1794     if (blockDependency & TNG_PARTICLE_DEPENDENT)
1795     {
1796         tng_num_particles_get(input, nAtoms);
1797         stat = tng_util_particle_data_next_frame_read(
1798                 input, blockId, &data, &datatype, frameNumber, frameTime);
1799     }
1800     else
1801     {
1802         *nAtoms = 1; /* There are not actually any atoms, but it is used for
1803                         allocating memory */
1804         stat = tng_util_non_particle_data_next_frame_read(
1805                 input, blockId, &data, &datatype, frameNumber, frameTime);
1806     }
1807     if (stat == TNG_CRITICAL)
1808     {
1809         gmx_file("Cannot read next frame of TNG file");
1810     }
1811     if (stat == TNG_FAILURE)
1812     {
1813         *bOK = TRUE;
1814         return FALSE;
1815     }
1816
1817     stat = tng_data_block_num_values_per_frame_get(input, blockId, nValuesPerFrame);
1818     if (stat != TNG_SUCCESS)
1819     {
1820         gmx_file("Cannot read next frame of TNG file");
1821     }
1822     srenew(*values, sizeof(real) * *nValuesPerFrame * *nAtoms);
1823     convert_array_to_real_array(
1824             data, *values, getDistanceScaleFactor(gmx_tng_input), *nAtoms, *nValuesPerFrame, datatype);
1825
1826     tng_util_frame_current_compression_get(input, blockId, &codecId, &localPrec);
1827
1828     /* This must be updated if/when more lossy compression methods are added */
1829     if (codecId != TNG_TNG_COMPRESSION)
1830     {
1831         *prec = -1.0;
1832     }
1833     else
1834     {
1835         *prec = localPrec;
1836     }
1837
1838     sfree(data);
1839
1840     *bOK = TRUE;
1841     return TRUE;
1842 #else
1843     GMX_UNUSED_VALUE(gmx_tng_input);
1844     GMX_UNUSED_VALUE(blockId);
1845     GMX_UNUSED_VALUE(values);
1846     GMX_UNUSED_VALUE(frameNumber);
1847     GMX_UNUSED_VALUE(frameTime);
1848     GMX_UNUSED_VALUE(nValuesPerFrame);
1849     GMX_UNUSED_VALUE(nAtoms);
1850     GMX_UNUSED_VALUE(prec);
1851     GMX_UNUSED_VALUE(name);
1852     GMX_UNUSED_VALUE(maxLen);
1853     GMX_UNUSED_VALUE(bOK);
1854     return FALSE;
1855 #endif
1856 }
1857
1858 int gmx_tng_get_box_output_interval(gmx_tng_trajectory_t gmx_tng)
1859 {
1860 #if GMX_USE_TNG
1861     return gmx_tng->boxOutputInterval;
1862 #else
1863     GMX_UNUSED_VALUE(gmx_tng);
1864     return -1;
1865 #endif
1866 }
1867
1868 int gmx_tng_get_lambda_output_interval(gmx_tng_trajectory_t gmx_tng)
1869 {
1870 #if GMX_USE_TNG
1871     return gmx_tng->lambdaOutputInterval;
1872 #else
1873     GMX_UNUSED_VALUE(gmx_tng);
1874     return -1;
1875 #endif
1876 }