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 by the GROMACS development team.
7 * Copyright (c) 2013,2014,2015,2016,2017 by the GROMACS development team.
8 * Copyright (c) 2018,2019,2020,2021, by the GROMACS development team, led by
9 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
10 * and including many others, as listed in the AUTHORS file in the
11 * top-level source directory and at http://www.gromacs.org.
13 * GROMACS is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Lesser General Public License
15 * as published by the Free Software Foundation; either version 2.1
16 * of the License, or (at your option) any later version.
18 * GROMACS is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * Lesser General Public License for more details.
23 * You should have received a copy of the GNU Lesser General Public
24 * License along with GROMACS; if not, see
25 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
26 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28 * If you want to redistribute modifications to GROMACS, please
29 * consider that scientific software is very special. Version
30 * control is crucial - bugs must be traceable. We will be happy to
31 * consider code for inclusion in the official distribution, but
32 * derived work must not be called official GROMACS. Details are found
33 * in the README & COPYING files - if they are missing, get the
34 * official version at http://www.gromacs.org.
36 * To help us fund GROMACS development, we humbly ask that you cite
37 * the research papers on the package. Check out http://www.gromacs.org.
41 * Implements gmx::analysismodules::Sasa.
43 * \author Teemu Murtola <teemu.murtola@gmail.com> (C++ conversion)
44 * \ingroup module_trajectoryanalysis
54 #include "gromacs/analysisdata/analysisdata.h"
55 #include "gromacs/analysisdata/modules/average.h"
56 #include "gromacs/analysisdata/modules/plot.h"
57 #include "gromacs/fileio/confio.h"
58 #include "gromacs/fileio/pdbio.h"
59 #include "gromacs/math/units.h"
60 #include "gromacs/math/vec.h"
61 #include "gromacs/options/basicoptions.h"
62 #include "gromacs/options/filenameoption.h"
63 #include "gromacs/options/ioptionscontainer.h"
64 #include "gromacs/pbcutil/pbc.h"
65 #include "gromacs/selection/selection.h"
66 #include "gromacs/selection/selectionoption.h"
67 #include "gromacs/topology/atomprop.h"
68 #include "gromacs/topology/symtab.h"
69 #include "gromacs/topology/topology.h"
70 #include "gromacs/trajectory/trajectoryframe.h"
71 #include "gromacs/trajectoryanalysis/analysismodule.h"
72 #include "gromacs/trajectoryanalysis/analysissettings.h"
73 #include "gromacs/trajectoryanalysis/topologyinformation.h"
74 #include "gromacs/utility/exceptions.h"
75 #include "gromacs/utility/futil.h"
76 #include "gromacs/utility/pleasecite.h"
77 #include "gromacs/utility/smalloc.h"
78 #include "gromacs/utility/stringutil.h"
79 #include "gromacs/utility/unique_cptr.h"
81 #include "surfacearea.h"
86 namespace analysismodules
92 //! \addtogroup module_trajectoryanalysis
95 //! Tracks information on two nearest neighbors of a single surface dot.
98 //! Index of the second nearest neighbor dot.
100 //! Index of the nearest neighbor dot.
102 //! Squared distance to `aa`.
104 //! Squared distance to `ab`.
109 * Updates nearest neighbor information for a surface dot.
111 * \param[in,out] c Nearest neighbor information array to update.
112 * \param[in] i Index in `c` to update.
113 * \param[in] j Index of the other surface dot to add to the array.
114 * \param[in] d2 Squared distance between `i` and `j`.
116 void add_rec(t_conect c[], int i, int j, real d2)
119 { // NOLINT bugprone-branch-clone
123 else if (c[i].ab == -1)
124 { // NOLINT bugprone-branch-clone
128 else if (d2 < c[i].d2a)
133 else if (d2 < c[i].d2b)
138 /* Swap them if necessary: a must be larger than b */
139 if (c[i].d2a < c[i].d2b)
151 * Adds CONECT records for surface dots.
153 * \param[in] fn PDB file to append the CONECT records to.
154 * \param[in] n Number of dots in `x`.
155 * \param[in] x Array of surface dot positions.
157 * Adds a CONECT record that connects each surface dot to its two nearest
158 * neighbors. The function is copied verbatim from the old gmx_sas.c
161 void do_conect(const char* fn, int n, rvec x[])
163 t_conect* c = nullptr;
165 fprintf(stderr, "Building CONECT records\n");
167 for (int i = 0; (i < n); i++)
169 c[i].aa = c[i].ab = -1;
172 for (int i = 0; (i < n); i++)
174 for (int j = i + 1; (j < n); j++)
177 rvec_sub(x[i], x[j], dx);
178 const real d2 = iprod(dx, dx);
179 add_rec(c, i, j, d2);
180 add_rec(c, j, i, d2);
183 FILE* fp = gmx_ffopen(fn, "a");
184 for (int i = 0; (i < n); i++)
186 if ((c[i].aa == -1) || (c[i].ab == -1))
188 fprintf(stderr, "Warning dot %d has no connections\n", i + 1);
190 fprintf(fp, "CONECT%5d%5d%5d\n", i + 1, c[i].aa + 1, c[i].ab + 1);
197 * Plots the surface into a PDB file, optionally including the original atoms.
199 void connolly_plot(const char* fn,
207 gmx_bool bIncludeSolute)
209 const char* const atomnm = "DOT";
210 const char* const resnm = "DOT";
211 const char* const title = "Connolly Dot Surface Generated by gmx sasa";
213 rvec* xnew = nullptr;
218 int r0 = atoms->nres;
219 srenew(atoms->atom, atoms->nr + ndots);
220 memset(&atoms->atom[i0], 0, sizeof(*atoms->atom) * ndots);
221 srenew(atoms->atomname, atoms->nr + ndots);
222 srenew(atoms->resinfo, r0 + 1);
223 atoms->atom[i0].resind = r0;
224 t_atoms_set_resinfo(atoms, i0, symtab, resnm, r0 + 1, ' ', 0, ' ');
225 if (atoms->pdbinfo != nullptr)
227 srenew(atoms->pdbinfo, atoms->nr + ndots);
229 snew(xnew, atoms->nr + ndots);
230 for (int i = 0; (i < atoms->nr); i++)
232 copy_rvec(x[i], xnew[i]);
235 for (int i = 0; (i < ndots); i++)
238 atoms->atomname[ii0] = put_symtab(symtab, atomnm);
239 atoms->atom[ii0].resind = r0;
240 xnew[ii0][XX] = dots[k++];
241 xnew[ii0][YY] = dots[k++];
242 xnew[ii0][ZZ] = dots[k++];
243 if (atoms->pdbinfo != nullptr)
245 atoms->pdbinfo[ii0].type = PdbRecordType::Atom;
246 atoms->pdbinfo[ii0].atomnr = ii0;
247 atoms->pdbinfo[ii0].bfac = 0.0;
248 atoms->pdbinfo[ii0].occup = 0.0;
251 atoms->nr = i0 + ndots;
252 atoms->nres = r0 + 1;
253 write_sto_conf(fn, title, atoms, xnew, nullptr, pbcType, const_cast<rvec*>(box));
260 init_t_atoms(&aaa, ndots, TRUE);
261 aaa.atom[0].resind = 0;
262 t_atoms_set_resinfo(&aaa, 0, symtab, resnm, 1, ' ', 0, ' ');
265 for (int i = 0; (i < ndots); i++)
268 aaa.atomname[ii0] = put_symtab(symtab, atomnm);
269 aaa.pdbinfo[ii0].type = PdbRecordType::Atom;
270 aaa.pdbinfo[ii0].atomnr = ii0;
271 aaa.atom[ii0].resind = 0;
272 xnew[ii0][XX] = dots[k++];
273 xnew[ii0][YY] = dots[k++];
274 xnew[ii0][ZZ] = dots[k++];
275 aaa.pdbinfo[ii0].bfac = 0.0;
276 aaa.pdbinfo[ii0].occup = 0.0;
279 write_sto_conf(fn, title, &aaa, xnew, nullptr, pbcType, const_cast<rvec*>(box));
280 do_conect(fn, ndots, xnew);
286 /********************************************************************
287 * Actual analysis module
291 * Implements `gmx sas` trajectory analysis module.
293 class Sasa : public TrajectoryAnalysisModule
298 void initOptions(IOptionsContainer* options, TrajectoryAnalysisSettings* settings) override;
299 void initAnalysis(const TrajectoryAnalysisSettings& settings, const TopologyInformation& top) override;
301 TrajectoryAnalysisModuleDataPointer startFrames(const AnalysisDataParallelOptions& opt,
302 const SelectionCollection& selections) override;
303 void analyzeFrame(int frnr, const t_trxframe& fr, t_pbc* pbc, TrajectoryAnalysisModuleData* pdata) override;
305 void finishAnalysis(int nframes) override;
306 void writeOutput() override;
310 * Surface areas as a function of time.
312 * First column is for the calculation group, and the rest for the
313 * output groups. This data is always produced.
317 * Per-atom surface areas as a function of time.
319 * Contains one data set for each column in `area_`.
320 * Each column corresponds to a selection position in `surfaceSel_`.
321 * This data is only produced if atom or residue areas have been
324 AnalysisData atomArea_;
326 * Per-residue surface areas as a function of time.
328 * Contains one data set for each column in `area_`.
329 * Each column corresponds to a distinct residue `surfaceSel_`.
330 * For example, if `surfaceSel_` selects residues 2, 5, and 7, there
331 * will be three columns here.
332 * This data is only produced if atom or residue areas have been
335 AnalysisData residueArea_;
337 * Free energy estimates as a function of time.
339 * Column layout is the same as for `area_`.
340 * This data is only produced if the output is requested.
342 AnalysisData dgSolv_;
344 * Total volume and density of the calculation group as a function of
347 * The first column is the volume and the second column is the density.
348 * This data is only produced if the output is requested.
350 AnalysisData volume_;
353 * The selection to calculate the surface for.
355 * Selection::originalId() and Selection::mappedId() store the mapping
356 * from the positions to the columns of `residueArea_`.
357 * The selection is computed with SelectionOption::dynamicMask(), i.e.,
358 * even in the presence of a dynamic selection, the number of returned
359 * positions is fixed, and SelectionPosition::selected() is used.
361 Selection surfaceSel_;
363 * List of optional additional output groups.
365 * Each of these must be a subset of the `surfaceSel_`.
366 * Selection::originalId() and Selection::mappedId() store the mapping
367 * from the positions to the corresponsing positions in `surfaceSel_`.
369 SelectionList outputSel_;
372 std::string fnAtomArea_;
373 std::string fnResidueArea_;
374 std::string fnDGSolv_;
375 std::string fnVolume_;
376 std::string fnConnolly_;
382 bool bIncludeSolute_;
384 //! Global topology corresponding to the input.
386 //! Per-atom data corresponding to the input.
388 //! Combined VdW and probe radii for each atom in the calculation group.
389 std::vector<real> radii_;
391 * Solvation free energy coefficients for each atom in the calculation
394 * Empty if the free energy output has not been requested.
396 std::vector<real> dgsFactor_;
397 //! Calculation algorithm.
398 SurfaceAreaCalculator calculator_;
400 // Copy and assign disallowed by base.
404 solsize_(0.14), ndots_(24), dgsDefault_(0), bIncludeSolute_(true), mtop_(nullptr), atoms_(nullptr)
407 registerAnalysisDataset(&area_, "area");
408 registerAnalysisDataset(&atomArea_, "atomarea");
409 registerAnalysisDataset(&residueArea_, "resarea");
410 registerAnalysisDataset(&dgSolv_, "dgsolv");
411 registerAnalysisDataset(&volume_, "volume");
414 void Sasa::initOptions(IOptionsContainer* options, TrajectoryAnalysisSettings* settings)
416 static const char* const desc[] = {
417 "[THISMODULE] computes solvent accessible surface areas.",
418 "See Eisenhaber F, Lijnzaad P, Argos P, Sander C, & Scharf M",
419 "(1995) J. Comput. Chem. 16, 273-284 for the algorithm used.",
420 "With [TT]-q[tt], the Connolly surface can be generated as well",
421 "in a [REF].pdb[ref] file where the nodes are represented as atoms",
422 "and the edges connecting the nearest nodes as CONECT records.",
423 "[TT]-odg[tt] allows for estimation of solvation free energies",
424 "from per-atom solvation energies per exposed surface area.[PAR]",
426 "The program requires a selection for the surface calculation to be",
427 "specified with [TT]-surface[tt]. This should always consist of all",
428 "non-solvent atoms in the system. The area of this group is always",
429 "calculated. Optionally, [TT]-output[tt] can specify additional",
430 "selections, which should be subsets of the calculation group.",
431 "The solvent-accessible areas for these groups are also extracted",
432 "from the full surface.[PAR]",
434 "The average and standard deviation of the area over the trajectory",
435 "can be calculated per residue and atom (options [TT]-or[tt] and",
436 "[TT]-oa[tt]).[PAR]",
437 //"In combination with the latter option an [REF].itp[ref] file can be",
438 //"generated (option [TT]-i[tt])",
439 //"which can be used to restrain surface atoms.[PAR]",
441 "With the [TT]-tv[tt] option the total volume and density of the",
442 "molecule can be computed. With [TT]-pbc[tt] (the default), you",
443 "must ensure that your molecule/surface group is not split across PBC.",
444 "Otherwise, you will get non-sensical results.",
445 "Please also consider whether the normal probe radius is appropriate",
446 "in this case or whether you would rather use, e.g., 0. It is good",
447 "to keep in mind that the results for volume and density are very",
448 "approximate. For example, in ice Ih, one can easily fit water molecules in the",
449 "pores which would yield a volume that is too low, and surface area and density",
450 "that are both too high."
453 settings->setHelpText(desc);
455 options->addOption(FileNameOption("o")
456 .filetype(OptionFileType::Plot)
460 .defaultBasename("area")
461 .description("Total area as a function of time"));
463 FileNameOption("odg")
464 .filetype(OptionFileType::Plot)
467 .defaultBasename("dgsolv")
468 .description("Estimated solvation free energy as a function of time"));
469 options->addOption(FileNameOption("or")
470 .filetype(OptionFileType::Plot)
472 .store(&fnResidueArea_)
473 .defaultBasename("resarea")
474 .description("Average area per residue"));
475 options->addOption(FileNameOption("oa")
476 .filetype(OptionFileType::Plot)
479 .defaultBasename("atomarea")
480 .description("Average area per atom"));
481 options->addOption(FileNameOption("tv")
482 .filetype(OptionFileType::Plot)
485 .defaultBasename("volume")
486 .description("Total volume and density as a function of time"));
487 options->addOption(FileNameOption("q")
488 .filetype(OptionFileType::PDB)
491 .defaultBasename("connolly")
492 .description("PDB file for Connolly surface"));
495 DoubleOption("probe").store(&solsize_).description("Radius of the solvent probe (nm)"));
496 options->addOption(IntegerOption("ndots").store(&ndots_).description(
497 "Number of dots per sphere, more dots means more accuracy"));
499 BooleanOption("prot").store(&bIncludeSolute_).description("Output the protein to the Connolly [REF].pdb[ref] file too"));
501 DoubleOption("dgs").store(&dgsDefault_).description("Default value for solvation free energy per area (kJ/mol/nm^2)"));
503 // Selections must select atoms for the VdW radii lookup to work.
504 // The calculation group uses dynamicMask() so that the coordinates
505 // match a static array of VdW radii.
506 options->addOption(SelectionOption("surface")
511 .description("Surface calculation selection"));
513 SelectionOption("output").storeVector(&outputSel_).onlySortedAtoms().multiValue().description("Output selection(s)"));
515 // Atom names etc. are required for the VdW radii lookup.
516 settings->setFlag(TrajectoryAnalysisSettings::efRequireTop);
519 void Sasa::initAnalysis(const TrajectoryAnalysisSettings& settings, const TopologyInformation& top)
522 atoms_ = top.copyAtoms();
524 // bITP = opt2bSet("-i", nfile, fnm);
525 const bool bResAt = !fnResidueArea_.empty() || !fnAtomArea_.empty(); // || bITP;
526 const bool bDGsol = !fnDGSolv_.empty();
531 fprintf(stderr, "Probe size too small, setting it to %g\n", solsize_);
536 fprintf(stderr, "Ndots too small, setting it to %d\n", ndots_);
539 please_cite(stderr, "Eisenhaber95");
543 if (!top.hasFullTopology())
545 GMX_THROW(InconsistentInputError(
546 "Cannot compute Delta G of solvation without a tpr file"));
550 if (strcmp(*(atoms_->atomtype[0]), "?") == 0)
552 GMX_THROW(InconsistentInputError(
553 "Your input tpr file is too old (does not contain atom types). Cannot not "
554 "compute Delta G of solvation"));
558 printf("Free energy of solvation predictions:\n");
559 please_cite(stdout, "Eisenberg86a");
564 // Now compute atomic radii including solvent probe size.
565 // Also, fetch solvation free energy coefficients and
566 // compute the residue indices that map the calculation atoms
567 // to the columns of residueArea_.
568 radii_.reserve(surfaceSel_.posCount());
571 dgsFactor_.reserve(surfaceSel_.posCount());
574 const int resCount = surfaceSel_.initOriginalIdsToGroup(top.mtop(), INDEX_RES);
576 // TODO: Not exception-safe, but nice solution would be to have a C++
577 // atom properties class...
580 ArrayRef<const int> atomIndices = surfaceSel_.atomIndices();
582 for (int i = 0; i < surfaceSel_.posCount(); i++)
584 const int ii = atomIndices[i];
585 const int resind = atoms_->atom[ii].resind;
587 if (!aps.setAtomProperty(epropVDW, *(atoms_->resinfo[resind].name), *(atoms_->atomname[ii]), &radius))
591 radii_.push_back(radius + solsize_);
595 if (!aps.setAtomProperty(
596 epropDGsol, *(atoms_->resinfo[resind].name), *(atoms_->atomtype[ii]), &dgsFactor))
598 dgsFactor = dgsDefault_;
600 dgsFactor_.push_back(dgsFactor);
605 fprintf(stderr, "WARNING: could not find a Van der Waals radius for %d atoms\n", ndefault);
608 // Pre-compute mapping from the output groups to the calculation group,
609 // and store it in the selection ID map for easy lookup.
610 for (size_t g = 0; g < outputSel_.size(); ++g)
612 ArrayRef<const int> outputIndices = outputSel_[g].atomIndices();
613 for (int i = 0, j = 0; i < outputSel_[g].posCount(); ++i)
615 while (j < surfaceSel_.posCount() && outputIndices[i] > atomIndices[j])
619 if (j == surfaceSel_.posCount() || outputIndices[i] != atomIndices[j])
621 const std::string message = formatString(
622 "Output selection '%s' is not a subset of "
623 "the surface selection (atom %d is the first "
624 "atom not in the surface selection)",
625 outputSel_[g].name(),
626 outputIndices[i] + 1);
627 GMX_THROW(InconsistentInputError(message));
629 outputSel_[g].setOriginalId(i, j);
633 calculator_.setDotCount(ndots_);
634 calculator_.setRadii(radii_);
636 // Initialize all the output data objects and initialize the output plotters.
638 area_.setColumnCount(0, 1 + outputSel_.size());
640 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
641 plotm->setFileName(fnArea_);
642 plotm->setTitle("Solvent Accessible Surface");
643 plotm->setXAxisIsTime();
644 plotm->setYLabel("Area (nm\\S2\\N)");
645 plotm->appendLegend("Total");
646 for (size_t i = 0; i < outputSel_.size(); ++i)
648 plotm->appendLegend(outputSel_[i].name());
650 area_.addModule(plotm);
655 atomArea_.setDataSetCount(1 + outputSel_.size());
656 residueArea_.setDataSetCount(1 + outputSel_.size());
657 for (size_t i = 0; i <= outputSel_.size(); ++i)
659 atomArea_.setColumnCount(i, surfaceSel_.posCount());
660 residueArea_.setColumnCount(i, resCount);
663 AnalysisDataAverageModulePointer avem(new AnalysisDataAverageModule);
664 for (int i = 0; i < surfaceSel_.posCount(); ++i)
666 avem->setXAxisValue(i, surfaceSel_.position(i).atomIndices()[0] + 1);
668 atomArea_.addModule(avem);
669 if (!fnAtomArea_.empty())
671 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
672 plotm->setFileName(fnAtomArea_);
673 plotm->setTitle("Area per atom over the trajectory");
674 plotm->setXLabel("Atom");
675 plotm->setXFormat(8, 0);
676 plotm->setYLabel("Area (nm\\S2\\N)");
677 plotm->setErrorsAsSeparateColumn(true);
678 plotm->appendLegend("Average (nm\\S2\\N)");
679 plotm->appendLegend("Standard deviation (nm\\S2\\N)");
680 avem->addModule(plotm);
684 AnalysisDataAverageModulePointer avem(new AnalysisDataAverageModule);
686 for (int i = 0; i < surfaceSel_.posCount(); ++i)
688 const int residueGroup = surfaceSel_.position(i).mappedId();
689 if (residueGroup >= nextRow)
691 GMX_ASSERT(residueGroup == nextRow,
692 "Inconsistent (non-uniformly increasing) residue grouping");
693 const int atomIndex = surfaceSel_.position(i).atomIndices()[0];
694 const int residueIndex = atoms_->atom[atomIndex].resind;
695 avem->setXAxisValue(nextRow, atoms_->resinfo[residueIndex].nr);
699 residueArea_.addModule(avem);
700 if (!fnResidueArea_.empty())
702 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
703 plotm->setFileName(fnResidueArea_);
704 plotm->setTitle("Area per residue over the trajectory");
705 plotm->setXLabel("Residue");
706 plotm->setXFormat(8, 0);
707 plotm->setYLabel("Area (nm\\S2\\N)");
708 plotm->setErrorsAsSeparateColumn(true);
709 plotm->appendLegend("Average (nm\\S2\\N)");
710 plotm->appendLegend("Standard deviation (nm\\S2\\N)");
711 avem->addModule(plotm);
716 if (!fnDGSolv_.empty())
718 dgSolv_.setColumnCount(0, 1 + outputSel_.size());
719 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
720 plotm->setFileName(fnDGSolv_);
721 plotm->setTitle("Free Energy of Solvation");
722 plotm->setXAxisIsTime();
723 plotm->setYLabel("D Gsolv");
724 plotm->appendLegend("Total");
725 for (size_t i = 0; i < outputSel_.size(); ++i)
727 plotm->appendLegend(outputSel_[i].name());
729 dgSolv_.addModule(plotm);
732 if (!fnVolume_.empty())
734 volume_.setColumnCount(0, 2);
735 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
736 plotm->setFileName(fnVolume_);
737 plotm->setTitle("Volume and Density");
738 plotm->setXAxisIsTime();
739 plotm->appendLegend("Volume (nm\\S3\\N)");
740 plotm->appendLegend("Density (g/l)");
741 volume_.addModule(plotm);
746 * Temporary memory for use within a single-frame calculation.
748 class SasaModuleData : public TrajectoryAnalysisModuleData
752 * Reserves memory for the frame-local data.
754 * `residueCount` will be zero if per-residue data is not being
757 SasaModuleData(TrajectoryAnalysisModule* module,
758 const AnalysisDataParallelOptions& opt,
759 const SelectionCollection& selections,
762 TrajectoryAnalysisModuleData(module, opt, selections)
764 index_.reserve(atomCount);
765 // If the calculation group is not dynamic, pre-calculate
766 // the index, since it is not going to change.
767 for (int i = 0; i < atomCount; ++i)
771 atomAreas_.resize(atomCount);
772 res_a_.resize(residueCount);
775 void finish() override { finishDataHandles(); }
777 //! Indices of the calculation selection positions selected for the frame.
778 std::vector<int> index_;
780 * Atom areas for each calculation selection position for the frame.
782 * One entry for each position in the calculation group.
783 * Values for atoms not selected are set to zero.
785 std::vector<real> atomAreas_;
787 * Working array to accumulate areas for each residue.
789 * One entry for each distinct residue in the calculation group;
790 * indices are not directly residue numbers or residue indices.
792 * This vector is empty if residue area calculations are not being
795 std::vector<real> res_a_;
798 TrajectoryAnalysisModuleDataPointer Sasa::startFrames(const AnalysisDataParallelOptions& opt,
799 const SelectionCollection& selections)
801 return TrajectoryAnalysisModuleDataPointer(new SasaModuleData(
802 this, opt, selections, surfaceSel_.posCount(), residueArea_.columnCount(0)));
806 * Helper method to compute the areas for a single selection.
808 * \param[in] surfaceSel The calculation selection.
809 * \param[in] sel The selection to compute the areas for (can be
810 * `surfaceSel` or one of the output selections).
811 * \param[in] atomAreas Atom areas for each position in `surfaceSel`.
812 * \param[in] dgsFactor Free energy coefficients for each position in
813 * `surfaceSel`. If empty, free energies are not calculated.
814 * \param[out] totalAreaOut Total area of `sel` (sum of atom areas it selects).
815 * \param[out] dgsolvOut Solvation free energy.
816 * Will be zero of `dgsFactor` is empty.
817 * \param atomAreaHandle Data handle to use for storing atom areas for `sel`.
818 * \param resAreaHandle Data handle to use for storing residue areas for `sel`.
819 * \param resAreaWork Work array for accumulating the residue areas.
820 * If empty, atom and residue areas are not calculated.
822 * `atomAreaHandle` and `resAreaHandle` are not used if `resAreaWork` is empty.
824 void computeAreas(const Selection& surfaceSel,
825 const Selection& sel,
826 const std::vector<real>& atomAreas,
827 const std::vector<real>& dgsFactor,
830 AnalysisDataHandle atomAreaHandle,
831 AnalysisDataHandle resAreaHandle,
832 std::vector<real>* resAreaWork)
834 const bool bResAt = !resAreaWork->empty();
835 const bool bDGsolv = !dgsFactor.empty();
841 std::fill(resAreaWork->begin(), resAreaWork->end(), 0.0_real);
843 for (int i = 0; i < sel.posCount(); ++i)
845 // Get the index of the atom in the calculation group.
846 // For the output groups, the mapping has been precalculated in
848 const int ii = (sel != surfaceSel ? sel.position(i).mappedId() : i);
849 if (!surfaceSel.position(ii).selected())
851 // For the calculation group, skip unselected atoms.
852 if (sel == surfaceSel)
856 GMX_THROW(InconsistentInputError(
857 "Output selection is not a subset of the surface selection"));
859 // Get the internal index of the matching residue.
860 // These have been precalculated in initAnalysis().
861 const int ri = surfaceSel.position(ii).mappedId();
862 const real atomArea = atomAreas[ii];
863 totalArea += atomArea;
866 atomAreaHandle.setPoint(ii, atomArea);
867 (*resAreaWork)[ri] += atomArea;
871 dgsolv += atomArea * dgsFactor[ii];
876 for (size_t i = 0; i < (*resAreaWork).size(); ++i)
878 resAreaHandle.setPoint(i, (*resAreaWork)[i]);
881 *totalAreaOut = totalArea;
885 void Sasa::analyzeFrame(int frnr, const t_trxframe& fr, t_pbc* pbc, TrajectoryAnalysisModuleData* pdata)
887 AnalysisDataHandle ah = pdata->dataHandle(area_);
888 AnalysisDataHandle dgh = pdata->dataHandle(dgSolv_);
889 AnalysisDataHandle aah = pdata->dataHandle(atomArea_);
890 AnalysisDataHandle rah = pdata->dataHandle(residueArea_);
891 AnalysisDataHandle vh = pdata->dataHandle(volume_);
892 const Selection& surfaceSel = pdata->parallelSelection(surfaceSel_);
893 const SelectionList& outputSel = pdata->parallelSelections(outputSel_);
894 SasaModuleData& frameData = *static_cast<SasaModuleData*>(pdata);
896 const bool bResAt = !frameData.res_a_.empty();
897 const bool bDGsol = !dgsFactor_.empty();
898 const bool bConnolly = (frnr == 0 && !fnConnolly_.empty());
900 // Update indices of selected atoms in the work array.
901 if (surfaceSel.isDynamic())
903 frameData.index_.clear();
904 for (int i = 0; i < surfaceSel.posCount(); ++i)
906 if (surfaceSel.position(i).selected())
908 frameData.index_.push_back(i);
913 // Determine what needs to be calculated.
915 if (bResAt || bDGsol || !outputSel.empty())
917 flag |= FLAG_ATOM_AREA;
923 if (volume_.columnCount() > 0)
928 // Do the low-level calculation.
929 // totarea and totvolume receive the values for the calculation group.
930 // area array contains the per-atom areas for the selected positions.
931 // surfacedots contains nsurfacedots entries, and contains the actual
935 real *area = nullptr, *surfacedots = nullptr;
936 int nsurfacedots = 0;
937 calculator_.calculate(surfaceSel.coordinates().data(),
939 frameData.index_.size(),
940 frameData.index_.data(),
947 // Unpack the atomwise areas into the frameData.atomAreas_ array for easier
948 // indexing in the case of dynamic surfaceSel.
951 if (surfaceSel.isDynamic())
953 std::fill(frameData.atomAreas_.begin(), frameData.atomAreas_.end(), 0.0_real);
954 for (size_t i = 0; i < frameData.index_.size(); ++i)
956 frameData.atomAreas_[frameData.index_[i]] = area[i];
961 std::copy(area, area + surfaceSel.posCount(), frameData.atomAreas_.begin());
965 const sfree_guard dotsGuard(surfacedots);
969 if (fr.natoms != mtop_->natoms)
972 InconsistentInputError("Connolly plot (-q) is only supported for trajectories "
973 "that contain all the atoms"));
975 // This is somewhat nasty, as it modifies the atoms and symtab
976 // structures. But since it is only used in the first frame, and no
977 // one else uses the topology after initialization, it may just work
978 // even with future parallelization.
979 connolly_plot(fnConnolly_.c_str(),
990 ah.startFrame(frnr, fr.time);
993 aah.startFrame(frnr, fr.time);
994 rah.startFrame(frnr, fr.time);
998 dgh.startFrame(frnr, fr.time);
1001 ah.setPoint(0, totarea);
1005 if (bResAt || bDGsol)
1007 computeAreas(surfaceSel,
1009 frameData.atomAreas_,
1018 dgh.setPoint(0, dgsolv);
1021 for (size_t g = 0; g < outputSel.size(); ++g)
1025 aah.selectDataSet(g + 1);
1026 rah.selectDataSet(g + 1);
1028 computeAreas(surfaceSel,
1030 frameData.atomAreas_,
1037 ah.setPoint(g + 1, totalArea);
1040 dgh.setPoint(g + 1, dgsolv);
1058 for (int i = 0; i < surfaceSel.posCount(); ++i)
1060 totmass += surfaceSel.position(i).mass();
1062 const real density = totmass * gmx::c_amu / (totvolume * gmx::c_nano * gmx::c_nano * gmx::c_nano);
1063 vh.startFrame(frnr, fr.time);
1064 vh.setPoint(0, totvolume);
1065 vh.setPoint(1, density);
1070 void Sasa::finishAnalysis(int /*nframes*/) {}
1072 void Sasa::writeOutput() {}
1078 const char SasaInfo::name[] = "sasa";
1079 const char SasaInfo::shortDescription[] = "Compute solvent accessible surface area";
1081 TrajectoryAnalysisModulePointer SasaInfo::create()
1083 return TrajectoryAnalysisModulePointer(new Sasa);
1086 } // namespace analysismodules