Python detection consolidation.
[alexxy/gromacs.git] / src / gromacs / tools / trjcat.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5  * Copyright (c) 2001-2004, The GROMACS development team.
6  * Copyright (c) 2013,2014,2015,2016,2017,2018,2019, by the GROMACS development team, led by
7  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
8  * and including many others, as listed in the AUTHORS file in the
9  * top-level source directory and at http://www.gromacs.org.
10  *
11  * GROMACS is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public License
13  * as published by the Free Software Foundation; either version 2.1
14  * of the License, or (at your option) any later version.
15  *
16  * GROMACS is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with GROMACS; if not, see
23  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
24  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
25  *
26  * If you want to redistribute modifications to GROMACS, please
27  * consider that scientific software is very special. Version
28  * control is crucial - bugs must be traceable. We will be happy to
29  * consider code for inclusion in the official distribution, but
30  * derived work must not be called official GROMACS. Details are found
31  * in the README & COPYING files - if they are missing, get the
32  * official version at http://www.gromacs.org.
33  *
34  * To help us fund GROMACS development, we humbly ask that you cite
35  * the research papers on the package. Check out http://www.gromacs.org.
36  */
37 #include "gmxpre.h"
38
39 #include "trjcat.h"
40
41 #include <cmath>
42 #include <cstdlib>
43 #include <cstring>
44
45 #include <algorithm>
46 #include <string>
47
48 #include "gromacs/commandline/pargs.h"
49 #include "gromacs/fileio/confio.h"
50 #include "gromacs/fileio/gmxfio.h"
51 #include "gromacs/fileio/pdbio.h"
52 #include "gromacs/fileio/tngio.h"
53 #include "gromacs/fileio/trxio.h"
54 #include "gromacs/fileio/xtcio.h"
55 #include "gromacs/fileio/xvgr.h"
56 #include "gromacs/math/vec.h"
57 #include "gromacs/mdtypes/md_enums.h"
58 #include "gromacs/topology/index.h"
59 #include "gromacs/trajectory/trajectoryframe.h"
60 #include "gromacs/utility/arrayref.h"
61 #include "gromacs/utility/arraysize.h"
62 #include "gromacs/utility/fatalerror.h"
63 #include "gromacs/utility/futil.h"
64 #include "gromacs/utility/smalloc.h"
65 #include "gromacs/utility/stringutil.h"
66
67 #define TIME_EXPLICIT 0
68 #define TIME_CONTINUE 1
69 #define TIME_LAST     2
70 #ifndef FLT_MAX
71 #define FLT_MAX 1e36
72 #endif
73 #define FLAGS (TRX_READ_X | TRX_READ_V | TRX_READ_F)
74
75 static void scan_trj_files(gmx::ArrayRef<const std::string> files,
76                            real *readtime,
77                            real *timestep, int imax,
78                            const gmx_output_env_t *oenv)
79 {
80     /* Check start time of all files */
81     int          natoms = 0;
82     t_trxstatus *status;
83     t_trxframe   fr;
84     bool         ok;
85
86     for (gmx::index i = 0; i < files.ssize(); i++)
87     {
88         ok = read_first_frame(oenv, &status, files[i].c_str(), &fr, FLAGS);
89
90         if (!ok)
91         {
92             gmx_fatal(FARGS, "\nCouldn't read frame from file." );
93         }
94         if (fr.bTime)
95         {
96             readtime[i] = fr.time;
97         }
98         else
99         {
100             readtime[i] = 0;
101             fprintf(stderr, "\nWARNING: Couldn't find a time in the frame.\n");
102         }
103
104         if (i == 0)
105         {
106             natoms = fr.natoms;
107         }
108         else
109         {
110             if (imax == -1)
111             {
112                 if (natoms != fr.natoms)
113                 {
114                     gmx_fatal(FARGS, "\nDifferent numbers of atoms (%d/%d) in files",
115                               natoms, fr.natoms);
116                 }
117             }
118             else
119             {
120                 if (fr.natoms <= imax)
121                 {
122                     gmx_fatal(FARGS, "\nNot enough atoms (%d) for index group (%d)",
123                               fr.natoms, imax);
124                 }
125             }
126         }
127         ok = read_next_frame(oenv, status, &fr);
128         if (ok && fr.bTime)
129         {
130             timestep[i] = fr.time - readtime[i];
131         }
132         else
133         {
134             timestep[i] = 0;
135         }
136
137         close_trx(status);
138         if (fr.bX)
139         {
140             sfree(fr.x);
141         }
142         if (fr.bV)
143         {
144             sfree(fr.v);
145         }
146         if (fr.bF)
147         {
148             sfree(fr.f);
149         }
150     }
151     fprintf(stderr, "\n");
152
153 }
154
155 static void sort_files(gmx::ArrayRef<std::string> files, real *settime)
156 {
157     for (gmx::index i = 0; i < files.ssize(); i++)
158     {
159         gmx::index minidx = i;
160         for (gmx::index j = i + 1; j < files.ssize(); j++)
161         {
162             if (settime[j] < settime[minidx])
163             {
164                 minidx = j;
165             }
166         }
167         if (minidx != i)
168         {
169             real timeswap   = settime[i];
170             settime[i]      = settime[minidx];
171             settime[minidx] = timeswap;
172             std::swap(files[i], files[minidx]);
173         }
174     }
175 }
176
177 static void edit_files(gmx::ArrayRef<std::string> files,
178                        real *readtime, real *timestep,
179                        real *settime, int *cont_type, gmx_bool bSetTime,
180                        gmx_bool bSort, const gmx_output_env_t *oenv)
181 {
182     gmx_bool ok;
183     char     inputstring[STRLEN], *chptr;
184
185     auto     timeUnit = output_env_get_time_unit(oenv);
186     if (bSetTime)
187     {
188         fprintf(stderr, "\n\nEnter the new start time (%s) for each file.\n"
189                 "There are two special options, both disable sorting:\n\n"
190                 "c (continue) - The start time is taken from the end\n"
191                 "of the previous file. Use it when your continuation run\n"
192                 "restarts with t=0.\n\n"
193                 "l (last) - The time in this file will be changed the\n"
194                 "same amount as in the previous. Use it when the time in the\n"
195                 "new run continues from the end of the previous one,\n"
196                 "since this takes possible overlap into account.\n\n",
197                 timeUnit.c_str());
198
199         fprintf(
200                 stderr,
201                 "          File             Current start (%s)  New start (%s)\n"
202                 "---------------------------------------------------------\n",
203                 timeUnit.c_str(), timeUnit.c_str());
204
205         for (gmx::index i = 0; i < files.ssize(); i++)
206         {
207             fprintf(stderr, "%25s   %10.3f %s          ", files[i].c_str(),
208                     output_env_conv_time(oenv, readtime[i]), timeUnit.c_str());
209             ok = FALSE;
210             do
211             {
212                 if (nullptr == fgets(inputstring, STRLEN - 1, stdin))
213                 {
214                     gmx_fatal(FARGS, "Error reading user input" );
215                 }
216
217                 inputstring[std::strlen(inputstring)-1] = 0;
218
219                 if (inputstring[0] == 'c' || inputstring[0] == 'C')
220                 {
221                     cont_type[i] = TIME_CONTINUE;
222                     bSort        = FALSE;
223                     ok           = TRUE;
224                     settime[i]   = FLT_MAX;
225                 }
226                 else if (inputstring[0] == 'l' ||
227                          inputstring[0] == 'L')
228                 {
229                     cont_type[i] = TIME_LAST;
230                     bSort        = FALSE;
231                     ok           = TRUE;
232                     settime[i]   = FLT_MAX;
233                 }
234                 else
235                 {
236                     settime[i] = strtod(inputstring, &chptr)*
237                         output_env_get_time_invfactor(oenv);
238                     if (chptr == inputstring)
239                     {
240                         fprintf(stderr, "'%s' not recognized as a floating point number, 'c' or 'l'. "
241                                 "Try again: ", inputstring);
242                     }
243                     else
244                     {
245                         cont_type[i] = TIME_EXPLICIT;
246                         ok           = TRUE;
247                     }
248                 }
249             }
250             while (!ok);
251         }
252         if (cont_type[0] != TIME_EXPLICIT)
253         {
254             cont_type[0] = TIME_EXPLICIT;
255             settime[0]   = 0;
256         }
257     }
258     else
259     {
260         for (gmx::index i = 0; i < files.ssize(); i++)
261         {
262             settime[i] = readtime[i];
263         }
264     }
265     if (!bSort)
266     {
267         fprintf(stderr, "Sorting disabled.\n");
268     }
269     else
270     {
271         sort_files(files, settime);
272     }
273     /* Write out the new order and start times */
274     fprintf(stderr, "\nSummary of files and start times used:\n\n"
275             "          File                Start time       Time step\n"
276             "---------------------------------------------------------\n");
277     for (gmx::index i = 0; i < files.ssize(); i++)
278     {
279         switch (cont_type[i])
280         {
281             case TIME_EXPLICIT:
282                 fprintf(stderr, "%25s   %10.3f %s   %10.3f %s",
283                         files[i].c_str(),
284                         output_env_conv_time(oenv, settime[i]), timeUnit.c_str(),
285                         output_env_conv_time(oenv, timestep[i]), timeUnit.c_str());
286                 if (i > 0 &&
287                     cont_type[i-1] == TIME_EXPLICIT && settime[i] == settime[i-1])
288                 {
289                     fprintf(stderr, " WARNING: same Start time as previous");
290                 }
291                 fprintf(stderr, "\n");
292                 break;
293             case TIME_CONTINUE:
294                 fprintf(stderr, "%25s        Continue from last file\n", files[i].c_str());
295                 break;
296             case TIME_LAST:
297                 fprintf(stderr, "%25s        Change by same amount as last file\n",
298                         files[i].c_str());
299                 break;
300         }
301     }
302     fprintf(stderr, "\n");
303
304     settime[files.size()]   = FLT_MAX;
305     cont_type[files.size()] = TIME_EXPLICIT;
306     readtime[files.size()]  = FLT_MAX;
307 }
308
309 static void do_demux(gmx::ArrayRef<const std::string> inFiles,
310                      gmx::ArrayRef<const std::string> outFiles, int nval,
311                      real **value, real *time, real dt_remd, int isize,
312                      int index[], real dt, const gmx_output_env_t *oenv)
313 {
314     int           k, natoms;
315     t_trxstatus **fp_in, **fp_out;
316     gmx_bool      bCont, *bSet;
317     real          t, first_time = 0;
318     t_trxframe   *trx;
319
320     snew(fp_in, inFiles.size());
321     snew(trx, inFiles.size());
322     snew(bSet, inFiles.size());
323     natoms = -1;
324     t      = -1;
325     for (gmx::index i = 0; i < inFiles.ssize(); i++)
326     {
327         read_first_frame(oenv, &(fp_in[i]), inFiles[i].c_str(), &(trx[i]),
328                          TRX_NEED_X);
329         if (natoms == -1)
330         {
331             natoms     = trx[i].natoms;
332             first_time = trx[i].time;
333         }
334         else if (natoms != trx[i].natoms)
335         {
336             gmx_fatal(FARGS, "Trajectory file %s has %d atoms while previous trajs had %d atoms", inFiles[i].c_str(), trx[i].natoms, natoms);
337         }
338         if (t == -1)
339         {
340             t = trx[i].time;
341         }
342         else if (t != trx[i].time)
343         {
344             gmx_fatal(FARGS, "Trajectory file %s has time %f while previous trajs had time %f", inFiles[i].c_str(), trx[i].time, t);
345         }
346     }
347
348     snew(fp_out, inFiles.size());
349     for (gmx::index i = 0; i < inFiles.ssize(); i++)
350     {
351         fp_out[i] = open_trx(outFiles[i].c_str(), "w");
352     }
353     k = 0;
354     if (std::round(time[k] - t) != 0)
355     {
356         gmx_fatal(FARGS, "First time in demuxing table does not match trajectories");
357     }
358     do
359     {
360         while ((k+1 < nval) && ((trx[0].time - time[k+1]) > dt_remd*0.1))
361         {
362             k++;
363         }
364         if (debug)
365         {
366             fprintf(debug, "trx[0].time = %g, time[k] = %g\n", trx[0].time, time[k]);
367         }
368         for (gmx::index i = 0; i < inFiles.ssize(); i++)
369         {
370             bSet[i] = FALSE;
371         }
372         for (gmx::index i = 0; i < inFiles.ssize(); i++)
373         {
374             int j = gmx::roundToInt(value[i][k]);
375             range_check(j, 0, inFiles.size());
376             if (bSet[j])
377             {
378                 gmx_fatal(FARGS, "Demuxing the same replica %d twice at time %f",
379                           j, trx[0].time);
380             }
381             bSet[j] = TRUE;
382
383             if (dt == 0 || bRmod(trx[i].time, first_time, dt))
384             {
385                 if (index)
386                 {
387                     write_trxframe_indexed(fp_out[j], &trx[i], isize, index, nullptr);
388                 }
389                 else
390                 {
391                     write_trxframe(fp_out[j], &trx[i], nullptr);
392                 }
393             }
394         }
395
396         bCont = (k < nval);
397         for (gmx::index i = 0; i < inFiles.ssize(); i++)
398         {
399             bCont = bCont && read_next_frame(oenv, fp_in[i], &trx[i]);
400         }
401     }
402     while (bCont);
403
404     for (gmx::index i = 0; i < inFiles.ssize(); i++)
405     {
406         close_trx(fp_in[i]);
407         close_trx(fp_out[i]);
408     }
409 }
410
411 int gmx_trjcat(int argc, char *argv[])
412 {
413     const char     *desc[] =
414     {
415         "[THISMODULE] concatenates several input trajectory files in sorted order. ",
416         "In case of double time frames the one in the later file is used. ",
417         "By specifying [TT]-settime[tt] you will be asked for the start time ",
418         "of each file. The input files are taken from the command line, ",
419         "such that a command like [TT]gmx trjcat -f *.trr -o fixed.trr[tt] should do ",
420         "the trick. Using [TT]-cat[tt], you can simply paste several files ",
421         "together without removal of frames with identical time stamps.[PAR]",
422         "One important option is inferred when the output file is amongst the",
423         "input files. In that case that particular file will be appended to",
424         "which implies you do not need to store double the amount of data.",
425         "Obviously the file to append to has to be the one with lowest starting",
426         "time since one can only append at the end of a file.[PAR]",
427         "If the [TT]-demux[tt] option is given, the N trajectories that are",
428         "read, are written in another order as specified in the [REF].xvg[ref] file.",
429         "The [REF].xvg[ref] file should contain something like::",
430         "",
431         "    0  0  1  2  3  4  5",
432         "    2  1  0  2  3  5  4",
433         "",
434         "The first number is the time, and subsequent numbers point to",
435         "trajectory indices.",
436         "The frames corresponding to the numbers present at the first line",
437         "are collected into the output trajectory. If the number of frames in",
438         "the trajectory does not match that in the [REF].xvg[ref] file then the program",
439         "tries to be smart. Beware."
440     };
441     static gmx_bool bCat            = FALSE;
442     static gmx_bool bSort           = TRUE;
443     static gmx_bool bKeepLast       = FALSE;
444     static gmx_bool bKeepLastAppend = FALSE;
445     static gmx_bool bOverwrite      = FALSE;
446     static gmx_bool bSetTime        = FALSE;
447     static gmx_bool bDeMux;
448     static real     begin = -1;
449     static real     end   = -1;
450     static real     dt    = 0;
451
452     t_pargs
453         pa[] =
454     {
455         { "-b", FALSE, etTIME,
456           { &begin }, "First time to use (%t)" },
457         { "-e", FALSE, etTIME,
458           { &end }, "Last time to use (%t)" },
459         { "-dt", FALSE, etTIME,
460           { &dt }, "Only write frame when t MOD dt = first time (%t)" },
461         { "-settime", FALSE, etBOOL,
462           { &bSetTime }, "Change starting time interactively" },
463         { "-sort", FALSE, etBOOL,
464           { &bSort }, "Sort trajectory files (not frames)" },
465         { "-keeplast", FALSE, etBOOL,
466           { &bKeepLast }, "Keep overlapping frames at end of trajectory" },
467         { "-overwrite", FALSE, etBOOL,
468           { &bOverwrite }, "Overwrite overlapping frames during appending" },
469         { "-cat", FALSE, etBOOL,
470           { &bCat }, "Do not discard double time frames" }
471     };
472 #define npargs asize(pa)
473     int               ftpin, i, frame, frame_out;
474     t_trxstatus      *status, *trxout = nullptr;
475     real              t_corr;
476     t_trxframe        fr, frout;
477     int               n_append;
478     gmx_bool          bNewFile, bIndex, bWrite;
479     int              *cont_type;
480     real             *readtime, *timest, *settime;
481     real              first_time  = 0, lasttime = 0, last_ok_t = -1, timestep;
482     gmx_bool          lastTimeSet = FALSE;
483     real              last_frame_time, searchtime;
484     int               isize = 0, j;
485     int              *index = nullptr, imax;
486     char             *grpname;
487     real            **val = nullptr, *t = nullptr, dt_remd;
488     int               n, nset, ftpout = -1, prevEndStep = 0, filetype;
489     gmx_off_t         fpos;
490     gmx_output_env_t *oenv;
491     t_filenm          fnm[] =
492     {
493         { efTRX, "-f", nullptr, ffRDMULT },
494         { efTRO, "-o", nullptr, ffWRMULT },
495         { efNDX, "-n", "index", ffOPTRD },
496         { efXVG, "-demux", "remd", ffOPTRD }
497     };
498
499 #define NFILE asize(fnm)
500
501     if (!parse_common_args(&argc, argv, PCA_TIME_UNIT, NFILE, fnm,
502                            asize(pa), pa, asize(desc), desc, 0, nullptr, &oenv))
503     {
504         return 0;
505     }
506     fprintf(stdout, "Note that major changes are planned in future for "
507             "trjcat, to improve usability and utility.");
508
509     auto timeUnit = output_env_get_time_unit(oenv);
510
511     bIndex = ftp2bSet(efNDX, NFILE, fnm);
512     bDeMux = ftp2bSet(efXVG, NFILE, fnm);
513     bSort  = bSort && !bDeMux;
514
515     imax = -1;
516     if (bIndex)
517     {
518         printf("Select group for output\n");
519         rd_index(ftp2fn(efNDX, NFILE, fnm), 1, &isize, &index, &grpname);
520         /* scan index */
521         imax = index[0];
522         for (i = 1; i < isize; i++)
523         {
524             imax = std::max(imax, index[i]);
525         }
526     }
527     if (bDeMux)
528     {
529         nset    = 0;
530         dt_remd = 0;
531         val     = read_xvg_time(opt2fn("-demux", NFILE, fnm), TRUE,
532                                 opt2parg_bSet("-b", npargs, pa), begin,
533                                 opt2parg_bSet("-e", npargs, pa), end, 1, &nset, &n,
534                                 &dt_remd, &t);
535         printf("Read %d sets of %d points, dt = %g\n\n", nset, n, dt_remd);
536         if (debug)
537         {
538             fprintf(debug, "Dump of replica_index.xvg\n");
539             for (i = 0; (i < n); i++)
540             {
541                 fprintf(debug, "%10g", t[i]);
542                 for (j = 0; (j < nset); j++)
543                 {
544                     fprintf(debug, "  %3d", static_cast<int>(std::round(val[j][i])));
545                 }
546                 fprintf(debug, "\n");
547             }
548         }
549     }
550
551     gmx::ArrayRef<const std::string> inFiles = opt2fns("-f", NFILE, fnm);
552     if (inFiles.empty())
553     {
554         gmx_fatal(FARGS, "No input files!" );
555     }
556
557     if (bDeMux && ssize(inFiles) != nset)
558     {
559         gmx_fatal(FARGS, "You have specified %td files and %d entries in the demux table", inFiles.ssize(), nset);
560     }
561
562     ftpin = fn2ftp(inFiles[0].c_str());
563
564     if (ftpin != efTRR && ftpin != efXTC && ftpin != efTNG)
565     {
566         gmx_fatal(FARGS, "gmx trjcat can only handle binary trajectory formats (trr, xtc, tng)");
567     }
568
569     for (const std::string &inFile : inFiles)
570     {
571         if (ftpin != fn2ftp(inFile.c_str()))
572         {
573             gmx_fatal(FARGS, "All input files must be of the same (trr, xtc or tng) format");
574         }
575     }
576
577     gmx::ArrayRef<const std::string> outFiles = opt2fns("-o", NFILE, fnm);
578     if (outFiles.empty())
579     {
580         gmx_fatal(FARGS, "No output files!");
581     }
582     if ((outFiles.size() > 1) && !bDeMux)
583     {
584         gmx_fatal(FARGS, "Don't know what to do with more than 1 output file if  not demultiplexing");
585     }
586     else if (bDeMux && ssize(outFiles) != nset && outFiles.size() != 1)
587     {
588         gmx_fatal(FARGS, "Number of output files should be 1 or %d (#input files), not %td", nset, outFiles.ssize());
589     }
590     if (bDeMux)
591     {
592         auto outFilesDemux = gmx::copyOf(outFiles);
593         if (gmx::ssize(outFilesDemux) != nset)
594         {
595             std::string name = outFilesDemux[0];
596             outFilesDemux.resize(nset);
597             for (i = 0; (i < nset); i++)
598             {
599                 outFilesDemux[0] = gmx::formatString("%d_%s", i, name.c_str());
600             }
601         }
602         do_demux(inFiles, outFilesDemux, n, val, t, dt_remd, isize, index, dt, oenv);
603     }
604     else
605     {
606         snew(readtime, inFiles.size() + 1);
607         snew(timest, inFiles.size() + 1);
608         scan_trj_files(inFiles, readtime, timest, imax, oenv);
609
610         snew(settime, inFiles.size() + 1);
611         snew(cont_type, inFiles.size() + 1);
612         auto inFilesEdited = gmx::copyOf(inFiles);
613         edit_files(inFilesEdited, readtime, timest, settime, cont_type, bSetTime, bSort,
614                    oenv);
615
616         /* Check whether the output file is amongst the input files
617          * This has to be done after sorting etc.
618          */
619         const char *out_file = outFiles[0].c_str();
620         ftpout   = fn2ftp(out_file);
621         n_append = -1;
622         for (size_t i = 0; i < inFilesEdited.size() && n_append == -1; i++)
623         {
624             if (std::strcmp(inFilesEdited[i].c_str(), out_file) == 0)
625             {
626                 n_append = i;
627             }
628         }
629         if (n_append == 0)
630         {
631             fprintf(stderr, "Will append to %s rather than creating a new file\n",
632                     out_file);
633         }
634         else if (n_append != -1)
635         {
636             gmx_fatal(FARGS, "Can only append to the first file which is %s (not %s)",
637                       inFilesEdited[0].c_str(), out_file);
638         }
639
640         /* Not checking input format, could be dangerous :-) */
641         /* Not checking output format, equally dangerous :-) */
642
643         frame     = -1;
644         frame_out = -1;
645         /* the default is not to change the time at all,
646          * but this is overridden by the edit_files routine
647          */
648         t_corr = 0;
649
650         if (n_append == -1)
651         {
652             if (ftpout == efTNG)
653             {
654                 if (ftpout != ftpin)
655                 {
656                     gmx_fatal(FARGS, "When writing TNG the input file format must also be TNG");
657                 }
658                 if (bIndex)
659                 {
660                     trxout = trjtools_gmx_prepare_tng_writing(out_file, 'w', nullptr,
661                                                               inFilesEdited[0].c_str(), isize, nullptr, gmx::arrayRefFromArray(index, isize), grpname);
662                 }
663                 else
664                 {
665                     trxout = trjtools_gmx_prepare_tng_writing(out_file, 'w', nullptr,
666                                                               inFilesEdited[0].c_str(), -1, nullptr, {}, nullptr);
667                 }
668             }
669             else
670             {
671                 trxout = open_trx(out_file, "w");
672             }
673             std::memset(&frout, 0, sizeof(frout));
674         }
675         else
676         {
677             t_fileio *stfio;
678
679             if (!read_first_frame(oenv, &status, out_file, &fr, FLAGS))
680             {
681                 gmx_fatal(FARGS, "Reading first frame from %s", out_file);
682             }
683
684             stfio = trx_get_fileio(status);
685             if (!bKeepLast && !bOverwrite)
686             {
687                 fprintf(stderr, "\n\nWARNING: Appending without -overwrite implies -keeplast "
688                         "between the first two files. \n"
689                         "If the trajectories have an overlap and have not been written binary \n"
690                         "reproducible this will produce an incorrect trajectory!\n\n");
691
692                 filetype = gmx_fio_getftp(stfio);
693                 /* Fails if last frame is incomplete
694                  * We can't do anything about it without overwriting
695                  * */
696                 if (filetype == efXTC || filetype == efTNG)
697                 {
698                     lasttime = trx_get_time_of_final_frame(status);
699                     fr.time  = lasttime;
700                 }
701                 else
702                 {
703                     while (read_next_frame(oenv, status, &fr))
704                     {
705                         ;
706                     }
707                     lasttime = fr.time;
708                 }
709                 lastTimeSet     = TRUE;
710                 bKeepLastAppend = TRUE;
711                 close_trx(status);
712                 trxout = open_trx(out_file, "a");
713             }
714             else if (bOverwrite)
715             {
716                 if (gmx_fio_getftp(stfio) != efXTC)
717                 {
718                     gmx_fatal(FARGS, "Overwrite only supported for XTC." );
719                 }
720                 last_frame_time = trx_get_time_of_final_frame(status);
721
722                 /* xtc_seek_time broken for trajectories containing only 1 or 2 frames
723                  *     or when seek time = 0 */
724                 if (inFilesEdited.size() > 1 && settime[1] < last_frame_time+timest[0]*0.5)
725                 {
726                     /* Jump to one time-frame before the start of next
727                      *  trajectory file */
728                     searchtime = settime[1]-timest[0]*1.25;
729                 }
730                 else
731                 {
732                     searchtime = last_frame_time;
733                 }
734                 if (xtc_seek_time(stfio, searchtime, fr.natoms, TRUE))
735                 {
736                     gmx_fatal(FARGS, "Error seeking to append position.");
737                 }
738                 read_next_frame(oenv, status, &fr);
739                 if (std::abs(searchtime - fr.time) > timest[0]*0.5)
740                 {
741                     gmx_fatal(FARGS, "Error seeking: attempted to seek to %f but got %f.",
742                               searchtime, fr.time);
743                 }
744                 lasttime    = fr.time;
745                 lastTimeSet = TRUE;
746                 fpos        = gmx_fio_ftell(stfio);
747                 close_trx(status);
748                 trxout = open_trx(out_file, "r+");
749                 if (gmx_fio_seek(trx_get_fileio(trxout), fpos))
750                 {
751                     gmx_fatal(FARGS, "Error seeking to append position.");
752                 }
753             }
754             if (lastTimeSet)
755             {
756                 printf("\n Will append after %f \n", lasttime);
757             }
758             frout = fr;
759         }
760         /* Lets stitch up some files */
761         timestep = timest[0];
762         for (size_t i = n_append + 1; i < inFilesEdited.size(); i++)
763         {
764             /* Open next file */
765
766             /* set the next time from the last frame in previous file */
767             if (i > 0)
768             {
769                 /* When writing TNG the step determine which frame to write. Use an
770                  * offset to be able to increase steps properly when changing files. */
771                 if (ftpout == efTNG)
772                 {
773                     prevEndStep = frout.step;
774                 }
775
776                 if (frame_out >= 0)
777                 {
778                     if (cont_type[i] == TIME_CONTINUE)
779                     {
780                         begin        = frout.time;
781                         begin       += 0.5*timestep;
782                         settime[i]   = frout.time;
783                         cont_type[i] = TIME_EXPLICIT;
784                     }
785                     else if (cont_type[i] == TIME_LAST)
786                     {
787                         begin  = frout.time;
788                         begin += 0.5*timestep;
789                     }
790                     /* Or, if the time in the next part should be changed by the
791                      * same amount, start at half a timestep from the last time
792                      * so we dont repeat frames.
793                      */
794                     /* I don't understand the comment above, but for all the cases
795                      * I tried the code seems to work properly. B. Hess 2008-4-2.
796                      */
797                 }
798                 /* Or, if time is set explicitly, we check for overlap/gap */
799                 if (cont_type[i] == TIME_EXPLICIT)
800                 {
801                     if (i < inFilesEdited.size() &&
802                         frout.time < settime[i] - 1.5*timestep)
803                     {
804                         fprintf(stderr, "WARNING: Frames around t=%f %s have a different "
805                                 "spacing than the rest,\n"
806                                 "might be a gap or overlap that couldn't be corrected "
807                                 "automatically.\n", output_env_conv_time(oenv, frout.time),
808                                 timeUnit.c_str());
809                     }
810                 }
811             }
812
813             /* if we don't have a timestep in the current file, use the old one */
814             if (timest[i] != 0)
815             {
816                 timestep = timest[i];
817             }
818             read_first_frame(oenv, &status, inFilesEdited[i].c_str(), &fr, FLAGS);
819             if (!fr.bTime)
820             {
821                 fr.time = 0;
822                 fprintf(stderr, "\nWARNING: Couldn't find a time in the frame.\n");
823             }
824
825             if (cont_type[i] == TIME_EXPLICIT)
826             {
827                 t_corr = settime[i]-fr.time;
828             }
829             /* t_corr is the amount we want to change the time.
830              * If the user has chosen not to change the time for
831              * this part of the trajectory t_corr remains at
832              * the value it had in the last part, changing this
833              * by the same amount.
834              * If no value was given for the first trajectory part
835              * we let the time start at zero, see the edit_files routine.
836              */
837
838             bNewFile = TRUE;
839
840             if (!lastTimeSet)
841             {
842                 lasttime    = 0;
843                 lastTimeSet = true;
844             }
845             printf("\n");
846             printf("lasttime %g\n", lasttime);
847
848             do
849             {
850                 /* copy the input frame to the output frame */
851                 frout = fr;
852                 /* set the new time by adding the correct calculated above */
853                 frout.time += t_corr;
854                 if (ftpout == efTNG)
855                 {
856                     frout.step += prevEndStep;
857                 }
858                 /* quit if we have reached the end of what should be written */
859                 if ((end > 0) && (frout.time > end+GMX_REAL_EPS))
860                 {
861                     i = inFilesEdited.size();
862                     break;
863                 }
864
865                 /* determine if we should write this frame (dt is handled elsewhere) */
866                 if (bCat) /* write all frames of all files */
867                 {
868                     bWrite = TRUE;
869                 }
870                 else if (bKeepLast || (bKeepLastAppend && i == 1))
871                 /* write till last frame of this traj
872                    and skip first frame(s) of next traj */
873                 {
874                     bWrite = ( frout.time > lasttime+0.5*timestep );
875                 }
876                 else /* write till first frame of next traj */
877                 {
878                     bWrite = ( frout.time < settime[i+1]-0.5*timestep );
879                 }
880
881                 if (bWrite && (frout.time >= begin) )
882                 {
883                     frame++;
884                     if (frame_out == -1)
885                     {
886                         first_time = frout.time;
887                     }
888                     lasttime    = frout.time;
889                     lastTimeSet = TRUE;
890                     if (dt == 0 || bRmod(frout.time, first_time, dt))
891                     {
892                         frame_out++;
893                         last_ok_t = frout.time;
894                         if (bNewFile)
895                         {
896                             fprintf(stderr, "\nContinue writing frames from %s t=%g %s, "
897                                     "frame=%d      \n",
898                                     inFilesEdited[i].c_str(),
899                                     output_env_conv_time(oenv, frout.time), timeUnit.c_str(),
900                                     frame);
901                             bNewFile = FALSE;
902                         }
903
904                         if (bIndex)
905                         {
906                             write_trxframe_indexed(trxout, &frout, isize, index,
907                                                    nullptr);
908                         }
909                         else
910                         {
911                             write_trxframe(trxout, &frout, nullptr);
912                         }
913                         if ( ((frame % 10) == 0) || (frame < 10) )
914                         {
915                             fprintf(stderr, " ->  frame %6d time %8.3f %s     \r",
916                                     frame_out, output_env_conv_time(oenv, frout.time), timeUnit.c_str());
917                             fflush(stderr);
918                         }
919                     }
920                 }
921             }
922             while (read_next_frame(oenv, status, &fr));
923
924             close_trx(status);
925         }
926         if (trxout)
927         {
928             close_trx(trxout);
929         }
930         fprintf(stderr, "\nLast frame written was %d, time %f %s\n",
931                 frame, output_env_conv_time(oenv, last_ok_t), timeUnit.c_str());
932     }
933
934     return 0;
935 }