681a99033db1d362ecd39ebe499cfe3151ad9a9a
[alexxy/gromacs.git] / src / gromacs / gmxana / gmx_tune_pme.c
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2009,2010,2011,2012,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 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
38
39
40 #include <time.h>
41 #ifdef HAVE_SYS_TIME_H
42 #include <sys/time.h>
43 #endif
44
45
46
47 #include "gromacs/commandline/pargs.h"
48 #include "typedefs.h"
49 #include "types/commrec.h"
50 #include "gromacs/utility/smalloc.h"
51 #include "vec.h"
52 #include "copyrite.h"
53 #include "gromacs/fileio/tpxio.h"
54 #include "string2.h"
55 #include "readinp.h"
56 #include "calcgrid.h"
57 #include "checkpoint.h"
58 #include "macros.h"
59 #include "gmx_ana.h"
60 #include "names.h"
61 #include "perf_est.h"
62 #include "inputrec.h"
63 #include "gromacs/timing/walltime_accounting.h"
64 #include "gromacs/math/utilities.h"
65
66
67 /* Enum for situations that can occur during log file parsing, the
68  * corresponding string entries can be found in do_the_tests() in
69  * const char* ParseLog[] */
70 enum {
71     eParselogOK,
72     eParselogNotFound,
73     eParselogNoPerfData,
74     eParselogTerm,
75     eParselogResetProblem,
76     eParselogNoDDGrid,
77     eParselogTPXVersion,
78     eParselogNotParallel,
79     eParselogLargePrimeFactor,
80     eParselogFatal,
81     eParselogNr
82 };
83
84
85 typedef struct
86 {
87     int     nPMEnodes;    /* number of PME-only nodes used in this test */
88     int     nx, ny, nz;   /* DD grid */
89     int     guessPME;     /* if nPMEnodes == -1, this is the guessed number of PME nodes */
90     double *Gcycles;      /* This can contain more than one value if doing multiple tests */
91     double  Gcycles_Av;
92     float  *ns_per_day;
93     float   ns_per_day_Av;
94     float  *PME_f_load;     /* PME mesh/force load average*/
95     float   PME_f_load_Av;  /* Average average ;) ... */
96     char   *mdrun_cmd_line; /* Mdrun command line used for this test */
97 } t_perf;
98
99
100 typedef struct
101 {
102     int             nr_inputfiles;  /* The number of tpr and mdp input files */
103     gmx_int64_t     orig_sim_steps; /* Number of steps to be done in the real simulation */
104     gmx_int64_t     orig_init_step; /* Init step for the real simulation */
105     real           *rcoulomb;       /* The coulomb radii [0...nr_inputfiles] */
106     real           *rvdw;           /* The vdW radii */
107     real           *rlist;          /* Neighbourlist cutoff radius */
108     real           *rlistlong;
109     int            *nkx, *nky, *nkz;
110     real           *fsx, *fsy, *fsz; /* Fourierspacing in x,y,z dimension */
111 } t_inputinfo;
112
113
114 static void sep_line(FILE *fp)
115 {
116     fprintf(fp, "\n------------------------------------------------------------\n");
117 }
118
119
120 /* Wrapper for system calls */
121 static int gmx_system_call(char *command)
122 {
123 #ifdef GMX_NO_SYSTEM
124     gmx_fatal(FARGS, "No calls to system(3) supported on this platform. Attempted to call:\n'%s'\n", command);
125 #else
126     return ( system(command) );
127 #endif
128 }
129
130
131 /* Check if string starts with substring */
132 static gmx_bool str_starts(const char *string, const char *substring)
133 {
134     return ( strncmp(string, substring, strlen(substring)) == 0);
135 }
136
137
138 static void cleandata(t_perf *perfdata, int test_nr)
139 {
140     perfdata->Gcycles[test_nr]    = 0.0;
141     perfdata->ns_per_day[test_nr] = 0.0;
142     perfdata->PME_f_load[test_nr] = 0.0;
143
144     return;
145 }
146
147
148 static gmx_bool is_equal(real a, real b)
149 {
150     real diff, eps = 1.0e-7;
151
152
153     diff = a - b;
154
155     if (diff < 0.0)
156     {
157         diff = -diff;
158     }
159
160     if (diff < eps)
161     {
162         return TRUE;
163     }
164     else
165     {
166         return FALSE;
167     }
168 }
169
170
171 static void finalize(const char *fn_out)
172 {
173     char  buf[STRLEN];
174     FILE *fp;
175
176
177     fp = fopen(fn_out, "r");
178     fprintf(stdout, "\n\n");
179
180     while (fgets(buf, STRLEN-1, fp) != NULL)
181     {
182         fprintf(stdout, "%s", buf);
183     }
184     fclose(fp);
185     fprintf(stdout, "\n\n");
186 }
187
188
189 enum {
190     eFoundNothing, eFoundDDStr, eFoundAccountingStr, eFoundCycleStr
191 };
192
193 static int parse_logfile(const char *logfile, const char *errfile,
194                          t_perf *perfdata, int test_nr, int presteps, gmx_int64_t cpt_steps,
195                          int nnodes)
196 {
197     FILE           *fp;
198     char            line[STRLEN], dumstring[STRLEN], dumstring2[STRLEN];
199     const char      matchstrdd[]  = "Domain decomposition grid";
200     const char      matchstrcr[]  = "resetting all time and cycle counters";
201     const char      matchstrbal[] = "Average PME mesh/force load:";
202     const char      matchstring[] = "R E A L   C Y C L E   A N D   T I M E   A C C O U N T I N G";
203     const char      errSIG[]      = "signal, stopping at the next";
204     int             iFound;
205     int             procs;
206     float           dum1, dum2, dum3, dum4;
207     int             ndum;
208     int             npme;
209     gmx_int64_t     resetsteps     = -1;
210     gmx_bool        bFoundResetStr = FALSE;
211     gmx_bool        bResetChecked  = FALSE;
212
213
214     if (!gmx_fexist(logfile))
215     {
216         fprintf(stderr, "WARNING: Could not find logfile %s.\n", logfile);
217         cleandata(perfdata, test_nr);
218         return eParselogNotFound;
219     }
220
221     fp = fopen(logfile, "r");
222     perfdata->PME_f_load[test_nr] = -1.0;
223     perfdata->guessPME            = -1;
224
225     iFound = eFoundNothing;
226     if (1 == nnodes)
227     {
228         iFound = eFoundDDStr; /* Skip some case statements */
229     }
230
231     while (fgets(line, STRLEN, fp) != NULL)
232     {
233         /* Remove leading spaces */
234         ltrim(line);
235
236         /* Check for TERM and INT signals from user: */
237         if (strstr(line, errSIG) != NULL)
238         {
239             fclose(fp);
240             cleandata(perfdata, test_nr);
241             return eParselogTerm;
242         }
243
244         /* Check whether cycle resetting  worked */
245         if (presteps > 0 && !bFoundResetStr)
246         {
247             if (strstr(line, matchstrcr) != NULL)
248             {
249                 sprintf(dumstring, "step %s", "%"GMX_SCNd64);
250                 sscanf(line, dumstring, &resetsteps);
251                 bFoundResetStr = TRUE;
252                 if (resetsteps == presteps+cpt_steps)
253                 {
254                     bResetChecked = TRUE;
255                 }
256                 else
257                 {
258                     sprintf(dumstring, "%"GMX_PRId64, resetsteps);
259                     sprintf(dumstring2, "%"GMX_PRId64, presteps+cpt_steps);
260                     fprintf(stderr, "WARNING: Time step counters were reset at step %s,\n"
261                             "         though they were supposed to be reset at step %s!\n",
262                             dumstring, dumstring2);
263                 }
264             }
265         }
266
267         /* Look for strings that appear in a certain order in the log file: */
268         switch (iFound)
269         {
270             case eFoundNothing:
271                 /* Look for domain decomp grid and separate PME nodes: */
272                 if (str_starts(line, matchstrdd))
273                 {
274                     sscanf(line, "Domain decomposition grid %d x %d x %d, separate PME nodes %d",
275                            &(perfdata->nx), &(perfdata->ny), &(perfdata->nz), &npme);
276                     if (perfdata->nPMEnodes == -1)
277                     {
278                         perfdata->guessPME = npme;
279                     }
280                     else if (perfdata->nPMEnodes != npme)
281                     {
282                         gmx_fatal(FARGS, "PME nodes from command line and output file are not identical");
283                     }
284                     iFound = eFoundDDStr;
285                 }
286                 /* Catch a few errors that might have occured: */
287                 else if (str_starts(line, "There is no domain decomposition for"))
288                 {
289                     fclose(fp);
290                     return eParselogNoDDGrid;
291                 }
292                 else if (str_starts(line, "The number of nodes you selected"))
293                 {
294                     fclose(fp);
295                     return eParselogLargePrimeFactor;
296                 }
297                 else if (str_starts(line, "reading tpx file"))
298                 {
299                     fclose(fp);
300                     return eParselogTPXVersion;
301                 }
302                 else if (str_starts(line, "The -dd or -npme option request a parallel simulation"))
303                 {
304                     fclose(fp);
305                     return eParselogNotParallel;
306                 }
307                 break;
308             case eFoundDDStr:
309                 /* Look for PME mesh/force balance (not necessarily present, though) */
310                 if (str_starts(line, matchstrbal))
311                 {
312                     sscanf(&line[strlen(matchstrbal)], "%f", &(perfdata->PME_f_load[test_nr]));
313                 }
314                 /* Look for matchstring */
315                 if (str_starts(line, matchstring))
316                 {
317                     iFound = eFoundAccountingStr;
318                 }
319                 break;
320             case eFoundAccountingStr:
321                 /* Already found matchstring - look for cycle data */
322                 if (str_starts(line, "Total  "))
323                 {
324                     sscanf(line, "Total %d %lf", &procs, &(perfdata->Gcycles[test_nr]));
325                     iFound = eFoundCycleStr;
326                 }
327                 break;
328             case eFoundCycleStr:
329                 /* Already found cycle data - look for remaining performance info and return */
330                 if (str_starts(line, "Performance:"))
331                 {
332                     ndum = sscanf(line, "%s %f %f %f %f", dumstring, &dum1, &dum2, &dum3, &dum4);
333                     /* (ns/day) is the second last entry, depending on whether GMX_DETAILED_PERF_STATS was set in print_perf(), nrnb.c */
334                     perfdata->ns_per_day[test_nr] = (ndum == 5) ? dum3 : dum1;
335                     fclose(fp);
336                     if (bResetChecked || presteps == 0)
337                     {
338                         return eParselogOK;
339                     }
340                     else
341                     {
342                         return eParselogResetProblem;
343                     }
344                 }
345                 break;
346         }
347     } /* while */
348
349     /* Close the log file */
350     fclose(fp);
351
352     /* Check why there is no performance data in the log file.
353      * Did a fatal errors occur? */
354     if (gmx_fexist(errfile))
355     {
356         fp = fopen(errfile, "r");
357         while (fgets(line, STRLEN, fp) != NULL)
358         {
359             if (str_starts(line, "Fatal error:") )
360             {
361                 if (fgets(line, STRLEN, fp) != NULL)
362                 {
363                     fprintf(stderr, "\nWARNING: An error occured during this benchmark:\n"
364                             "%s\n", line);
365                 }
366                 fclose(fp);
367                 cleandata(perfdata, test_nr);
368                 return eParselogFatal;
369             }
370         }
371         fclose(fp);
372     }
373     else
374     {
375         fprintf(stderr, "WARNING: Could not find stderr file %s.\n", errfile);
376     }
377
378     /* Giving up ... we could not find out why there is no performance data in
379      * the log file. */
380     fprintf(stdout, "No performance data in log file.\n");
381     cleandata(perfdata, test_nr);
382
383     return eParselogNoPerfData;
384 }
385
386
387 static gmx_bool analyze_data(
388         FILE         *fp,
389         const char   *fn,
390         t_perf      **perfdata,
391         int           nnodes,
392         int           ntprs,
393         int           ntests,
394         int           nrepeats,
395         t_inputinfo  *info,
396         int          *index_tpr,    /* OUT: Nr of mdp file with best settings */
397         int          *npme_optimal) /* OUT: Optimal number of PME nodes */
398 {
399     int      i, j, k;
400     int      line  = 0, line_win = -1;
401     int      k_win = -1, i_win = -1, winPME;
402     double   s     = 0.0; /* standard deviation */
403     t_perf  *pd;
404     char     strbuf[STRLEN];
405     char     str_PME_f_load[13];
406     gmx_bool bCanUseOrigTPR;
407     gmx_bool bRefinedCoul, bRefinedVdW, bRefinedGrid;
408
409
410     if (nrepeats > 1)
411     {
412         sep_line(fp);
413         fprintf(fp, "Summary of successful runs:\n");
414         fprintf(fp, "Line tpr PME nodes  Gcycles Av.     Std.dev.       ns/day        PME/f");
415         if (nnodes > 1)
416         {
417             fprintf(fp, "    DD grid");
418         }
419         fprintf(fp, "\n");
420     }
421
422
423     for (k = 0; k < ntprs; k++)
424     {
425         for (i = 0; i < ntests; i++)
426         {
427             /* Select the right dataset: */
428             pd = &(perfdata[k][i]);
429
430             pd->Gcycles_Av    = 0.0;
431             pd->PME_f_load_Av = 0.0;
432             pd->ns_per_day_Av = 0.0;
433
434             if (pd->nPMEnodes == -1)
435             {
436                 sprintf(strbuf, "(%3d)", pd->guessPME);
437             }
438             else
439             {
440                 sprintf(strbuf, "     ");
441             }
442
443             /* Get the average run time of a setting */
444             for (j = 0; j < nrepeats; j++)
445             {
446                 pd->Gcycles_Av    += pd->Gcycles[j];
447                 pd->PME_f_load_Av += pd->PME_f_load[j];
448             }
449             pd->Gcycles_Av    /= nrepeats;
450             pd->PME_f_load_Av /= nrepeats;
451
452             for (j = 0; j < nrepeats; j++)
453             {
454                 if (pd->ns_per_day[j] > 0.0)
455                 {
456                     pd->ns_per_day_Av += pd->ns_per_day[j];
457                 }
458                 else
459                 {
460                     /* Somehow the performance number was not aquired for this run,
461                      * therefor set the average to some negative value: */
462                     pd->ns_per_day_Av = -1.0f*nrepeats;
463                     break;
464                 }
465             }
466             pd->ns_per_day_Av /= nrepeats;
467
468             /* Nicer output: */
469             if (pd->PME_f_load_Av > 0.0)
470             {
471                 sprintf(str_PME_f_load, "%12.3f", pd->PME_f_load_Av);
472             }
473             else
474             {
475                 sprintf(str_PME_f_load, "%s", "         -  ");
476             }
477
478
479             /* We assume we had a successful run if both averages are positive */
480             if (pd->Gcycles_Av > 0.0 && pd->ns_per_day_Av > 0.0)
481             {
482                 /* Output statistics if repeats were done */
483                 if (nrepeats > 1)
484                 {
485                     /* Calculate the standard deviation */
486                     s = 0.0;
487                     for (j = 0; j < nrepeats; j++)
488                     {
489                         s += pow( pd->Gcycles[j] - pd->Gcycles_Av, 2 );
490                     }
491                     s /= (nrepeats - 1);
492                     s  = sqrt(s);
493
494                     fprintf(fp, "%4d %3d %4d%s %12.3f %12.3f %12.3f %s",
495                             line, k, pd->nPMEnodes, strbuf, pd->Gcycles_Av, s,
496                             pd->ns_per_day_Av, str_PME_f_load);
497                     if (nnodes > 1)
498                     {
499                         fprintf(fp, "  %3d %3d %3d", pd->nx, pd->ny, pd->nz);
500                     }
501                     fprintf(fp, "\n");
502                 }
503                 /* Store the index of the best run found so far in 'winner': */
504                 if ( (k_win == -1) || (pd->Gcycles_Av < perfdata[k_win][i_win].Gcycles_Av) )
505                 {
506                     k_win    = k;
507                     i_win    = i;
508                     line_win = line;
509                 }
510                 line++;
511             }
512         }
513     }
514
515     if (k_win == -1)
516     {
517         gmx_fatal(FARGS, "None of the runs was successful! Check %s for problems.", fn);
518     }
519
520     sep_line(fp);
521
522     winPME = perfdata[k_win][i_win].nPMEnodes;
523
524     if (1 == ntests)
525     {
526         /* We stuck to a fixed number of PME-only nodes */
527         sprintf(strbuf, "settings No. %d", k_win);
528     }
529     else
530     {
531         /* We have optimized the number of PME-only nodes */
532         if (winPME == -1)
533         {
534             sprintf(strbuf, "%s", "the automatic number of PME nodes");
535         }
536         else
537         {
538             sprintf(strbuf, "%d PME nodes", winPME);
539         }
540     }
541     fprintf(fp, "Best performance was achieved with %s", strbuf);
542     if ((nrepeats > 1) && (ntests > 1))
543     {
544         fprintf(fp, " (see line %d)", line_win);
545     }
546     fprintf(fp, "\n");
547
548     /* Only mention settings if they were modified: */
549     bRefinedCoul = !is_equal(info->rcoulomb[k_win], info->rcoulomb[0]);
550     bRefinedVdW  = !is_equal(info->rvdw[k_win], info->rvdw[0]    );
551     bRefinedGrid = !(info->nkx[k_win] == info->nkx[0] &&
552                      info->nky[k_win] == info->nky[0] &&
553                      info->nkz[k_win] == info->nkz[0]);
554
555     if (bRefinedCoul || bRefinedVdW || bRefinedGrid)
556     {
557         fprintf(fp, "Optimized PME settings:\n");
558         bCanUseOrigTPR = FALSE;
559     }
560     else
561     {
562         bCanUseOrigTPR = TRUE;
563     }
564
565     if (bRefinedCoul)
566     {
567         fprintf(fp, "   New Coulomb radius: %f nm (was %f nm)\n", info->rcoulomb[k_win], info->rcoulomb[0]);
568     }
569
570     if (bRefinedVdW)
571     {
572         fprintf(fp, "   New Van der Waals radius: %f nm (was %f nm)\n", info->rvdw[k_win], info->rvdw[0]);
573     }
574
575     if (bRefinedGrid)
576     {
577         fprintf(fp, "   New Fourier grid xyz: %d %d %d (was %d %d %d)\n", info->nkx[k_win], info->nky[k_win], info->nkz[k_win],
578                 info->nkx[0], info->nky[0], info->nkz[0]);
579     }
580
581     if (bCanUseOrigTPR && ntprs > 1)
582     {
583         fprintf(fp, "and original PME settings.\n");
584     }
585
586     fflush(fp);
587
588     /* Return the index of the mdp file that showed the highest performance
589      * and the optimal number of PME nodes */
590     *index_tpr    = k_win;
591     *npme_optimal = winPME;
592
593     return bCanUseOrigTPR;
594 }
595
596
597 /* Get the commands we need to set up the runs from environment variables */
598 static void get_program_paths(gmx_bool bThreads, char *cmd_mpirun[], char *cmd_mdrun[])
599 {
600     char      *cp;
601     FILE      *fp;
602     const char def_mpirun[]   = "mpirun";
603     const char def_mdrun[]    = "mdrun";
604
605     const char empty_mpirun[] = "";
606
607     /* Get the commands we need to set up the runs from environment variables */
608     if (!bThreads)
609     {
610         if ( (cp = getenv("MPIRUN")) != NULL)
611         {
612             *cmd_mpirun = strdup(cp);
613         }
614         else
615         {
616             *cmd_mpirun = strdup(def_mpirun);
617         }
618     }
619     else
620     {
621         *cmd_mpirun = strdup(empty_mpirun);
622     }
623
624     if ( (cp = getenv("MDRUN" )) != NULL)
625     {
626         *cmd_mdrun  = strdup(cp);
627     }
628     else
629     {
630         *cmd_mdrun  = strdup(def_mdrun);
631     }
632 }
633
634 /* Check that the commands will run mdrun (perhaps via mpirun) by
635  * running a very quick test simulation. Requires MPI environment to
636  * be available if applicable. */
637 static void check_mdrun_works(gmx_bool    bThreads,
638                               const char *cmd_mpirun,
639                               const char *cmd_np,
640                               const char *cmd_mdrun)
641 {
642     char      *command = NULL;
643     char      *cp;
644     char       line[STRLEN];
645     FILE      *fp;
646     const char filename[]     = "benchtest.log";
647
648     /* This string should always be identical to the one in copyrite.c,
649      * gmx_print_version_info() in the defined(GMX_MPI) section */
650     const char match_mpi[]    = "MPI library:        MPI";
651     const char match_mdrun[]  = "Program: ";
652     gmx_bool   bMdrun         = FALSE;
653     gmx_bool   bMPI           = FALSE;
654
655     /* Run a small test to see whether mpirun + mdrun work  */
656     fprintf(stdout, "Making sure that mdrun can be executed. ");
657     if (bThreads)
658     {
659         snew(command, strlen(cmd_mdrun) + strlen(cmd_np) + strlen(filename) + 50);
660         sprintf(command, "%s%s-version -maxh 0.001 1> %s 2>&1", cmd_mdrun, cmd_np, filename);
661     }
662     else
663     {
664         snew(command, strlen(cmd_mpirun) + strlen(cmd_np) + strlen(cmd_mdrun) + strlen(filename) + 50);
665         sprintf(command, "%s%s%s -version -maxh 0.001 1> %s 2>&1", cmd_mpirun, cmd_np, cmd_mdrun, filename);
666     }
667     fprintf(stdout, "Trying '%s' ... ", command);
668     make_backup(filename);
669     gmx_system_call(command);
670
671     /* Check if we find the characteristic string in the output: */
672     if (!gmx_fexist(filename))
673     {
674         gmx_fatal(FARGS, "Output from test run could not be found.");
675     }
676
677     fp = fopen(filename, "r");
678     /* We need to scan the whole output file, since sometimes the queuing system
679      * also writes stuff to stdout/err */
680     while (!feof(fp) )
681     {
682         cp = fgets(line, STRLEN, fp);
683         if (cp != NULL)
684         {
685             if (str_starts(line, match_mdrun) )
686             {
687                 bMdrun = TRUE;
688             }
689             if (str_starts(line, match_mpi) )
690             {
691                 bMPI = TRUE;
692             }
693         }
694     }
695     fclose(fp);
696
697     if (bThreads)
698     {
699         if (bMPI)
700         {
701             gmx_fatal(FARGS, "Need a threaded version of mdrun. This one\n"
702                       "(%s)\n"
703                       "seems to have been compiled with MPI instead.",
704                       *cmd_mdrun);
705         }
706     }
707     else
708     {
709         if (bMdrun && !bMPI)
710         {
711             gmx_fatal(FARGS, "Need an MPI-enabled version of mdrun. This one\n"
712                       "(%s)\n"
713                       "seems to have been compiled without MPI support.",
714                       *cmd_mdrun);
715         }
716     }
717
718     if (!bMdrun)
719     {
720         gmx_fatal(FARGS, "Cannot execute mdrun. Please check %s for problems!",
721                   filename);
722     }
723
724     fprintf(stdout, "passed.\n");
725
726     /* Clean up ... */
727     remove(filename);
728     sfree(command);
729 }
730
731
732 static void launch_simulation(
733         gmx_bool    bLaunch,        /* Should the simulation be launched? */
734         FILE       *fp,             /* General log file */
735         gmx_bool    bThreads,       /* whether to use threads */
736         char       *cmd_mpirun,     /* Command for mpirun */
737         char       *cmd_np,         /* Switch for -np or -ntmpi or empty */
738         char       *cmd_mdrun,      /* Command for mdrun */
739         char       *args_for_mdrun, /* Arguments for mdrun */
740         const char *simulation_tpr, /* This tpr will be simulated */
741         int         nPMEnodes)      /* Number of PME nodes to use */
742 {
743     char  *command;
744
745
746     /* Make enough space for the system call command,
747      * (100 extra chars for -npme ... etc. options should suffice): */
748     snew(command, strlen(cmd_mpirun)+strlen(cmd_mdrun)+strlen(cmd_np)+strlen(args_for_mdrun)+strlen(simulation_tpr)+100);
749
750     /* Note that the -passall options requires args_for_mdrun to be at the end
751      * of the command line string */
752     if (bThreads)
753     {
754         sprintf(command, "%s%s-npme %d -s %s %s",
755                 cmd_mdrun, cmd_np, nPMEnodes, simulation_tpr, args_for_mdrun);
756     }
757     else
758     {
759         sprintf(command, "%s%s%s -npme %d -s %s %s",
760                 cmd_mpirun, cmd_np, cmd_mdrun, nPMEnodes, simulation_tpr, args_for_mdrun);
761     }
762
763     fprintf(fp, "%s this command line to launch the simulation:\n\n%s", bLaunch ? "Using" : "Please use", command);
764     sep_line(fp);
765     fflush(fp);
766
767     /* Now the real thing! */
768     if (bLaunch)
769     {
770         fprintf(stdout, "\nLaunching simulation with best parameters now.\nExecuting '%s'", command);
771         sep_line(stdout);
772         fflush(stdout);
773         gmx_system_call(command);
774     }
775 }
776
777
778 static void modify_PMEsettings(
779         gmx_int64_t     simsteps,    /* Set this value as number of time steps */
780         gmx_int64_t     init_step,   /* Set this value as init_step */
781         const char     *fn_best_tpr, /* tpr file with the best performance */
782         const char     *fn_sim_tpr)  /* name of tpr file to be launched */
783 {
784     t_inputrec   *ir;
785     t_state       state;
786     gmx_mtop_t    mtop;
787     char          buf[200];
788
789     snew(ir, 1);
790     read_tpx_state(fn_best_tpr, ir, &state, NULL, &mtop);
791
792     /* Reset nsteps and init_step to the value of the input .tpr file */
793     ir->nsteps    = simsteps;
794     ir->init_step = init_step;
795
796     /* Write the tpr file which will be launched */
797     sprintf(buf, "Writing optimized simulation file %s with nsteps=%s.\n", fn_sim_tpr, "%"GMX_PRId64);
798     fprintf(stdout, buf, ir->nsteps);
799     fflush(stdout);
800     write_tpx_state(fn_sim_tpr, ir, &state, &mtop);
801
802     sfree(ir);
803 }
804
805
806 #define EPME_SWITCHED(e) ((e) == eelPMESWITCH || (e) == eelPMEUSERSWITCH)
807
808 /* Make additional TPR files with more computational load for the
809  * direct space processors: */
810 static void make_benchmark_tprs(
811         const char     *fn_sim_tpr,      /* READ : User-provided tpr file                 */
812         char           *fn_bench_tprs[], /* WRITE: Names of benchmark tpr files           */
813         gmx_int64_t     benchsteps,      /* Number of time steps for benchmark runs       */
814         gmx_int64_t     statesteps,      /* Step counter in checkpoint file               */
815         real            rmin,            /* Minimal Coulomb radius                        */
816         real            rmax,            /* Maximal Coulomb radius                        */
817         real            bScaleRvdw,      /* Scale rvdw along with rcoulomb                */
818         int            *ntprs,           /* No. of TPRs to write, each with a different
819                                             rcoulomb and fourierspacing                   */
820         t_inputinfo    *info,            /* Contains information about mdp file options   */
821         FILE           *fp)              /* Write the output here                         */
822 {
823     int           i, j, d;
824     t_inputrec   *ir;
825     t_state       state;
826     gmx_mtop_t    mtop;
827     real          nlist_buffer;     /* Thickness of the buffer regions for PME-switch potentials */
828     char          buf[200];
829     rvec          box_size;
830     gmx_bool      bNote = FALSE;
831     real          add;              /* Add this to rcoul for the next test    */
832     real          fac = 1.0;        /* Scaling factor for Coulomb radius      */
833     real          fourierspacing;   /* Basic fourierspacing from tpr          */
834
835
836     sprintf(buf, "Making benchmark tpr file%s with %s time step%s",
837             *ntprs > 1 ? "s" : "", "%"GMX_PRId64, benchsteps > 1 ? "s" : "");
838     fprintf(stdout, buf, benchsteps);
839     if (statesteps > 0)
840     {
841         sprintf(buf, " (adding %s steps from checkpoint file)", "%"GMX_PRId64);
842         fprintf(stdout, buf, statesteps);
843         benchsteps += statesteps;
844     }
845     fprintf(stdout, ".\n");
846
847
848     snew(ir, 1);
849     read_tpx_state(fn_sim_tpr, ir, &state, NULL, &mtop);
850
851     /* Check if some kind of PME was chosen */
852     if (EEL_PME(ir->coulombtype) == FALSE)
853     {
854         gmx_fatal(FARGS, "Can only do optimizations for simulations with %s electrostatics.",
855                   EELTYPE(eelPME));
856     }
857
858     /* Check if rcoulomb == rlist, which is necessary for plain PME. */
859     if (  (ir->cutoff_scheme != ecutsVERLET) &&
860           (eelPME == ir->coulombtype) && !(ir->rcoulomb == ir->rlist))
861     {
862         gmx_fatal(FARGS, "%s requires rcoulomb (%f) to be equal to rlist (%f).",
863                   EELTYPE(eelPME), ir->rcoulomb, ir->rlist);
864     }
865     /* For other PME types, rcoulomb is allowed to be smaller than rlist */
866     else if (ir->rcoulomb > ir->rlist)
867     {
868         gmx_fatal(FARGS, "%s requires rcoulomb (%f) to be equal to or smaller than rlist (%f)",
869                   EELTYPE(ir->coulombtype), ir->rcoulomb, ir->rlist);
870     }
871
872     if (bScaleRvdw && ir->rvdw != ir->rcoulomb)
873     {
874         fprintf(stdout, "NOTE: input rvdw != rcoulomb, will not scale rvdw\n");
875         bScaleRvdw = FALSE;
876     }
877
878     /* Reduce the number of steps for the benchmarks */
879     info->orig_sim_steps = ir->nsteps;
880     ir->nsteps           = benchsteps;
881     /* We must not use init_step from the input tpr file for the benchmarks */
882     info->orig_init_step = ir->init_step;
883     ir->init_step        = 0;
884
885     /* For PME-switch potentials, keep the radial distance of the buffer region */
886     nlist_buffer   = ir->rlist - ir->rcoulomb;
887
888     /* Determine length of triclinic box vectors */
889     for (d = 0; d < DIM; d++)
890     {
891         box_size[d] = 0;
892         for (i = 0; i < DIM; i++)
893         {
894             box_size[d] += state.box[d][i]*state.box[d][i];
895         }
896         box_size[d] = sqrt(box_size[d]);
897     }
898
899     if (ir->fourier_spacing > 0)
900     {
901         info->fsx[0] = ir->fourier_spacing;
902         info->fsy[0] = ir->fourier_spacing;
903         info->fsz[0] = ir->fourier_spacing;
904     }
905     else
906     {
907         /* Reconstruct fourierspacing per dimension from the number of grid points and box size */
908         info->fsx[0] = box_size[XX]/ir->nkx;
909         info->fsy[0] = box_size[YY]/ir->nky;
910         info->fsz[0] = box_size[ZZ]/ir->nkz;
911     }
912
913     /* If no value for the fourierspacing was provided on the command line, we
914      * use the reconstruction from the tpr file */
915     if (ir->fourier_spacing > 0)
916     {
917         /* Use the spacing from the tpr */
918         fourierspacing = ir->fourier_spacing;
919     }
920     else
921     {
922         /* Use the maximum observed spacing */
923         fourierspacing = max(max(info->fsx[0], info->fsy[0]), info->fsz[0]);
924     }
925
926     fprintf(stdout, "Calculating PME grid points on the basis of a fourierspacing of %f nm\n", fourierspacing);
927
928     /* For performance comparisons the number of particles is useful to have */
929     fprintf(fp, "   Number of particles  : %d\n", mtop.natoms);
930
931     /* Print information about settings of which some are potentially modified: */
932     fprintf(fp, "   Coulomb type         : %s\n", EELTYPE(ir->coulombtype));
933     fprintf(fp, "   Grid spacing x y z   : %f %f %f\n",
934             box_size[XX]/ir->nkx, box_size[YY]/ir->nky, box_size[ZZ]/ir->nkz);
935     fprintf(fp, "   Van der Waals type   : %s\n", EVDWTYPE(ir->vdwtype));
936     if (ir_vdw_switched(ir))
937     {
938         fprintf(fp, "   rvdw_switch          : %f nm\n", ir->rvdw_switch);
939     }
940     if (EPME_SWITCHED(ir->coulombtype))
941     {
942         fprintf(fp, "   rlist                : %f nm\n", ir->rlist);
943     }
944     if (ir->rlistlong != max_cutoff(ir->rvdw, ir->rcoulomb))
945     {
946         fprintf(fp, "   rlistlong            : %f nm\n", ir->rlistlong);
947     }
948
949     /* Print a descriptive line about the tpr settings tested */
950     fprintf(fp, "\nWill try these real/reciprocal workload settings:\n");
951     fprintf(fp, " No.   scaling  rcoulomb");
952     fprintf(fp, "  nkx  nky  nkz");
953     fprintf(fp, "   spacing");
954     if (evdwCUT == ir->vdwtype)
955     {
956         fprintf(fp, "      rvdw");
957     }
958     if (EPME_SWITCHED(ir->coulombtype))
959     {
960         fprintf(fp, "     rlist");
961     }
962     if (ir->rlistlong != max_cutoff(ir->rlist, max_cutoff(ir->rvdw, ir->rcoulomb)) )
963     {
964         fprintf(fp, " rlistlong");
965     }
966     fprintf(fp, "  tpr file\n");
967
968     /* Loop to create the requested number of tpr input files */
969     for (j = 0; j < *ntprs; j++)
970     {
971         /* The first .tpr is the provided one, just need to modify nsteps,
972          * so skip the following block */
973         if (j != 0)
974         {
975             /* Determine which Coulomb radii rc to use in the benchmarks */
976             add = (rmax-rmin)/(*ntprs-1);
977             if (is_equal(rmin, info->rcoulomb[0]))
978             {
979                 ir->rcoulomb = rmin + j*add;
980             }
981             else if (is_equal(rmax, info->rcoulomb[0]))
982             {
983                 ir->rcoulomb = rmin + (j-1)*add;
984             }
985             else
986             {
987                 /* rmin != rcoul != rmax, ergo test between rmin and rmax */
988                 add          = (rmax-rmin)/(*ntprs-2);
989                 ir->rcoulomb = rmin + (j-1)*add;
990             }
991
992             /* Determine the scaling factor fac */
993             fac = ir->rcoulomb/info->rcoulomb[0];
994
995             /* Scale the Fourier grid spacing */
996             ir->nkx = ir->nky = ir->nkz = 0;
997             calc_grid(NULL, state.box, fourierspacing*fac, &ir->nkx, &ir->nky, &ir->nkz);
998
999             /* Adjust other radii since various conditions need to be fulfilled */
1000             if (eelPME == ir->coulombtype)
1001             {
1002                 /* plain PME, rcoulomb must be equal to rlist */
1003                 ir->rlist = ir->rcoulomb;
1004             }
1005             else
1006             {
1007                 /* rlist must be >= rcoulomb, we keep the size of the buffer region */
1008                 ir->rlist = ir->rcoulomb + nlist_buffer;
1009             }
1010
1011             if (bScaleRvdw && evdwCUT == ir->vdwtype)
1012             {
1013                 if (ecutsVERLET == ir->cutoff_scheme)
1014                 {
1015                     /* With Verlet, the van der Waals radius must always equal the Coulomb radius */
1016                     ir->rvdw = ir->rcoulomb;
1017                 }
1018                 else
1019                 {
1020                     /* For vdw cutoff, rvdw >= rlist */
1021                     ir->rvdw = max(info->rvdw[0], ir->rlist);
1022                 }
1023             }
1024
1025             ir->rlistlong = max_cutoff(ir->rlist, max_cutoff(ir->rvdw, ir->rcoulomb));
1026
1027         } /* end of "if (j != 0)" */
1028
1029         /* for j==0: Save the original settings
1030          * for j >0: Save modified radii and Fourier grids */
1031         info->rcoulomb[j]  = ir->rcoulomb;
1032         info->rvdw[j]      = ir->rvdw;
1033         info->nkx[j]       = ir->nkx;
1034         info->nky[j]       = ir->nky;
1035         info->nkz[j]       = ir->nkz;
1036         info->rlist[j]     = ir->rlist;
1037         info->rlistlong[j] = ir->rlistlong;
1038         info->fsx[j]       = fac*fourierspacing;
1039         info->fsy[j]       = fac*fourierspacing;
1040         info->fsz[j]       = fac*fourierspacing;
1041
1042         /* Write the benchmark tpr file */
1043         strncpy(fn_bench_tprs[j], fn_sim_tpr, strlen(fn_sim_tpr)-strlen(".tpr"));
1044         sprintf(buf, "_bench%.2d.tpr", j);
1045         strcat(fn_bench_tprs[j], buf);
1046         fprintf(stdout, "Writing benchmark tpr %s with nsteps=", fn_bench_tprs[j]);
1047         fprintf(stdout, "%"GMX_PRId64, ir->nsteps);
1048         if (j > 0)
1049         {
1050             fprintf(stdout, ", scaling factor %f\n", fac);
1051         }
1052         else
1053         {
1054             fprintf(stdout, ", unmodified settings\n");
1055         }
1056
1057         write_tpx_state(fn_bench_tprs[j], ir, &state, &mtop);
1058
1059         /* Write information about modified tpr settings to log file */
1060         fprintf(fp, "%4d%10f%10f", j, fac, ir->rcoulomb);
1061         fprintf(fp, "%5d%5d%5d", ir->nkx, ir->nky, ir->nkz);
1062         fprintf(fp, " %9f ", info->fsx[j]);
1063         if (evdwCUT == ir->vdwtype)
1064         {
1065             fprintf(fp, "%10f", ir->rvdw);
1066         }
1067         if (EPME_SWITCHED(ir->coulombtype))
1068         {
1069             fprintf(fp, "%10f", ir->rlist);
1070         }
1071         if (info->rlistlong[0] != max_cutoff(info->rlist[0], max_cutoff(info->rvdw[0], info->rcoulomb[0])) )
1072         {
1073             fprintf(fp, "%10f", ir->rlistlong);
1074         }
1075         fprintf(fp, "  %-14s\n", fn_bench_tprs[j]);
1076
1077         /* Make it clear to the user that some additional settings were modified */
1078         if (!is_equal(ir->rvdw, info->rvdw[0])
1079             || !is_equal(ir->rlistlong, info->rlistlong[0]) )
1080         {
1081             bNote = TRUE;
1082         }
1083     }
1084     if (bNote)
1085     {
1086         fprintf(fp, "\nNote that in addition to the Coulomb radius and the Fourier grid\n"
1087                 "other input settings were also changed (see table above).\n"
1088                 "Please check if the modified settings are appropriate.\n");
1089     }
1090     fflush(stdout);
1091     fflush(fp);
1092     sfree(ir);
1093 }
1094
1095
1096 /* Rename the files we want to keep to some meaningful filename and
1097  * delete the rest */
1098 static void cleanup(const t_filenm *fnm, int nfile, int k, int nnodes,
1099                     int nPMEnodes, int nr, gmx_bool bKeepStderr)
1100 {
1101     char        numstring[STRLEN];
1102     char        newfilename[STRLEN];
1103     const char *fn = NULL;
1104     int         i;
1105     const char *opt;
1106
1107
1108     fprintf(stdout, "Cleaning up, deleting benchmark temp files ...\n");
1109
1110     for (i = 0; i < nfile; i++)
1111     {
1112         opt = (char *)fnm[i].opt;
1113         if (strcmp(opt, "-p") == 0)
1114         {
1115             /* do nothing; keep this file */
1116             ;
1117         }
1118         else if (strcmp(opt, "-bg") == 0)
1119         {
1120             /* Give the log file a nice name so one can later see which parameters were used */
1121             numstring[0] = '\0';
1122             if (nr > 0)
1123             {
1124                 sprintf(numstring, "_%d", nr);
1125             }
1126             sprintf(newfilename, "%s_no%d_np%d_npme%d%s", opt2fn("-bg", nfile, fnm), k, nnodes, nPMEnodes, numstring);
1127             if (gmx_fexist(opt2fn("-bg", nfile, fnm)))
1128             {
1129                 fprintf(stdout, "renaming log file to %s\n", newfilename);
1130                 make_backup(newfilename);
1131                 rename(opt2fn("-bg", nfile, fnm), newfilename);
1132             }
1133         }
1134         else if (strcmp(opt, "-err") == 0)
1135         {
1136             /* This file contains the output of stderr. We want to keep it in
1137              * cases where there have been problems. */
1138             fn           = opt2fn(opt, nfile, fnm);
1139             numstring[0] = '\0';
1140             if (nr > 0)
1141             {
1142                 sprintf(numstring, "_%d", nr);
1143             }
1144             sprintf(newfilename, "%s_no%d_np%d_npme%d%s", fn, k, nnodes, nPMEnodes, numstring);
1145             if (gmx_fexist(fn))
1146             {
1147                 if (bKeepStderr)
1148                 {
1149                     fprintf(stdout, "Saving stderr output in %s\n", newfilename);
1150                     make_backup(newfilename);
1151                     rename(fn, newfilename);
1152                 }
1153                 else
1154                 {
1155                     fprintf(stdout, "Deleting %s\n", fn);
1156                     remove(fn);
1157                 }
1158             }
1159         }
1160         /* Delete the files which are created for each benchmark run: (options -b*) */
1161         else if ( (0 == strncmp(opt, "-b", 2)) && (opt2bSet(opt, nfile, fnm) || !is_optional(&fnm[i])) )
1162         {
1163             fn = opt2fn(opt, nfile, fnm);
1164             if (gmx_fexist(fn))
1165             {
1166                 fprintf(stdout, "Deleting %s\n", fn);
1167                 remove(fn);
1168             }
1169         }
1170     }
1171 }
1172
1173
1174 enum {
1175     eNpmeAuto, eNpmeAll, eNpmeReduced, eNpmeSubset, eNpmeNr
1176 };
1177
1178 /* Create a list of numbers of PME nodes to test */
1179 static void make_npme_list(
1180         const char *npmevalues_opt, /* Make a complete list with all
1181                                      * possibilities or a short list that keeps only
1182                                      * reasonable numbers of PME nodes                  */
1183         int        *nentries,       /* Number of entries we put in the nPMEnodes list   */
1184         int        *nPMEnodes[],    /* Each entry contains the value for -npme          */
1185         int         nnodes,         /* Total number of nodes to do the tests on         */
1186         int         minPMEnodes,    /* Minimum number of PME nodes                      */
1187         int         maxPMEnodes)    /* Maximum number of PME nodes                      */
1188 {
1189     int i, npme, npp;
1190     int min_factor = 1;   /* We request that npp and npme have this minimal
1191                            * largest common factor (depends on npp)           */
1192     int nlistmax;         /* Max. list size                                   */
1193     int nlist;            /* Actual number of entries in list                 */
1194     int eNPME = 0;
1195
1196
1197     /* Do we need to check all possible values for -npme or is a reduced list enough? */
1198     if (0 == strcmp(npmevalues_opt, "all") )
1199     {
1200         eNPME = eNpmeAll;
1201     }
1202     else if (0 == strcmp(npmevalues_opt, "subset") )
1203     {
1204         eNPME = eNpmeSubset;
1205     }
1206     else /* "auto" or "range" */
1207     {
1208         if (nnodes <= 64)
1209         {
1210             eNPME = eNpmeAll;
1211         }
1212         else if (nnodes < 128)
1213         {
1214             eNPME = eNpmeReduced;
1215         }
1216         else
1217         {
1218             eNPME = eNpmeSubset;
1219         }
1220     }
1221
1222     /* Calculate how many entries we could possibly have (in case of -npme all) */
1223     if (nnodes > 2)
1224     {
1225         nlistmax = maxPMEnodes - minPMEnodes + 3;
1226         if (0 == minPMEnodes)
1227         {
1228             nlistmax--;
1229         }
1230     }
1231     else
1232     {
1233         nlistmax = 1;
1234     }
1235
1236     /* Now make the actual list which is at most of size nlist */
1237     snew(*nPMEnodes, nlistmax);
1238     nlist = 0; /* start counting again, now the real entries in the list */
1239     for (i = 0; i < nlistmax - 2; i++)
1240     {
1241         npme = maxPMEnodes - i;
1242         npp  = nnodes-npme;
1243         switch (eNPME)
1244         {
1245             case eNpmeAll:
1246                 min_factor = 1;
1247                 break;
1248             case eNpmeReduced:
1249                 min_factor = 2;
1250                 break;
1251             case eNpmeSubset:
1252                 /* For 2d PME we want a common largest factor of at least the cube
1253                  * root of the number of PP nodes */
1254                 min_factor = (int) pow(npp, 1.0/3.0);
1255                 break;
1256             default:
1257                 gmx_fatal(FARGS, "Unknown option for eNPME in make_npme_list");
1258                 break;
1259         }
1260         if (gmx_greatest_common_divisor(npp, npme) >= min_factor)
1261         {
1262             (*nPMEnodes)[nlist] = npme;
1263             nlist++;
1264         }
1265     }
1266     /* We always test 0 PME nodes and the automatic number */
1267     *nentries             = nlist + 2;
1268     (*nPMEnodes)[nlist  ] =  0;
1269     (*nPMEnodes)[nlist+1] = -1;
1270
1271     fprintf(stderr, "Will try the following %d different values for -npme:\n", *nentries);
1272     for (i = 0; i < *nentries-1; i++)
1273     {
1274         fprintf(stderr, "%d, ", (*nPMEnodes)[i]);
1275     }
1276     fprintf(stderr, "and %d (auto).\n", (*nPMEnodes)[*nentries-1]);
1277 }
1278
1279
1280 /* Allocate memory to store the performance data */
1281 static void init_perfdata(t_perf *perfdata[], int ntprs, int datasets, int repeats)
1282 {
1283     int i, j, k;
1284
1285
1286     for (k = 0; k < ntprs; k++)
1287     {
1288         snew(perfdata[k], datasets);
1289         for (i = 0; i < datasets; i++)
1290         {
1291             for (j = 0; j < repeats; j++)
1292             {
1293                 snew(perfdata[k][i].Gcycles, repeats);
1294                 snew(perfdata[k][i].ns_per_day, repeats);
1295                 snew(perfdata[k][i].PME_f_load, repeats);
1296             }
1297         }
1298     }
1299 }
1300
1301
1302 /* Check for errors on mdrun -h */
1303 static void make_sure_it_runs(char *mdrun_cmd_line, int length, FILE *fp)
1304 {
1305     char *command, *msg;
1306     int   ret;
1307
1308
1309     snew(command, length +  15);
1310     snew(msg, length + 500);
1311
1312     fprintf(stdout, "Making sure the benchmarks can be executed ...\n");
1313     /* FIXME: mdrun -h no longer actually does anything useful.
1314      * It unconditionally prints the help, ignoring all other options. */
1315     sprintf(command, "%s-h -quiet", mdrun_cmd_line);
1316     ret = gmx_system_call(command);
1317
1318     if (0 != ret)
1319     {
1320         /* To prevent confusion, do not again issue a gmx_fatal here since we already
1321          * get the error message from mdrun itself */
1322         sprintf(msg,    "Cannot run the benchmark simulations! Please check the error message of\n"
1323                 "mdrun for the source of the problem. Did you provide a command line\n"
1324                 "argument that neither g_tune_pme nor mdrun understands? Offending command:\n"
1325                 "\n%s\n\n", command);
1326
1327         fprintf(stderr, "%s", msg);
1328         sep_line(fp);
1329         fprintf(fp, "%s", msg);
1330
1331         exit(ret);
1332     }
1333
1334     sfree(command);
1335     sfree(msg    );
1336 }
1337
1338
1339 static void do_the_tests(
1340         FILE           *fp,             /* General g_tune_pme output file         */
1341         char          **tpr_names,      /* Filenames of the input files to test   */
1342         int             maxPMEnodes,    /* Max fraction of nodes to use for PME   */
1343         int             minPMEnodes,    /* Min fraction of nodes to use for PME   */
1344         int             npme_fixed,     /* If >= -1, test fixed number of PME
1345                                          * nodes only                             */
1346         const char     *npmevalues_opt, /* Which -npme values should be tested    */
1347         t_perf        **perfdata,       /* Here the performace data is stored     */
1348         int            *pmeentries,     /* Entries in the nPMEnodes list          */
1349         int             repeats,        /* Repeat each test this often            */
1350         int             nnodes,         /* Total number of nodes = nPP + nPME     */
1351         int             nr_tprs,        /* Total number of tpr files to test      */
1352         gmx_bool        bThreads,       /* Threads or MPI?                        */
1353         char           *cmd_mpirun,     /* mpirun command string                  */
1354         char           *cmd_np,         /* "-np", "-n", whatever mpirun needs     */
1355         char           *cmd_mdrun,      /* mdrun command string                   */
1356         char           *cmd_args_bench, /* arguments for mdrun in a string        */
1357         const t_filenm *fnm,            /* List of filenames from command line    */
1358         int             nfile,          /* Number of files specified on the cmdl. */
1359         int             presteps,       /* DLB equilibration steps, is checked    */
1360         gmx_int64_t     cpt_steps)      /* Time step counter in the checkpoint    */
1361 {
1362     int      i, nr, k, ret, count = 0, totaltests;
1363     int     *nPMEnodes = NULL;
1364     t_perf  *pd        = NULL;
1365     int      cmdline_length;
1366     char    *command, *cmd_stub;
1367     char     buf[STRLEN];
1368     gmx_bool bResetProblem = FALSE;
1369     gmx_bool bFirst        = TRUE;
1370
1371
1372     /* This string array corresponds to the eParselog enum type at the start
1373      * of this file */
1374     const char* ParseLog[] = {
1375         "OK.",
1376         "Logfile not found!",
1377         "No timings, logfile truncated?",
1378         "Run was terminated.",
1379         "Counters were not reset properly.",
1380         "No DD grid found for these settings.",
1381         "TPX version conflict!",
1382         "mdrun was not started in parallel!",
1383         "Number of PP nodes has a prime factor that is too large.",
1384         "An error occured."
1385     };
1386     char        str_PME_f_load[13];
1387
1388
1389     /* Allocate space for the mdrun command line. 100 extra characters should
1390        be more than enough for the -npme etcetera arguments */
1391     cmdline_length =  strlen(cmd_mpirun)
1392         + strlen(cmd_np)
1393         + strlen(cmd_mdrun)
1394         + strlen(cmd_args_bench)
1395         + strlen(tpr_names[0]) + 100;
1396     snew(command, cmdline_length);
1397     snew(cmd_stub, cmdline_length);
1398
1399     /* Construct the part of the command line that stays the same for all tests: */
1400     if (bThreads)
1401     {
1402         sprintf(cmd_stub, "%s%s", cmd_mdrun, cmd_np);
1403     }
1404     else
1405     {
1406         sprintf(cmd_stub, "%s%s%s ", cmd_mpirun, cmd_np, cmd_mdrun);
1407     }
1408
1409     /* Create a list of numbers of PME nodes to test */
1410     if (npme_fixed < -1)
1411     {
1412         make_npme_list(npmevalues_opt, pmeentries, &nPMEnodes,
1413                        nnodes, minPMEnodes, maxPMEnodes);
1414     }
1415     else
1416     {
1417         *pmeentries  = 1;
1418         snew(nPMEnodes, 1);
1419         nPMEnodes[0] = npme_fixed;
1420         fprintf(stderr, "Will use a fixed number of %d PME-only nodes.\n", nPMEnodes[0]);
1421     }
1422
1423     if (0 == repeats)
1424     {
1425         fprintf(fp, "\nNo benchmarks done since number of repeats (-r) is 0.\n");
1426         gmx_ffclose(fp);
1427         finalize(opt2fn("-p", nfile, fnm));
1428         exit(0);
1429     }
1430
1431     /* Allocate one dataset for each tpr input file: */
1432     init_perfdata(perfdata, nr_tprs, *pmeentries, repeats);
1433
1434     /*****************************************/
1435     /* Main loop over all tpr files to test: */
1436     /*****************************************/
1437     totaltests = nr_tprs*(*pmeentries)*repeats;
1438     for (k = 0; k < nr_tprs; k++)
1439     {
1440         fprintf(fp, "\nIndividual timings for input file %d (%s):\n", k, tpr_names[k]);
1441         fprintf(fp, "PME nodes      Gcycles       ns/day        PME/f    Remark\n");
1442         /* Loop over various numbers of PME nodes: */
1443         for (i = 0; i < *pmeentries; i++)
1444         {
1445             pd = &perfdata[k][i];
1446
1447             /* Loop over the repeats for each scenario: */
1448             for (nr = 0; nr < repeats; nr++)
1449             {
1450                 pd->nPMEnodes = nPMEnodes[i];
1451
1452                 /* Add -npme and -s to the command line and save it. Note that
1453                  * the -passall (if set) options requires cmd_args_bench to be
1454                  * at the end of the command line string */
1455                 snew(pd->mdrun_cmd_line, cmdline_length);
1456                 sprintf(pd->mdrun_cmd_line, "%s-npme %d -s %s %s",
1457                         cmd_stub, pd->nPMEnodes, tpr_names[k], cmd_args_bench);
1458
1459                 /* To prevent that all benchmarks fail due to a show-stopper argument
1460                  * on the mdrun command line, we make a quick check with mdrun -h first */
1461                 if (bFirst)
1462                 {
1463                     make_sure_it_runs(pd->mdrun_cmd_line, cmdline_length, fp);
1464                 }
1465                 bFirst = FALSE;
1466
1467                 /* Do a benchmark simulation: */
1468                 if (repeats > 1)
1469                 {
1470                     sprintf(buf, ", pass %d/%d", nr+1, repeats);
1471                 }
1472                 else
1473                 {
1474                     buf[0] = '\0';
1475                 }
1476                 fprintf(stdout, "\n=== Progress %2.0f%%, tpr %d/%d, run %d/%d%s:\n",
1477                         (100.0*count)/totaltests,
1478                         k+1, nr_tprs, i+1, *pmeentries, buf);
1479                 make_backup(opt2fn("-err", nfile, fnm));
1480                 sprintf(command, "%s 1> /dev/null 2>%s", pd->mdrun_cmd_line, opt2fn("-err", nfile, fnm));
1481                 fprintf(stdout, "%s\n", pd->mdrun_cmd_line);
1482                 gmx_system_call(command);
1483
1484                 /* Collect the performance data from the log file; also check stderr
1485                  * for fatal errors */
1486                 ret = parse_logfile(opt2fn("-bg", nfile, fnm), opt2fn("-err", nfile, fnm),
1487                                     pd, nr, presteps, cpt_steps, nnodes);
1488                 if ((presteps > 0) && (ret == eParselogResetProblem))
1489                 {
1490                     bResetProblem = TRUE;
1491                 }
1492
1493                 if (-1 == pd->nPMEnodes)
1494                 {
1495                     sprintf(buf, "(%3d)", pd->guessPME);
1496                 }
1497                 else
1498                 {
1499                     sprintf(buf, "     ");
1500                 }
1501
1502                 /* Nicer output */
1503                 if (pd->PME_f_load[nr] > 0.0)
1504                 {
1505                     sprintf(str_PME_f_load, "%12.3f", pd->PME_f_load[nr]);
1506                 }
1507                 else
1508                 {
1509                     sprintf(str_PME_f_load, "%s", "         -  ");
1510                 }
1511
1512                 /* Write the data we got to disk */
1513                 fprintf(fp, "%4d%s %12.3f %12.3f %s    %s", pd->nPMEnodes,
1514                         buf, pd->Gcycles[nr], pd->ns_per_day[nr], str_PME_f_load, ParseLog[ret]);
1515                 if (!(ret == eParselogOK || ret == eParselogNoDDGrid || ret == eParselogNotFound) )
1516                 {
1517                     fprintf(fp, " Check %s file for problems.", ret == eParselogFatal ? "err" : "log");
1518                 }
1519                 fprintf(fp, "\n");
1520                 fflush(fp);
1521                 count++;
1522
1523                 /* Do some cleaning up and delete the files we do not need any more */
1524                 cleanup(fnm, nfile, k, nnodes, pd->nPMEnodes, nr, ret == eParselogFatal);
1525
1526                 /* If the first run with this number of processors already failed, do not try again: */
1527                 if (pd->Gcycles[0] <= 0.0 && repeats > 1)
1528                 {
1529                     fprintf(stdout, "Skipping remaining passes of unsuccessful setting, see log file for details.\n");
1530                     count += repeats-(nr+1);
1531                     break;
1532                 }
1533             } /* end of repeats loop */
1534         }     /* end of -npme loop */
1535     }         /* end of tpr file loop */
1536
1537     if (bResetProblem)
1538     {
1539         sep_line(fp);
1540         fprintf(fp, "WARNING: The cycle and time step counters could not be reset properly. ");
1541         sep_line(fp);
1542     }
1543     sfree(command);
1544     sfree(cmd_stub);
1545 }
1546
1547
1548 static void check_input(
1549         int             nnodes,
1550         int             repeats,
1551         int            *ntprs,
1552         real           *rmin,
1553         real            rcoulomb,
1554         real           *rmax,
1555         real            maxPMEfraction,
1556         real            minPMEfraction,
1557         int             npme_fixed,
1558         gmx_int64_t     bench_nsteps,
1559         const t_filenm *fnm,
1560         int             nfile,
1561         int             sim_part,
1562         int             presteps,
1563         int             npargs,
1564         t_pargs        *pa)
1565 {
1566     int old;
1567
1568
1569     /* Make sure the input file exists */
1570     if (!gmx_fexist(opt2fn("-s", nfile, fnm)))
1571     {
1572         gmx_fatal(FARGS, "File %s not found.", opt2fn("-s", nfile, fnm));
1573     }
1574
1575     /* Make sure that the checkpoint file is not overwritten during benchmarking */
1576     if ( (0 == strcmp(opt2fn("-cpi", nfile, fnm), opt2fn("-bcpo", nfile, fnm)) ) && (sim_part > 1) )
1577     {
1578         gmx_fatal(FARGS, "Checkpoint input (-cpi) and benchmark checkpoint output (-bcpo) files must not be identical.\n"
1579                   "The checkpoint input file must not be overwritten during the benchmarks.\n");
1580     }
1581
1582     /* Make sure that repeats is >= 0 (if == 0, only write tpr files) */
1583     if (repeats < 0)
1584     {
1585         gmx_fatal(FARGS, "Number of repeats < 0!");
1586     }
1587
1588     /* Check number of nodes */
1589     if (nnodes < 1)
1590     {
1591         gmx_fatal(FARGS, "Number of nodes/threads must be a positive integer.");
1592     }
1593
1594     /* Automatically choose -ntpr if not set */
1595     if (*ntprs < 1)
1596     {
1597         if (nnodes < 16)
1598         {
1599             *ntprs = 1;
1600         }
1601         else
1602         {
1603             *ntprs = 3;
1604             /* Set a reasonable scaling factor for rcoulomb */
1605             if (*rmax <= 0)
1606             {
1607                 *rmax = rcoulomb * 1.2;
1608             }
1609         }
1610         fprintf(stderr, "Will test %d tpr file%s.\n", *ntprs, *ntprs == 1 ? "" : "s");
1611     }
1612     else
1613     {
1614         if (1 == *ntprs)
1615         {
1616             fprintf(stderr, "Note: Choose ntpr>1 to shift PME load between real and reciprocal space.\n");
1617         }
1618     }
1619
1620     /* Make shure that rmin <= rcoulomb <= rmax */
1621     if (*rmin <= 0)
1622     {
1623         *rmin = rcoulomb;
1624     }
1625     if (*rmax <= 0)
1626     {
1627         *rmax = rcoulomb;
1628     }
1629     if (!(*rmin <= *rmax) )
1630     {
1631         gmx_fatal(FARGS, "Please choose the Coulomb radii such that rmin <= rmax.\n"
1632                   "rmin = %g, rmax = %g, actual rcoul from .tpr file = %g\n", *rmin, *rmax, rcoulomb);
1633     }
1634     /* Add test scenarios if rmin or rmax were set */
1635     if (*ntprs <= 2)
1636     {
1637         if (!is_equal(*rmin, rcoulomb) && (*ntprs == 1) )
1638         {
1639             (*ntprs)++;
1640             fprintf(stderr, "NOTE: Setting -rmin to %g changed -ntpr to %d\n",
1641                     *rmin, *ntprs);
1642         }
1643         if (!is_equal(*rmax, rcoulomb) && (*ntprs == 1) )
1644         {
1645             (*ntprs)++;
1646             fprintf(stderr, "NOTE: Setting -rmax to %g changed -ntpr to %d\n",
1647                     *rmax, *ntprs);
1648         }
1649     }
1650     old = *ntprs;
1651     /* If one of rmin, rmax is set, we need 2 tpr files at minimum */
1652     if (!is_equal(*rmax, rcoulomb) || !is_equal(*rmin, rcoulomb) )
1653     {
1654         *ntprs = max(*ntprs, 2);
1655     }
1656
1657     /* If both rmin, rmax are set, we need 3 tpr files at minimum */
1658     if (!is_equal(*rmax, rcoulomb) && !is_equal(*rmin, rcoulomb) )
1659     {
1660         *ntprs = max(*ntprs, 3);
1661     }
1662
1663     if (old != *ntprs)
1664     {
1665         fprintf(stderr, "NOTE: Your rmin, rmax setting changed -ntpr to %d\n", *ntprs);
1666     }
1667
1668     if (*ntprs > 1)
1669     {
1670         if (is_equal(*rmin, rcoulomb) && is_equal(rcoulomb, *rmax)) /* We have just a single rc */
1671         {
1672             fprintf(stderr, "WARNING: Resetting -ntpr to 1 since no Coulomb radius scaling is requested.\n"
1673                     "Please set rmin < rmax to test Coulomb radii in the [rmin, rmax] interval\n"
1674                     "with correspondingly adjusted PME grid settings\n");
1675             *ntprs = 1;
1676         }
1677     }
1678
1679     /* Check whether max and min fraction are within required values */
1680     if (maxPMEfraction > 0.5 || maxPMEfraction < 0)
1681     {
1682         gmx_fatal(FARGS, "-max must be between 0 and 0.5");
1683     }
1684     if (minPMEfraction > 0.5 || minPMEfraction < 0)
1685     {
1686         gmx_fatal(FARGS, "-min must be between 0 and 0.5");
1687     }
1688     if (maxPMEfraction < minPMEfraction)
1689     {
1690         gmx_fatal(FARGS, "-max must be larger or equal to -min");
1691     }
1692
1693     /* Check whether the number of steps - if it was set - has a reasonable value */
1694     if (bench_nsteps < 0)
1695     {
1696         gmx_fatal(FARGS, "Number of steps must be positive.");
1697     }
1698
1699     if (bench_nsteps > 10000 || bench_nsteps < 100)
1700     {
1701         fprintf(stderr, "WARNING: steps=");
1702         fprintf(stderr, "%"GMX_PRId64, bench_nsteps);
1703         fprintf(stderr, ". Are you sure you want to perform so %s steps for each benchmark?\n", (bench_nsteps < 100) ? "few" : "many");
1704     }
1705
1706     if (presteps < 0)
1707     {
1708         gmx_fatal(FARGS, "Cannot have a negative number of presteps.\n");
1709     }
1710
1711     /* Check for rcoulomb scaling if more than one .tpr file is tested */
1712     if (*ntprs > 1)
1713     {
1714         if (*rmin/rcoulomb < 0.75 || *rmax/rcoulomb > 1.25)
1715         {
1716             fprintf(stderr, "WARNING: Applying extreme scaling factor. I hope you know what you are doing.\n");
1717         }
1718     }
1719
1720     /* If a fixed number of PME nodes is set we do rcoulomb and PME gird tuning
1721      * only. We need to check whether the requested number of PME-only nodes
1722      * makes sense. */
1723     if (npme_fixed > -1)
1724     {
1725         /* No more than 50% of all nodes can be assigned as PME-only nodes. */
1726         if (2*npme_fixed > nnodes)
1727         {
1728             gmx_fatal(FARGS, "Cannot have more than %d PME-only nodes for a total of %d nodes (you chose %d).\n",
1729                       nnodes/2, nnodes, npme_fixed);
1730         }
1731         if ((npme_fixed > 0) && (5*npme_fixed < nnodes))
1732         {
1733             fprintf(stderr, "WARNING: Only %g percent of the nodes are assigned as PME-only nodes.\n",
1734                     100.0*((real)npme_fixed / (real)nnodes));
1735         }
1736         if (opt2parg_bSet("-min", npargs, pa) || opt2parg_bSet("-max", npargs, pa))
1737         {
1738             fprintf(stderr, "NOTE: The -min, -max, and -npme options have no effect when a\n"
1739                     "      fixed number of PME-only nodes is requested with -fix.\n");
1740         }
1741     }
1742 }
1743
1744
1745 /* Returns TRUE when "opt" is needed at launch time */
1746 static gmx_bool is_launch_file(char *opt, gmx_bool bSet)
1747 {
1748     if (0 == strncmp(opt, "-swap", 5))
1749     {
1750         return bSet;
1751     }
1752
1753     /* Apart from the input .tpr and the output log files we need all options that
1754      * were set on the command line and that do not start with -b */
1755     if    (0 == strncmp(opt, "-b", 2) || 0 == strncmp(opt, "-s", 2)
1756            || 0 == strncmp(opt, "-err", 4) || 0 == strncmp(opt, "-p", 2) )
1757     {
1758         return FALSE;
1759     }
1760
1761     return bSet;
1762 }
1763
1764
1765 /* Returns TRUE when "opt" defines a file which is needed for the benchmarks runs */
1766 static gmx_bool is_bench_file(char *opt, gmx_bool bSet, gmx_bool bOptional, gmx_bool bIsOutput)
1767 {
1768     /* Apart from the input .tpr, all files starting with "-b" are for
1769      * _b_enchmark files exclusively */
1770     if (0 == strncmp(opt, "-s", 2))
1771     {
1772         return FALSE;
1773     }
1774
1775     if (0 == strncmp(opt, "-b", 2) || 0 == strncmp(opt, "-s", 2))
1776     {
1777         if (!bOptional || bSet)
1778         {
1779             return TRUE;
1780         }
1781         else
1782         {
1783             return FALSE;
1784         }
1785     }
1786     else
1787     {
1788         if (bIsOutput)
1789         {
1790             return FALSE;
1791         }
1792         else
1793         {
1794             if (bSet) /* These are additional input files like -cpi -ei */
1795             {
1796                 return TRUE;
1797             }
1798             else
1799             {
1800                 return FALSE;
1801             }
1802         }
1803     }
1804 }
1805
1806
1807 /* Adds 'buf' to 'str' */
1808 static void add_to_string(char **str, char *buf)
1809 {
1810     int len;
1811
1812
1813     len = strlen(*str) + strlen(buf) + 1;
1814     srenew(*str, len);
1815     strcat(*str, buf);
1816 }
1817
1818
1819 /* Create the command line for the benchmark as well as for the real run */
1820 static void create_command_line_snippets(
1821         gmx_bool  bAppendFiles,
1822         gmx_bool  bKeepAndNumCPT,
1823         gmx_bool  bResetHWay,
1824         int       presteps,
1825         int       nfile,
1826         t_filenm  fnm[],
1827         char     *cmd_args_bench[],  /* command line arguments for benchmark runs */
1828         char     *cmd_args_launch[], /* command line arguments for simulation run */
1829         char      extra_args[])      /* Add this to the end of the command line */
1830 {
1831     int         i;
1832     char       *opt;
1833     const char *name;
1834     char        strbuf[STRLEN];
1835
1836
1837     /* strlen needs at least '\0' as a string: */
1838     snew(*cmd_args_bench, 1);
1839     snew(*cmd_args_launch, 1);
1840     *cmd_args_launch[0] = '\0';
1841     *cmd_args_bench[0]  = '\0';
1842
1843
1844     /*******************************************/
1845     /* 1. Process other command line arguments */
1846     /*******************************************/
1847     if (presteps > 0)
1848     {
1849         /* Add equilibration steps to benchmark options */
1850         sprintf(strbuf, "-resetstep %d ", presteps);
1851         add_to_string(cmd_args_bench, strbuf);
1852     }
1853     /* These switches take effect only at launch time */
1854     if (FALSE == bAppendFiles)
1855     {
1856         add_to_string(cmd_args_launch, "-noappend ");
1857     }
1858     if (bKeepAndNumCPT)
1859     {
1860         add_to_string(cmd_args_launch, "-cpnum ");
1861     }
1862     if (bResetHWay)
1863     {
1864         add_to_string(cmd_args_launch, "-resethway ");
1865     }
1866
1867     /********************/
1868     /* 2. Process files */
1869     /********************/
1870     for (i = 0; i < nfile; i++)
1871     {
1872         opt  = (char *)fnm[i].opt;
1873         name = opt2fn(opt, nfile, fnm);
1874
1875         /* Strbuf contains the options, now let's sort out where we need that */
1876         sprintf(strbuf, "%s %s ", opt, name);
1877
1878         if (is_bench_file(opt, opt2bSet(opt, nfile, fnm), is_optional(&fnm[i]), is_output(&fnm[i])) )
1879         {
1880             /* All options starting with -b* need the 'b' removed,
1881              * therefore overwrite strbuf */
1882             if (0 == strncmp(opt, "-b", 2))
1883             {
1884                 sprintf(strbuf, "-%s %s ", &opt[2], name);
1885             }
1886
1887             add_to_string(cmd_args_bench, strbuf);
1888         }
1889
1890         if (is_launch_file(opt, opt2bSet(opt, nfile, fnm)) )
1891         {
1892             add_to_string(cmd_args_launch, strbuf);
1893         }
1894     }
1895
1896     add_to_string(cmd_args_bench, extra_args);
1897     add_to_string(cmd_args_launch, extra_args);
1898 }
1899
1900
1901 /* Set option opt */
1902 static void setopt(const char *opt, int nfile, t_filenm fnm[])
1903 {
1904     int i;
1905
1906     for (i = 0; (i < nfile); i++)
1907     {
1908         if (strcmp(opt, fnm[i].opt) == 0)
1909         {
1910             fnm[i].flag |= ffSET;
1911         }
1912     }
1913 }
1914
1915
1916 /* This routine inspects the tpr file and ...
1917  * 1. checks for output files that get triggered by a tpr option. These output
1918  *    files are marked as 'set' to allow for a proper cleanup after each
1919  *    tuning run.
1920  * 2. returns the PME:PP load ratio
1921  * 3. returns rcoulomb from the tpr */
1922 static float inspect_tpr(int nfile, t_filenm fnm[], real *rcoulomb)
1923 {
1924     gmx_bool     bPull;     /* Is pulling requested in .tpr file?             */
1925     gmx_bool     bTpi;      /* Is test particle insertion requested?          */
1926     gmx_bool     bFree;     /* Is a free energy simulation requested?         */
1927     gmx_bool     bNM;       /* Is a normal mode analysis requested?           */
1928     gmx_bool     bSwap;     /* Is water/ion position swapping requested?      */
1929     t_inputrec   ir;
1930     t_state      state;
1931     gmx_mtop_t   mtop;
1932
1933
1934     /* Check tpr file for options that trigger extra output files */
1935     read_tpx_state(opt2fn("-s", nfile, fnm), &ir, &state, NULL, &mtop);
1936     bPull = (epullNO != ir.ePull);
1937     bFree = (efepNO  != ir.efep );
1938     bNM   = (eiNM    == ir.eI   );
1939     bSwap = (eswapNO != ir.eSwapCoords);
1940     bTpi  = EI_TPI(ir.eI);
1941
1942     /* Set these output files on the tuning command-line */
1943     if (bPull)
1944     {
1945         setopt("-pf", nfile, fnm);
1946         setopt("-px", nfile, fnm);
1947     }
1948     if (bFree)
1949     {
1950         setopt("-dhdl", nfile, fnm);
1951     }
1952     if (bTpi)
1953     {
1954         setopt("-tpi", nfile, fnm);
1955         setopt("-tpid", nfile, fnm);
1956     }
1957     if (bNM)
1958     {
1959         setopt("-mtx", nfile, fnm);
1960     }
1961     if (bSwap)
1962     {
1963         setopt("-swap", nfile, fnm);
1964     }
1965
1966     *rcoulomb = ir.rcoulomb;
1967
1968     /* Return the estimate for the number of PME nodes */
1969     return pme_load_estimate(&mtop, &ir, state.box);
1970 }
1971
1972
1973 static void couple_files_options(int nfile, t_filenm fnm[])
1974 {
1975     int      i;
1976     gmx_bool bSet, bBench;
1977     char    *opt;
1978     char     buf[20];
1979
1980
1981     for (i = 0; i < nfile; i++)
1982     {
1983         opt    = (char *)fnm[i].opt;
1984         bSet   = ((fnm[i].flag & ffSET) != 0);
1985         bBench = (0 == strncmp(opt, "-b", 2));
1986
1987         /* Check optional files */
1988         /* If e.g. -eo is set, then -beo also needs to be set */
1989         if (is_optional(&fnm[i]) && bSet && !bBench)
1990         {
1991             sprintf(buf, "-b%s", &opt[1]);
1992             setopt(buf, nfile, fnm);
1993         }
1994         /* If -beo is set, then -eo also needs to be! */
1995         if (is_optional(&fnm[i]) && bSet && bBench)
1996         {
1997             sprintf(buf, "-%s", &opt[2]);
1998             setopt(buf, nfile, fnm);
1999         }
2000     }
2001 }
2002
2003
2004 #define BENCHSTEPS (1000)
2005
2006 int gmx_tune_pme(int argc, char *argv[])
2007 {
2008     const char     *desc[] = {
2009         "For a given number [TT]-np[tt] or [TT]-ntmpi[tt] of processors/threads, [THISMODULE] systematically",
2010         "times [gmx-mdrun] with various numbers of PME-only nodes and determines",
2011         "which setting is fastest. It will also test whether performance can",
2012         "be enhanced by shifting load from the reciprocal to the real space",
2013         "part of the Ewald sum. ",
2014         "Simply pass your [TT].tpr[tt] file to [THISMODULE] together with other options",
2015         "for [gmx-mdrun] as needed.[PAR]",
2016         "Which executables are used can be set in the environment variables",
2017         "MPIRUN and MDRUN. If these are not present, 'mpirun' and 'mdrun'",
2018         "will be used as defaults. Note that for certain MPI frameworks you",
2019         "need to provide a machine- or hostfile. This can also be passed",
2020         "via the MPIRUN variable, e.g.[PAR]",
2021         "[TT]export MPIRUN=\"/usr/local/mpirun -machinefile hosts\"[tt][PAR]",
2022         "Please call [THISMODULE] with the normal options you would pass to",
2023         "[gmx-mdrun] and add [TT]-np[tt] for the number of processors to perform the",
2024         "tests on, or [TT]-ntmpi[tt] for the number of threads. You can also add [TT]-r[tt]",
2025         "to repeat each test several times to get better statistics. [PAR]",
2026         "[THISMODULE] can test various real space / reciprocal space workloads",
2027         "for you. With [TT]-ntpr[tt] you control how many extra [TT].tpr[tt] files will be",
2028         "written with enlarged cutoffs and smaller Fourier grids respectively.",
2029         "Typically, the first test (number 0) will be with the settings from the input",
2030         "[TT].tpr[tt] file; the last test (number [TT]ntpr[tt]) will have the Coulomb cutoff",
2031         "specified by [TT]-rmax[tt] with a somwhat smaller PME grid at the same time. ",
2032         "In this last test, the Fourier spacing is multiplied with [TT]rmax[tt]/rcoulomb. ",
2033         "The remaining [TT].tpr[tt] files will have equally-spaced Coulomb radii (and Fourier "
2034         "spacings) between these extremes. [BB]Note[bb] that you can set [TT]-ntpr[tt] to 1",
2035         "if you just seek the optimal number of PME-only nodes; in that case",
2036         "your input [TT].tpr[tt] file will remain unchanged.[PAR]",
2037         "For the benchmark runs, the default of 1000 time steps should suffice for most",
2038         "MD systems. The dynamic load balancing needs about 100 time steps",
2039         "to adapt to local load imbalances, therefore the time step counters",
2040         "are by default reset after 100 steps. For large systems (>1M atoms), as well as ",
2041         "for a higher accuarcy of the measurements, you should set [TT]-resetstep[tt] to a higher value.",
2042         "From the 'DD' load imbalance entries in the md.log output file you",
2043         "can tell after how many steps the load is sufficiently balanced. Example call:[PAR]"
2044         "[TT]gmx tune_pme -np 64 -s protein.tpr -launch[tt][PAR]",
2045         "After calling [gmx-mdrun] several times, detailed performance information",
2046         "is available in the output file [TT]perf.out[tt].",
2047         "[BB]Note[bb] that during the benchmarks, a couple of temporary files are written",
2048         "(options [TT]-b[tt]*), these will be automatically deleted after each test.[PAR]",
2049         "If you want the simulation to be started automatically with the",
2050         "optimized parameters, use the command line option [TT]-launch[tt].[PAR]",
2051     };
2052
2053     int             nnodes         = 1;
2054     int             repeats        = 2;
2055     int             pmeentries     = 0; /* How many values for -npme do we actually test for each tpr file */
2056     real            maxPMEfraction = 0.50;
2057     real            minPMEfraction = 0.25;
2058     int             maxPMEnodes, minPMEnodes;
2059     float           guessPMEratio;                    /* guessed PME:PP ratio based on the tpr file */
2060     float           guessPMEnodes;
2061     int             npme_fixed     = -2;              /* If >= -1, use only this number
2062                                                        * of PME-only nodes                */
2063     int             ntprs          = 0;
2064     real            rmin           = 0.0, rmax = 0.0; /* min and max value for rcoulomb if scaling is requested */
2065     real            rcoulomb       = -1.0;            /* Coulomb radius as set in .tpr file */
2066     gmx_bool        bScaleRvdw     = TRUE;
2067     gmx_int64_t     bench_nsteps   = BENCHSTEPS;
2068     gmx_int64_t     new_sim_nsteps = -1;  /* -1 indicates: not set by the user */
2069     gmx_int64_t     cpt_steps      = 0;   /* Step counter in .cpt input file   */
2070     int             presteps       = 100; /* Do a full cycle reset after presteps steps */
2071     gmx_bool        bOverwrite     = FALSE, bKeepTPR;
2072     gmx_bool        bLaunch        = FALSE;
2073     char           *ExtraArgs      = NULL;
2074     char          **tpr_names      = NULL;
2075     const char     *simulation_tpr = NULL;
2076     int             best_npme, best_tpr;
2077     int             sim_part = 1; /* For benchmarks with checkpoint files */
2078     char            bbuf[STRLEN];
2079
2080     /* Default program names if nothing else is found */
2081     char         *cmd_mpirun = NULL, *cmd_mdrun = NULL;
2082     char         *cmd_args_bench, *cmd_args_launch;
2083     char         *cmd_np = NULL;
2084
2085     t_perf      **perfdata = NULL;
2086     t_inputinfo  *info;
2087     int           i;
2088     FILE         *fp;
2089     t_commrec    *cr;
2090
2091     /* Print out how long the tuning took */
2092     double          seconds;
2093
2094     static t_filenm fnm[] = {
2095         /* g_tune_pme */
2096         { efOUT, "-p",      "perf",     ffWRITE },
2097         { efLOG, "-err",    "bencherr", ffWRITE },
2098         { efTPX, "-so",     "tuned",    ffWRITE },
2099         /* mdrun: */
2100         { efTPX, NULL,      NULL,       ffREAD },
2101         { efTRN, "-o",      NULL,       ffWRITE },
2102         { efCOMPRESSED, "-x", NULL,     ffOPTWR },
2103         { efCPT, "-cpi",    NULL,       ffOPTRD },
2104         { efCPT, "-cpo",    NULL,       ffOPTWR },
2105         { efSTO, "-c",      "confout",  ffWRITE },
2106         { efEDR, "-e",      "ener",     ffWRITE },
2107         { efLOG, "-g",      "md",       ffWRITE },
2108         { efXVG, "-dhdl",   "dhdl",     ffOPTWR },
2109         { efXVG, "-field",  "field",    ffOPTWR },
2110         { efXVG, "-table",  "table",    ffOPTRD },
2111         { efXVG, "-tabletf", "tabletf",   ffOPTRD },
2112         { efXVG, "-tablep", "tablep",   ffOPTRD },
2113         { efXVG, "-tableb", "table",    ffOPTRD },
2114         { efTRX, "-rerun",  "rerun",    ffOPTRD },
2115         { efXVG, "-tpi",    "tpi",      ffOPTWR },
2116         { efXVG, "-tpid",   "tpidist",  ffOPTWR },
2117         { efEDI, "-ei",     "sam",      ffOPTRD },
2118         { efXVG, "-eo",     "edsam",    ffOPTWR },
2119         { efXVG, "-devout", "deviatie", ffOPTWR },
2120         { efXVG, "-runav",  "runaver",  ffOPTWR },
2121         { efXVG, "-px",     "pullx",    ffOPTWR },
2122         { efXVG, "-pf",     "pullf",    ffOPTWR },
2123         { efXVG, "-ro",     "rotation", ffOPTWR },
2124         { efLOG, "-ra",     "rotangles", ffOPTWR },
2125         { efLOG, "-rs",     "rotslabs", ffOPTWR },
2126         { efLOG, "-rt",     "rottorque", ffOPTWR },
2127         { efMTX, "-mtx",    "nm",       ffOPTWR },
2128         { efNDX, "-dn",     "dipole",   ffOPTWR },
2129         { efXVG, "-swap",   "swapions", ffOPTWR },
2130         /* Output files that are deleted after each benchmark run */
2131         { efTRN, "-bo",     "bench",    ffWRITE },
2132         { efXTC, "-bx",     "bench",    ffWRITE },
2133         { efCPT, "-bcpo",   "bench",    ffWRITE },
2134         { efSTO, "-bc",     "bench",    ffWRITE },
2135         { efEDR, "-be",     "bench",    ffWRITE },
2136         { efLOG, "-bg",     "bench",    ffWRITE },
2137         { efXVG, "-beo",    "benchedo", ffOPTWR },
2138         { efXVG, "-bdhdl",  "benchdhdl", ffOPTWR },
2139         { efXVG, "-bfield", "benchfld", ffOPTWR },
2140         { efXVG, "-btpi",   "benchtpi", ffOPTWR },
2141         { efXVG, "-btpid",  "benchtpid", ffOPTWR },
2142         { efXVG, "-bdevout", "benchdev", ffOPTWR },
2143         { efXVG, "-brunav", "benchrnav", ffOPTWR },
2144         { efXVG, "-bpx",    "benchpx",  ffOPTWR },
2145         { efXVG, "-bpf",    "benchpf",  ffOPTWR },
2146         { efXVG, "-bro",    "benchrot", ffOPTWR },
2147         { efLOG, "-bra",    "benchrota", ffOPTWR },
2148         { efLOG, "-brs",    "benchrots", ffOPTWR },
2149         { efLOG, "-brt",    "benchrott", ffOPTWR },
2150         { efMTX, "-bmtx",   "benchn",   ffOPTWR },
2151         { efNDX, "-bdn",    "bench",    ffOPTWR },
2152         { efXVG, "-bswap",  "benchswp", ffOPTWR }
2153     };
2154
2155     gmx_bool        bThreads     = FALSE;
2156
2157     int             nthreads = 1;
2158
2159     const char     *procstring[] =
2160     { NULL, "-np", "-n", "none", NULL };
2161     const char     *npmevalues_opt[] =
2162     { NULL, "auto", "all", "subset", NULL };
2163
2164     gmx_bool     bAppendFiles          = TRUE;
2165     gmx_bool     bKeepAndNumCPT        = FALSE;
2166     gmx_bool     bResetCountersHalfWay = FALSE;
2167     gmx_bool     bBenchmark            = TRUE;
2168
2169     output_env_t oenv = NULL;
2170
2171     t_pargs      pa[] = {
2172         /***********************/
2173         /* g_tune_pme options: */
2174         /***********************/
2175         { "-np",       FALSE, etINT,  {&nnodes},
2176           "Number of nodes to run the tests on (must be > 2 for separate PME nodes)" },
2177         { "-npstring", FALSE, etENUM, {procstring},
2178           "Specify the number of processors to [TT]$MPIRUN[tt] using this string"},
2179         { "-ntmpi",    FALSE, etINT,  {&nthreads},
2180           "Number of MPI-threads to run the tests on (turns MPI & mpirun off)"},
2181         { "-r",        FALSE, etINT,  {&repeats},
2182           "Repeat each test this often" },
2183         { "-max",      FALSE, etREAL, {&maxPMEfraction},
2184           "Max fraction of PME nodes to test with" },
2185         { "-min",      FALSE, etREAL, {&minPMEfraction},
2186           "Min fraction of PME nodes to test with" },
2187         { "-npme",     FALSE, etENUM, {npmevalues_opt},
2188           "Within -min and -max, benchmark all possible values for [TT]-npme[tt], or just a reasonable subset. "
2189           "Auto neglects -min and -max and chooses reasonable values around a guess for npme derived from the .tpr"},
2190         { "-fix",      FALSE, etINT,  {&npme_fixed},
2191           "If >= -1, do not vary the number of PME-only nodes, instead use this fixed value and only vary rcoulomb and the PME grid spacing."},
2192         { "-rmax",     FALSE, etREAL, {&rmax},
2193           "If >0, maximal rcoulomb for -ntpr>1 (rcoulomb upscaling results in fourier grid downscaling)" },
2194         { "-rmin",     FALSE, etREAL, {&rmin},
2195           "If >0, minimal rcoulomb for -ntpr>1" },
2196         { "-scalevdw",  FALSE, etBOOL, {&bScaleRvdw},
2197           "Scale rvdw along with rcoulomb"},
2198         { "-ntpr",     FALSE, etINT,  {&ntprs},
2199           "Number of [TT].tpr[tt] files to benchmark. Create this many files with different rcoulomb scaling factors depending on -rmin and -rmax. "
2200           "If < 1, automatically choose the number of [TT].tpr[tt] files to test" },
2201         { "-steps",    FALSE, etINT64, {&bench_nsteps},
2202           "Take timings for this many steps in the benchmark runs" },
2203         { "-resetstep", FALSE, etINT,  {&presteps},
2204           "Let dlb equilibrate this many steps before timings are taken (reset cycle counters after this many steps)" },
2205         { "-simsteps", FALSE, etINT64, {&new_sim_nsteps},
2206           "If non-negative, perform this many steps in the real run (overwrites nsteps from [TT].tpr[tt], add [TT].cpt[tt] steps)" },
2207         { "-launch",   FALSE, etBOOL, {&bLaunch},
2208           "Launch the real simulation after optimization" },
2209         { "-bench",    FALSE, etBOOL, {&bBenchmark},
2210           "Run the benchmarks or just create the input [TT].tpr[tt] files?" },
2211         /******************/
2212         /* mdrun options: */
2213         /******************/
2214         /* We let g_tune_pme parse and understand these options, because we need to
2215          * prevent that they appear on the mdrun command line for the benchmarks */
2216         { "-append",   FALSE, etBOOL, {&bAppendFiles},
2217           "Append to previous output files when continuing from checkpoint instead of adding the simulation part number to all file names (for launch only)" },
2218         { "-cpnum",    FALSE, etBOOL, {&bKeepAndNumCPT},
2219           "Keep and number checkpoint files (launch only)" },
2220         { "-resethway", FALSE, etBOOL, {&bResetCountersHalfWay},
2221           "HIDDENReset the cycle counters after half the number of steps or halfway [TT]-maxh[tt] (launch only)" }
2222     };
2223
2224 #define NFILE asize(fnm)
2225
2226     seconds = gmx_gettime();
2227
2228     if (!parse_common_args(&argc, argv, PCA_NOEXIT_ON_ARGS,
2229                            NFILE, fnm, asize(pa), pa, asize(desc), desc,
2230                            0, NULL, &oenv))
2231     {
2232         return 0;
2233     }
2234
2235     /* Store the remaining unparsed command line entries in a string which
2236      * is then attached to the mdrun command line */
2237     snew(ExtraArgs, 1);
2238     ExtraArgs[0] = '\0';
2239     for (i = 1; i < argc; i++) /* argc will now be 1 if everything was understood */
2240     {
2241         add_to_string(&ExtraArgs, argv[i]);
2242         add_to_string(&ExtraArgs, " ");
2243     }
2244
2245     if (opt2parg_bSet("-ntmpi", asize(pa), pa))
2246     {
2247         bThreads = TRUE;
2248         if (opt2parg_bSet("-npstring", asize(pa), pa))
2249         {
2250             fprintf(stderr, "WARNING: -npstring has no effect when using threads.\n");
2251         }
2252
2253         if (nnodes > 1)
2254         {
2255             gmx_fatal(FARGS, "Can't run multi-threaded MPI simulation yet!");
2256         }
2257         /* and now we just set this; a bit of an ugly hack*/
2258         nnodes = nthreads;
2259     }
2260     /* Check for PME:PP ratio and whether tpr triggers additional output files */
2261     guessPMEratio = inspect_tpr(NFILE, fnm, &rcoulomb);
2262
2263     /* Automatically set -beo options if -eo is set etc. */
2264     couple_files_options(NFILE, fnm);
2265
2266     /* Construct the command line arguments for benchmark runs
2267      * as well as for the simulation run */
2268     if (bThreads)
2269     {
2270         sprintf(bbuf, " -ntmpi %d ", nthreads);
2271     }
2272     else
2273     {
2274         /* This string will be used for MPI runs and will appear after the
2275          * mpirun command. */
2276         if (strcmp(procstring[0], "none") != 0)
2277         {
2278             sprintf(bbuf, " %s %d ", procstring[0], nnodes);
2279         }
2280         else
2281         {
2282             sprintf(bbuf, " ");
2283         }
2284     }
2285
2286     cmd_np = bbuf;
2287
2288     create_command_line_snippets(bAppendFiles, bKeepAndNumCPT, bResetCountersHalfWay, presteps,
2289                                  NFILE, fnm, &cmd_args_bench, &cmd_args_launch, ExtraArgs);
2290
2291     /* Read in checkpoint file if requested */
2292     sim_part = 1;
2293     if (opt2bSet("-cpi", NFILE, fnm))
2294     {
2295         snew(cr, 1);
2296         cr->duty = DUTY_PP; /* makes the following routine happy */
2297         read_checkpoint_simulation_part(opt2fn("-cpi", NFILE, fnm),
2298                                         &sim_part, &cpt_steps, cr,
2299                                         FALSE, NFILE, fnm, NULL, NULL);
2300         sfree(cr);
2301         sim_part++;
2302         /* sim_part will now be 1 if no checkpoint file was found */
2303         if (sim_part <= 1)
2304         {
2305             gmx_fatal(FARGS, "Checkpoint file %s not found!", opt2fn("-cpi", NFILE, fnm));
2306         }
2307     }
2308
2309     /* Open performance output file and write header info */
2310     fp = gmx_ffopen(opt2fn("-p", NFILE, fnm), "w");
2311
2312     /* Make a quick consistency check of command line parameters */
2313     check_input(nnodes, repeats, &ntprs, &rmin, rcoulomb, &rmax,
2314                 maxPMEfraction, minPMEfraction, npme_fixed,
2315                 bench_nsteps, fnm, NFILE, sim_part, presteps,
2316                 asize(pa), pa);
2317
2318     /* Determine the maximum and minimum number of PME nodes to test,
2319      * the actual list of settings is build in do_the_tests(). */
2320     if ((nnodes > 2) && (npme_fixed < -1))
2321     {
2322         if (0 == strcmp(npmevalues_opt[0], "auto"))
2323         {
2324             /* Determine the npme range automatically based on the PME:PP load guess */
2325             if (guessPMEratio > 1.0)
2326             {
2327                 /* More PME than PP work, probably we do not need separate PME nodes at all! */
2328                 maxPMEnodes = nnodes/2;
2329                 minPMEnodes = nnodes/2;
2330             }
2331             else
2332             {
2333                 /* PME : PP load is in the range 0..1, let's test around the guess */
2334                 guessPMEnodes = nnodes/(1.0 + 1.0/guessPMEratio);
2335                 minPMEnodes   = floor(0.7*guessPMEnodes);
2336                 maxPMEnodes   =  ceil(1.6*guessPMEnodes);
2337                 maxPMEnodes   = min(maxPMEnodes, nnodes/2);
2338             }
2339         }
2340         else
2341         {
2342             /* Determine the npme range based on user input */
2343             maxPMEnodes = floor(maxPMEfraction*nnodes);
2344             minPMEnodes = max(floor(minPMEfraction*nnodes), 0);
2345             fprintf(stdout, "Will try runs with %d ", minPMEnodes);
2346             if (maxPMEnodes != minPMEnodes)
2347             {
2348                 fprintf(stdout, "- %d ", maxPMEnodes);
2349             }
2350             fprintf(stdout, "PME-only nodes.\n  Note that the automatic number of PME-only nodes and no separate PME nodes are always tested.\n");
2351         }
2352     }
2353     else
2354     {
2355         maxPMEnodes = 0;
2356         minPMEnodes = 0;
2357     }
2358
2359     /* Get the commands we need to set up the runs from environment variables */
2360     get_program_paths(bThreads, &cmd_mpirun, &cmd_mdrun);
2361     if (bBenchmark && repeats > 0)
2362     {
2363         check_mdrun_works(bThreads, cmd_mpirun, cmd_np, cmd_mdrun);
2364     }
2365
2366     /* Print some header info to file */
2367     sep_line(fp);
2368     fprintf(fp, "\n      P E R F O R M A N C E   R E S U L T S\n");
2369     sep_line(fp);
2370     fprintf(fp, "%s for Gromacs %s\n", ShortProgram(), GromacsVersion());
2371     if (!bThreads)
2372     {
2373         fprintf(fp, "Number of nodes         : %d\n", nnodes);
2374         fprintf(fp, "The mpirun command is   : %s\n", cmd_mpirun);
2375         if (strcmp(procstring[0], "none") != 0)
2376         {
2377             fprintf(fp, "Passing # of nodes via  : %s\n", procstring[0]);
2378         }
2379         else
2380         {
2381             fprintf(fp, "Not setting number of nodes in system call\n");
2382         }
2383     }
2384     else
2385     {
2386         fprintf(fp, "Number of threads       : %d\n", nnodes);
2387     }
2388
2389     fprintf(fp, "The mdrun  command is   : %s\n", cmd_mdrun);
2390     fprintf(fp, "mdrun args benchmarks   : %s\n", cmd_args_bench);
2391     fprintf(fp, "Benchmark steps         : ");
2392     fprintf(fp, "%"GMX_PRId64, bench_nsteps);
2393     fprintf(fp, "\n");
2394     fprintf(fp, "dlb equilibration steps : %d\n", presteps);
2395     if (sim_part > 1)
2396     {
2397         fprintf(fp, "Checkpoint time step    : ");
2398         fprintf(fp, "%"GMX_PRId64, cpt_steps);
2399         fprintf(fp, "\n");
2400     }
2401     fprintf(fp, "mdrun args at launchtime: %s\n", cmd_args_launch);
2402
2403     if (new_sim_nsteps >= 0)
2404     {
2405         bOverwrite = TRUE;
2406         fprintf(stderr, "Note: Simulation input file %s will have ", opt2fn("-so", NFILE, fnm));
2407         fprintf(stderr, "%"GMX_PRId64, new_sim_nsteps+cpt_steps);
2408         fprintf(stderr, " steps.\n");
2409         fprintf(fp, "Simulation steps        : ");
2410         fprintf(fp, "%"GMX_PRId64, new_sim_nsteps);
2411         fprintf(fp, "\n");
2412     }
2413     if (repeats > 1)
2414     {
2415         fprintf(fp, "Repeats for each test   : %d\n", repeats);
2416     }
2417
2418     if (npme_fixed >= -1)
2419     {
2420         fprintf(fp, "Fixing -npme at         : %d\n", npme_fixed);
2421     }
2422
2423     fprintf(fp, "Input file              : %s\n", opt2fn("-s", NFILE, fnm));
2424     fprintf(fp, "   PME/PP load estimate : %g\n", guessPMEratio);
2425
2426     /* Allocate memory for the inputinfo struct: */
2427     snew(info, 1);
2428     info->nr_inputfiles = ntprs;
2429     for (i = 0; i < ntprs; i++)
2430     {
2431         snew(info->rcoulomb, ntprs);
2432         snew(info->rvdw, ntprs);
2433         snew(info->rlist, ntprs);
2434         snew(info->rlistlong, ntprs);
2435         snew(info->nkx, ntprs);
2436         snew(info->nky, ntprs);
2437         snew(info->nkz, ntprs);
2438         snew(info->fsx, ntprs);
2439         snew(info->fsy, ntprs);
2440         snew(info->fsz, ntprs);
2441     }
2442     /* Make alternative tpr files to test: */
2443     snew(tpr_names, ntprs);
2444     for (i = 0; i < ntprs; i++)
2445     {
2446         snew(tpr_names[i], STRLEN);
2447     }
2448
2449     /* It can be that ntprs is reduced by make_benchmark_tprs if not enough
2450      * different grids could be found. */
2451     make_benchmark_tprs(opt2fn("-s", NFILE, fnm), tpr_names, bench_nsteps+presteps,
2452                         cpt_steps, rmin, rmax, bScaleRvdw, &ntprs, info, fp);
2453
2454     /********************************************************************************/
2455     /* Main loop over all scenarios we need to test: tpr files, PME nodes, repeats  */
2456     /********************************************************************************/
2457     snew(perfdata, ntprs);
2458     if (bBenchmark)
2459     {
2460         do_the_tests(fp, tpr_names, maxPMEnodes, minPMEnodes, npme_fixed, npmevalues_opt[0], perfdata, &pmeentries,
2461                      repeats, nnodes, ntprs, bThreads, cmd_mpirun, cmd_np, cmd_mdrun,
2462                      cmd_args_bench, fnm, NFILE, presteps, cpt_steps);
2463
2464         fprintf(fp, "\nTuning took%8.1f minutes.\n", (gmx_gettime()-seconds)/60.0);
2465
2466         /* Analyse the results and give a suggestion for optimal settings: */
2467         bKeepTPR = analyze_data(fp, opt2fn("-p", NFILE, fnm), perfdata, nnodes, ntprs, pmeentries,
2468                                 repeats, info, &best_tpr, &best_npme);
2469
2470         /* Take the best-performing tpr file and enlarge nsteps to original value */
2471         if (bKeepTPR && !bOverwrite)
2472         {
2473             simulation_tpr = opt2fn("-s", NFILE, fnm);
2474         }
2475         else
2476         {
2477             simulation_tpr = opt2fn("-so", NFILE, fnm);
2478             modify_PMEsettings(bOverwrite ? (new_sim_nsteps+cpt_steps) : info->orig_sim_steps,
2479                                info->orig_init_step, tpr_names[best_tpr], simulation_tpr);
2480         }
2481
2482         /* Let's get rid of the temporary benchmark input files */
2483         for (i = 0; i < ntprs; i++)
2484         {
2485             fprintf(stdout, "Deleting temporary benchmark input file %s\n", tpr_names[i]);
2486             remove(tpr_names[i]);
2487         }
2488
2489         /* Now start the real simulation if the user requested it ... */
2490         launch_simulation(bLaunch, fp, bThreads, cmd_mpirun, cmd_np, cmd_mdrun,
2491                           cmd_args_launch, simulation_tpr, best_npme);
2492     }
2493     gmx_ffclose(fp);
2494
2495     /* ... or simply print the performance results to screen: */
2496     if (!bLaunch)
2497     {
2498         finalize(opt2fn("-p", NFILE, fnm));
2499     }
2500
2501     return 0;
2502 }