eb7a3d3e69b215282c4ae987ce4151b690ec0e30
[alexxy/gromacs.git] / src / gromacs / trajectoryanalysis / modules / sasa.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5  * Copyright (c) 2001-2006, The GROMACS development team.
6  * Copyright (c) 2008,2009,2010,2011,2012,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 /*! \internal \file
38  * \brief
39  * Implements gmx::analysismodules::Sasa.
40  *
41  * \author Teemu Murtola <teemu.murtola@gmail.com> (C++ conversion)
42  * \ingroup module_trajectoryanalysis
43  */
44 #include "sasa.h"
45
46 #include <algorithm>
47 #include <string>
48 #include <vector>
49
50 #include "gromacs/legacyheaders/atomprop.h"
51 #include "gromacs/legacyheaders/copyrite.h"
52 #include "gromacs/legacyheaders/pbc.h"
53 #include "gromacs/legacyheaders/physics.h"
54 #include "gromacs/legacyheaders/symtab.h"
55 #include "gromacs/legacyheaders/vec.h"
56
57 #include "gromacs/analysisdata/analysisdata.h"
58 #include "gromacs/analysisdata/modules/average.h"
59 #include "gromacs/analysisdata/modules/plot.h"
60 #include "gromacs/fileio/confio.h"
61 #include "gromacs/fileio/futil.h"
62 #include "gromacs/fileio/pdbio.h"
63 #include "gromacs/options/basicoptions.h"
64 #include "gromacs/options/filenameoption.h"
65 #include "gromacs/options/options.h"
66 #include "gromacs/selection/selection.h"
67 #include "gromacs/selection/selectionoption.h"
68 #include "gromacs/trajectoryanalysis/analysismodule.h"
69 #include "gromacs/trajectoryanalysis/analysissettings.h"
70 #include "gromacs/utility/exceptions.h"
71 #include "gromacs/utility/scoped_ptr_sfree.h"
72 #include "gromacs/utility/smalloc.h"
73 #include "gromacs/utility/stringutil.h"
74
75 #include "nsc.h"
76
77 namespace gmx
78 {
79
80 namespace analysismodules
81 {
82
83 namespace
84 {
85
86 //! \addtogroup module_trajectoryanalysis
87 //! \{
88
89 //! Tracks information on two nearest neighbors of a single surface dot.
90 struct t_conect
91 {
92     //! Index of the second nearest neighbor dot.
93     atom_id  aa;
94     //! Index of the nearest neighbor dot.
95     atom_id  ab;
96     //! Squared distance to `aa`.
97     real     d2a;
98     //! Squared distance to `ab`.
99     real     d2b;
100 };
101
102 /*! \brief
103  * Updates nearest neighbor information for a surface dot.
104  *
105  * \param[in,out] c  Nearest neighbor information array to update.
106  * \param[in]     i  Index in `c` to update.
107  * \param[in]     j  Index of the other surface dot to add to the array.
108  * \param[in]     d2 Squared distance between `i` and `j`.
109  */
110 void add_rec(t_conect c[], atom_id i, atom_id j, real d2)
111 {
112     if (c[i].aa == NO_ATID)
113     {
114         c[i].aa  = j;
115         c[i].d2a = d2;
116     }
117     else if (c[i].ab == NO_ATID)
118     {
119         c[i].ab  = j;
120         c[i].d2b = d2;
121     }
122     else if (d2 < c[i].d2a)
123     {
124         c[i].aa  = j;
125         c[i].d2a = d2;
126     }
127     else if (d2 < c[i].d2b)
128     {
129         c[i].ab  = j;
130         c[i].d2b = d2;
131     }
132     /* Swap them if necessary: a must be larger than b */
133     if (c[i].d2a < c[i].d2b)
134     {
135         j        = c[i].ab;
136         c[i].ab  = c[i].aa;
137         c[i].aa  = j;
138         d2       = c[i].d2b;
139         c[i].d2b = c[i].d2a;
140         c[i].d2a = d2;
141     }
142 }
143
144 /*! \brief
145  * Adds CONECT records for surface dots.
146  *
147  * \param[in] fn  PDB file to append the CONECT records to.
148  * \param[in] n   Number of dots in `x`.
149  * \param[in] x   Array of surface dot positions.
150  *
151  * Adds a CONECT record that connects each surface dot to its two nearest
152  * neighbors.  The function is copied verbatim from the old gmx_sas.c
153  * implementation.
154  */
155 void do_conect(const char *fn, int n, rvec x[])
156 {
157     FILE     *fp;
158     int       i, j;
159     t_conect *c;
160     rvec      dx;
161     real      d2;
162
163     fprintf(stderr, "Building CONECT records\n");
164     snew(c, n);
165     for (i = 0; (i < n); i++)
166     {
167         c[i].aa = c[i].ab = NO_ATID;
168     }
169
170     for (i = 0; (i < n); i++)
171     {
172         for (j = i+1; (j < n); j++)
173         {
174             rvec_sub(x[i], x[j], dx);
175             d2 = iprod(dx, dx);
176             add_rec(c, i, j, d2);
177             add_rec(c, j, i, d2);
178         }
179     }
180     fp = gmx_ffopen(fn, "a");
181     for (i = 0; (i < n); i++)
182     {
183         if ((c[i].aa == NO_ATID) || (c[i].ab == NO_ATID))
184         {
185             fprintf(stderr, "Warning dot %d has no conections\n", i+1);
186         }
187         fprintf(fp, "CONECT%5d%5d%5d\n", i+1, c[i].aa+1, c[i].ab+1);
188     }
189     gmx_ffclose(fp);
190     sfree(c);
191 }
192
193 /*! \brief
194  * Plots the surface into a PDB file, optionally including the original atoms.
195  */
196 void connolly_plot(const char *fn, int ndots, real dots[], rvec x[], t_atoms *atoms,
197                    t_symtab *symtab, int ePBC, const matrix box, gmx_bool bIncludeSolute)
198 {
199     const char *const  atomnm = "DOT";
200     const char *const  resnm  = "DOT";
201     const char *const  title  = "Connolly Dot Surface Generated by gmx sasa";
202
203     int                i, i0, r0, ii0, k;
204     rvec              *xnew;
205     t_atoms            aaa;
206
207     if (bIncludeSolute)
208     {
209         i0 = atoms->nr;
210         r0 = atoms->nres;
211         srenew(atoms->atom, atoms->nr+ndots);
212         memset(&atoms->atom[i0], 0, sizeof(*atoms->atom)*ndots);
213         srenew(atoms->atomname, atoms->nr+ndots);
214         srenew(atoms->resinfo, r0+1);
215         atoms->atom[i0].resind = r0;
216         t_atoms_set_resinfo(atoms, i0, symtab, resnm, r0+1, ' ', 0, ' ');
217         if (atoms->pdbinfo != NULL)
218         {
219             srenew(atoms->pdbinfo, atoms->nr+ndots);
220         }
221         snew(xnew, atoms->nr+ndots);
222         for (i = 0; (i < atoms->nr); i++)
223         {
224             copy_rvec(x[i], xnew[i]);
225         }
226         for (i = k = 0; (i < ndots); i++)
227         {
228             ii0                        = i0+i;
229             atoms->atomname[ii0]       = put_symtab(symtab, atomnm);
230             atoms->atom[ii0].resind    = r0;
231             xnew[ii0][XX]              = dots[k++];
232             xnew[ii0][YY]              = dots[k++];
233             xnew[ii0][ZZ]              = dots[k++];
234             if (atoms->pdbinfo != NULL)
235             {
236                 atoms->pdbinfo[ii0].type   = epdbATOM;
237                 atoms->pdbinfo[ii0].atomnr = ii0;
238                 atoms->pdbinfo[ii0].bfac   = 0.0;
239                 atoms->pdbinfo[ii0].occup  = 0.0;
240             }
241         }
242         atoms->nr   = i0+ndots;
243         atoms->nres = r0+1;
244         write_sto_conf(fn, title, atoms, xnew, NULL, ePBC, const_cast<rvec *>(box));
245         atoms->nres = r0;
246         atoms->nr   = i0;
247     }
248     else
249     {
250         init_t_atoms(&aaa, ndots, TRUE);
251         aaa.atom[0].resind = 0;
252         t_atoms_set_resinfo(&aaa, 0, symtab, resnm, 1, ' ', 0, ' ');
253         snew(xnew, ndots);
254         for (i = k = 0; (i < ndots); i++)
255         {
256             ii0                     = i;
257             aaa.atomname[ii0]       = put_symtab(symtab, atomnm);
258             aaa.pdbinfo[ii0].type   = epdbATOM;
259             aaa.pdbinfo[ii0].atomnr = ii0;
260             aaa.atom[ii0].resind    = 0;
261             xnew[ii0][XX]           = dots[k++];
262             xnew[ii0][YY]           = dots[k++];
263             xnew[ii0][ZZ]           = dots[k++];
264             aaa.pdbinfo[ii0].bfac   = 0.0;
265             aaa.pdbinfo[ii0].occup  = 0.0;
266         }
267         aaa.nr = ndots;
268         write_sto_conf(fn, title, &aaa, xnew, NULL, ePBC, const_cast<rvec *>(box));
269         do_conect(fn, ndots, xnew);
270         free_t_atoms(&aaa, FALSE);
271     }
272     sfree(xnew);
273 }
274
275 /********************************************************************
276  * Actual analysis module
277  */
278
279 /*! \brief
280  * Implements `gmx sas` trajectory analysis module.
281  */
282 class Sasa : public TrajectoryAnalysisModule
283 {
284     public:
285         Sasa();
286
287         virtual void initOptions(Options                    *options,
288                                  TrajectoryAnalysisSettings *settings);
289         virtual void initAnalysis(const TrajectoryAnalysisSettings &settings,
290                                   const TopologyInformation        &top);
291
292         virtual TrajectoryAnalysisModuleDataPointer startFrames(
293             const AnalysisDataParallelOptions &opt,
294             const SelectionCollection         &selections);
295         virtual void analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
296                                   TrajectoryAnalysisModuleData *pdata);
297
298         virtual void finishAnalysis(int nframes);
299         virtual void writeOutput();
300
301     private:
302         /*! \brief
303          * Surface areas as a function of time.
304          *
305          * First column is for the calculation group, and the rest for the
306          * output groups.  This data is always produced.
307          */
308         AnalysisData            area_;
309         /*! \brief
310          * Per-atom surface areas as a function of time.
311          *
312          * Contains one data set for each column in `area_`.
313          * Each column corresponds to a selection position in `surfaceSel_`.
314          * This data is only produced if atom or residue areas have been
315          * requested.
316          */
317         AnalysisData            atomArea_;
318         /*! \brief
319          * Per-residue surface areas as a function of time.
320          *
321          * Contains one data set for each column in `area_`.
322          * Each column corresponds to a distinct residue `surfaceSel_`.
323          * For example, if `surfaceSel_` selects residues 2, 5, and 7, there
324          * will be three columns here.
325          * This data is only produced if atom or residue areas have been
326          * requested.
327          */
328         AnalysisData            residueArea_;
329         /*! \brief
330          * Free energy estimates as a function of time.
331          *
332          * Column layout is the same as for `area_`.
333          * This data is only produced if the output is requested.
334          */
335         AnalysisData            dgSolv_;
336         /*! \brief
337          * Total volume and density of the calculation group as a function of
338          * time.
339          *
340          * The first column is the volume and the second column is the density.
341          * This data is only produced if the output is requested.
342          */
343         AnalysisData            volume_;
344
345         /*! \brief
346          * The selection to calculate the surface for.
347          *
348          * Selection::originalId() and Selection::mappedId() store the mapping
349          * from the positions to the columns of `residueArea_`.
350          * The selection is computed with SelectionOption::dynamicMask(), i.e.,
351          * even in the presence of a dynamic selection, the number of returned
352          * positions is fixed, and SelectionPosition::selected() is used.
353          */
354         Selection               surfaceSel_;
355         /*! \brief
356          * List of optional additional output groups.
357          *
358          * Each of these must be a subset of the `surfaceSel_`.
359          * Selection::originalId() and Selection::mappedId() store the mapping
360          * from the positions to the corresponsing positions in `surfaceSel_`.
361          */
362         SelectionList           outputSel_;
363
364         std::string             fnArea_;
365         std::string             fnAtomArea_;
366         std::string             fnResidueArea_;
367         std::string             fnDGSolv_;
368         std::string             fnVolume_;
369         std::string             fnConnolly_;
370
371         double                  solsize_;
372         int                     ndots_;
373         //double                  minarea_;
374         double                  dgsDefault_;
375         bool                    bIncludeSolute_;
376
377         t_topology             *top_;
378         //! Combined VdW and probe radii for each atom in the calculation group.
379         std::vector<real>       radii_;
380         /*! \brief
381          * Solvation free energy coefficients for each atom in the calculation
382          * group.
383          *
384          * Empty if the free energy output has not been requested.
385          */
386         std::vector<real>       dgsFactor_;
387
388         // Copy and assign disallowed by base.
389 };
390
391 Sasa::Sasa()
392     : TrajectoryAnalysisModule(SasaInfo::name, SasaInfo::shortDescription),
393       solsize_(0.14), ndots_(24), dgsDefault_(0), bIncludeSolute_(true), top_(NULL)
394 {
395     //minarea_ = 0.5;
396     registerAnalysisDataset(&area_, "area");
397     registerAnalysisDataset(&atomArea_, "atomarea");
398     registerAnalysisDataset(&residueArea_, "resarea");
399     registerAnalysisDataset(&dgSolv_, "dgsolv");
400     registerAnalysisDataset(&volume_, "volume");
401 }
402
403 void
404 Sasa::initOptions(Options *options, TrajectoryAnalysisSettings *settings)
405 {
406     static const char *const desc[] = {
407         "[THISMODULE] computes solvent accessible surface areas.",
408         "See Eisenhaber F, Lijnzaad P, Argos P, Sander C, & Scharf M",
409         "(1995) J. Comput. Chem. 16, 273-284 for the algorithm used.",
410         "With [TT]-q[tt], the Connolly surface can be generated as well",
411         "in a [TT].pdb[tt] file where the nodes are represented as atoms",
412         "and the edges connecting the nearest nodes as CONECT records.",
413         "[TT]-odg[tt] allows for estimation of solvation free energies",
414         "from per-atom solvation energies per exposed surface area.[PAR]",
415
416         "The program requires a selection for the surface calculation to be",
417         "specified with [TT]-surface[tt]. This should always consist of all",
418         "non-solvent atoms in the system. The area of this group is always",
419         "calculated. Optionally, [TT]-output[tt] can specify additional",
420         "selections, which should be subsets of the calculation group.",
421         "The solvent-accessible areas for these groups are also extracted",
422         "from the full surface.[PAR]",
423
424         "The average and standard deviation of the area over the trajectory",
425         "can be calculated per residue and atom (options [TT]-or[tt] and",
426         "[TT]-oa[tt]).[PAR]",
427         //"In combination with the latter option an [TT].itp[tt] file can be",
428         //"generated (option [TT]-i[tt])",
429         //"which can be used to restrain surface atoms.[PAR]",
430
431         "With the [TT]-tv[tt] option the total volume and density of the",
432         "molecule can be computed.",
433         "Please consider whether the normal probe radius is appropriate",
434         "in this case or whether you would rather use, e.g., 0. It is good",
435         "to keep in mind that the results for volume and density are very",
436         "approximate. For example, in ice Ih, one can easily fit water molecules in the",
437         "pores which would yield a volume that is too low, and surface area and density",
438         "that are both too high."
439     };
440
441     options->setDescription(concatenateStrings(desc));
442
443     options->addOption(FileNameOption("o").filetype(eftPlot).outputFile().required()
444                            .store(&fnArea_).defaultBasename("area")
445                            .description("Total area as a function of time"));
446     options->addOption(FileNameOption("odg").filetype(eftPlot).outputFile()
447                            .store(&fnDGSolv_).defaultBasename("dgsolv")
448                            .description("Estimated solvation free energy as a function of time"));
449     options->addOption(FileNameOption("or").filetype(eftPlot).outputFile()
450                            .store(&fnResidueArea_).defaultBasename("resarea")
451                            .description("Average area per residue"));
452     options->addOption(FileNameOption("oa").filetype(eftPlot).outputFile()
453                            .store(&fnAtomArea_).defaultBasename("atomarea")
454                            .description("Average area per atom"));
455     options->addOption(FileNameOption("tv").filetype(eftPlot).outputFile()
456                            .store(&fnVolume_).defaultBasename("volume")
457                            .description("Total volume and density as a function of time"));
458     options->addOption(FileNameOption("q").filetype(eftPDB).outputFile()
459                            .store(&fnConnolly_).defaultBasename("connolly")
460                            .description("PDB file for Connolly surface"));
461     //options->addOption(FileNameOption("i").filetype(eftITP).outputFile()
462     //                       .store(&fnRestraints_).defaultBasename("surfat")
463     //                       .description("Topology file for position restraings on surface atoms"));
464
465
466     options->addOption(DoubleOption("probe").store(&solsize_)
467                            .description("Radius of the solvent probe (nm)"));
468     options->addOption(IntegerOption("ndots").store(&ndots_)
469                            .description("Number of dots per sphere, more dots means more accuracy"));
470     //options->addOption(DoubleOption("minarea").store(&minarea_)
471     //                       .description("The minimum area (nm^2) to count an atom as a surface atom when writing a position restraint file (see help)"));
472     options->addOption(BooleanOption("prot").store(&bIncludeSolute_)
473                            .description("Output the protein to the Connolly [TT].pdb[tt] file too"));
474     options->addOption(DoubleOption("dgs").store(&dgsDefault_)
475                            .description("Default value for solvation free energy per area (kJ/mol/nm^2)"));
476
477     // Selections must select atoms for the VdW radii lookup to work.
478     // The calculation group uses dynamicMask() so that the coordinates
479     // match a static array of VdW radii.
480     options->addOption(SelectionOption("surface").store(&surfaceSel_)
481                            .required().onlyAtoms().dynamicMask()
482                            .description("Surface calculation selection"));
483     options->addOption(SelectionOption("output").storeVector(&outputSel_)
484                            .onlyAtoms().multiValue()
485                            .description("Output selection(s)"));
486
487     // Atom names etc. are required for the VdW radii lookup.
488     settings->setFlag(TrajectoryAnalysisSettings::efRequireTop);
489 }
490
491 void
492 Sasa::initAnalysis(const TrajectoryAnalysisSettings &settings,
493                    const TopologyInformation        &top)
494 {
495     const t_atoms &atoms = top.topology()->atoms;
496     top_ = top.topology();
497
498     //bITP   = opt2bSet("-i", nfile, fnm);
499     const bool bResAt =
500         !fnResidueArea_.empty() || !fnAtomArea_.empty(); // || bITP;
501     const bool bDGsol = !fnDGSolv_.empty();
502
503     if (solsize_ < 0)
504     {
505         solsize_ = 1e-3;
506         fprintf(stderr, "Probe size too small, setting it to %g\n", solsize_);
507     }
508     if (ndots_ < 20)
509     {
510         ndots_ = 20;
511         fprintf(stderr, "Ndots too small, setting it to %d\n", ndots_);
512     }
513
514     please_cite(stderr, "Eisenhaber95");
515     //if ((top.ePBC() != epbcXYZ) || (TRICLINIC(fr.box)))
516     //{
517     //    fprintf(stderr, "\n\nWARNING: non-rectangular boxes may give erroneous results or crashes.\n"
518     //            "Analysis based on vacuum simulations (with the possibility of evaporation)\n"
519     //            "will certainly crash the analysis.\n\n");
520     //}
521
522     if (bDGsol)
523     {
524         if (!top.hasFullTopology())
525         {
526             GMX_THROW(InconsistentInputError("Cannot compute Delta G of solvation without a tpr file"));
527         }
528         else
529         {
530             if (strcmp(*(atoms.atomtype[0]), "?") == 0)
531             {
532                 GMX_THROW(InconsistentInputError("Your input tpr file is too old (does not contain atom types). Cannot not compute Delta G of solvation"));
533             }
534             else
535             {
536                 printf("Free energy of solvation predictions:\n");
537                 please_cite(stdout, "Eisenberg86a");
538             }
539         }
540     }
541
542     // Now compute atomic radii including solvent probe size.
543     // Also, fetch solvation free energy coefficients and
544     // compute the residue indices that map the calculation atoms
545     // to the columns of residueArea_.
546     radii_.reserve(surfaceSel_.posCount());
547     if (bDGsol)
548     {
549         dgsFactor_.reserve(surfaceSel_.posCount());
550     }
551
552     // TODO: Not exception-safe, but nice solution would be to have a C++
553     // atom properties class...
554     gmx_atomprop_t     aps = gmx_atomprop_init();
555
556     ConstArrayRef<int> atomIndices = surfaceSel_.atomIndices();
557     int                prevResind  = atoms.atom[atomIndices[0]].resind;
558     int                resCount    = 0;
559     int                ndefault    = 0;
560     for (int i = 0; i < surfaceSel_.posCount(); i++)
561     {
562         const int ii     = atomIndices[i];
563         const int resind = atoms.atom[ii].resind;
564         if (resind != prevResind)
565         {
566             ++resCount;
567             prevResind = resind;
568         }
569         surfaceSel_.setOriginalId(i, resCount);
570         real      radius;
571         if (!gmx_atomprop_query(aps, epropVDW,
572                                 *(atoms.resinfo[resind].name),
573                                 *(atoms.atomname[ii]), &radius))
574         {
575             ndefault++;
576         }
577         radii_.push_back(radius + solsize_);
578         if (bDGsol)
579         {
580             real dgsFactor;
581             if (!gmx_atomprop_query(aps, epropDGsol,
582                                     *(atoms.resinfo[resind].name),
583                                     *(atoms.atomtype[ii]), &dgsFactor))
584             {
585                 dgsFactor = dgsDefault_;
586             }
587             dgsFactor_.push_back(dgsFactor);
588         }
589     }
590     if (ndefault > 0)
591     {
592         fprintf(stderr, "WARNING: could not find a Van der Waals radius for %d atoms\n", ndefault);
593     }
594     gmx_atomprop_destroy(aps);
595
596     // The loop above doesn't count the last residue.
597     ++resCount;
598
599     // Pre-compute mapping from the output groups to the calculation group,
600     // and store it in the selection ID map for easy lookup.
601     for (size_t g = 0; g < outputSel_.size(); ++g)
602     {
603         ConstArrayRef<int> outputIndices = outputSel_[g].atomIndices();
604         for (int i = 0, j = 0; i < outputSel_[g].posCount(); ++i)
605         {
606             while (j < surfaceSel_.posCount() && outputIndices[i] > atomIndices[j])
607             {
608                 ++j;
609             }
610             if (j == surfaceSel_.posCount() || outputIndices[i] != atomIndices[j])
611             {
612                 GMX_THROW(InconsistentInputError("Output selection is not a subset of the input selection"));
613             }
614             outputSel_[g].setOriginalId(i, j);
615         }
616     }
617
618     // Initialize all the output data objects and initialize the output plotters.
619
620     area_.setColumnCount(0, 1 + outputSel_.size());
621     {
622         AnalysisDataPlotModulePointer plotm(
623                 new AnalysisDataPlotModule(settings.plotSettings()));
624         plotm->setFileName(fnArea_);
625         plotm->setTitle("Solvent Accessible Surface");
626         plotm->setXAxisIsTime();
627         plotm->setYLabel("Area (nm\\S2\\N)");
628         plotm->appendLegend("Total");
629         for (size_t i = 0; i < outputSel_.size(); ++i)
630         {
631             plotm->appendLegend(outputSel_[i].name());
632         }
633         area_.addModule(plotm);
634     }
635
636     if (bResAt)
637     {
638         atomArea_.setDataSetCount(1 + outputSel_.size());
639         residueArea_.setDataSetCount(1 + outputSel_.size());
640         for (size_t i = 0; i <= outputSel_.size(); ++i)
641         {
642             atomArea_.setColumnCount(i, surfaceSel_.posCount());
643             residueArea_.setColumnCount(i, resCount);
644         }
645         {
646             AnalysisDataAverageModulePointer avem(new AnalysisDataAverageModule);
647             for (int i = 0; i < surfaceSel_.posCount(); ++i)
648             {
649                 avem->setXAxisValue(i, surfaceSel_.position(i).atomIndices()[0] + 1);
650             }
651             atomArea_.addModule(avem);
652             if (!fnAtomArea_.empty())
653             {
654                 AnalysisDataPlotModulePointer plotm(
655                         new AnalysisDataPlotModule(settings.plotSettings()));
656                 plotm->setFileName(fnAtomArea_);
657                 plotm->setTitle("Area per residue over the trajectory");
658                 plotm->setXLabel("Atom");
659                 plotm->setXFormat(8, 0);
660                 plotm->setYLabel("Area (nm\\S2\\N)");
661                 plotm->setErrorsAsSeparateColumn(true);
662                 plotm->appendLegend("Average (nm\\S2\\N)");
663                 plotm->appendLegend("Standard deviation (nm\\S2\\N)");
664                 avem->addModule(plotm);
665             }
666         }
667         {
668             AnalysisDataAverageModulePointer avem(new AnalysisDataAverageModule);
669             for (int i = 0; i < surfaceSel_.posCount(); ++i)
670             {
671                 const int atomIndex     = surfaceSel_.position(i).atomIndices()[0];
672                 const int residueIndex  = atoms.atom[atomIndex].resind;
673                 avem->setXAxisValue(i, atoms.resinfo[residueIndex].nr);
674             }
675             residueArea_.addModule(avem);
676             if (!fnResidueArea_.empty())
677             {
678                 AnalysisDataPlotModulePointer plotm(
679                         new AnalysisDataPlotModule(settings.plotSettings()));
680                 plotm->setFileName(fnResidueArea_);
681                 plotm->setTitle("Area per atom over the trajectory");
682                 plotm->setXLabel("Residue");
683                 plotm->setXFormat(8, 0);
684                 plotm->setYLabel("Area (nm\\S2\\N)");
685                 plotm->setErrorsAsSeparateColumn(true);
686                 plotm->appendLegend("Average (nm\\S2\\N)");
687                 plotm->appendLegend("Standard deviation (nm\\S2\\N)");
688                 avem->addModule(plotm);
689             }
690         }
691     }
692
693     if (!fnDGSolv_.empty())
694     {
695         dgSolv_.setColumnCount(0, 1 + outputSel_.size());
696         AnalysisDataPlotModulePointer plotm(
697                 new AnalysisDataPlotModule(settings.plotSettings()));
698         plotm->setFileName(fnDGSolv_);
699         plotm->setTitle("Free Energy of Solvation");
700         plotm->setXAxisIsTime();
701         plotm->setYLabel("D Gsolv");
702         plotm->appendLegend("Total");
703         for (size_t i = 0; i < outputSel_.size(); ++i)
704         {
705             plotm->appendLegend(outputSel_[i].name());
706         }
707         dgSolv_.addModule(plotm);
708     }
709
710     if (!fnVolume_.empty())
711     {
712         volume_.setColumnCount(0, 2);
713         AnalysisDataPlotModulePointer plotm(
714                 new AnalysisDataPlotModule(settings.plotSettings()));
715         plotm->setFileName(fnVolume_);
716         plotm->setTitle("Volume and Density");
717         plotm->setXAxisIsTime();
718         plotm->appendLegend("Volume (nm\\S3\\N)");
719         plotm->appendLegend("Density (g/l)");
720         volume_.addModule(plotm);
721     }
722 }
723
724 /*! \brief
725  * Temporary memory for use within a single-frame calculation.
726  */
727 class SasaModuleData : public TrajectoryAnalysisModuleData
728 {
729     public:
730         /*! \brief
731          * Reserves memory for the frame-local data.
732          *
733          * `residueCount` will be zero if per-residue data is not being
734          * calculated.
735          */
736         SasaModuleData(TrajectoryAnalysisModule          *module,
737                        const AnalysisDataParallelOptions &opt,
738                        const SelectionCollection         &selections,
739                        int atomCount, int residueCount)
740             : TrajectoryAnalysisModuleData(module, opt, selections)
741         {
742             index_.reserve(atomCount);
743             // If the calculation group is not dynamic, pre-calculate
744             // the index, since it is not going to change.
745             for (int i = 0; i < atomCount; ++i)
746             {
747                 index_.push_back(i);
748             }
749             atomAreas_.resize(atomCount);
750             res_a_.resize(residueCount);
751         }
752
753         virtual void finish() { finishDataHandles(); }
754
755         //! Indices of the calculation selection positions selected for the frame.
756         std::vector<int>        index_;
757         /*! \brief
758          * Atom areas for each calculation selection position for the frame.
759          *
760          * One entry for each position in the calculation group.
761          * Values for atoms not selected are set to zero.
762          */
763         std::vector<real>       atomAreas_;
764         /*! \brief
765          * Working array to accumulate areas for each residue.
766          *
767          * One entry for each distinct residue in the calculation group;
768          * indices are not directly residue numbers or residue indices.
769          *
770          * This vector is empty if residue area calculations are not being
771          * performed.
772          */
773         std::vector<real>       res_a_;
774 };
775
776 TrajectoryAnalysisModuleDataPointer Sasa::startFrames(
777         const AnalysisDataParallelOptions &opt,
778         const SelectionCollection         &selections)
779 {
780     return TrajectoryAnalysisModuleDataPointer(
781             new SasaModuleData(this, opt, selections, surfaceSel_.posCount(),
782                                residueArea_.columnCount(0)));
783 }
784
785 /*! \brief
786  * Helper method to compute the areas for a single selection.
787  *
788  * \param[in]  surfaceSel     The calculation selection.
789  * \param[in]  sel            The selection to compute the areas for (can be
790  *     `surfaceSel` or one of the output selections).
791  * \param[in]  atomAreas      Atom areas for each position in `surfaceSel`.
792  * \param[in]  dgsFactor      Free energy coefficients for each position in
793  *     `surfaceSel`. If empty, free energies are not calculated.
794  * \param[out] totalAreaOut Total area of `sel` (sum of atom areas it selects).
795  * \param[out] dgsolvOut      Solvation free energy.
796  *     Will be zero of `dgsFactor` is empty.
797  * \param      atomAreaHandle Data handle to use for storing atom areas for `sel`.
798  * \param      resAreaHandle  Data handle to use for storing residue areas for `sel`.
799  * \param      resAreaWork    Work array for accumulating the residue areas.
800  *     If empty, atom and residue areas are not calculated.
801  *
802  * `atomAreaHandle` and `resAreaHandle` are not used if `resAreaWork` is empty.
803  */
804 void computeAreas(const Selection &surfaceSel, const Selection &sel,
805                   const std::vector<real> &atomAreas,
806                   const std::vector<real> &dgsFactor,
807                   real *totalAreaOut, real *dgsolvOut,
808                   AnalysisDataHandle atomAreaHandle,
809                   AnalysisDataHandle resAreaHandle,
810                   std::vector<real> *resAreaWork)
811 {
812     const bool bResAt    = !resAreaWork->empty();
813     const bool bDGsolv   = !dgsFactor.empty();
814     real       totalArea = 0;
815     real       dgsolv    = 0;
816
817     if (bResAt)
818     {
819         std::fill(resAreaWork->begin(), resAreaWork->end(),
820                   static_cast<real>(0.0));
821     }
822     for (int i = 0; i < sel.posCount(); ++i)
823     {
824         // Get the index of the atom in the calculation group.
825         // For the output groups, the mapping has been precalculated in
826         // initAnalysis().
827         const int  ii = (sel != surfaceSel ? sel.position(i).mappedId() : i);
828         if (!surfaceSel.position(ii).selected())
829         {
830             // For the calculation group, skip unselected atoms.
831             if (sel == surfaceSel)
832             {
833                 continue;
834             }
835             GMX_THROW(InconsistentInputError("Output selection is not a subset of the surface selection"));
836         }
837         // Get the internal index of the matching residue.
838         // These have been precalculated in initAnalysis().
839         const int  ri       = surfaceSel.position(ii).mappedId();
840         const real atomArea = atomAreas[ii];
841         totalArea += atomArea;
842         if (bResAt)
843         {
844             atomAreaHandle.setPoint(ii, atomArea);
845             (*resAreaWork)[ri] += atomArea;
846         }
847         if (bDGsolv)
848         {
849             dgsolv += atomArea * dgsFactor[ii];
850         }
851     }
852     if (bResAt)
853     {
854         for (size_t i = 0; i < (*resAreaWork).size(); ++i)
855         {
856             resAreaHandle.setPoint(i, (*resAreaWork)[i]);
857         }
858     }
859     *totalAreaOut = totalArea;
860     *dgsolvOut    = dgsolv;
861 }
862
863 void
864 Sasa::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
865                    TrajectoryAnalysisModuleData *pdata)
866 {
867     AnalysisDataHandle   ah         = pdata->dataHandle(area_);
868     AnalysisDataHandle   dgh        = pdata->dataHandle(dgSolv_);
869     AnalysisDataHandle   aah        = pdata->dataHandle(atomArea_);
870     AnalysisDataHandle   rah        = pdata->dataHandle(residueArea_);
871     AnalysisDataHandle   vh         = pdata->dataHandle(volume_);
872     const Selection     &surfaceSel = pdata->parallelSelection(surfaceSel_);
873     const SelectionList &outputSel  = pdata->parallelSelections(outputSel_);
874     SasaModuleData      &frameData  = *static_cast<SasaModuleData *>(pdata);
875
876     const bool           bResAt    = !frameData.res_a_.empty();
877     const bool           bDGsol    = !dgsFactor_.empty();
878     const bool           bConnolly = (frnr == 0 && !fnConnolly_.empty());
879
880     // Update indices of selected atoms in the work array.
881     if (surfaceSel.isDynamic())
882     {
883         frameData.index_.clear();
884         for (int i = 0; i < surfaceSel.posCount(); ++i)
885         {
886             if (surfaceSel.position(i).selected())
887             {
888                 frameData.index_.push_back(i);
889             }
890         }
891     }
892
893     // Determine what needs to be calculated.
894     int                  flag      = 0;
895     if (bResAt || bDGsol || !outputSel.empty())
896     {
897         flag |= FLAG_ATOM_AREA;
898     }
899     if (bConnolly)
900     {
901         flag |= FLAG_DOTS;
902     }
903     if (volume_.columnCount() > 0)
904     {
905         flag |= FLAG_VOLUME;
906     }
907
908     // Do the low-level calculation.
909     // totarea and totvolume receive the values for the calculation group.
910     // area array contains the per-atom areas for the selected positions.
911     // surfacedots contains nsurfacedots entries, and contains the actual
912     // points.
913     real  totarea, totvolume;
914     real *area = NULL, *surfacedots = NULL;
915     int   nsurfacedots;
916     int   retval = nsc_dclm_pbc(surfaceSel.coordinates().data(), &radii_[0],
917                                 frameData.index_.size(), ndots_, flag, &totarea,
918                                 &area, &totvolume, &surfacedots, &nsurfacedots,
919                                 &frameData.index_[0],
920                                 pbc != NULL ? pbc->ePBC : epbcNONE,
921                                 pbc != NULL ? pbc->box : NULL);
922     // Unpack the atomwise areas into the frameData.atomAreas_ array for easier
923     // indexing in the case of dynamic surfaceSel.
924     if (area != NULL)
925     {
926         if (surfaceSel.isDynamic())
927         {
928             std::fill(frameData.atomAreas_.begin(), frameData.atomAreas_.end(),
929                       static_cast<real>(0.0));
930             for (size_t i = 0; i < frameData.index_.size(); ++i)
931             {
932                 frameData.atomAreas_[frameData.index_[i]] = area[i];
933             }
934         }
935         else
936         {
937             std::copy(area, area + surfaceSel.posCount(),
938                       frameData.atomAreas_.begin());
939         }
940         sfree(area);
941     }
942     scoped_ptr_sfree dotsGuard(surfacedots);
943     if (retval != 0)
944     {
945         GMX_THROW(InternalError("nsc_dclm_pbc failed"));
946     }
947
948     if (bConnolly)
949     {
950         // This is somewhat nasty, as it modifies the atoms and symtab
951         // structures.  But since it is only used in the first frame, and no
952         // one else uses the topology after initialization, it may just work
953         // even with future parallelization.
954         connolly_plot(fnConnolly_.c_str(),
955                       nsurfacedots, surfacedots, fr.x, &top_->atoms,
956                       &top_->symtab, fr.ePBC, fr.box, bIncludeSolute_);
957     }
958
959     ah.startFrame(frnr, fr.time);
960     if (bResAt)
961     {
962         aah.startFrame(frnr, fr.time);
963         rah.startFrame(frnr, fr.time);
964     }
965     if (bDGsol)
966     {
967         dgh.startFrame(frnr, fr.time);
968     }
969
970     ah.setPoint(0, totarea);
971
972     real totalArea, dgsolv;
973     if (bResAt || bDGsol)
974     {
975         computeAreas(surfaceSel, surfaceSel, frameData.atomAreas_, dgsFactor_,
976                      &totalArea, &dgsolv, aah, rah, &frameData.res_a_);
977         if (bDGsol)
978         {
979             dgh.setPoint(0, dgsolv);
980         }
981     }
982     for (size_t g = 0; g < outputSel.size(); ++g)
983     {
984         if (bResAt)
985         {
986             aah.selectDataSet(g + 1);
987             rah.selectDataSet(g + 1);
988         }
989         computeAreas(surfaceSel, outputSel[g], frameData.atomAreas_, dgsFactor_,
990                      &totalArea, &dgsolv, aah, rah, &frameData.res_a_);
991         ah.setPoint(g + 1, totalArea);
992         if (bDGsol)
993         {
994             dgh.setPoint(g + 1, dgsolv);
995         }
996     }
997
998     ah.finishFrame();
999     if (bResAt)
1000     {
1001         aah.finishFrame();
1002         rah.finishFrame();
1003     }
1004     if (bDGsol)
1005     {
1006         dgh.finishFrame();
1007     }
1008
1009     if (vh.isValid())
1010     {
1011         real totmass = 0;
1012         for (int i = 0; i < surfaceSel.posCount(); ++i)
1013         {
1014             totmass += surfaceSel.position(i).mass();
1015         }
1016         const real density = totmass*AMU/(totvolume*NANO*NANO*NANO);
1017         vh.startFrame(frnr, fr.time);
1018         vh.setPoint(0, totvolume);
1019         vh.setPoint(1, density);
1020         vh.finishFrame();
1021     }
1022 }
1023
1024 void
1025 Sasa::finishAnalysis(int /*nframes*/)
1026 {
1027     //if (bITP)
1028     //{
1029     //    fp3 = ftp2FILE(efITP, nfile, fnm, "w");
1030     //    fprintf(fp3, "[ position_restraints ]\n"
1031     //            "#define FCX 1000\n"
1032     //            "#define FCY 1000\n"
1033     //            "#define FCZ 1000\n"
1034     //            "; Atom  Type  fx   fy   fz\n");
1035     //    for (i = 0; i < nx[0]; i++)
1036     //    {
1037     //        if (atom_area[i] > minarea)
1038     //        {
1039     //            fprintf(fp3, "%5d   1     FCX  FCX  FCZ\n", ii+1);
1040     //        }
1041     //    }
1042     //    ffclose(fp3);
1043     //}
1044 }
1045
1046 void
1047 Sasa::writeOutput()
1048 {
1049 }
1050
1051 //! \}
1052
1053 }       // namespace
1054
1055 const char SasaInfo::name[]             = "sasa";
1056 const char SasaInfo::shortDescription[] =
1057     "Compute solvent accessible surface area";
1058
1059 TrajectoryAnalysisModulePointer SasaInfo::create()
1060 {
1061     return TrajectoryAnalysisModulePointer(new Sasa);
1062 }
1063
1064 } // namespace analysismodules
1065
1066 } // namespace gmx