Add coolquotes
[gromacs.git] / src / gromacs / trajectoryanalysis / modules / sasa.cpp
bloba33148e5901e14f9d975e009e431c84d96535f01
1 /*
2 * This file is part of the GROMACS molecular simulation package.
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,2015,2016,2017,2018, 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.
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.
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.
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.
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.
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.
37 /*! \internal \file
38 * \brief
39 * Implements gmx::analysismodules::Sasa.
41 * \author Teemu Murtola <teemu.murtola@gmail.com> (C++ conversion)
42 * \ingroup module_trajectoryanalysis
44 #include "gmxpre.h"
46 #include "sasa.h"
48 #include <algorithm>
49 #include <string>
50 #include <vector>
52 #include "gromacs/analysisdata/analysisdata.h"
53 #include "gromacs/analysisdata/modules/average.h"
54 #include "gromacs/analysisdata/modules/plot.h"
55 #include "gromacs/fileio/confio.h"
56 #include "gromacs/fileio/pdbio.h"
57 #include "gromacs/math/units.h"
58 #include "gromacs/math/vec.h"
59 #include "gromacs/options/basicoptions.h"
60 #include "gromacs/options/filenameoption.h"
61 #include "gromacs/options/ioptionscontainer.h"
62 #include "gromacs/pbcutil/pbc.h"
63 #include "gromacs/selection/selection.h"
64 #include "gromacs/selection/selectionoption.h"
65 #include "gromacs/topology/atomprop.h"
66 #include "gromacs/topology/symtab.h"
67 #include "gromacs/topology/topology.h"
68 #include "gromacs/trajectory/trajectoryframe.h"
69 #include "gromacs/trajectoryanalysis/analysismodule.h"
70 #include "gromacs/trajectoryanalysis/analysissettings.h"
71 #include "gromacs/trajectoryanalysis/topologyinformation.h"
72 #include "gromacs/utility/exceptions.h"
73 #include "gromacs/utility/futil.h"
74 #include "gromacs/utility/pleasecite.h"
75 #include "gromacs/utility/smalloc.h"
76 #include "gromacs/utility/stringutil.h"
77 #include "gromacs/utility/unique_cptr.h"
79 #include "surfacearea.h"
81 namespace gmx
84 namespace analysismodules
87 namespace
90 //! \addtogroup module_trajectoryanalysis
91 //! \{
93 //! Tracks information on two nearest neighbors of a single surface dot.
94 struct t_conect
96 //! Index of the second nearest neighbor dot.
97 int aa;
98 //! Index of the nearest neighbor dot.
99 int ab;
100 //! Squared distance to `aa`.
101 real d2a;
102 //! Squared distance to `ab`.
103 real d2b;
106 /*! \brief
107 * Updates nearest neighbor information for a surface dot.
109 * \param[in,out] c Nearest neighbor information array to update.
110 * \param[in] i Index in `c` to update.
111 * \param[in] j Index of the other surface dot to add to the array.
112 * \param[in] d2 Squared distance between `i` and `j`.
114 void add_rec(t_conect c[], int i, int j, real d2)
116 if (c[i].aa == -1)
118 c[i].aa = j;
119 c[i].d2a = d2;
121 else if (c[i].ab == -1)
123 c[i].ab = j;
124 c[i].d2b = d2;
126 else if (d2 < c[i].d2a)
128 c[i].aa = j;
129 c[i].d2a = d2;
131 else if (d2 < c[i].d2b)
133 c[i].ab = j;
134 c[i].d2b = d2;
136 /* Swap them if necessary: a must be larger than b */
137 if (c[i].d2a < c[i].d2b)
139 j = c[i].ab;
140 c[i].ab = c[i].aa;
141 c[i].aa = j;
142 d2 = c[i].d2b;
143 c[i].d2b = c[i].d2a;
144 c[i].d2a = d2;
148 /*! \brief
149 * Adds CONECT records for surface dots.
151 * \param[in] fn PDB file to append the CONECT records to.
152 * \param[in] n Number of dots in `x`.
153 * \param[in] x Array of surface dot positions.
155 * Adds a CONECT record that connects each surface dot to its two nearest
156 * neighbors. The function is copied verbatim from the old gmx_sas.c
157 * implementation.
159 void do_conect(const char *fn, int n, rvec x[])
161 FILE *fp;
162 int i, j;
163 t_conect *c;
164 rvec dx;
165 real d2;
167 fprintf(stderr, "Building CONECT records\n");
168 snew(c, n);
169 for (i = 0; (i < n); i++)
171 c[i].aa = c[i].ab = -1;
174 for (i = 0; (i < n); i++)
176 for (j = i+1; (j < n); j++)
178 rvec_sub(x[i], x[j], dx);
179 d2 = iprod(dx, dx);
180 add_rec(c, i, j, d2);
181 add_rec(c, j, i, d2);
184 fp = gmx_ffopen(fn, "a");
185 for (i = 0; (i < n); i++)
187 if ((c[i].aa == -1) || (c[i].ab == -1))
189 fprintf(stderr, "Warning dot %d has no connections\n", i+1);
191 fprintf(fp, "CONECT%5d%5d%5d\n", i+1, c[i].aa+1, c[i].ab+1);
193 gmx_ffclose(fp);
194 sfree(c);
197 /*! \brief
198 * Plots the surface into a PDB file, optionally including the original atoms.
200 void connolly_plot(const char *fn, int ndots, const real dots[], rvec x[], t_atoms *atoms,
201 t_symtab *symtab, int ePBC, const matrix box, gmx_bool bIncludeSolute)
203 const char *const atomnm = "DOT";
204 const char *const resnm = "DOT";
205 const char *const title = "Connolly Dot Surface Generated by gmx sasa";
207 int i, i0, r0, ii0, k;
208 rvec *xnew;
209 t_atoms aaa;
211 if (bIncludeSolute)
213 i0 = atoms->nr;
214 r0 = atoms->nres;
215 srenew(atoms->atom, atoms->nr+ndots);
216 memset(&atoms->atom[i0], 0, sizeof(*atoms->atom)*ndots);
217 srenew(atoms->atomname, atoms->nr+ndots);
218 srenew(atoms->resinfo, r0+1);
219 atoms->atom[i0].resind = r0;
220 t_atoms_set_resinfo(atoms, i0, symtab, resnm, r0+1, ' ', 0, ' ');
221 if (atoms->pdbinfo != nullptr)
223 srenew(atoms->pdbinfo, atoms->nr+ndots);
225 snew(xnew, atoms->nr+ndots);
226 for (i = 0; (i < atoms->nr); i++)
228 copy_rvec(x[i], xnew[i]);
230 for (i = k = 0; (i < ndots); i++)
232 ii0 = i0+i;
233 atoms->atomname[ii0] = put_symtab(symtab, atomnm);
234 atoms->atom[ii0].resind = r0;
235 xnew[ii0][XX] = dots[k++];
236 xnew[ii0][YY] = dots[k++];
237 xnew[ii0][ZZ] = dots[k++];
238 if (atoms->pdbinfo != nullptr)
240 atoms->pdbinfo[ii0].type = epdbATOM;
241 atoms->pdbinfo[ii0].atomnr = ii0;
242 atoms->pdbinfo[ii0].bfac = 0.0;
243 atoms->pdbinfo[ii0].occup = 0.0;
246 atoms->nr = i0+ndots;
247 atoms->nres = r0+1;
248 write_sto_conf(fn, title, atoms, xnew, nullptr, ePBC, const_cast<rvec *>(box));
249 atoms->nres = r0;
250 atoms->nr = i0;
252 else
254 init_t_atoms(&aaa, ndots, TRUE);
255 aaa.atom[0].resind = 0;
256 t_atoms_set_resinfo(&aaa, 0, symtab, resnm, 1, ' ', 0, ' ');
257 snew(xnew, ndots);
258 for (i = k = 0; (i < ndots); i++)
260 ii0 = i;
261 aaa.atomname[ii0] = put_symtab(symtab, atomnm);
262 aaa.pdbinfo[ii0].type = epdbATOM;
263 aaa.pdbinfo[ii0].atomnr = ii0;
264 aaa.atom[ii0].resind = 0;
265 xnew[ii0][XX] = dots[k++];
266 xnew[ii0][YY] = dots[k++];
267 xnew[ii0][ZZ] = dots[k++];
268 aaa.pdbinfo[ii0].bfac = 0.0;
269 aaa.pdbinfo[ii0].occup = 0.0;
271 aaa.nr = ndots;
272 write_sto_conf(fn, title, &aaa, xnew, nullptr, ePBC, const_cast<rvec *>(box));
273 do_conect(fn, ndots, xnew);
274 done_atom(&aaa);
276 sfree(xnew);
279 /********************************************************************
280 * Actual analysis module
283 /*! \brief
284 * Implements `gmx sas` trajectory analysis module.
286 class Sasa : public TrajectoryAnalysisModule
288 public:
289 Sasa();
291 void initOptions(IOptionsContainer *options,
292 TrajectoryAnalysisSettings *settings) override;
293 void initAnalysis(const TrajectoryAnalysisSettings &settings,
294 const TopologyInformation &top) override;
296 TrajectoryAnalysisModuleDataPointer startFrames(
297 const AnalysisDataParallelOptions &opt,
298 const SelectionCollection &selections) override;
299 void analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
300 TrajectoryAnalysisModuleData *pdata) override;
302 void finishAnalysis(int nframes) override;
303 void writeOutput() override;
305 private:
306 /*! \brief
307 * Surface areas as a function of time.
309 * First column is for the calculation group, and the rest for the
310 * output groups. This data is always produced.
312 AnalysisData area_;
313 /*! \brief
314 * Per-atom surface areas as a function of time.
316 * Contains one data set for each column in `area_`.
317 * Each column corresponds to a selection position in `surfaceSel_`.
318 * This data is only produced if atom or residue areas have been
319 * requested.
321 AnalysisData atomArea_;
322 /*! \brief
323 * Per-residue surface areas as a function of time.
325 * Contains one data set for each column in `area_`.
326 * Each column corresponds to a distinct residue `surfaceSel_`.
327 * For example, if `surfaceSel_` selects residues 2, 5, and 7, there
328 * will be three columns here.
329 * This data is only produced if atom or residue areas have been
330 * requested.
332 AnalysisData residueArea_;
333 /*! \brief
334 * Free energy estimates as a function of time.
336 * Column layout is the same as for `area_`.
337 * This data is only produced if the output is requested.
339 AnalysisData dgSolv_;
340 /*! \brief
341 * Total volume and density of the calculation group as a function of
342 * time.
344 * The first column is the volume and the second column is the density.
345 * This data is only produced if the output is requested.
347 AnalysisData volume_;
349 /*! \brief
350 * The selection to calculate the surface for.
352 * Selection::originalId() and Selection::mappedId() store the mapping
353 * from the positions to the columns of `residueArea_`.
354 * The selection is computed with SelectionOption::dynamicMask(), i.e.,
355 * even in the presence of a dynamic selection, the number of returned
356 * positions is fixed, and SelectionPosition::selected() is used.
358 Selection surfaceSel_;
359 /*! \brief
360 * List of optional additional output groups.
362 * Each of these must be a subset of the `surfaceSel_`.
363 * Selection::originalId() and Selection::mappedId() store the mapping
364 * from the positions to the corresponsing positions in `surfaceSel_`.
366 SelectionList outputSel_;
368 std::string fnArea_;
369 std::string fnAtomArea_;
370 std::string fnResidueArea_;
371 std::string fnDGSolv_;
372 std::string fnVolume_;
373 std::string fnConnolly_;
375 double solsize_;
376 int ndots_;
377 //double minarea_;
378 double dgsDefault_;
379 bool bIncludeSolute_;
381 //! Global topology corresponding to the input.
382 gmx_mtop_t *mtop_;
383 //! Per-atom data corresponding to the input.
384 AtomsDataPtr atoms_;
385 //! Combined VdW and probe radii for each atom in the calculation group.
386 std::vector<real> radii_;
387 /*! \brief
388 * Solvation free energy coefficients for each atom in the calculation
389 * group.
391 * Empty if the free energy output has not been requested.
393 std::vector<real> dgsFactor_;
394 //! Calculation algorithm.
395 SurfaceAreaCalculator calculator_;
397 // Copy and assign disallowed by base.
400 Sasa::Sasa()
401 : solsize_(0.14), ndots_(24), dgsDefault_(0), bIncludeSolute_(true),
402 mtop_(nullptr), atoms_(nullptr)
404 //minarea_ = 0.5;
405 registerAnalysisDataset(&area_, "area");
406 registerAnalysisDataset(&atomArea_, "atomarea");
407 registerAnalysisDataset(&residueArea_, "resarea");
408 registerAnalysisDataset(&dgSolv_, "dgsolv");
409 registerAnalysisDataset(&volume_, "volume");
412 void
413 Sasa::initOptions(IOptionsContainer *options, TrajectoryAnalysisSettings *settings)
415 static const char *const desc[] = {
416 "[THISMODULE] computes solvent accessible surface areas.",
417 "See Eisenhaber F, Lijnzaad P, Argos P, Sander C, & Scharf M",
418 "(1995) J. Comput. Chem. 16, 273-284 for the algorithm used.",
419 "With [TT]-q[tt], the Connolly surface can be generated as well",
420 "in a [REF].pdb[ref] file where the nodes are represented as atoms",
421 "and the edges connecting the nearest nodes as CONECT records.",
422 "[TT]-odg[tt] allows for estimation of solvation free energies",
423 "from per-atom solvation energies per exposed surface area.[PAR]",
425 "The program requires a selection for the surface calculation to be",
426 "specified with [TT]-surface[tt]. This should always consist of all",
427 "non-solvent atoms in the system. The area of this group is always",
428 "calculated. Optionally, [TT]-output[tt] can specify additional",
429 "selections, which should be subsets of the calculation group.",
430 "The solvent-accessible areas for these groups are also extracted",
431 "from the full surface.[PAR]",
433 "The average and standard deviation of the area over the trajectory",
434 "can be calculated per residue and atom (options [TT]-or[tt] and",
435 "[TT]-oa[tt]).[PAR]",
436 //"In combination with the latter option an [REF].itp[ref] file can be",
437 //"generated (option [TT]-i[tt])",
438 //"which can be used to restrain surface atoms.[PAR]",
440 "With the [TT]-tv[tt] option the total volume and density of the",
441 "molecule can be computed. With [TT]-pbc[tt] (the default), you",
442 "must ensure that your molecule/surface group is not split across PBC.",
443 "Otherwise, you will get non-sensical results.",
444 "Please also consider whether the normal probe radius is appropriate",
445 "in this case or whether you would rather use, e.g., 0. It is good",
446 "to keep in mind that the results for volume and density are very",
447 "approximate. For example, in ice Ih, one can easily fit water molecules in the",
448 "pores which would yield a volume that is too low, and surface area and density",
449 "that are both too high."
452 settings->setHelpText(desc);
454 options->addOption(FileNameOption("o").filetype(eftPlot).outputFile().required()
455 .store(&fnArea_).defaultBasename("area")
456 .description("Total area as a function of time"));
457 options->addOption(FileNameOption("odg").filetype(eftPlot).outputFile()
458 .store(&fnDGSolv_).defaultBasename("dgsolv")
459 .description("Estimated solvation free energy as a function of time"));
460 options->addOption(FileNameOption("or").filetype(eftPlot).outputFile()
461 .store(&fnResidueArea_).defaultBasename("resarea")
462 .description("Average area per residue"));
463 options->addOption(FileNameOption("oa").filetype(eftPlot).outputFile()
464 .store(&fnAtomArea_).defaultBasename("atomarea")
465 .description("Average area per atom"));
466 options->addOption(FileNameOption("tv").filetype(eftPlot).outputFile()
467 .store(&fnVolume_).defaultBasename("volume")
468 .description("Total volume and density as a function of time"));
469 options->addOption(FileNameOption("q").filetype(eftPDB).outputFile()
470 .store(&fnConnolly_).defaultBasename("connolly")
471 .description("PDB file for Connolly surface"));
472 //options->addOption(FileNameOption("i").filetype(eftITP).outputFile()
473 // .store(&fnRestraints_).defaultBasename("surfat")
474 // .description("Topology file for position restraings on surface atoms"));
477 options->addOption(DoubleOption("probe").store(&solsize_)
478 .description("Radius of the solvent probe (nm)"));
479 options->addOption(IntegerOption("ndots").store(&ndots_)
480 .description("Number of dots per sphere, more dots means more accuracy"));
481 //options->addOption(DoubleOption("minarea").store(&minarea_)
482 // .description("The minimum area (nm^2) to count an atom as a surface atom when writing a position restraint file (see help)"));
483 options->addOption(BooleanOption("prot").store(&bIncludeSolute_)
484 .description("Output the protein to the Connolly [REF].pdb[ref] file too"));
485 options->addOption(DoubleOption("dgs").store(&dgsDefault_)
486 .description("Default value for solvation free energy per area (kJ/mol/nm^2)"));
488 // Selections must select atoms for the VdW radii lookup to work.
489 // The calculation group uses dynamicMask() so that the coordinates
490 // match a static array of VdW radii.
491 options->addOption(SelectionOption("surface").store(&surfaceSel_)
492 .required().onlySortedAtoms().dynamicMask()
493 .description("Surface calculation selection"));
494 options->addOption(SelectionOption("output").storeVector(&outputSel_)
495 .onlySortedAtoms().multiValue()
496 .description("Output selection(s)"));
498 // Atom names etc. are required for the VdW radii lookup.
499 settings->setFlag(TrajectoryAnalysisSettings::efRequireTop);
502 void
503 Sasa::initAnalysis(const TrajectoryAnalysisSettings &settings,
504 const TopologyInformation &top)
506 mtop_ = top.mtop();
507 atoms_ = top.copyAtoms();
509 //bITP = opt2bSet("-i", nfile, fnm);
510 const bool bResAt =
511 !fnResidueArea_.empty() || !fnAtomArea_.empty(); // || bITP;
512 const bool bDGsol = !fnDGSolv_.empty();
514 if (solsize_ < 0)
516 solsize_ = 1e-3;
517 fprintf(stderr, "Probe size too small, setting it to %g\n", solsize_);
519 if (ndots_ < 20)
521 ndots_ = 20;
522 fprintf(stderr, "Ndots too small, setting it to %d\n", ndots_);
525 please_cite(stderr, "Eisenhaber95");
526 //if ((top.ePBC() != epbcXYZ) || (TRICLINIC(fr.box)))
528 // fprintf(stderr, "\n\nWARNING: non-rectangular boxes may give erroneous results or crashes.\n"
529 // "Analysis based on vacuum simulations (with the possibility of evaporation)\n"
530 // "will certainly crash the analysis.\n\n");
533 if (bDGsol)
535 if (!top.hasFullTopology())
537 GMX_THROW(InconsistentInputError("Cannot compute Delta G of solvation without a tpr file"));
539 else
541 if (strcmp(*(atoms_->atomtype[0]), "?") == 0)
543 GMX_THROW(InconsistentInputError("Your input tpr file is too old (does not contain atom types). Cannot not compute Delta G of solvation"));
545 else
547 printf("Free energy of solvation predictions:\n");
548 please_cite(stdout, "Eisenberg86a");
553 // Now compute atomic radii including solvent probe size.
554 // Also, fetch solvation free energy coefficients and
555 // compute the residue indices that map the calculation atoms
556 // to the columns of residueArea_.
557 radii_.reserve(surfaceSel_.posCount());
558 if (bDGsol)
560 dgsFactor_.reserve(surfaceSel_.posCount());
563 const int resCount = surfaceSel_.initOriginalIdsToGroup(top.mtop(), INDEX_RES);
565 // TODO: Not exception-safe, but nice solution would be to have a C++
566 // atom properties class...
567 gmx_atomprop_t aps = gmx_atomprop_init();
569 ArrayRef<const int> atomIndices = surfaceSel_.atomIndices();
570 int ndefault = 0;
571 for (int i = 0; i < surfaceSel_.posCount(); i++)
573 const int ii = atomIndices[i];
574 const int resind = atoms_->atom[ii].resind;
575 real radius;
576 if (!gmx_atomprop_query(aps, epropVDW,
577 *(atoms_->resinfo[resind].name),
578 *(atoms_->atomname[ii]), &radius))
580 ndefault++;
582 radii_.push_back(radius + solsize_);
583 if (bDGsol)
585 real dgsFactor;
586 if (!gmx_atomprop_query(aps, epropDGsol,
587 *(atoms_->resinfo[resind].name),
588 *(atoms_->atomtype[ii]), &dgsFactor))
590 dgsFactor = dgsDefault_;
592 dgsFactor_.push_back(dgsFactor);
595 if (ndefault > 0)
597 fprintf(stderr, "WARNING: could not find a Van der Waals radius for %d atoms\n", ndefault);
599 gmx_atomprop_destroy(aps);
601 // Pre-compute mapping from the output groups to the calculation group,
602 // and store it in the selection ID map for easy lookup.
603 for (size_t g = 0; g < outputSel_.size(); ++g)
605 ArrayRef<const int> outputIndices = outputSel_[g].atomIndices();
606 for (int i = 0, j = 0; i < outputSel_[g].posCount(); ++i)
608 while (j < surfaceSel_.posCount() && outputIndices[i] > atomIndices[j])
610 ++j;
612 if (j == surfaceSel_.posCount() || outputIndices[i] != atomIndices[j])
614 const std::string message
615 = formatString("Output selection '%s' is not a subset of "
616 "the surface selection (atom %d is the first "
617 "atom not in the surface selection)",
618 outputSel_[g].name(), outputIndices[i] + 1);
619 GMX_THROW(InconsistentInputError(message));
621 outputSel_[g].setOriginalId(i, j);
625 calculator_.setDotCount(ndots_);
626 calculator_.setRadii(radii_);
628 // Initialize all the output data objects and initialize the output plotters.
630 area_.setColumnCount(0, 1 + outputSel_.size());
632 AnalysisDataPlotModulePointer plotm(
633 new AnalysisDataPlotModule(settings.plotSettings()));
634 plotm->setFileName(fnArea_);
635 plotm->setTitle("Solvent Accessible Surface");
636 plotm->setXAxisIsTime();
637 plotm->setYLabel("Area (nm\\S2\\N)");
638 plotm->appendLegend("Total");
639 for (size_t i = 0; i < outputSel_.size(); ++i)
641 plotm->appendLegend(outputSel_[i].name());
643 area_.addModule(plotm);
646 if (bResAt)
648 atomArea_.setDataSetCount(1 + outputSel_.size());
649 residueArea_.setDataSetCount(1 + outputSel_.size());
650 for (size_t i = 0; i <= outputSel_.size(); ++i)
652 atomArea_.setColumnCount(i, surfaceSel_.posCount());
653 residueArea_.setColumnCount(i, resCount);
656 AnalysisDataAverageModulePointer avem(new AnalysisDataAverageModule);
657 for (int i = 0; i < surfaceSel_.posCount(); ++i)
659 avem->setXAxisValue(i, surfaceSel_.position(i).atomIndices()[0] + 1);
661 atomArea_.addModule(avem);
662 if (!fnAtomArea_.empty())
664 AnalysisDataPlotModulePointer plotm(
665 new AnalysisDataPlotModule(settings.plotSettings()));
666 plotm->setFileName(fnAtomArea_);
667 plotm->setTitle("Area per atom over the trajectory");
668 plotm->setXLabel("Atom");
669 plotm->setXFormat(8, 0);
670 plotm->setYLabel("Area (nm\\S2\\N)");
671 plotm->setErrorsAsSeparateColumn(true);
672 plotm->appendLegend("Average (nm\\S2\\N)");
673 plotm->appendLegend("Standard deviation (nm\\S2\\N)");
674 avem->addModule(plotm);
678 AnalysisDataAverageModulePointer avem(new AnalysisDataAverageModule);
679 int nextRow = 0;
680 for (int i = 0; i < surfaceSel_.posCount(); ++i)
682 const int residueGroup = surfaceSel_.position(i).mappedId();
683 if (residueGroup >= nextRow)
685 GMX_ASSERT(residueGroup == nextRow,
686 "Inconsistent (non-uniformly increasing) residue grouping");
687 const int atomIndex = surfaceSel_.position(i).atomIndices()[0];
688 const int residueIndex = atoms_->atom[atomIndex].resind;
689 avem->setXAxisValue(nextRow, atoms_->resinfo[residueIndex].nr);
690 ++nextRow;
693 residueArea_.addModule(avem);
694 if (!fnResidueArea_.empty())
696 AnalysisDataPlotModulePointer plotm(
697 new AnalysisDataPlotModule(settings.plotSettings()));
698 plotm->setFileName(fnResidueArea_);
699 plotm->setTitle("Area per residue over the trajectory");
700 plotm->setXLabel("Residue");
701 plotm->setXFormat(8, 0);
702 plotm->setYLabel("Area (nm\\S2\\N)");
703 plotm->setErrorsAsSeparateColumn(true);
704 plotm->appendLegend("Average (nm\\S2\\N)");
705 plotm->appendLegend("Standard deviation (nm\\S2\\N)");
706 avem->addModule(plotm);
711 if (!fnDGSolv_.empty())
713 dgSolv_.setColumnCount(0, 1 + outputSel_.size());
714 AnalysisDataPlotModulePointer plotm(
715 new AnalysisDataPlotModule(settings.plotSettings()));
716 plotm->setFileName(fnDGSolv_);
717 plotm->setTitle("Free Energy of Solvation");
718 plotm->setXAxisIsTime();
719 plotm->setYLabel("D Gsolv");
720 plotm->appendLegend("Total");
721 for (size_t i = 0; i < outputSel_.size(); ++i)
723 plotm->appendLegend(outputSel_[i].name());
725 dgSolv_.addModule(plotm);
728 if (!fnVolume_.empty())
730 volume_.setColumnCount(0, 2);
731 AnalysisDataPlotModulePointer plotm(
732 new AnalysisDataPlotModule(settings.plotSettings()));
733 plotm->setFileName(fnVolume_);
734 plotm->setTitle("Volume and Density");
735 plotm->setXAxisIsTime();
736 plotm->appendLegend("Volume (nm\\S3\\N)");
737 plotm->appendLegend("Density (g/l)");
738 volume_.addModule(plotm);
742 /*! \brief
743 * Temporary memory for use within a single-frame calculation.
745 class SasaModuleData : public TrajectoryAnalysisModuleData
747 public:
748 /*! \brief
749 * Reserves memory for the frame-local data.
751 * `residueCount` will be zero if per-residue data is not being
752 * calculated.
754 SasaModuleData(TrajectoryAnalysisModule *module,
755 const AnalysisDataParallelOptions &opt,
756 const SelectionCollection &selections,
757 int atomCount, int residueCount)
758 : TrajectoryAnalysisModuleData(module, opt, selections)
760 index_.reserve(atomCount);
761 // If the calculation group is not dynamic, pre-calculate
762 // the index, since it is not going to change.
763 for (int i = 0; i < atomCount; ++i)
765 index_.push_back(i);
767 atomAreas_.resize(atomCount);
768 res_a_.resize(residueCount);
771 void finish() override { finishDataHandles(); }
773 //! Indices of the calculation selection positions selected for the frame.
774 std::vector<int> index_;
775 /*! \brief
776 * Atom areas for each calculation selection position for the frame.
778 * One entry for each position in the calculation group.
779 * Values for atoms not selected are set to zero.
781 std::vector<real> atomAreas_;
782 /*! \brief
783 * Working array to accumulate areas for each residue.
785 * One entry for each distinct residue in the calculation group;
786 * indices are not directly residue numbers or residue indices.
788 * This vector is empty if residue area calculations are not being
789 * performed.
791 std::vector<real> res_a_;
794 TrajectoryAnalysisModuleDataPointer Sasa::startFrames(
795 const AnalysisDataParallelOptions &opt,
796 const SelectionCollection &selections)
798 return TrajectoryAnalysisModuleDataPointer(
799 new SasaModuleData(this, opt, selections, surfaceSel_.posCount(),
800 residueArea_.columnCount(0)));
803 /*! \brief
804 * Helper method to compute the areas for a single selection.
806 * \param[in] surfaceSel The calculation selection.
807 * \param[in] sel The selection to compute the areas for (can be
808 * `surfaceSel` or one of the output selections).
809 * \param[in] atomAreas Atom areas for each position in `surfaceSel`.
810 * \param[in] dgsFactor Free energy coefficients for each position in
811 * `surfaceSel`. If empty, free energies are not calculated.
812 * \param[out] totalAreaOut Total area of `sel` (sum of atom areas it selects).
813 * \param[out] dgsolvOut Solvation free energy.
814 * Will be zero of `dgsFactor` is empty.
815 * \param atomAreaHandle Data handle to use for storing atom areas for `sel`.
816 * \param resAreaHandle Data handle to use for storing residue areas for `sel`.
817 * \param resAreaWork Work array for accumulating the residue areas.
818 * If empty, atom and residue areas are not calculated.
820 * `atomAreaHandle` and `resAreaHandle` are not used if `resAreaWork` is empty.
822 void computeAreas(const Selection &surfaceSel, const Selection &sel,
823 const std::vector<real> &atomAreas,
824 const std::vector<real> &dgsFactor,
825 real *totalAreaOut, real *dgsolvOut,
826 AnalysisDataHandle atomAreaHandle,
827 AnalysisDataHandle resAreaHandle,
828 std::vector<real> *resAreaWork)
830 const bool bResAt = !resAreaWork->empty();
831 const bool bDGsolv = !dgsFactor.empty();
832 real totalArea = 0;
833 real dgsolv = 0;
835 if (bResAt)
837 std::fill(resAreaWork->begin(), resAreaWork->end(), 0.0_real);
839 for (int i = 0; i < sel.posCount(); ++i)
841 // Get the index of the atom in the calculation group.
842 // For the output groups, the mapping has been precalculated in
843 // initAnalysis().
844 const int ii = (sel != surfaceSel ? sel.position(i).mappedId() : i);
845 if (!surfaceSel.position(ii).selected())
847 // For the calculation group, skip unselected atoms.
848 if (sel == surfaceSel)
850 continue;
852 GMX_THROW(InconsistentInputError("Output selection is not a subset of the surface selection"));
854 // Get the internal index of the matching residue.
855 // These have been precalculated in initAnalysis().
856 const int ri = surfaceSel.position(ii).mappedId();
857 const real atomArea = atomAreas[ii];
858 totalArea += atomArea;
859 if (bResAt)
861 atomAreaHandle.setPoint(ii, atomArea);
862 (*resAreaWork)[ri] += atomArea;
864 if (bDGsolv)
866 dgsolv += atomArea * dgsFactor[ii];
869 if (bResAt)
871 for (size_t i = 0; i < (*resAreaWork).size(); ++i)
873 resAreaHandle.setPoint(i, (*resAreaWork)[i]);
876 *totalAreaOut = totalArea;
877 *dgsolvOut = dgsolv;
880 void
881 Sasa::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
882 TrajectoryAnalysisModuleData *pdata)
884 AnalysisDataHandle ah = pdata->dataHandle(area_);
885 AnalysisDataHandle dgh = pdata->dataHandle(dgSolv_);
886 AnalysisDataHandle aah = pdata->dataHandle(atomArea_);
887 AnalysisDataHandle rah = pdata->dataHandle(residueArea_);
888 AnalysisDataHandle vh = pdata->dataHandle(volume_);
889 const Selection &surfaceSel = pdata->parallelSelection(surfaceSel_);
890 const SelectionList &outputSel = pdata->parallelSelections(outputSel_);
891 SasaModuleData &frameData = *static_cast<SasaModuleData *>(pdata);
893 const bool bResAt = !frameData.res_a_.empty();
894 const bool bDGsol = !dgsFactor_.empty();
895 const bool bConnolly = (frnr == 0 && !fnConnolly_.empty());
897 // Update indices of selected atoms in the work array.
898 if (surfaceSel.isDynamic())
900 frameData.index_.clear();
901 for (int i = 0; i < surfaceSel.posCount(); ++i)
903 if (surfaceSel.position(i).selected())
905 frameData.index_.push_back(i);
910 // Determine what needs to be calculated.
911 int flag = 0;
912 if (bResAt || bDGsol || !outputSel.empty())
914 flag |= FLAG_ATOM_AREA;
916 if (bConnolly)
918 flag |= FLAG_DOTS;
920 if (volume_.columnCount() > 0)
922 flag |= FLAG_VOLUME;
925 // Do the low-level calculation.
926 // totarea and totvolume receive the values for the calculation group.
927 // area array contains the per-atom areas for the selected positions.
928 // surfacedots contains nsurfacedots entries, and contains the actual
929 // points.
930 real totarea, totvolume;
931 real *area = nullptr, *surfacedots = nullptr;
932 int nsurfacedots;
933 calculator_.calculate(surfaceSel.coordinates().data(), pbc,
934 frameData.index_.size(), frameData.index_.data(), flag,
935 &totarea, &totvolume, &area,
936 &surfacedots, &nsurfacedots);
937 // Unpack the atomwise areas into the frameData.atomAreas_ array for easier
938 // indexing in the case of dynamic surfaceSel.
939 if (area != nullptr)
941 if (surfaceSel.isDynamic())
943 std::fill(frameData.atomAreas_.begin(), frameData.atomAreas_.end(),
944 0.0_real);
945 for (size_t i = 0; i < frameData.index_.size(); ++i)
947 frameData.atomAreas_[frameData.index_[i]] = area[i];
950 else
952 std::copy(area, area + surfaceSel.posCount(),
953 frameData.atomAreas_.begin());
955 sfree(area);
957 const sfree_guard dotsGuard(surfacedots);
959 if (bConnolly)
961 if (fr.natoms != mtop_->natoms)
963 GMX_THROW(InconsistentInputError("Connolly plot (-q) is only supported for trajectories that contain all the atoms"));
965 // This is somewhat nasty, as it modifies the atoms and symtab
966 // structures. But since it is only used in the first frame, and no
967 // one else uses the topology after initialization, it may just work
968 // even with future parallelization.
969 connolly_plot(fnConnolly_.c_str(),
970 nsurfacedots, surfacedots, fr.x, atoms_.get(),
971 &mtop_->symtab, fr.ePBC, fr.box, bIncludeSolute_);
974 ah.startFrame(frnr, fr.time);
975 if (bResAt)
977 aah.startFrame(frnr, fr.time);
978 rah.startFrame(frnr, fr.time);
980 if (bDGsol)
982 dgh.startFrame(frnr, fr.time);
985 ah.setPoint(0, totarea);
987 real totalArea, dgsolv;
988 if (bResAt || bDGsol)
990 computeAreas(surfaceSel, surfaceSel, frameData.atomAreas_, dgsFactor_,
991 &totalArea, &dgsolv, aah, rah, &frameData.res_a_);
992 if (bDGsol)
994 dgh.setPoint(0, dgsolv);
997 for (size_t g = 0; g < outputSel.size(); ++g)
999 if (bResAt)
1001 aah.selectDataSet(g + 1);
1002 rah.selectDataSet(g + 1);
1004 computeAreas(surfaceSel, outputSel[g], frameData.atomAreas_, dgsFactor_,
1005 &totalArea, &dgsolv, aah, rah, &frameData.res_a_);
1006 ah.setPoint(g + 1, totalArea);
1007 if (bDGsol)
1009 dgh.setPoint(g + 1, dgsolv);
1013 ah.finishFrame();
1014 if (bResAt)
1016 aah.finishFrame();
1017 rah.finishFrame();
1019 if (bDGsol)
1021 dgh.finishFrame();
1024 if (vh.isValid())
1026 real totmass = 0;
1027 for (int i = 0; i < surfaceSel.posCount(); ++i)
1029 totmass += surfaceSel.position(i).mass();
1031 const real density = totmass*AMU/(totvolume*NANO*NANO*NANO);
1032 vh.startFrame(frnr, fr.time);
1033 vh.setPoint(0, totvolume);
1034 vh.setPoint(1, density);
1035 vh.finishFrame();
1039 void
1040 Sasa::finishAnalysis(int /*nframes*/)
1042 //if (bITP)
1044 // fp3 = ftp2FILE(efITP, nfile, fnm, "w");
1045 // fprintf(fp3, "[ position_restraints ]\n"
1046 // "#define FCX 1000\n"
1047 // "#define FCY 1000\n"
1048 // "#define FCZ 1000\n"
1049 // "; Atom Type fx fy fz\n");
1050 // for (i = 0; i < nx[0]; i++)
1051 // {
1052 // if (atom_area[i] > minarea)
1053 // {
1054 // fprintf(fp3, "%5d 1 FCX FCX FCZ\n", ii+1);
1055 // }
1056 // }
1057 // ffclose(fp3);
1061 void
1062 Sasa::writeOutput()
1066 //! \}
1068 } // namespace
1070 const char SasaInfo::name[] = "sasa";
1071 const char SasaInfo::shortDescription[] =
1072 "Compute solvent accessible surface area";
1074 TrajectoryAnalysisModulePointer SasaInfo::create()
1076 return TrajectoryAnalysisModulePointer(new Sasa);
1079 } // namespace analysismodules
1081 } // namespace gmx