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, 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[])
169 fprintf(stderr, "Building CONECT records\n");
171 for (i = 0; (i < n); i++)
173 c[i].aa = c[i].ab = -1;
176 for (i = 0; (i < n); i++)
178 for (j = i + 1; (j < n); j++)
180 rvec_sub(x[i], x[j], dx);
182 add_rec(c, i, j, d2);
183 add_rec(c, j, i, d2);
186 fp = gmx_ffopen(fn, "a");
187 for (i = 0; (i < n); i++)
189 if ((c[i].aa == -1) || (c[i].ab == -1))
191 fprintf(stderr, "Warning dot %d has no connections\n", i + 1);
193 fprintf(fp, "CONECT%5d%5d%5d\n", i + 1, c[i].aa + 1, c[i].ab + 1);
200 * Plots the surface into a PDB file, optionally including the original atoms.
202 void connolly_plot(const char* fn,
210 gmx_bool bIncludeSolute)
212 const char* const atomnm = "DOT";
213 const char* const resnm = "DOT";
214 const char* const title = "Connolly Dot Surface Generated by gmx sasa";
216 int i, i0, r0, ii0, k;
224 srenew(atoms->atom, atoms->nr + ndots);
225 memset(&atoms->atom[i0], 0, sizeof(*atoms->atom) * ndots);
226 srenew(atoms->atomname, atoms->nr + ndots);
227 srenew(atoms->resinfo, r0 + 1);
228 atoms->atom[i0].resind = r0;
229 t_atoms_set_resinfo(atoms, i0, symtab, resnm, r0 + 1, ' ', 0, ' ');
230 if (atoms->pdbinfo != nullptr)
232 srenew(atoms->pdbinfo, atoms->nr + ndots);
234 snew(xnew, atoms->nr + ndots);
235 for (i = 0; (i < atoms->nr); i++)
237 copy_rvec(x[i], xnew[i]);
239 for (i = k = 0; (i < ndots); i++)
242 atoms->atomname[ii0] = put_symtab(symtab, atomnm);
243 atoms->atom[ii0].resind = r0;
244 xnew[ii0][XX] = dots[k++];
245 xnew[ii0][YY] = dots[k++];
246 xnew[ii0][ZZ] = dots[k++];
247 if (atoms->pdbinfo != nullptr)
249 atoms->pdbinfo[ii0].type = epdbATOM;
250 atoms->pdbinfo[ii0].atomnr = ii0;
251 atoms->pdbinfo[ii0].bfac = 0.0;
252 atoms->pdbinfo[ii0].occup = 0.0;
255 atoms->nr = i0 + ndots;
256 atoms->nres = r0 + 1;
257 write_sto_conf(fn, title, atoms, xnew, nullptr, pbcType, const_cast<rvec*>(box));
263 init_t_atoms(&aaa, ndots, TRUE);
264 aaa.atom[0].resind = 0;
265 t_atoms_set_resinfo(&aaa, 0, symtab, resnm, 1, ' ', 0, ' ');
267 for (i = k = 0; (i < ndots); i++)
270 aaa.atomname[ii0] = put_symtab(symtab, atomnm);
271 aaa.pdbinfo[ii0].type = epdbATOM;
272 aaa.pdbinfo[ii0].atomnr = ii0;
273 aaa.atom[ii0].resind = 0;
274 xnew[ii0][XX] = dots[k++];
275 xnew[ii0][YY] = dots[k++];
276 xnew[ii0][ZZ] = dots[k++];
277 aaa.pdbinfo[ii0].bfac = 0.0;
278 aaa.pdbinfo[ii0].occup = 0.0;
281 write_sto_conf(fn, title, &aaa, xnew, nullptr, pbcType, const_cast<rvec*>(box));
282 do_conect(fn, ndots, xnew);
288 /********************************************************************
289 * Actual analysis module
293 * Implements `gmx sas` trajectory analysis module.
295 class Sasa : public TrajectoryAnalysisModule
300 void initOptions(IOptionsContainer* options, TrajectoryAnalysisSettings* settings) override;
301 void initAnalysis(const TrajectoryAnalysisSettings& settings, const TopologyInformation& top) override;
303 TrajectoryAnalysisModuleDataPointer startFrames(const AnalysisDataParallelOptions& opt,
304 const SelectionCollection& selections) override;
305 void analyzeFrame(int frnr, const t_trxframe& fr, t_pbc* pbc, TrajectoryAnalysisModuleData* pdata) override;
307 void finishAnalysis(int nframes) override;
308 void writeOutput() override;
312 * Surface areas as a function of time.
314 * First column is for the calculation group, and the rest for the
315 * output groups. This data is always produced.
319 * Per-atom surface areas as a function of time.
321 * Contains one data set for each column in `area_`.
322 * Each column corresponds to a selection position in `surfaceSel_`.
323 * This data is only produced if atom or residue areas have been
326 AnalysisData atomArea_;
328 * Per-residue surface areas as a function of time.
330 * Contains one data set for each column in `area_`.
331 * Each column corresponds to a distinct residue `surfaceSel_`.
332 * For example, if `surfaceSel_` selects residues 2, 5, and 7, there
333 * will be three columns here.
334 * This data is only produced if atom or residue areas have been
337 AnalysisData residueArea_;
339 * Free energy estimates as a function of time.
341 * Column layout is the same as for `area_`.
342 * This data is only produced if the output is requested.
344 AnalysisData dgSolv_;
346 * Total volume and density of the calculation group as a function of
349 * The first column is the volume and the second column is the density.
350 * This data is only produced if the output is requested.
352 AnalysisData volume_;
355 * The selection to calculate the surface for.
357 * Selection::originalId() and Selection::mappedId() store the mapping
358 * from the positions to the columns of `residueArea_`.
359 * The selection is computed with SelectionOption::dynamicMask(), i.e.,
360 * even in the presence of a dynamic selection, the number of returned
361 * positions is fixed, and SelectionPosition::selected() is used.
363 Selection surfaceSel_;
365 * List of optional additional output groups.
367 * Each of these must be a subset of the `surfaceSel_`.
368 * Selection::originalId() and Selection::mappedId() store the mapping
369 * from the positions to the corresponsing positions in `surfaceSel_`.
371 SelectionList outputSel_;
374 std::string fnAtomArea_;
375 std::string fnResidueArea_;
376 std::string fnDGSolv_;
377 std::string fnVolume_;
378 std::string fnConnolly_;
384 bool bIncludeSolute_;
386 //! Global topology corresponding to the input.
388 //! Per-atom data corresponding to the input.
390 //! Combined VdW and probe radii for each atom in the calculation group.
391 std::vector<real> radii_;
393 * Solvation free energy coefficients for each atom in the calculation
396 * Empty if the free energy output has not been requested.
398 std::vector<real> dgsFactor_;
399 //! Calculation algorithm.
400 SurfaceAreaCalculator calculator_;
402 // Copy and assign disallowed by base.
409 bIncludeSolute_(true),
414 registerAnalysisDataset(&area_, "area");
415 registerAnalysisDataset(&atomArea_, "atomarea");
416 registerAnalysisDataset(&residueArea_, "resarea");
417 registerAnalysisDataset(&dgSolv_, "dgsolv");
418 registerAnalysisDataset(&volume_, "volume");
421 void Sasa::initOptions(IOptionsContainer* options, TrajectoryAnalysisSettings* settings)
423 static const char* const desc[] = {
424 "[THISMODULE] computes solvent accessible surface areas.",
425 "See Eisenhaber F, Lijnzaad P, Argos P, Sander C, & Scharf M",
426 "(1995) J. Comput. Chem. 16, 273-284 for the algorithm used.",
427 "With [TT]-q[tt], the Connolly surface can be generated as well",
428 "in a [REF].pdb[ref] file where the nodes are represented as atoms",
429 "and the edges connecting the nearest nodes as CONECT records.",
430 "[TT]-odg[tt] allows for estimation of solvation free energies",
431 "from per-atom solvation energies per exposed surface area.[PAR]",
433 "The program requires a selection for the surface calculation to be",
434 "specified with [TT]-surface[tt]. This should always consist of all",
435 "non-solvent atoms in the system. The area of this group is always",
436 "calculated. Optionally, [TT]-output[tt] can specify additional",
437 "selections, which should be subsets of the calculation group.",
438 "The solvent-accessible areas for these groups are also extracted",
439 "from the full surface.[PAR]",
441 "The average and standard deviation of the area over the trajectory",
442 "can be calculated per residue and atom (options [TT]-or[tt] and", "[TT]-oa[tt]).[PAR]",
443 //"In combination with the latter option an [REF].itp[ref] file can be",
444 //"generated (option [TT]-i[tt])",
445 //"which can be used to restrain surface atoms.[PAR]",
447 "With the [TT]-tv[tt] option the total volume and density of the",
448 "molecule can be computed. With [TT]-pbc[tt] (the default), you",
449 "must ensure that your molecule/surface group is not split across PBC.",
450 "Otherwise, you will get non-sensical results.",
451 "Please also consider whether the normal probe radius is appropriate",
452 "in this case or whether you would rather use, e.g., 0. It is good",
453 "to keep in mind that the results for volume and density are very",
454 "approximate. For example, in ice Ih, one can easily fit water molecules in the",
455 "pores which would yield a volume that is too low, and surface area and density",
456 "that are both too high."
459 settings->setHelpText(desc);
461 options->addOption(FileNameOption("o")
466 .defaultBasename("area")
467 .description("Total area as a function of time"));
469 FileNameOption("odg")
473 .defaultBasename("dgsolv")
474 .description("Estimated solvation free energy as a function of time"));
475 options->addOption(FileNameOption("or")
478 .store(&fnResidueArea_)
479 .defaultBasename("resarea")
480 .description("Average area per residue"));
481 options->addOption(FileNameOption("oa")
485 .defaultBasename("atomarea")
486 .description("Average area per atom"));
487 options->addOption(FileNameOption("tv")
491 .defaultBasename("volume")
492 .description("Total volume and density as a function of time"));
493 options->addOption(FileNameOption("q")
497 .defaultBasename("connolly")
498 .description("PDB file for Connolly surface"));
499 // options->addOption(FileNameOption("i").filetype(eftITP).outputFile()
500 // .store(&fnRestraints_).defaultBasename("surfat")
501 // .description("Topology file for position restraings on surface atoms"));
505 DoubleOption("probe").store(&solsize_).description("Radius of the solvent probe (nm)"));
506 options->addOption(IntegerOption("ndots").store(&ndots_).description(
507 "Number of dots per sphere, more dots means more accuracy"));
508 // options->addOption(DoubleOption("minarea").store(&minarea_)
509 // .description("The minimum area (nm^2) to count an atom as a surface atom when writing a position restraint file (see help)"));
511 BooleanOption("prot").store(&bIncludeSolute_).description("Output the protein to the Connolly [REF].pdb[ref] file too"));
513 DoubleOption("dgs").store(&dgsDefault_).description("Default value for solvation free energy per area (kJ/mol/nm^2)"));
515 // Selections must select atoms for the VdW radii lookup to work.
516 // The calculation group uses dynamicMask() so that the coordinates
517 // match a static array of VdW radii.
518 options->addOption(SelectionOption("surface")
523 .description("Surface calculation selection"));
525 SelectionOption("output").storeVector(&outputSel_).onlySortedAtoms().multiValue().description("Output selection(s)"));
527 // Atom names etc. are required for the VdW radii lookup.
528 settings->setFlag(TrajectoryAnalysisSettings::efRequireTop);
531 void Sasa::initAnalysis(const TrajectoryAnalysisSettings& settings, const TopologyInformation& top)
534 atoms_ = top.copyAtoms();
536 // bITP = opt2bSet("-i", nfile, fnm);
537 const bool bResAt = !fnResidueArea_.empty() || !fnAtomArea_.empty(); // || bITP;
538 const bool bDGsol = !fnDGSolv_.empty();
543 fprintf(stderr, "Probe size too small, setting it to %g\n", solsize_);
548 fprintf(stderr, "Ndots too small, setting it to %d\n", ndots_);
551 please_cite(stderr, "Eisenhaber95");
552 // if ((top.pbcType() != PbcType::Xyz) || (TRICLINIC(fr.box)))
554 // fprintf(stderr, "\n\nWARNING: non-rectangular boxes may give erroneous results or crashes.\n"
555 // "Analysis based on vacuum simulations (with the possibility of evaporation)\n"
556 // "will certainly crash the analysis.\n\n");
561 if (!top.hasFullTopology())
563 GMX_THROW(InconsistentInputError(
564 "Cannot compute Delta G of solvation without a tpr file"));
568 if (strcmp(*(atoms_->atomtype[0]), "?") == 0)
570 GMX_THROW(InconsistentInputError(
571 "Your input tpr file is too old (does not contain atom types). Cannot not "
572 "compute Delta G of solvation"));
576 printf("Free energy of solvation predictions:\n");
577 please_cite(stdout, "Eisenberg86a");
582 // Now compute atomic radii including solvent probe size.
583 // Also, fetch solvation free energy coefficients and
584 // compute the residue indices that map the calculation atoms
585 // to the columns of residueArea_.
586 radii_.reserve(surfaceSel_.posCount());
589 dgsFactor_.reserve(surfaceSel_.posCount());
592 const int resCount = surfaceSel_.initOriginalIdsToGroup(top.mtop(), INDEX_RES);
594 // TODO: Not exception-safe, but nice solution would be to have a C++
595 // atom properties class...
598 ArrayRef<const int> atomIndices = surfaceSel_.atomIndices();
600 for (int i = 0; i < surfaceSel_.posCount(); i++)
602 const int ii = atomIndices[i];
603 const int resind = atoms_->atom[ii].resind;
605 if (!aps.setAtomProperty(epropVDW, *(atoms_->resinfo[resind].name), *(atoms_->atomname[ii]), &radius))
609 radii_.push_back(radius + solsize_);
613 if (!aps.setAtomProperty(epropDGsol, *(atoms_->resinfo[resind].name),
614 *(atoms_->atomtype[ii]), &dgsFactor))
616 dgsFactor = dgsDefault_;
618 dgsFactor_.push_back(dgsFactor);
623 fprintf(stderr, "WARNING: could not find a Van der Waals radius for %d atoms\n", ndefault);
626 // Pre-compute mapping from the output groups to the calculation group,
627 // and store it in the selection ID map for easy lookup.
628 for (size_t g = 0; g < outputSel_.size(); ++g)
630 ArrayRef<const int> outputIndices = outputSel_[g].atomIndices();
631 for (int i = 0, j = 0; i < outputSel_[g].posCount(); ++i)
633 while (j < surfaceSel_.posCount() && outputIndices[i] > atomIndices[j])
637 if (j == surfaceSel_.posCount() || outputIndices[i] != atomIndices[j])
639 const std::string message = formatString(
640 "Output selection '%s' is not a subset of "
641 "the surface selection (atom %d is the first "
642 "atom not in the surface selection)",
643 outputSel_[g].name(), outputIndices[i] + 1);
644 GMX_THROW(InconsistentInputError(message));
646 outputSel_[g].setOriginalId(i, j);
650 calculator_.setDotCount(ndots_);
651 calculator_.setRadii(radii_);
653 // Initialize all the output data objects and initialize the output plotters.
655 area_.setColumnCount(0, 1 + outputSel_.size());
657 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
658 plotm->setFileName(fnArea_);
659 plotm->setTitle("Solvent Accessible Surface");
660 plotm->setXAxisIsTime();
661 plotm->setYLabel("Area (nm\\S2\\N)");
662 plotm->appendLegend("Total");
663 for (size_t i = 0; i < outputSel_.size(); ++i)
665 plotm->appendLegend(outputSel_[i].name());
667 area_.addModule(plotm);
672 atomArea_.setDataSetCount(1 + outputSel_.size());
673 residueArea_.setDataSetCount(1 + outputSel_.size());
674 for (size_t i = 0; i <= outputSel_.size(); ++i)
676 atomArea_.setColumnCount(i, surfaceSel_.posCount());
677 residueArea_.setColumnCount(i, resCount);
680 AnalysisDataAverageModulePointer avem(new AnalysisDataAverageModule);
681 for (int i = 0; i < surfaceSel_.posCount(); ++i)
683 avem->setXAxisValue(i, surfaceSel_.position(i).atomIndices()[0] + 1);
685 atomArea_.addModule(avem);
686 if (!fnAtomArea_.empty())
688 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
689 plotm->setFileName(fnAtomArea_);
690 plotm->setTitle("Area per atom over the trajectory");
691 plotm->setXLabel("Atom");
692 plotm->setXFormat(8, 0);
693 plotm->setYLabel("Area (nm\\S2\\N)");
694 plotm->setErrorsAsSeparateColumn(true);
695 plotm->appendLegend("Average (nm\\S2\\N)");
696 plotm->appendLegend("Standard deviation (nm\\S2\\N)");
697 avem->addModule(plotm);
701 AnalysisDataAverageModulePointer avem(new AnalysisDataAverageModule);
703 for (int i = 0; i < surfaceSel_.posCount(); ++i)
705 const int residueGroup = surfaceSel_.position(i).mappedId();
706 if (residueGroup >= nextRow)
708 GMX_ASSERT(residueGroup == nextRow,
709 "Inconsistent (non-uniformly increasing) residue grouping");
710 const int atomIndex = surfaceSel_.position(i).atomIndices()[0];
711 const int residueIndex = atoms_->atom[atomIndex].resind;
712 avem->setXAxisValue(nextRow, atoms_->resinfo[residueIndex].nr);
716 residueArea_.addModule(avem);
717 if (!fnResidueArea_.empty())
719 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
720 plotm->setFileName(fnResidueArea_);
721 plotm->setTitle("Area per residue over the trajectory");
722 plotm->setXLabel("Residue");
723 plotm->setXFormat(8, 0);
724 plotm->setYLabel("Area (nm\\S2\\N)");
725 plotm->setErrorsAsSeparateColumn(true);
726 plotm->appendLegend("Average (nm\\S2\\N)");
727 plotm->appendLegend("Standard deviation (nm\\S2\\N)");
728 avem->addModule(plotm);
733 if (!fnDGSolv_.empty())
735 dgSolv_.setColumnCount(0, 1 + outputSel_.size());
736 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
737 plotm->setFileName(fnDGSolv_);
738 plotm->setTitle("Free Energy of Solvation");
739 plotm->setXAxisIsTime();
740 plotm->setYLabel("D Gsolv");
741 plotm->appendLegend("Total");
742 for (size_t i = 0; i < outputSel_.size(); ++i)
744 plotm->appendLegend(outputSel_[i].name());
746 dgSolv_.addModule(plotm);
749 if (!fnVolume_.empty())
751 volume_.setColumnCount(0, 2);
752 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
753 plotm->setFileName(fnVolume_);
754 plotm->setTitle("Volume and Density");
755 plotm->setXAxisIsTime();
756 plotm->appendLegend("Volume (nm\\S3\\N)");
757 plotm->appendLegend("Density (g/l)");
758 volume_.addModule(plotm);
763 * Temporary memory for use within a single-frame calculation.
765 class SasaModuleData : public TrajectoryAnalysisModuleData
769 * Reserves memory for the frame-local data.
771 * `residueCount` will be zero if per-residue data is not being
774 SasaModuleData(TrajectoryAnalysisModule* module,
775 const AnalysisDataParallelOptions& opt,
776 const SelectionCollection& selections,
779 TrajectoryAnalysisModuleData(module, opt, selections)
781 index_.reserve(atomCount);
782 // If the calculation group is not dynamic, pre-calculate
783 // the index, since it is not going to change.
784 for (int i = 0; i < atomCount; ++i)
788 atomAreas_.resize(atomCount);
789 res_a_.resize(residueCount);
792 void finish() override { finishDataHandles(); }
794 //! Indices of the calculation selection positions selected for the frame.
795 std::vector<int> index_;
797 * Atom areas for each calculation selection position for the frame.
799 * One entry for each position in the calculation group.
800 * Values for atoms not selected are set to zero.
802 std::vector<real> atomAreas_;
804 * Working array to accumulate areas for each residue.
806 * One entry for each distinct residue in the calculation group;
807 * indices are not directly residue numbers or residue indices.
809 * This vector is empty if residue area calculations are not being
812 std::vector<real> res_a_;
815 TrajectoryAnalysisModuleDataPointer Sasa::startFrames(const AnalysisDataParallelOptions& opt,
816 const SelectionCollection& selections)
818 return TrajectoryAnalysisModuleDataPointer(new SasaModuleData(
819 this, opt, selections, surfaceSel_.posCount(), residueArea_.columnCount(0)));
823 * Helper method to compute the areas for a single selection.
825 * \param[in] surfaceSel The calculation selection.
826 * \param[in] sel The selection to compute the areas for (can be
827 * `surfaceSel` or one of the output selections).
828 * \param[in] atomAreas Atom areas for each position in `surfaceSel`.
829 * \param[in] dgsFactor Free energy coefficients for each position in
830 * `surfaceSel`. If empty, free energies are not calculated.
831 * \param[out] totalAreaOut Total area of `sel` (sum of atom areas it selects).
832 * \param[out] dgsolvOut Solvation free energy.
833 * Will be zero of `dgsFactor` is empty.
834 * \param atomAreaHandle Data handle to use for storing atom areas for `sel`.
835 * \param resAreaHandle Data handle to use for storing residue areas for `sel`.
836 * \param resAreaWork Work array for accumulating the residue areas.
837 * If empty, atom and residue areas are not calculated.
839 * `atomAreaHandle` and `resAreaHandle` are not used if `resAreaWork` is empty.
841 void computeAreas(const Selection& surfaceSel,
842 const Selection& sel,
843 const std::vector<real>& atomAreas,
844 const std::vector<real>& dgsFactor,
847 AnalysisDataHandle atomAreaHandle,
848 AnalysisDataHandle resAreaHandle,
849 std::vector<real>* resAreaWork)
851 const bool bResAt = !resAreaWork->empty();
852 const bool bDGsolv = !dgsFactor.empty();
858 std::fill(resAreaWork->begin(), resAreaWork->end(), 0.0_real);
860 for (int i = 0; i < sel.posCount(); ++i)
862 // Get the index of the atom in the calculation group.
863 // For the output groups, the mapping has been precalculated in
865 const int ii = (sel != surfaceSel ? sel.position(i).mappedId() : i);
866 if (!surfaceSel.position(ii).selected())
868 // For the calculation group, skip unselected atoms.
869 if (sel == surfaceSel)
873 GMX_THROW(InconsistentInputError(
874 "Output selection is not a subset of the surface selection"));
876 // Get the internal index of the matching residue.
877 // These have been precalculated in initAnalysis().
878 const int ri = surfaceSel.position(ii).mappedId();
879 const real atomArea = atomAreas[ii];
880 totalArea += atomArea;
883 atomAreaHandle.setPoint(ii, atomArea);
884 (*resAreaWork)[ri] += atomArea;
888 dgsolv += atomArea * dgsFactor[ii];
893 for (size_t i = 0; i < (*resAreaWork).size(); ++i)
895 resAreaHandle.setPoint(i, (*resAreaWork)[i]);
898 *totalAreaOut = totalArea;
902 void Sasa::analyzeFrame(int frnr, const t_trxframe& fr, t_pbc* pbc, TrajectoryAnalysisModuleData* pdata)
904 AnalysisDataHandle ah = pdata->dataHandle(area_);
905 AnalysisDataHandle dgh = pdata->dataHandle(dgSolv_);
906 AnalysisDataHandle aah = pdata->dataHandle(atomArea_);
907 AnalysisDataHandle rah = pdata->dataHandle(residueArea_);
908 AnalysisDataHandle vh = pdata->dataHandle(volume_);
909 const Selection& surfaceSel = TrajectoryAnalysisModuleData::parallelSelection(surfaceSel_);
910 const SelectionList& outputSel = TrajectoryAnalysisModuleData::parallelSelections(outputSel_);
911 SasaModuleData& frameData = *static_cast<SasaModuleData*>(pdata);
913 const bool bResAt = !frameData.res_a_.empty();
914 const bool bDGsol = !dgsFactor_.empty();
915 const bool bConnolly = (frnr == 0 && !fnConnolly_.empty());
917 // Update indices of selected atoms in the work array.
918 if (surfaceSel.isDynamic())
920 frameData.index_.clear();
921 for (int i = 0; i < surfaceSel.posCount(); ++i)
923 if (surfaceSel.position(i).selected())
925 frameData.index_.push_back(i);
930 // Determine what needs to be calculated.
932 if (bResAt || bDGsol || !outputSel.empty())
934 flag |= FLAG_ATOM_AREA;
940 if (volume_.columnCount() > 0)
945 // Do the low-level calculation.
946 // totarea and totvolume receive the values for the calculation group.
947 // area array contains the per-atom areas for the selected positions.
948 // surfacedots contains nsurfacedots entries, and contains the actual
950 real totarea, totvolume;
951 real *area = nullptr, *surfacedots = nullptr;
953 calculator_.calculate(surfaceSel.coordinates().data(), pbc, frameData.index_.size(),
954 frameData.index_.data(), flag, &totarea, &totvolume, &area, &surfacedots,
956 // Unpack the atomwise areas into the frameData.atomAreas_ array for easier
957 // indexing in the case of dynamic surfaceSel.
960 if (surfaceSel.isDynamic())
962 std::fill(frameData.atomAreas_.begin(), frameData.atomAreas_.end(), 0.0_real);
963 for (size_t i = 0; i < frameData.index_.size(); ++i)
965 frameData.atomAreas_[frameData.index_[i]] = area[i];
970 std::copy(area, area + surfaceSel.posCount(), frameData.atomAreas_.begin());
974 const sfree_guard dotsGuard(surfacedots);
978 if (fr.natoms != mtop_->natoms)
981 InconsistentInputError("Connolly plot (-q) is only supported for trajectories "
982 "that contain all the atoms"));
984 // This is somewhat nasty, as it modifies the atoms and symtab
985 // structures. But since it is only used in the first frame, and no
986 // one else uses the topology after initialization, it may just work
987 // even with future parallelization.
988 connolly_plot(fnConnolly_.c_str(), nsurfacedots, surfacedots, fr.x, atoms_.get(),
989 &mtop_->symtab, fr.pbcType, fr.box, bIncludeSolute_);
992 ah.startFrame(frnr, fr.time);
995 aah.startFrame(frnr, fr.time);
996 rah.startFrame(frnr, fr.time);
1000 dgh.startFrame(frnr, fr.time);
1003 ah.setPoint(0, totarea);
1005 real totalArea, dgsolv;
1006 if (bResAt || bDGsol)
1008 computeAreas(surfaceSel, surfaceSel, frameData.atomAreas_, dgsFactor_, &totalArea, &dgsolv,
1009 aah, rah, &frameData.res_a_);
1012 dgh.setPoint(0, dgsolv);
1015 for (size_t g = 0; g < outputSel.size(); ++g)
1019 aah.selectDataSet(g + 1);
1020 rah.selectDataSet(g + 1);
1022 computeAreas(surfaceSel, outputSel[g], frameData.atomAreas_, dgsFactor_, &totalArea,
1023 &dgsolv, aah, rah, &frameData.res_a_);
1024 ah.setPoint(g + 1, totalArea);
1027 dgh.setPoint(g + 1, dgsolv);
1045 for (int i = 0; i < surfaceSel.posCount(); ++i)
1047 totmass += surfaceSel.position(i).mass();
1049 const real density = totmass * AMU / (totvolume * NANO * NANO * NANO);
1050 vh.startFrame(frnr, fr.time);
1051 vh.setPoint(0, totvolume);
1052 vh.setPoint(1, density);
1057 void Sasa::finishAnalysis(int /*nframes*/)
1061 // fp3 = ftp2FILE(efITP, nfile, fnm, "w");
1062 // fprintf(fp3, "[ position_restraints ]\n"
1063 // "#define FCX 1000\n"
1064 // "#define FCY 1000\n"
1065 // "#define FCZ 1000\n"
1066 // "; Atom Type fx fy fz\n");
1067 // for (i = 0; i < nx[0]; i++)
1069 // if (atom_area[i] > minarea)
1071 // fprintf(fp3, "%5d 1 FCX FCX FCZ\n", ii+1);
1078 void Sasa::writeOutput() {}
1084 const char SasaInfo::name[] = "sasa";
1085 const char SasaInfo::shortDescription[] = "Compute solvent accessible surface area";
1087 TrajectoryAnalysisModulePointer SasaInfo::create()
1089 return TrajectoryAnalysisModulePointer(new Sasa);
1092 } // namespace analysismodules