Merge release-5-0 into master
[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, 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 #ifdef HAVE_UNISTD_H
42 #include <unistd.h>
43 #endif
44
45 #ifdef GMX_USE_TNG
46 #include "tng/tng_io.h"
47 #endif
48
49 #include "gromacs/legacyheaders/copyrite.h"
50 #include "gromacs/legacyheaders/types/ifunc.h"
51
52 #include "gromacs/fileio/gmxfio.h"
53 #include "gromacs/math/units.h"
54 #include "gromacs/math/utilities.h"
55 #include "gromacs/topology/topology.h"
56 #include "gromacs/utility/basenetwork.h"
57 #include "gromacs/utility/common.h"
58 #include "gromacs/utility/fatalerror.h"
59 #include "gromacs/utility/gmxassert.h"
60 #include "gromacs/utility/programcontext.h"
61
62 static const char *modeToVerb(char mode)
63 {
64     const char *p;
65     switch (mode)
66     {
67         case 'r':
68             p = "reading";
69             break;
70         case 'w':
71             p = "writing";
72             break;
73         case 'a':
74             p = "appending";
75             break;
76         default:
77             gmx_fatal(FARGS, "Invalid file opening mode %c", mode);
78             p = "";
79             break;
80     }
81     return p;
82 }
83
84 void gmx_tng_open(const char       *filename,
85                   char              mode,
86                   tng_trajectory_t *tng)
87 {
88 #ifdef GMX_USE_TNG
89     /* First check whether we have to make a backup,
90      * only for writing, not for read or append.
91      */
92     if (mode == 'w')
93     {
94 #ifndef GMX_FAHCORE
95         /* only make backups for normal gromacs */
96         make_backup(filename);
97 #endif
98     }
99
100     /* tng must not be pointing at already allocated memory.
101      * Memory will be allocated by tng_util_trajectory_open() and must
102      * later on be freed by tng_util_trajectory_close(). */
103     if (TNG_SUCCESS != tng_util_trajectory_open(filename, mode, tng))
104     {
105         /* TNG does return more than one degree of error, but there is
106            no use case for GROMACS handling the non-fatal errors
107            gracefully. */
108         gmx_fatal(FARGS,
109                   "%s while opening %s for %s",
110                   gmx_strerror("file"),
111                   filename,
112                   modeToVerb(mode));
113     }
114
115     if (mode == 'w' || mode == 'a')
116     {
117         /* FIXME in TNG: When adding data to the header, subsequent blocks might get
118          * overwritten. This could be solved by moving the first trajectory
119          * frame set(s) to the end of the file. Could that cause other problems,
120          * e.g. when continuing a simulation? */
121         char hostname[256];
122         gmx_gethostname(hostname, 256);
123         if (mode == 'w')
124         {
125             tng_first_computer_name_set(*tng, hostname);
126         }
127 /* TODO: This should be implemented when the above fixme is done (adding data to
128  * the header). */
129 //         else
130 //         {
131 //             tng_last_computer_name_set(*tng, hostname);
132 //         }
133
134         char        programInfo[256];
135         const char *precisionString = "";
136 #ifdef GMX_DOUBLE
137         precisionString = " (double precision)";
138 #endif
139         sprintf(programInfo, "%.100s, %.128s%.24s",
140                 gmx::getProgramContext().displayName(),
141                 GromacsVersion(), precisionString);
142         if (mode == 'w')
143         {
144             tng_first_program_name_set(*tng, programInfo);
145         }
146 /* TODO: This should be implemented when the above fixme is done (adding data to
147  * the header). */
148 //         else
149 //         {
150 //             tng_last_program_name_set(*tng, programInfo);
151 //         }
152
153 #ifdef HAVE_UNISTD_H
154         char username[256];
155         getlogin_r(username, 256);
156         if (mode == 'w')
157         {
158             tng_first_user_name_set(*tng, username);
159         }
160 /* TODO: This should be implemented when the above fixme is done (adding data to
161  * the header). */
162 //         else
163 //         {
164 //             tng_last_user_name_set(*tng, username);
165 //         }
166 #endif
167     }
168 #else
169     gmx_file("GROMACS was compiled without TNG support, cannot handle this file type");
170     GMX_UNUSED_VALUE(filename);
171     GMX_UNUSED_VALUE(mode);
172     GMX_UNUSED_VALUE(tng);
173 #endif
174 }
175
176 void gmx_tng_close(tng_trajectory_t *tng)
177 {
178     /* We have to check that tng is set because
179      * tng_util_trajectory_close wants to return a NULL in it, and
180      * gives a fatal error if it is NULL. */
181 #ifdef GMX_USE_TNG
182     if (tng)
183     {
184         tng_util_trajectory_close(tng);
185     }
186 #else
187     GMX_UNUSED_VALUE(tng);
188 #endif
189 }
190
191 #ifdef GMX_USE_TNG
192 static void addTngMoleculeFromTopology(tng_trajectory_t     tng,
193                                        const char          *moleculeName,
194                                        const t_atoms       *atoms,
195                                        gmx_int64_t          numMolecules,
196                                        tng_molecule_t      *tngMol)
197 {
198     tng_chain_t      tngChain = NULL;
199     tng_residue_t    tngRes   = NULL;
200
201     if (tng_molecule_add(tng, moleculeName, tngMol) != TNG_SUCCESS)
202     {
203         gmx_file("Cannot add molecule to TNG molecular system.");
204     }
205
206     /* FIXME: The TNG atoms should contain mass and atomB info (for free
207      * energy calculations), i.e. in when it's available in TNG (2.0). */
208     for (int atomIndex = 0; atomIndex < atoms->nr; atomIndex++)
209     {
210         const t_atom *at = &atoms->atom[atomIndex];
211         /* FIXME: Currently the TNG API can only add atoms belonging to a
212          * residue and chain. Wait for TNG 2.0*/
213         if (atoms->nres > 0)
214         {
215             const t_resinfo *resInfo        = &atoms->resinfo[at->resind];
216             char             chainName[2]   = {resInfo->chainid, 0};
217             tng_atom_t       tngAtom        = NULL;
218             t_atom          *prevAtom;
219
220             if (atomIndex > 0)
221             {
222                 prevAtom = &atoms->atom[atomIndex - 1];
223             }
224             else
225             {
226                 prevAtom = 0;
227             }
228
229             /* If this is the first atom or if the residue changed add the
230              * residue to the TNG molecular system. */
231             if (!prevAtom || resInfo != &atoms->resinfo[prevAtom->resind])
232             {
233                 /* If this is the first atom or if the chain changed add
234                  * the chain to the TNG molecular system. */
235                 if (!prevAtom || resInfo->chainid !=
236                     atoms->resinfo[prevAtom->resind].chainid)
237                 {
238                     tng_molecule_chain_add(tng, *tngMol, chainName,
239                                            &tngChain);
240                 }
241                 /* FIXME: When TNG supports both residue index and residue
242                  * number the latter should be used. Wait for TNG 2.0*/
243                 tng_chain_residue_add(tng, tngChain, *resInfo->name, &tngRes);
244             }
245             tng_residue_atom_add(tng, tngRes, *(atoms->atomname[atomIndex]), *(atoms->atomtype[atomIndex]), &tngAtom);
246         }
247     }
248     tng_molecule_cnt_set(tng, *tngMol, numMolecules);
249 }
250
251 void gmx_tng_add_mtop(tng_trajectory_t  tng,
252                       const gmx_mtop_t *mtop)
253 {
254     int                  i, j;
255     const t_ilist       *ilist;
256     tng_bond_t           tngBond;
257
258     if (!mtop)
259     {
260         /* No topology information available to add. */
261         return;
262     }
263
264     for (int molIndex = 0; molIndex < mtop->nmolblock; molIndex++)
265     {
266         tng_molecule_t       tngMol  = NULL;
267         const gmx_moltype_t *molType =
268             &mtop->moltype[mtop->molblock[molIndex].type];
269
270         /* Add a molecule to the TNG trajectory with the same name as the
271          * current molecule. */
272         addTngMoleculeFromTopology(tng,
273                                    *(molType->name),
274                                    &molType->atoms,
275                                    mtop->molblock[molIndex].nmol,
276                                    &tngMol);
277
278         /* Bonds have to be deduced from interactions (constraints etc). Different
279          * interactions have different sets of parameters. */
280         /* Constraints are specified using two atoms */
281         for (i = 0; i < F_NRE; i++)
282         {
283             if (IS_CHEMBOND(i))
284             {
285                 ilist = &molType->ilist[i];
286                 if (ilist)
287                 {
288                     j = 1;
289                     while (j < ilist->nr)
290                     {
291                         tng_molecule_bond_add(tng, tngMol, ilist->iatoms[j], ilist->iatoms[j+1], &tngBond);
292                         j += 3;
293                     }
294                 }
295             }
296         }
297         /* Settle is described using three atoms */
298         ilist = &molType->ilist[F_SETTLE];
299         if (ilist)
300         {
301             j = 1;
302             while (j < ilist->nr)
303             {
304                 tng_molecule_bond_add(tng, tngMol, ilist->iatoms[j], ilist->iatoms[j+1], &tngBond);
305                 tng_molecule_bond_add(tng, tngMol, ilist->iatoms[j], ilist->iatoms[j+2], &tngBond);
306                 j += 4;
307             }
308         }
309     }
310 }
311
312 /*! \libinternal \brief Compute greatest common divisor of n1 and n2
313  * if they are positive.
314  *
315  * If only one of n1 and n2 is positive, then return it.
316  * If neither n1 or n2 is positive, then return -1. */
317 static int
318 greatest_common_divisor_if_positive(int n1, int n2)
319 {
320     if (0 >= n1)
321     {
322         return (0 >= n2) ? -1 : n2;
323     }
324     if (0 >= n2)
325     {
326         return n1;
327     }
328
329     /* We have a non-trivial greatest common divisor to compute. */
330     return gmx_greatest_common_divisor(n1, n2);
331 }
332
333 /* By default try to write 100 frames (of actual output) in each frame set.
334  * This number is the number of outputs of the most frequently written data
335  * type per frame set.
336  * TODO for 5.1: Verify that 100 frames per frame set is efficient for most
337  * setups regarding compression efficiency and compression time. Make this
338  * a hidden command-line option? */
339 const int defaultFramesPerFrameSet = 100;
340
341 /*! \libinternal \brief  Set the number of frames per frame
342  * set according to output intervals.
343  * The default is that 100 frames are written of the data
344  * that is written most often. */
345 static void tng_set_frames_per_frame_set(tng_trajectory_t  tng,
346                                          const gmx_bool    bUseLossyCompression,
347                                          const t_inputrec *ir)
348 {
349     int     gcd = -1;
350
351     /* Set the number of frames per frame set to contain at least
352      * defaultFramesPerFrameSet of the lowest common denominator of
353      * the writing interval of positions and velocities. */
354     /* FIXME after 5.0: consider nstenergy also? */
355     if (bUseLossyCompression)
356     {
357         gcd = ir->nstxout_compressed;
358     }
359     else
360     {
361         gcd = greatest_common_divisor_if_positive(ir->nstxout, ir->nstvout);
362         gcd = greatest_common_divisor_if_positive(gcd, ir->nstfout);
363     }
364     if (0 >= gcd)
365     {
366         return;
367     }
368
369     tng_num_frames_per_frame_set_set(tng, gcd * defaultFramesPerFrameSet);
370 }
371
372 /*! \libinternal \brief Set the data-writing intervals, and number of
373  * frames per frame set */
374 static void set_writing_intervals(tng_trajectory_t  tng,
375                                   const gmx_bool    bUseLossyCompression,
376                                   const t_inputrec *ir)
377 {
378     /* Define pointers to specific writing functions depending on if we
379      * write float or double data */
380     typedef tng_function_status (*set_writing_interval_func_pointer)(tng_trajectory_t,
381                                                                      const gmx_int64_t,
382                                                                      const gmx_int64_t,
383                                                                      const gmx_int64_t,
384                                                                      const char*,
385                                                                      const char,
386                                                                      const char);
387 #ifdef GMX_DOUBLE
388     set_writing_interval_func_pointer set_writing_interval = tng_util_generic_write_interval_double_set;
389 #else
390     set_writing_interval_func_pointer set_writing_interval = tng_util_generic_write_interval_set;
391 #endif
392     int  xout, vout, fout;
393 //     int  gcd = -1, lowest = -1;
394     char compression;
395
396     tng_set_frames_per_frame_set(tng, bUseLossyCompression, ir);
397
398     if (bUseLossyCompression)
399     {
400         xout        = ir->nstxout_compressed;
401         vout        = 0;
402         fout        = 0;
403         compression = TNG_TNG_COMPRESSION;
404     }
405     else
406     {
407         xout        = ir->nstxout;
408         vout        = ir->nstvout;
409         fout        = ir->nstfout;
410         compression = TNG_GZIP_COMPRESSION;
411     }
412     if (xout)
413     {
414         set_writing_interval(tng, xout, 3, TNG_TRAJ_POSITIONS,
415                              "POSITIONS", TNG_PARTICLE_BLOCK_DATA,
416                              compression);
417         /* The design of TNG makes it awkward to try to write a box
418          * with multiple periodicities, which might be co-prime. Since
419          * the use cases for the box with a frame consisting only of
420          * velocities seem low, for now we associate box writing with
421          * position writing. */
422         set_writing_interval(tng, xout, 9, TNG_TRAJ_BOX_SHAPE,
423                              "BOX SHAPE", TNG_NON_PARTICLE_BLOCK_DATA,
424                              TNG_GZIP_COMPRESSION);
425         /* TODO: if/when we write energies to TNG also, reconsider how
426          * and when box information is written, because GROMACS
427          * behaviour pre-5.0 was to write the box with every
428          * trajectory frame and every energy frame, and probably
429          * people depend on this. */
430
431         /* TODO: If we need to write lambda values at steps when
432          * positions (or other data) are not also being written, then
433          * code in mdoutf.c will need to match however that is
434          * organized here. */
435         set_writing_interval(tng, xout, 1, TNG_GMX_LAMBDA,
436                              "LAMBDAS", TNG_NON_PARTICLE_BLOCK_DATA,
437                              TNG_GZIP_COMPRESSION);
438
439         /* FIXME: gcd and lowest currently not used. */
440 //         gcd = greatest_common_divisor_if_positive(gcd, xout);
441 //         if (lowest < 0 || xout < lowest)
442 //         {
443 //             lowest = xout;
444 //         }
445     }
446     if (vout)
447     {
448         set_writing_interval(tng, ir->nstvout, 3, TNG_TRAJ_VELOCITIES,
449                              "VELOCITIES", TNG_PARTICLE_BLOCK_DATA,
450                              compression);
451
452         /* FIXME: gcd and lowest currently not used. */
453 //         gcd = greatest_common_divisor_if_positive(gcd, vout);
454 //         if (lowest < 0 || vout < lowest)
455 //         {
456 //             lowest = vout;
457 //         }
458     }
459     if (fout)
460     {
461         set_writing_interval(tng, ir->nstfout, 3, TNG_TRAJ_FORCES,
462                              "FORCES", TNG_PARTICLE_BLOCK_DATA,
463                              TNG_GZIP_COMPRESSION);
464
465         /* FIXME: gcd and lowest currently not used. */
466 //         gcd = greatest_common_divisor_if_positive(gcd, fout);
467 //         if (lowest < 0 || fout < lowest)
468 //         {
469 //             lowest = fout;
470 //         }
471     }
472     /* FIXME: See above. gcd interval for lambdas is disabled. */
473 //     if (gcd > 0)
474 //     {
475 //         /* Lambdas written at an interval of the lowest common denominator
476 //          * of other output */
477 //         set_writing_interval(tng, gcd, 1, TNG_GMX_LAMBDA,
478 //                                  "LAMBDAS", TNG_NON_PARTICLE_BLOCK_DATA,
479 //                                  TNG_GZIP_COMPRESSION);
480 //
481 //         if (gcd < lowest / 10)
482 //         {
483 //             gmx_warning("The lowest common denominator of trajectory output is "
484 //                         "every %d step(s), whereas the shortest output interval "
485 //                         "is every %d steps.", gcd, lowest);
486 //         }
487 //     }
488 }
489 #endif
490
491 void gmx_tng_prepare_md_writing(tng_trajectory_t  tng,
492                                 const gmx_mtop_t *mtop,
493                                 const t_inputrec *ir)
494 {
495 #ifdef GMX_USE_TNG
496     gmx_tng_add_mtop(tng, mtop);
497     set_writing_intervals(tng, FALSE, ir);
498     tng_time_per_frame_set(tng, ir->delta_t * PICO);
499 #else
500     GMX_UNUSED_VALUE(tng);
501     GMX_UNUSED_VALUE(mtop);
502     GMX_UNUSED_VALUE(ir);
503 #endif
504 }
505
506 #ifdef GMX_USE_TNG
507 /* Check if all atoms in the molecule system are selected
508  * by a selection group of type specified by gtype. */
509 static gmx_bool all_atoms_selected(const gmx_mtop_t *mtop,
510                                    const int         gtype)
511 {
512     const gmx_moltype_t     *molType;
513     const t_atoms           *atoms;
514
515     /* Iterate through all atoms in the molecule system and
516      * check if they belong to a selection group of the
517      * requested type. */
518     for (int molIndex = 0, i = 0; molIndex < mtop->nmoltype; molIndex++)
519     {
520         molType = &mtop->moltype[mtop->molblock[molIndex].type];
521
522         atoms = &molType->atoms;
523
524         for (int j = 0; j < mtop->molblock[molIndex].nmol; j++)
525         {
526             for (int atomIndex = 0; atomIndex < atoms->nr; atomIndex++, i++)
527             {
528                 if (ggrpnr(&mtop->groups, gtype, i) != 0)
529                 {
530                     return FALSE;
531                 }
532             }
533         }
534     }
535     return TRUE;
536 }
537
538 /* Create TNG molecules which will represent each of the selection
539  * groups for writing. But do that only if there is actually a
540  * specified selection group and it is not the whole system.
541  * TODO: Currently the only selection that is taken into account
542  * is egcCompressedX, but other selections should be added when
543  * e.g. writing energies is implemented.
544  */
545 static void add_selection_groups(tng_trajectory_t  tng,
546                                  const gmx_mtop_t *mtop)
547 {
548     const gmx_moltype_t     *molType;
549     const t_atoms           *atoms;
550     const t_atom            *at;
551     const t_resinfo         *resInfo;
552     const t_ilist           *ilist;
553     int                      nameIndex;
554     int                      atom_offset = 0;
555     tng_molecule_t           mol, iterMol;
556     tng_chain_t              chain;
557     tng_residue_t            res;
558     tng_atom_t               atom;
559     tng_bond_t               tngBond;
560     gmx_int64_t              nMols;
561     char                    *groupName;
562
563     /* TODO: When the TNG molecules block is more flexible TNG selection
564      * groups should not need all atoms specified. It should be possible
565      * just to specify what molecules are selected (and/or which atoms in
566      * the molecule) and how many (if applicable). */
567
568     /* If no atoms are selected we do not need to create a
569      * TNG selection group molecule. */
570     if (mtop->groups.ngrpnr[egcCompressedX] == 0)
571     {
572         return;
573     }
574
575     /* If all atoms are selected we do not have to create a selection
576      * group molecule in the TNG molecule system. */
577     if (all_atoms_selected(mtop, egcCompressedX))
578     {
579         return;
580     }
581
582     /* The name of the TNG molecule containing the selection group is the
583      * same as the name of the selection group. */
584     nameIndex = *mtop->groups.grps[egcCompressedX].nm_ind;
585     groupName = *mtop->groups.grpname[nameIndex];
586
587     tng_molecule_alloc(tng, &mol);
588     tng_molecule_name_set(tng, mol, groupName);
589     tng_molecule_chain_add(tng, mol, "", &chain);
590     for (int molIndex = 0, i = 0; molIndex < mtop->nmoltype; molIndex++)
591     {
592         molType = &mtop->moltype[mtop->molblock[molIndex].type];
593
594         atoms = &molType->atoms;
595
596         for (int j = 0; j < mtop->molblock[molIndex].nmol; j++)
597         {
598             bool bAtomsAdded = FALSE;
599             for (int atomIndex = 0; atomIndex < atoms->nr; atomIndex++, i++)
600             {
601                 char *res_name;
602                 int   res_id;
603
604                 if (ggrpnr(&mtop->groups, egcCompressedX, i) != 0)
605                 {
606                     continue;
607                 }
608                 at = &atoms->atom[atomIndex];
609                 if (atoms->nres > 0)
610                 {
611                     resInfo = &atoms->resinfo[at->resind];
612                     /* FIXME: When TNG supports both residue index and residue
613                      * number the latter should be used. */
614                     res_name = *resInfo->name;
615                     res_id   = at->resind + 1;
616                 }
617                 else
618                 {
619                     res_name = (char *)"";
620                     res_id   = 0;
621                 }
622                 if (tng_chain_residue_find(tng, chain, res_name, res_id, &res)
623                     != TNG_SUCCESS)
624                 {
625                     /* Since there is ONE chain for selection groups do not keep the
626                      * original residue IDs - otherwise there might be conflicts. */
627                     tng_chain_residue_add(tng, chain, res_name, &res);
628                 }
629                 tng_residue_atom_w_id_add(tng, res, *(atoms->atomname[atomIndex]),
630                                           *(atoms->atomtype[atomIndex]),
631                                           atom_offset + atomIndex, &atom);
632                 bAtomsAdded = TRUE;
633             }
634             /* Add bonds. */
635             if (bAtomsAdded)
636             {
637                 for (int k = 0; k < F_NRE; k++)
638                 {
639                     if (IS_CHEMBOND(k))
640                     {
641                         ilist = &molType->ilist[k];
642                         if (ilist)
643                         {
644                             int l = 1;
645                             while (l < ilist->nr)
646                             {
647                                 int atom1, atom2;
648                                 atom1 = ilist->iatoms[l] + atom_offset;
649                                 atom2 = ilist->iatoms[l+1] + atom_offset;
650                                 if (ggrpnr(&mtop->groups, egcCompressedX, atom1) == 0 &&
651                                     ggrpnr(&mtop->groups, egcCompressedX, atom2) == 0)
652                                 {
653                                     tng_molecule_bond_add(tng, mol, ilist->iatoms[l],
654                                                           ilist->iatoms[l+1], &tngBond);
655                                 }
656                                 l += 3;
657                             }
658                         }
659                     }
660                 }
661                 /* Settle is described using three atoms */
662                 ilist = &molType->ilist[F_SETTLE];
663                 if (ilist)
664                 {
665                     int l = 1;
666                     while (l < ilist->nr)
667                     {
668                         int atom1, atom2, atom3;
669                         atom1 = ilist->iatoms[l] + atom_offset;
670                         atom2 = ilist->iatoms[l+1] + atom_offset;
671                         atom3 = ilist->iatoms[l+2] + atom_offset;
672                         if (ggrpnr(&mtop->groups, egcCompressedX, atom1) == 0)
673                         {
674                             if (ggrpnr(&mtop->groups, egcCompressedX, atom2) == 0)
675                             {
676                                 tng_molecule_bond_add(tng, mol, atom1,
677                                                       atom2, &tngBond);
678                             }
679                             if (ggrpnr(&mtop->groups, egcCompressedX, atom3) == 0)
680                             {
681                                 tng_molecule_bond_add(tng, mol, atom1,
682                                                       atom3, &tngBond);
683                             }
684                         }
685                         l += 4;
686                     }
687                 }
688             }
689             atom_offset += atoms->nr;
690         }
691     }
692     tng_molecule_existing_add(tng, &mol);
693     tng_molecule_cnt_set(tng, mol, 1);
694     tng_num_molecule_types_get(tng, &nMols);
695     for (gmx_int64_t k = 0; k < nMols; k++)
696     {
697         tng_molecule_of_index_get(tng, k, &iterMol);
698         if (iterMol == mol)
699         {
700             continue;
701         }
702         tng_molecule_cnt_set(tng, iterMol, 0);
703     }
704 }
705 #endif
706
707 void gmx_tng_set_compression_precision(tng_trajectory_t tng,
708                                        real             prec)
709 {
710 #ifdef GMX_USE_TNG
711     tng_compression_precision_set(tng, prec);
712 #else
713     GMX_UNUSED_VALUE(tng);
714     GMX_UNUSED_VALUE(prec);
715 #endif
716 }
717
718 void gmx_tng_prepare_low_prec_writing(tng_trajectory_t  tng,
719                                       const gmx_mtop_t *mtop,
720                                       const t_inputrec *ir)
721 {
722 #ifdef GMX_USE_TNG
723     gmx_tng_add_mtop(tng, mtop);
724     add_selection_groups(tng, mtop);
725     set_writing_intervals(tng, TRUE, ir);
726     tng_time_per_frame_set(tng, ir->delta_t * PICO);
727     gmx_tng_set_compression_precision(tng, ir->x_compression_precision);
728 #else
729     GMX_UNUSED_VALUE(tng);
730     GMX_UNUSED_VALUE(mtop);
731     GMX_UNUSED_VALUE(ir);
732 #endif
733 }
734
735 void gmx_fwrite_tng(tng_trajectory_t tng,
736                     const gmx_bool   bUseLossyCompression,
737                     int              step,
738                     real             elapsedPicoSeconds,
739                     real             lambda,
740                     const rvec      *box,
741                     int              nAtoms,
742                     const rvec      *x,
743                     const rvec      *v,
744                     const rvec      *f)
745 {
746 #ifdef GMX_USE_TNG
747     typedef tng_function_status (*write_data_func_pointer)(tng_trajectory_t,
748                                                            const gmx_int64_t,
749                                                            const double,
750                                                            const real*,
751                                                            const gmx_int64_t,
752                                                            const gmx_int64_t,
753                                                            const char*,
754                                                            const char,
755                                                            const char);
756 #ifdef GMX_DOUBLE
757     static write_data_func_pointer           write_data           = tng_util_generic_with_time_double_write;
758 #else
759     static write_data_func_pointer           write_data           = tng_util_generic_with_time_write;
760 #endif
761     double                                   elapsedSeconds = elapsedPicoSeconds * PICO;
762     gmx_int64_t                              nParticles;
763     char                                     compression;
764
765
766     if (!tng)
767     {
768         /* This function might get called when the type of the
769            compressed trajectory is actually XTC. So we exit and move
770            on. */
771         return;
772     }
773
774     tng_num_particles_get(tng, &nParticles);
775     if (nAtoms != (int)nParticles)
776     {
777         tng_implicit_num_particles_set(tng, nAtoms);
778     }
779
780     if (bUseLossyCompression)
781     {
782         compression = TNG_TNG_COMPRESSION;
783     }
784     else
785     {
786         compression = TNG_GZIP_COMPRESSION;
787     }
788
789     /* The writing is done using write_data, which writes float or double
790      * depending on the GROMACS compilation. */
791     if (x)
792     {
793         GMX_ASSERT(box, "Need a non-NULL box if positions are written");
794
795         if (write_data(tng, step, elapsedSeconds,
796                        reinterpret_cast<const real *>(x),
797                        3, TNG_TRAJ_POSITIONS, "POSITIONS",
798                        TNG_PARTICLE_BLOCK_DATA,
799                        compression) != TNG_SUCCESS)
800         {
801             gmx_file("Cannot write TNG trajectory frame; maybe you are out of disk space?");
802         }
803         /* TNG-MF1 compression only compresses positions and velocities. Use lossless
804          * compression for box shape regardless of output mode */
805         if (write_data(tng, step, elapsedSeconds,
806                        reinterpret_cast<const real *>(box),
807                        9, TNG_TRAJ_BOX_SHAPE, "BOX SHAPE",
808                        TNG_NON_PARTICLE_BLOCK_DATA,
809                        TNG_GZIP_COMPRESSION) != TNG_SUCCESS)
810         {
811             gmx_file("Cannot write TNG trajectory frame; maybe you are out of disk space?");
812         }
813     }
814
815     if (v)
816     {
817         if (write_data(tng, step, elapsedSeconds,
818                        reinterpret_cast<const real *>(v),
819                        3, TNG_TRAJ_VELOCITIES, "VELOCITIES",
820                        TNG_PARTICLE_BLOCK_DATA,
821                        compression) != TNG_SUCCESS)
822         {
823             gmx_file("Cannot write TNG trajectory frame; maybe you are out of disk space?");
824         }
825     }
826
827     if (f)
828     {
829         /* TNG-MF1 compression only compresses positions and velocities. Use lossless
830          * compression for forces regardless of output mode */
831         if (write_data(tng, step, elapsedSeconds,
832                        reinterpret_cast<const real *>(f),
833                        3, TNG_TRAJ_FORCES, "FORCES",
834                        TNG_PARTICLE_BLOCK_DATA,
835                        TNG_GZIP_COMPRESSION) != TNG_SUCCESS)
836         {
837             gmx_file("Cannot write TNG trajectory frame; maybe you are out of disk space?");
838         }
839     }
840
841     /* TNG-MF1 compression only compresses positions and velocities. Use lossless
842      * compression for lambdas regardless of output mode */
843     if (write_data(tng, step, elapsedSeconds,
844                    reinterpret_cast<const real *>(&lambda),
845                    1, TNG_GMX_LAMBDA, "LAMBDAS",
846                    TNG_NON_PARTICLE_BLOCK_DATA,
847                    TNG_GZIP_COMPRESSION) != TNG_SUCCESS)
848     {
849         gmx_file("Cannot write TNG trajectory frame; maybe you are out of disk space?");
850     }
851 #else
852     GMX_UNUSED_VALUE(tng);
853     GMX_UNUSED_VALUE(bUseLossyCompression);
854     GMX_UNUSED_VALUE(step);
855     GMX_UNUSED_VALUE(elapsedPicoSeconds);
856     GMX_UNUSED_VALUE(lambda);
857     GMX_UNUSED_VALUE(box);
858     GMX_UNUSED_VALUE(nAtoms);
859     GMX_UNUSED_VALUE(x);
860     GMX_UNUSED_VALUE(v);
861     GMX_UNUSED_VALUE(f);
862 #endif
863 }
864
865 void fflush_tng(tng_trajectory_t tng)
866 {
867 #ifdef GMX_USE_TNG
868     if (!tng)
869     {
870         return;
871     }
872     tng_frame_set_premature_write(tng, TNG_USE_HASH);
873 #else
874     GMX_UNUSED_VALUE(tng);
875 #endif
876 }
877
878 float gmx_tng_get_time_of_final_frame(tng_trajectory_t tng)
879 {
880 #ifdef GMX_USE_TNG
881     gmx_int64_t nFrames;
882     double      time;
883     float       fTime;
884
885     tng_num_frames_get(tng, &nFrames);
886     tng_util_time_of_frame_get(tng, nFrames - 1, &time);
887
888     fTime = time / PICO;
889     return fTime;
890 #else
891     GMX_UNUSED_VALUE(tng);
892     return -1.0;
893 #endif
894 }