59411cd54980dca7aed26f1851b1400f6c0bc588
[alexxy/gromacs.git] / src / gromacs / gmxana / gmx_covar.c
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5  * Copyright (c) 2001-2004, The GROMACS development team.
6  * Copyright (c) 2013,2014, by the GROMACS development team, led by
7  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
8  * and including many others, as listed in the AUTHORS file in the
9  * top-level source directory and at http://www.gromacs.org.
10  *
11  * GROMACS is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public License
13  * as published by the Free Software Foundation; either version 2.1
14  * of the License, or (at your option) any later version.
15  *
16  * GROMACS is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with GROMACS; if not, see
23  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
24  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
25  *
26  * If you want to redistribute modifications to GROMACS, please
27  * consider that scientific software is very special. Version
28  * control is crucial - bugs must be traceable. We will be happy to
29  * consider code for inclusion in the official distribution, but
30  * derived work must not be called official GROMACS. Details are found
31  * in the README & COPYING files - if they are missing, get the
32  * official version at http://www.gromacs.org.
33  *
34  * To help us fund GROMACS development, we humbly ask that you cite
35  * the research papers on the package. Check out http://www.gromacs.org.
36  */
37 #include "gmxpre.h"
38
39 #include "config.h"
40
41 #include <math.h>
42 #include <string.h>
43 #include <time.h>
44
45 #ifdef HAVE_SYS_TIME_H
46 #include <sys/time.h>
47 #endif
48
49 #include "gromacs/commandline/pargs.h"
50 #include "gromacs/legacyheaders/typedefs.h"
51 #include "gromacs/utility/smalloc.h"
52 #include "gromacs/legacyheaders/macros.h"
53 #include "gromacs/math/vec.h"
54 #include "gromacs/utility/futil.h"
55 #include "gromacs/topology/index.h"
56 #include "gromacs/fileio/confio.h"
57 #include "gromacs/fileio/trnio.h"
58 #include "gromacs/fileio/xvgr.h"
59 #include "gromacs/pbcutil/rmpbc.h"
60 #include "gromacs/legacyheaders/txtdump.h"
61 #include "gromacs/fileio/matio.h"
62 #include "eigio.h"
63 #include "gmx_ana.h"
64 #include "gromacs/utility/cstringutil.h"
65 #include "gromacs/fileio/trxio.h"
66
67 #include "gromacs/linearalgebra/eigensolver.h"
68 #include "gromacs/math/do_fit.h"
69 #include "gromacs/utility/fatalerror.h"
70
71 int gmx_covar(int argc, char *argv[])
72 {
73     const char     *desc[] = {
74         "[THISMODULE] calculates and diagonalizes the (mass-weighted)",
75         "covariance matrix.",
76         "All structures are fitted to the structure in the structure file.",
77         "When this is not a run input file periodicity will not be taken into",
78         "account. When the fit and analysis groups are identical and the analysis",
79         "is non mass-weighted, the fit will also be non mass-weighted.",
80         "[PAR]",
81         "The eigenvectors are written to a trajectory file ([TT]-v[tt]).",
82         "When the same atoms are used for the fit and the covariance analysis,",
83         "the reference structure for the fit is written first with t=-1.",
84         "The average (or reference when [TT]-ref[tt] is used) structure is",
85         "written with t=0, the eigenvectors",
86         "are written as frames with the eigenvector number as timestamp.",
87         "[PAR]",
88         "The eigenvectors can be analyzed with [gmx-anaeig].",
89         "[PAR]",
90         "Option [TT]-ascii[tt] writes the whole covariance matrix to",
91         "an ASCII file. The order of the elements is: x1x1, x1y1, x1z1, x1x2, ...",
92         "[PAR]",
93         "Option [TT]-xpm[tt] writes the whole covariance matrix to an [TT].xpm[tt] file.",
94         "[PAR]",
95         "Option [TT]-xpma[tt] writes the atomic covariance matrix to an [TT].xpm[tt] file,",
96         "i.e. for each atom pair the sum of the xx, yy and zz covariances is",
97         "written.",
98         "[PAR]",
99         "Note that the diagonalization of a matrix requires memory and time",
100         "that will increase at least as fast as than the square of the number",
101         "of atoms involved. It is easy to run out of memory, in which",
102         "case this tool will probably exit with a 'Segmentation fault'. You",
103         "should consider carefully whether a reduced set of atoms will meet",
104         "your needs for lower costs."
105     };
106     static gmx_bool bFit = TRUE, bRef = FALSE, bM = FALSE, bPBC = TRUE;
107     static int      end  = -1;
108     t_pargs         pa[] = {
109         { "-fit",  FALSE, etBOOL, {&bFit},
110           "Fit to a reference structure"},
111         { "-ref",  FALSE, etBOOL, {&bRef},
112           "Use the deviation from the conformation in the structure file instead of from the average" },
113         { "-mwa",  FALSE, etBOOL, {&bM},
114           "Mass-weighted covariance analysis"},
115         { "-last",  FALSE, etINT, {&end},
116           "Last eigenvector to write away (-1 is till the last)" },
117         { "-pbc",  FALSE,  etBOOL, {&bPBC},
118           "Apply corrections for periodic boundary conditions" }
119     };
120     FILE           *out = NULL; /* initialization makes all compilers happy */
121     t_trxstatus    *status;
122     t_trxstatus    *trjout;
123     t_topology      top;
124     int             ePBC;
125     t_atoms        *atoms;
126     rvec           *x, *xread, *xref, *xav, *xproj;
127     matrix          box, zerobox;
128     real           *sqrtm, *mat, *eigenvalues, sum, trace, inv_nframes;
129     real            t, tstart, tend, **mat2;
130     real            xj, *w_rls = NULL;
131     real            min, max, *axis;
132     int             ntopatoms, step;
133     int             natoms, nat, count, nframes0, nframes, nlevels;
134     gmx_int64_t     ndim, i, j, k, l;
135     int             WriteXref;
136     const char     *fitfile, *trxfile, *ndxfile;
137     const char     *eigvalfile, *eigvecfile, *averfile, *logfile;
138     const char     *asciifile, *xpmfile, *xpmafile;
139     char            str[STRLEN], *fitname, *ananame, *pcwd;
140     int             d, dj, nfit;
141     atom_id        *index, *ifit;
142     gmx_bool        bDiffMass1, bDiffMass2;
143     time_t          now;
144     char            timebuf[STRLEN];
145     t_rgb           rlo, rmi, rhi;
146     real           *eigenvectors;
147     output_env_t    oenv;
148     gmx_rmpbc_t     gpbc = NULL;
149
150     t_filenm        fnm[] = {
151         { efTRX, "-f",  NULL, ffREAD },
152         { efTPS, NULL,  NULL, ffREAD },
153         { efNDX, NULL,  NULL, ffOPTRD },
154         { efXVG, NULL,  "eigenval", ffWRITE },
155         { efTRN, "-v",  "eigenvec", ffWRITE },
156         { efSTO, "-av", "average.pdb", ffWRITE },
157         { efLOG, NULL,  "covar", ffWRITE },
158         { efDAT, "-ascii", "covar", ffOPTWR },
159         { efXPM, "-xpm", "covar", ffOPTWR },
160         { efXPM, "-xpma", "covara", ffOPTWR }
161     };
162 #define NFILE asize(fnm)
163
164     if (!parse_common_args(&argc, argv, PCA_CAN_TIME | PCA_TIME_UNIT,
165                            NFILE, fnm, asize(pa), pa, asize(desc), desc, 0, NULL, &oenv))
166     {
167         return 0;
168     }
169
170     clear_mat(zerobox);
171
172     fitfile    = ftp2fn(efTPS, NFILE, fnm);
173     trxfile    = ftp2fn(efTRX, NFILE, fnm);
174     ndxfile    = ftp2fn_null(efNDX, NFILE, fnm);
175     eigvalfile = ftp2fn(efXVG, NFILE, fnm);
176     eigvecfile = ftp2fn(efTRN, NFILE, fnm);
177     averfile   = ftp2fn(efSTO, NFILE, fnm);
178     logfile    = ftp2fn(efLOG, NFILE, fnm);
179     asciifile  = opt2fn_null("-ascii", NFILE, fnm);
180     xpmfile    = opt2fn_null("-xpm", NFILE, fnm);
181     xpmafile   = opt2fn_null("-xpma", NFILE, fnm);
182
183     read_tps_conf(fitfile, str, &top, &ePBC, &xref, NULL, box, TRUE);
184     atoms = &top.atoms;
185
186     if (bFit)
187     {
188         printf("\nChoose a group for the least squares fit\n");
189         get_index(atoms, ndxfile, 1, &nfit, &ifit, &fitname);
190         if (nfit < 3)
191         {
192             gmx_fatal(FARGS, "Need >= 3 points to fit!\n");
193         }
194     }
195     else
196     {
197         nfit = 0;
198     }
199     printf("\nChoose a group for the covariance analysis\n");
200     get_index(atoms, ndxfile, 1, &natoms, &index, &ananame);
201
202     bDiffMass1 = FALSE;
203     if (bFit)
204     {
205         snew(w_rls, atoms->nr);
206         for (i = 0; (i < nfit); i++)
207         {
208             w_rls[ifit[i]] = atoms->atom[ifit[i]].m;
209             if (i)
210             {
211                 bDiffMass1 = bDiffMass1 || (w_rls[ifit[i]] != w_rls[ifit[i-1]]);
212             }
213         }
214     }
215     bDiffMass2 = FALSE;
216     snew(sqrtm, natoms);
217     for (i = 0; (i < natoms); i++)
218     {
219         if (bM)
220         {
221             sqrtm[i] = sqrt(atoms->atom[index[i]].m);
222             if (i)
223             {
224                 bDiffMass2 = bDiffMass2 || (sqrtm[i] != sqrtm[i-1]);
225             }
226         }
227         else
228         {
229             sqrtm[i] = 1.0;
230         }
231     }
232
233     if (bFit && bDiffMass1 && !bDiffMass2)
234     {
235         bDiffMass1 = natoms != nfit;
236         i          = 0;
237         for (i = 0; (i < natoms) && !bDiffMass1; i++)
238         {
239             bDiffMass1 = index[i] != ifit[i];
240         }
241         if (!bDiffMass1)
242         {
243             fprintf(stderr, "\n"
244                     "Note: the fit and analysis group are identical,\n"
245                     "      while the fit is mass weighted and the analysis is not.\n"
246                     "      Making the fit non mass weighted.\n\n");
247             for (i = 0; (i < nfit); i++)
248             {
249                 w_rls[ifit[i]] = 1.0;
250             }
251         }
252     }
253
254     /* Prepare reference frame */
255     if (bPBC)
256     {
257         gpbc = gmx_rmpbc_init(&top.idef, ePBC, atoms->nr);
258         gmx_rmpbc(gpbc, atoms->nr, box, xref);
259     }
260     if (bFit)
261     {
262         reset_x(nfit, ifit, atoms->nr, NULL, xref, w_rls);
263     }
264
265     snew(x, natoms);
266     snew(xav, natoms);
267     ndim = natoms*DIM;
268     if (sqrt(GMX_INT64_MAX) < ndim)
269     {
270         gmx_fatal(FARGS, "Number of degrees of freedoms to large for matrix.\n");
271     }
272     snew(mat, ndim*ndim);
273
274     fprintf(stderr, "Calculating the average structure ...\n");
275     nframes0 = 0;
276     nat      = read_first_x(oenv, &status, trxfile, &t, &xread, box);
277     if (nat != atoms->nr)
278     {
279         fprintf(stderr, "\nWARNING: number of atoms in tpx (%d) and trajectory (%d) do not match\n", natoms, nat);
280     }
281     do
282     {
283         nframes0++;
284         /* calculate x: a fitted struture of the selected atoms */
285         if (bPBC)
286         {
287             gmx_rmpbc(gpbc, nat, box, xread);
288         }
289         if (bFit)
290         {
291             reset_x(nfit, ifit, nat, NULL, xread, w_rls);
292             do_fit(nat, w_rls, xref, xread);
293         }
294         for (i = 0; i < natoms; i++)
295         {
296             rvec_inc(xav[i], xread[index[i]]);
297         }
298     }
299     while (read_next_x(oenv, status, &t, xread, box));
300     close_trj(status);
301
302     inv_nframes = 1.0/nframes0;
303     for (i = 0; i < natoms; i++)
304     {
305         for (d = 0; d < DIM; d++)
306         {
307             xav[i][d]         *= inv_nframes;
308             xread[index[i]][d] = xav[i][d];
309         }
310     }
311     write_sto_conf_indexed(opt2fn("-av", NFILE, fnm), "Average structure",
312                            atoms, xread, NULL, epbcNONE, zerobox, natoms, index);
313     sfree(xread);
314
315     fprintf(stderr, "Constructing covariance matrix (%dx%d) ...\n", (int)ndim, (int)ndim);
316     nframes = 0;
317     nat     = read_first_x(oenv, &status, trxfile, &t, &xread, box);
318     tstart  = t;
319     do
320     {
321         nframes++;
322         tend = t;
323         /* calculate x: a (fitted) structure of the selected atoms */
324         if (bPBC)
325         {
326             gmx_rmpbc(gpbc, nat, box, xread);
327         }
328         if (bFit)
329         {
330             reset_x(nfit, ifit, nat, NULL, xread, w_rls);
331             do_fit(nat, w_rls, xref, xread);
332         }
333         if (bRef)
334         {
335             for (i = 0; i < natoms; i++)
336             {
337                 rvec_sub(xread[index[i]], xref[index[i]], x[i]);
338             }
339         }
340         else
341         {
342             for (i = 0; i < natoms; i++)
343             {
344                 rvec_sub(xread[index[i]], xav[i], x[i]);
345             }
346         }
347
348         for (j = 0; j < natoms; j++)
349         {
350             for (dj = 0; dj < DIM; dj++)
351             {
352                 k  = ndim*(DIM*j+dj);
353                 xj = x[j][dj];
354                 for (i = j; i < natoms; i++)
355                 {
356                     l = k+DIM*i;
357                     for (d = 0; d < DIM; d++)
358                     {
359                         mat[l+d] += x[i][d]*xj;
360                     }
361                 }
362             }
363         }
364     }
365     while (read_next_x(oenv, status, &t, xread, box) &&
366            (bRef || nframes < nframes0));
367     close_trj(status);
368     gmx_rmpbc_done(gpbc);
369
370     fprintf(stderr, "Read %d frames\n", nframes);
371
372     if (bRef)
373     {
374         /* copy the reference structure to the ouput array x */
375         snew(xproj, natoms);
376         for (i = 0; i < natoms; i++)
377         {
378             copy_rvec(xref[index[i]], xproj[i]);
379         }
380     }
381     else
382     {
383         xproj = xav;
384     }
385
386     /* correct the covariance matrix for the mass */
387     inv_nframes = 1.0/nframes;
388     for (j = 0; j < natoms; j++)
389     {
390         for (dj = 0; dj < DIM; dj++)
391         {
392             for (i = j; i < natoms; i++)
393             {
394                 k = ndim*(DIM*j+dj)+DIM*i;
395                 for (d = 0; d < DIM; d++)
396                 {
397                     mat[k+d] = mat[k+d]*inv_nframes*sqrtm[i]*sqrtm[j];
398                 }
399             }
400         }
401     }
402
403     /* symmetrize the matrix */
404     for (j = 0; j < ndim; j++)
405     {
406         for (i = j; i < ndim; i++)
407         {
408             mat[ndim*i+j] = mat[ndim*j+i];
409         }
410     }
411
412     trace = 0;
413     for (i = 0; i < ndim; i++)
414     {
415         trace += mat[i*ndim+i];
416     }
417     fprintf(stderr, "\nTrace of the covariance matrix: %g (%snm^2)\n",
418             trace, bM ? "u " : "");
419
420     if (asciifile)
421     {
422         out = gmx_ffopen(asciifile, "w");
423         for (j = 0; j < ndim; j++)
424         {
425             for (i = 0; i < ndim; i += 3)
426             {
427                 fprintf(out, "%g %g %g\n",
428                         mat[ndim*j+i], mat[ndim*j+i+1], mat[ndim*j+i+2]);
429             }
430         }
431         gmx_ffclose(out);
432     }
433
434     if (xpmfile)
435     {
436         min = 0;
437         max = 0;
438         snew(mat2, ndim);
439         for (j = 0; j < ndim; j++)
440         {
441             mat2[j] = &(mat[ndim*j]);
442             for (i = 0; i <= j; i++)
443             {
444                 if (mat2[j][i] < min)
445                 {
446                     min = mat2[j][i];
447                 }
448                 if (mat2[j][j] > max)
449                 {
450                     max = mat2[j][i];
451                 }
452             }
453         }
454         snew(axis, ndim);
455         for (i = 0; i < ndim; i++)
456         {
457             axis[i] = i+1;
458         }
459         rlo.r   = 0; rlo.g = 0; rlo.b = 1;
460         rmi.r   = 1; rmi.g = 1; rmi.b = 1;
461         rhi.r   = 1; rhi.g = 0; rhi.b = 0;
462         out     = gmx_ffopen(xpmfile, "w");
463         nlevels = 80;
464         write_xpm3(out, 0, "Covariance", bM ? "u nm^2" : "nm^2",
465                    "dim", "dim", ndim, ndim, axis, axis,
466                    mat2, min, 0.0, max, rlo, rmi, rhi, &nlevels);
467         gmx_ffclose(out);
468         sfree(axis);
469         sfree(mat2);
470     }
471
472     if (xpmafile)
473     {
474         min = 0;
475         max = 0;
476         snew(mat2, ndim/DIM);
477         for (i = 0; i < ndim/DIM; i++)
478         {
479             snew(mat2[i], ndim/DIM);
480         }
481         for (j = 0; j < ndim/DIM; j++)
482         {
483             for (i = 0; i <= j; i++)
484             {
485                 mat2[j][i] = 0;
486                 for (d = 0; d < DIM; d++)
487                 {
488                     mat2[j][i] += mat[ndim*(DIM*j+d)+DIM*i+d];
489                 }
490                 if (mat2[j][i] < min)
491                 {
492                     min = mat2[j][i];
493                 }
494                 if (mat2[j][j] > max)
495                 {
496                     max = mat2[j][i];
497                 }
498                 mat2[i][j] = mat2[j][i];
499             }
500         }
501         snew(axis, ndim/DIM);
502         for (i = 0; i < ndim/DIM; i++)
503         {
504             axis[i] = i+1;
505         }
506         rlo.r   = 0; rlo.g = 0; rlo.b = 1;
507         rmi.r   = 1; rmi.g = 1; rmi.b = 1;
508         rhi.r   = 1; rhi.g = 0; rhi.b = 0;
509         out     = gmx_ffopen(xpmafile, "w");
510         nlevels = 80;
511         write_xpm3(out, 0, "Covariance", bM ? "u nm^2" : "nm^2",
512                    "atom", "atom", ndim/DIM, ndim/DIM, axis, axis,
513                    mat2, min, 0.0, max, rlo, rmi, rhi, &nlevels);
514         gmx_ffclose(out);
515         sfree(axis);
516         for (i = 0; i < ndim/DIM; i++)
517         {
518             sfree(mat2[i]);
519         }
520         sfree(mat2);
521     }
522
523
524     /* call diagonalization routine */
525
526     snew(eigenvalues, ndim);
527     snew(eigenvectors, ndim*ndim);
528
529     memcpy(eigenvectors, mat, ndim*ndim*sizeof(real));
530     fprintf(stderr, "\nDiagonalizing ...\n");
531     fflush(stderr);
532     eigensolver(eigenvectors, ndim, 0, ndim, eigenvalues, mat);
533     sfree(eigenvectors);
534
535     /* now write the output */
536
537     sum = 0;
538     for (i = 0; i < ndim; i++)
539     {
540         sum += eigenvalues[i];
541     }
542     fprintf(stderr, "\nSum of the eigenvalues: %g (%snm^2)\n",
543             sum, bM ? "u " : "");
544     if (fabs(trace-sum) > 0.01*trace)
545     {
546         fprintf(stderr, "\nWARNING: eigenvalue sum deviates from the trace of the covariance matrix\n");
547     }
548
549     /* Set 'end', the maximum eigenvector and -value index used for output */
550     if (end == -1)
551     {
552         if (nframes-1 < ndim)
553         {
554             end = nframes-1;
555             fprintf(out, "WARNING: there are fewer frames in your trajectory than there are\n");
556             fprintf(out, "degrees of freedom in your system. Only generating the first\n");
557             fprintf(out, "%d out of %d eigenvectors and eigenvalues.\n", end, (int)ndim);
558         }
559         else
560         {
561             end = ndim;
562         }
563     }
564
565     fprintf(stderr, "\nWriting eigenvalues to %s\n", eigvalfile);
566
567     sprintf(str, "(%snm\\S2\\N)", bM ? "u " : "");
568     out = xvgropen(eigvalfile,
569                    "Eigenvalues of the covariance matrix",
570                    "Eigenvector index", str, oenv);
571     for (i = 0; (i < end); i++)
572     {
573         fprintf (out, "%10d %g\n", (int)i+1, eigenvalues[ndim-1-i]);
574     }
575     gmx_ffclose(out);
576
577     if (bFit)
578     {
579         /* misuse lambda: 0/1 mass weighted analysis no/yes */
580         if (nfit == natoms)
581         {
582             WriteXref = eWXR_YES;
583             for (i = 0; i < nfit; i++)
584             {
585                 copy_rvec(xref[ifit[i]], x[i]);
586             }
587         }
588         else
589         {
590             WriteXref = eWXR_NO;
591         }
592     }
593     else
594     {
595         /* misuse lambda: -1 for no fit */
596         WriteXref = eWXR_NOFIT;
597     }
598
599     write_eigenvectors(eigvecfile, natoms, mat, TRUE, 1, end,
600                        WriteXref, x, bDiffMass1, xproj, bM, eigenvalues);
601
602     out = gmx_ffopen(logfile, "w");
603
604     time(&now);
605     gmx_ctime_r(&now, timebuf, STRLEN);
606     fprintf(out, "Covariance analysis log, written %s\n", timebuf);
607
608     fprintf(out, "Program: %s\n", argv[0]);
609     gmx_getcwd(str, STRLEN);
610
611     fprintf(out, "Working directory: %s\n\n", str);
612
613     fprintf(out, "Read %d frames from %s (time %g to %g %s)\n", nframes, trxfile,
614             output_env_conv_time(oenv, tstart), output_env_conv_time(oenv, tend), output_env_get_time_unit(oenv));
615     if (bFit)
616     {
617         fprintf(out, "Read reference structure for fit from %s\n", fitfile);
618     }
619     if (ndxfile)
620     {
621         fprintf(out, "Read index groups from %s\n", ndxfile);
622     }
623     fprintf(out, "\n");
624
625     fprintf(out, "Analysis group is '%s' (%d atoms)\n", ananame, natoms);
626     if (bFit)
627     {
628         fprintf(out, "Fit group is '%s' (%d atoms)\n", fitname, nfit);
629     }
630     else
631     {
632         fprintf(out, "No fit was used\n");
633     }
634     fprintf(out, "Analysis is %smass weighted\n", bDiffMass2 ? "" : "non-");
635     if (bFit)
636     {
637         fprintf(out, "Fit is %smass weighted\n", bDiffMass1 ? "" : "non-");
638     }
639     fprintf(out, "Diagonalized the %dx%d covariance matrix\n", (int)ndim, (int)ndim);
640     fprintf(out, "Trace of the covariance matrix before diagonalizing: %g\n",
641             trace);
642     fprintf(out, "Trace of the covariance matrix after diagonalizing: %g\n\n",
643             sum);
644
645     fprintf(out, "Wrote %d eigenvalues to %s\n", (int)end, eigvalfile);
646     if (WriteXref == eWXR_YES)
647     {
648         fprintf(out, "Wrote reference structure to %s\n", eigvecfile);
649     }
650     fprintf(out, "Wrote average structure to %s and %s\n", averfile, eigvecfile);
651     fprintf(out, "Wrote eigenvectors %d to %d to %s\n", 1, end, eigvecfile);
652
653     gmx_ffclose(out);
654
655     fprintf(stderr, "Wrote the log to %s\n", logfile);
656
657     return 0;
658 }