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, 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.
39 * Implements gmx::analysismodules::Sasa.
41 * \author Teemu Murtola <teemu.murtola@gmail.com> (C++ conversion)
42 * \ingroup module_trajectoryanalysis
50 #include "gromacs/legacyheaders/atomprop.h"
51 #include "gromacs/legacyheaders/copyrite.h"
52 #include "gromacs/legacyheaders/pbc.h"
53 #include "gromacs/legacyheaders/physics.h"
54 #include "gromacs/legacyheaders/smalloc.h"
55 #include "gromacs/legacyheaders/symtab.h"
56 #include "gromacs/legacyheaders/vec.h"
58 #include "gromacs/analysisdata/analysisdata.h"
59 #include "gromacs/analysisdata/modules/average.h"
60 #include "gromacs/analysisdata/modules/plot.h"
61 #include "gromacs/fileio/confio.h"
62 #include "gromacs/fileio/futil.h"
63 #include "gromacs/fileio/pdbio.h"
64 #include "gromacs/options/basicoptions.h"
65 #include "gromacs/options/filenameoption.h"
66 #include "gromacs/options/options.h"
67 #include "gromacs/selection/selection.h"
68 #include "gromacs/selection/selectionoption.h"
69 #include "gromacs/trajectoryanalysis/analysismodule.h"
70 #include "gromacs/trajectoryanalysis/analysissettings.h"
71 #include "gromacs/utility/exceptions.h"
72 #include "gromacs/utility/stringutil.h"
79 namespace analysismodules
85 //! \addtogroup module_trajectoryanalysis
88 //! Tracks information on two nearest neighbors of a single surface dot.
91 //! Index of the second nearest neighbor dot.
93 //! Index of the nearest neighbor dot.
95 //! Squared distance to `aa`.
97 //! Squared distance to `ab`.
102 * Updates nearest neighbor information for a surface dot.
104 * \param[in,out] c Nearest neighbor information array to update.
105 * \param[in] i Index in `c` to update.
106 * \param[in] j Index of the other surface dot to add to the array.
107 * \param[in] d2 Squared distance between `i` and `j`.
109 void add_rec(t_conect c[], atom_id i, atom_id j, real d2)
111 if (c[i].aa == NO_ATID)
116 else if (c[i].ab == NO_ATID)
121 else if (d2 < c[i].d2a)
126 else if (d2 < c[i].d2b)
131 /* Swap them if necessary: a must be larger than b */
132 if (c[i].d2a < c[i].d2b)
144 * Adds CONECT records for surface dots.
146 * \param[in] fn PDB file to append the CONECT records to.
147 * \param[in] n Number of dots in `x`.
148 * \param[in] x Array of surface dot positions.
150 * Adds a CONECT record that connects each surface dot to its two nearest
151 * neighbors. The function is copied verbatim from the old gmx_sas.c
154 void do_conect(const char *fn, int n, rvec x[])
162 fprintf(stderr, "Building CONECT records\n");
164 for (i = 0; (i < n); i++)
166 c[i].aa = c[i].ab = NO_ATID;
169 for (i = 0; (i < n); i++)
171 for (j = i+1; (j < n); j++)
173 rvec_sub(x[i], x[j], dx);
175 add_rec(c, i, j, d2);
176 add_rec(c, j, i, d2);
179 fp = gmx_ffopen(fn, "a");
180 for (i = 0; (i < n); i++)
182 if ((c[i].aa == NO_ATID) || (c[i].ab == NO_ATID))
184 fprintf(stderr, "Warning dot %d has no conections\n", i+1);
186 fprintf(fp, "CONECT%5d%5d%5d\n", i+1, c[i].aa+1, c[i].ab+1);
193 * Plots the surface into a PDB file, optionally including the original atoms.
195 void connolly_plot(const char *fn, int ndots, real dots[], rvec x[], t_atoms *atoms,
196 t_symtab *symtab, int ePBC, const matrix box, gmx_bool bIncludeSolute)
198 const char *const atomnm = "DOT";
199 const char *const resnm = "DOT";
200 const char *const title = "Connolly Dot Surface Generated by gmx sasa";
202 int i, i0, r0, ii0, k;
210 srenew(atoms->atom, atoms->nr+ndots);
211 memset(&atoms->atom[i0], 0, sizeof(*atoms->atom)*ndots);
212 srenew(atoms->atomname, atoms->nr+ndots);
213 srenew(atoms->resinfo, r0+1);
214 atoms->atom[i0].resind = r0;
215 t_atoms_set_resinfo(atoms, i0, symtab, resnm, r0+1, ' ', 0, ' ');
216 if (atoms->pdbinfo != NULL)
218 srenew(atoms->pdbinfo, atoms->nr+ndots);
220 snew(xnew, atoms->nr+ndots);
221 for (i = 0; (i < atoms->nr); i++)
223 copy_rvec(x[i], xnew[i]);
225 for (i = k = 0; (i < ndots); i++)
228 atoms->atomname[ii0] = put_symtab(symtab, atomnm);
229 atoms->atom[ii0].resind = r0;
230 xnew[ii0][XX] = dots[k++];
231 xnew[ii0][YY] = dots[k++];
232 xnew[ii0][ZZ] = dots[k++];
233 if (atoms->pdbinfo != NULL)
235 atoms->pdbinfo[ii0].type = epdbATOM;
236 atoms->pdbinfo[ii0].atomnr = ii0;
237 atoms->pdbinfo[ii0].bfac = 0.0;
238 atoms->pdbinfo[ii0].occup = 0.0;
241 atoms->nr = i0+ndots;
243 write_sto_conf(fn, title, atoms, xnew, NULL, ePBC, const_cast<rvec *>(box));
249 init_t_atoms(&aaa, ndots, TRUE);
250 aaa.atom[0].resind = 0;
251 t_atoms_set_resinfo(&aaa, 0, symtab, resnm, 1, ' ', 0, ' ');
253 for (i = k = 0; (i < ndots); i++)
256 aaa.atomname[ii0] = put_symtab(symtab, atomnm);
257 aaa.pdbinfo[ii0].type = epdbATOM;
258 aaa.pdbinfo[ii0].atomnr = ii0;
259 aaa.atom[ii0].resind = 0;
260 xnew[ii0][XX] = dots[k++];
261 xnew[ii0][YY] = dots[k++];
262 xnew[ii0][ZZ] = dots[k++];
263 aaa.pdbinfo[ii0].bfac = 0.0;
264 aaa.pdbinfo[ii0].occup = 0.0;
267 write_sto_conf(fn, title, &aaa, xnew, NULL, ePBC, const_cast<rvec *>(box));
268 do_conect(fn, ndots, xnew);
269 free_t_atoms(&aaa, FALSE);
274 /********************************************************************
275 * Actual analysis module
279 * Implements `gmx sas` trajectory analysis module.
281 class Sasa : public TrajectoryAnalysisModule
286 virtual void initOptions(Options *options,
287 TrajectoryAnalysisSettings *settings);
288 virtual void initAnalysis(const TrajectoryAnalysisSettings &settings,
289 const TopologyInformation &top);
291 virtual TrajectoryAnalysisModuleDataPointer startFrames(
292 const AnalysisDataParallelOptions &opt,
293 const SelectionCollection &selections);
294 virtual void analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
295 TrajectoryAnalysisModuleData *pdata);
297 virtual void finishAnalysis(int nframes);
298 virtual void writeOutput();
302 * Surface areas as a function of time.
304 * First column is for the calculation group, and the rest for the
305 * output groups. This data is always produced.
309 * Per-atom surface areas as a function of time.
311 * Contains one data set for each column in `area_`.
312 * Each column corresponds to a selection position in `surfaceSel_`.
313 * This data is only produced if atom or residue areas have been
316 AnalysisData atomArea_;
318 * Per-residue surface areas as a function of time.
320 * Contains one data set for each column in `area_`.
321 * Each column corresponds to a distinct residue `surfaceSel_`.
322 * For example, if `surfaceSel_` selects residues 2, 5, and 7, there
323 * will be three columns here.
324 * This data is only produced if atom or residue areas have been
327 AnalysisData residueArea_;
329 * Free energy estimates as a function of time.
331 * Column layout is the same as for `area_`.
332 * This data is only produced if the output is requested.
334 AnalysisData dgSolv_;
336 * Total volume and density of the calculation group as a function of
339 * The first column is the volume and the second column is the density.
340 * This data is only produced if the output is requested.
342 AnalysisData volume_;
345 * The selection to calculate the surface for.
347 * Selection::originalId() and Selection::mappedId() store the mapping
348 * from the positions to the columns of `residueArea_`.
349 * The selection is computed with SelectionOption::dynamicMask(), i.e.,
350 * even in the presence of a dynamic selection, the number of returned
351 * positions is fixed, and SelectionPosition::selected() is used.
353 Selection surfaceSel_;
355 * List of optional additional output groups.
357 * Each of these must be a subset of the `surfaceSel_`.
358 * Selection::originalId() and Selection::mappedId() store the mapping
359 * from the positions to the corresponsing positions in `surfaceSel_`.
361 SelectionList outputSel_;
364 std::string fnAtomArea_;
365 std::string fnResidueArea_;
366 std::string fnDGSolv_;
367 std::string fnVolume_;
368 std::string fnConnolly_;
374 bool bIncludeSolute_;
377 //! Combined VdW and probe radii for each atom in the calculation group.
378 std::vector<real> radii_;
380 * Solvation free energy coefficients for each atom in the calculation
383 * Empty if the free energy output has not been requested.
385 std::vector<real> dgsFactor_;
387 // Copy and assign disallowed by base.
391 : TrajectoryAnalysisModule(SasaInfo::name, SasaInfo::shortDescription),
392 solsize_(0.14), ndots_(24), dgsDefault_(0), bIncludeSolute_(true), top_(NULL)
395 registerAnalysisDataset(&area_, "area");
396 registerAnalysisDataset(&atomArea_, "atomarea");
397 registerAnalysisDataset(&residueArea_, "resarea");
398 registerAnalysisDataset(&dgSolv_, "dgsolv");
399 registerAnalysisDataset(&volume_, "volume");
403 Sasa::initOptions(Options *options, TrajectoryAnalysisSettings *settings)
405 static const char *const desc[] = {
406 "[THISMODULE] computes solvent accessible surface areas.",
407 "See Eisenhaber F, Lijnzaad P, Argos P, Sander C, & Scharf M",
408 "(1995) J. Comput. Chem. 16, 273-284 for the algorithm used.",
409 "With [TT]-q[tt], the Connolly surface can be generated as well",
410 "in a [TT].pdb[tt] file where the nodes are represented as atoms",
411 "and the edges connecting the nearest nodes as CONECT records.",
412 "[TT]-odg[tt] allows for estimation of solvation free energies",
413 "from per-atom solvation energies per exposed surface area.[PAR]",
415 "The program requires a selection for the surface calculation to be",
416 "specified with [TT]-surface[tt]. This should always consist of all",
417 "non-solvent atoms in the system. The area of this group is always",
418 "calculated. Optionally, [TT]-output[tt] can specify additional",
419 "selections, which should be subsets of the calculation group.",
420 "The solvent-accessible areas for these groups are also extracted",
421 "from the full surface.[PAR]",
423 "The average and standard deviation of the area over the trajectory",
424 "can be calculated per residue and atom (options [TT]-or[tt] and",
425 "[TT]-oa[tt]).[PAR]",
426 //"In combination with the latter option an [TT].itp[tt] file can be",
427 //"generated (option [TT]-i[tt])",
428 //"which can be used to restrain surface atoms.[PAR]",
430 "With the [TT]-tv[tt] option the total volume and density of the",
431 "molecule can be computed.",
432 "Please consider whether the normal probe radius is appropriate",
433 "in this case or whether you would rather use, e.g., 0. It is good",
434 "to keep in mind that the results for volume and density are very",
435 "approximate. For example, in ice Ih, one can easily fit water molecules in the",
436 "pores which would yield a volume that is too low, and surface area and density",
437 "that are both too high."
440 options->setDescription(concatenateStrings(desc));
442 options->addOption(FileNameOption("o").filetype(eftPlot).outputFile().required()
443 .store(&fnArea_).defaultBasename("area")
444 .description("Total area as a function of time"));
445 options->addOption(FileNameOption("odg").filetype(eftPlot).outputFile()
446 .store(&fnDGSolv_).defaultBasename("dgsolv")
447 .description("Estimated solvation free energy as a function of time"));
448 options->addOption(FileNameOption("or").filetype(eftPlot).outputFile()
449 .store(&fnResidueArea_).defaultBasename("resarea")
450 .description("Average area per residue"));
451 options->addOption(FileNameOption("oa").filetype(eftPlot).outputFile()
452 .store(&fnAtomArea_).defaultBasename("atomarea")
453 .description("Average area per atom"));
454 options->addOption(FileNameOption("tv").filetype(eftPlot).outputFile()
455 .store(&fnVolume_).defaultBasename("volume")
456 .description("Total volume and density as a function of time"));
457 options->addOption(FileNameOption("q").filetype(eftPDB).outputFile()
458 .store(&fnConnolly_).defaultBasename("connolly")
459 .description("PDB file for Connolly surface"));
460 //options->addOption(FileNameOption("i").filetype(eftITP).outputFile()
461 // .store(&fnRestraints_).defaultBasename("surfat")
462 // .description("Topology file for position restraings on surface atoms"));
465 options->addOption(DoubleOption("probe").store(&solsize_)
466 .description("Radius of the solvent probe (nm)"));
467 options->addOption(IntegerOption("ndots").store(&ndots_)
468 .description("Number of dots per sphere, more dots means more accuracy"));
469 //options->addOption(DoubleOption("minarea").store(&minarea_)
470 // .description("The minimum area (nm^2) to count an atom as a surface atom when writing a position restraint file (see help)"));
471 options->addOption(BooleanOption("prot").store(&bIncludeSolute_)
472 .description("Output the protein to the Connolly [TT].pdb[tt] file too"));
473 options->addOption(DoubleOption("dgs").store(&dgsDefault_)
474 .description("Default value for solvation free energy per area (kJ/mol/nm^2)"));
476 // Selections must select atoms for the VdW radii lookup to work.
477 // The calculation group uses dynamicMask() so that the coordinates
478 // match a static array of VdW radii.
479 options->addOption(SelectionOption("surface").store(&surfaceSel_)
480 .required().onlyAtoms().dynamicMask()
481 .description("Surface calculation selection"));
482 options->addOption(SelectionOption("output").storeVector(&outputSel_)
483 .onlyAtoms().multiValue()
484 .description("Output selection(s)"));
486 // Atom names etc. are required for the VdW radii lookup.
487 settings->setFlag(TrajectoryAnalysisSettings::efRequireTop);
491 Sasa::initAnalysis(const TrajectoryAnalysisSettings &settings,
492 const TopologyInformation &top)
494 const t_atoms &atoms = top.topology()->atoms;
495 top_ = top.topology();
497 //bITP = opt2bSet("-i", nfile, fnm);
499 !fnResidueArea_.empty() || !fnAtomArea_.empty(); // || bITP;
500 const bool bDGsol = !fnDGSolv_.empty();
505 fprintf(stderr, "Probe size too small, setting it to %g\n", solsize_);
510 fprintf(stderr, "Ndots too small, setting it to %d\n", ndots_);
513 please_cite(stderr, "Eisenhaber95");
514 //if ((top.ePBC() != epbcXYZ) || (TRICLINIC(fr.box)))
516 // fprintf(stderr, "\n\nWARNING: non-rectangular boxes may give erroneous results or crashes.\n"
517 // "Analysis based on vacuum simulations (with the possibility of evaporation)\n"
518 // "will certainly crash the analysis.\n\n");
523 if (!top.hasFullTopology())
525 GMX_THROW(InconsistentInputError("Cannot compute Delta G of solvation without a tpr file"));
529 if (strcmp(*(atoms.atomtype[0]), "?") == 0)
531 GMX_THROW(InconsistentInputError("Your input tpr file is too old (does not contain atom types). Cannot not compute Delta G of solvation"));
535 printf("Free energy of solvation predictions:\n");
536 please_cite(stdout, "Eisenberg86a");
541 // Now compute atomic radii including solvent probe size.
542 // Also, fetch solvation free energy coefficients and
543 // compute the residue indices that map the calculation atoms
544 // to the columns of residueArea_.
545 radii_.reserve(surfaceSel_.posCount());
548 dgsFactor_.reserve(surfaceSel_.posCount());
551 // TODO: Not exception-safe, but nice solution would be to have a C++
552 // atom properties class...
553 gmx_atomprop_t aps = gmx_atomprop_init();
555 ConstArrayRef<int> atomIndices = surfaceSel_.atomIndices();
556 int prevResind = atoms.atom[atomIndices[0]].resind;
559 for (int i = 0; i < surfaceSel_.posCount(); i++)
561 const int ii = atomIndices[i];
562 const int resind = atoms.atom[ii].resind;
563 if (resind != prevResind)
568 surfaceSel_.setOriginalId(i, resCount);
570 if (!gmx_atomprop_query(aps, epropVDW,
571 *(atoms.resinfo[resind].name),
572 *(atoms.atomname[ii]), &radius))
576 radii_.push_back(radius + solsize_);
580 if (!gmx_atomprop_query(aps, epropDGsol,
581 *(atoms.resinfo[resind].name),
582 *(atoms.atomtype[ii]), &dgsFactor))
584 dgsFactor = dgsDefault_;
586 dgsFactor_.push_back(dgsFactor);
591 fprintf(stderr, "WARNING: could not find a Van der Waals radius for %d atoms\n", ndefault);
593 gmx_atomprop_destroy(aps);
595 // The loop above doesn't count the last residue.
598 // Pre-compute mapping from the output groups to the calculation group,
599 // and store it in the selection ID map for easy lookup.
600 for (size_t g = 0; g < outputSel_.size(); ++g)
602 ConstArrayRef<int> outputIndices = outputSel_[g].atomIndices();
603 for (int i = 0, j = 0; i < outputSel_[g].posCount(); ++i)
605 while (j < surfaceSel_.posCount() && outputIndices[i] > atomIndices[j])
609 if (j == surfaceSel_.posCount() || outputIndices[i] != atomIndices[j])
611 GMX_THROW(InconsistentInputError("Output selection is not a subset of the input selection"));
613 outputSel_[g].setOriginalId(i, j);
617 // Initialize all the output data objects and initialize the output plotters.
619 area_.setColumnCount(0, 1 + outputSel_.size());
621 AnalysisDataPlotModulePointer plotm(
622 new AnalysisDataPlotModule(settings.plotSettings()));
623 plotm->setFileName(fnArea_);
624 plotm->setTitle("Solvent Accessible Surface");
625 plotm->setXAxisIsTime();
626 plotm->setYLabel("Area (nm\\S2\\N)");
627 plotm->appendLegend("Total");
628 for (size_t i = 0; i < outputSel_.size(); ++i)
630 plotm->appendLegend(outputSel_[i].name());
632 area_.addModule(plotm);
637 atomArea_.setDataSetCount(1 + outputSel_.size());
638 residueArea_.setDataSetCount(1 + outputSel_.size());
639 for (size_t i = 0; i <= outputSel_.size(); ++i)
641 atomArea_.setColumnCount(i, surfaceSel_.posCount());
642 residueArea_.setColumnCount(i, resCount);
645 AnalysisDataAverageModulePointer avem(new AnalysisDataAverageModule);
646 for (int i = 0; i < surfaceSel_.posCount(); ++i)
648 avem->setXAxisValue(i, surfaceSel_.position(i).atomIndices()[0] + 1);
650 atomArea_.addModule(avem);
651 if (!fnAtomArea_.empty())
653 AnalysisDataPlotModulePointer plotm(
654 new AnalysisDataPlotModule(settings.plotSettings()));
655 plotm->setFileName(fnAtomArea_);
656 plotm->setTitle("Area per residue over the trajectory");
657 plotm->setXLabel("Atom");
658 plotm->setXFormat(8, 0);
659 plotm->setYLabel("Area (nm\\S2\\N)");
660 plotm->setErrorsAsSeparateColumn(true);
661 plotm->appendLegend("Average (nm\\S2\\N)");
662 plotm->appendLegend("Standard deviation (nm\\S2\\N)");
663 avem->addModule(plotm);
667 AnalysisDataAverageModulePointer avem(new AnalysisDataAverageModule);
668 for (int i = 0; i < surfaceSel_.posCount(); ++i)
670 const int atomIndex = surfaceSel_.position(i).atomIndices()[0];
671 const int residueIndex = atoms.atom[atomIndex].resind;
672 avem->setXAxisValue(i, atoms.resinfo[residueIndex].nr);
674 residueArea_.addModule(avem);
675 if (!fnResidueArea_.empty())
677 AnalysisDataPlotModulePointer plotm(
678 new AnalysisDataPlotModule(settings.plotSettings()));
679 plotm->setFileName(fnResidueArea_);
680 plotm->setTitle("Area per atom over the trajectory");
681 plotm->setXLabel("Residue");
682 plotm->setXFormat(8, 0);
683 plotm->setYLabel("Area (nm\\S2\\N)");
684 plotm->setErrorsAsSeparateColumn(true);
685 plotm->appendLegend("Average (nm\\S2\\N)");
686 plotm->appendLegend("Standard deviation (nm\\S2\\N)");
687 avem->addModule(plotm);
692 if (!fnDGSolv_.empty())
694 dgSolv_.setColumnCount(0, 1 + outputSel_.size());
695 AnalysisDataPlotModulePointer plotm(
696 new AnalysisDataPlotModule(settings.plotSettings()));
697 plotm->setFileName(fnDGSolv_);
698 plotm->setTitle("Free Energy of Solvation");
699 plotm->setXAxisIsTime();
700 plotm->setYLabel("D Gsolv");
701 plotm->appendLegend("Total");
702 for (size_t i = 0; i < outputSel_.size(); ++i)
704 plotm->appendLegend(outputSel_[i].name());
706 dgSolv_.addModule(plotm);
709 if (!fnVolume_.empty())
711 volume_.setColumnCount(0, 2);
712 AnalysisDataPlotModulePointer plotm(
713 new AnalysisDataPlotModule(settings.plotSettings()));
714 plotm->setFileName(fnVolume_);
715 plotm->setTitle("Volume and Density");
716 plotm->setXAxisIsTime();
717 plotm->appendLegend("Volume (nm\\S3\\N)");
718 plotm->appendLegend("Density (g/l)");
719 volume_.addModule(plotm);
724 * Temporary memory for use within a single-frame calculation.
726 class SasaModuleData : public TrajectoryAnalysisModuleData
730 * Reserves memory for the frame-local data.
732 * `residueCount` will be zero if per-residue data is not being
735 SasaModuleData(TrajectoryAnalysisModule *module,
736 const AnalysisDataParallelOptions &opt,
737 const SelectionCollection &selections,
738 int atomCount, int residueCount)
739 : TrajectoryAnalysisModuleData(module, opt, selections)
741 index_.reserve(atomCount);
742 // If the calculation group is not dynamic, pre-calculate
743 // the index, since it is not going to change.
744 for (int i = 0; i < atomCount; ++i)
748 atomAreas_.resize(atomCount);
749 res_a_.resize(residueCount);
752 virtual void finish() { finishDataHandles(); }
754 //! Indices of the calculation selection positions selected for the frame.
755 std::vector<int> index_;
757 * Atom areas for each calculation selection position for the frame.
759 * One entry for each position in the calculation group.
760 * Values for atoms not selected are set to zero.
762 std::vector<real> atomAreas_;
764 * Working array to accumulate areas for each residue.
766 * One entry for each distinct residue in the calculation group;
767 * indices are not directly residue numbers or residue indices.
769 * This vector is empty if residue area calculations are not being
772 std::vector<real> res_a_;
775 TrajectoryAnalysisModuleDataPointer Sasa::startFrames(
776 const AnalysisDataParallelOptions &opt,
777 const SelectionCollection &selections)
779 return TrajectoryAnalysisModuleDataPointer(
780 new SasaModuleData(this, opt, selections, surfaceSel_.posCount(),
781 residueArea_.columnCount(0)));
785 * Helper method to compute the areas for a single selection.
787 * \param[in] surfaceSel The calculation selection.
788 * \param[in] sel The selection to compute the areas for (can be
789 * `surfaceSel` or one of the output selections).
790 * \param[in] atomAreas Atom areas for each position in `surfaceSel`.
791 * \param[in] dgsFactor Free energy coefficients for each position in
792 * `surfaceSel`. If empty, free energies are not calculated.
793 * \param[out] totalAreaOut Total area of `sel` (sum of atom areas it selects).
794 * \param[out] dgsolvOut Solvation free energy.
795 * Will be zero of `dgsFactor` is empty.
796 * \param atomAreaHandle Data handle to use for storing atom areas for `sel`.
797 * \param resAreaHandle Data handle to use for storing residue areas for `sel`.
798 * \param resAreaWork Work array for accumulating the residue areas.
799 * If empty, atom and residue areas are not calculated.
801 * `atomAreaHandle` and `resAreaHandle` are not used if `resAreaWork` is empty.
803 void computeAreas(const Selection &surfaceSel, const Selection &sel,
804 const std::vector<real> &atomAreas,
805 const std::vector<real> &dgsFactor,
806 real *totalAreaOut, real *dgsolvOut,
807 AnalysisDataHandle atomAreaHandle,
808 AnalysisDataHandle resAreaHandle,
809 std::vector<real> *resAreaWork)
811 const bool bResAt = !resAreaWork->empty();
812 const bool bDGsolv = !dgsFactor.empty();
818 std::fill(resAreaWork->begin(), resAreaWork->end(),
819 static_cast<real>(0.0));
821 for (int i = 0; i < sel.posCount(); ++i)
823 // Get the index of the atom in the calculation group.
824 // For the output groups, the mapping has been precalculated in
826 const int ii = (sel != surfaceSel ? sel.position(i).mappedId() : i);
827 if (!surfaceSel.position(ii).selected())
829 // For the calculation group, skip unselected atoms.
830 if (sel == surfaceSel)
834 GMX_THROW(InconsistentInputError("Output selection is not a subset of the surface selection"));
836 // Get the internal index of the matching residue.
837 // These have been precalculated in initAnalysis().
838 const int ri = surfaceSel.position(ii).mappedId();
839 const real atomArea = atomAreas[ii];
840 totalArea += atomArea;
843 atomAreaHandle.setPoint(ii, atomArea);
844 (*resAreaWork)[ri] += atomArea;
848 dgsolv += atomArea * dgsFactor[ii];
853 for (size_t i = 0; i < (*resAreaWork).size(); ++i)
855 resAreaHandle.setPoint(i, (*resAreaWork)[i]);
858 *totalAreaOut = totalArea;
863 Sasa::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
864 TrajectoryAnalysisModuleData *pdata)
866 AnalysisDataHandle ah = pdata->dataHandle(area_);
867 AnalysisDataHandle dgh = pdata->dataHandle(dgSolv_);
868 AnalysisDataHandle aah = pdata->dataHandle(atomArea_);
869 AnalysisDataHandle rah = pdata->dataHandle(residueArea_);
870 AnalysisDataHandle vh = pdata->dataHandle(volume_);
871 const Selection &surfaceSel = pdata->parallelSelection(surfaceSel_);
872 const SelectionList &outputSel = pdata->parallelSelections(outputSel_);
873 SasaModuleData &frameData = *static_cast<SasaModuleData *>(pdata);
875 const bool bResAt = !frameData.res_a_.empty();
876 const bool bDGsol = !dgsFactor_.empty();
877 const bool bConnolly = (frnr == 0 && !fnConnolly_.empty());
879 // Update indices of selected atoms in the work array.
880 if (surfaceSel.isDynamic())
882 frameData.index_.clear();
883 for (int i = 0; i < surfaceSel.posCount(); ++i)
885 if (surfaceSel.position(i).selected())
887 frameData.index_.push_back(i);
892 // Determine what needs to be calculated.
894 if (bResAt || bDGsol || !outputSel.empty())
896 flag |= FLAG_ATOM_AREA;
902 if (volume_.columnCount() > 0)
907 // Do the low-level calculation.
908 // totarea and totvolume receive the values for the calculation group.
909 // area array contains the per-atom areas for the selected positions.
910 // surfacedots contains nsurfacedots entries, and contains the actual
912 real totarea, totvolume;
913 real *area = NULL, *surfacedots = NULL;
915 int retval = nsc_dclm_pbc(surfaceSel.coordinates().data(), &radii_[0],
916 frameData.index_.size(), ndots_, flag, &totarea,
917 &area, &totvolume, &surfacedots, &nsurfacedots,
918 &frameData.index_[0],
919 pbc != NULL ? pbc->ePBC : epbcNONE,
920 pbc != NULL ? pbc->box : NULL);
921 // Unpack the atomwise areas into the frameData.atomAreas_ array for easier
922 // indexing in the case of dynamic surfaceSel.
925 if (surfaceSel.isDynamic())
927 std::fill(frameData.atomAreas_.begin(), frameData.atomAreas_.end(),
928 static_cast<real>(0.0));
929 for (size_t i = 0; i < frameData.index_.size(); ++i)
931 frameData.atomAreas_[frameData.index_[i]] = area[i];
936 std::copy(area, area + surfaceSel.posCount(),
937 frameData.atomAreas_.begin());
941 scoped_ptr_sfree dotsGuard(surfacedots);
944 GMX_THROW(InternalError("nsc_dclm_pbc failed"));
949 // This is somewhat nasty, as it modifies the atoms and symtab
950 // structures. But since it is only used in the first frame, and no
951 // one else uses the topology after initialization, it may just work
952 // even with future parallelization.
953 connolly_plot(fnConnolly_.c_str(),
954 nsurfacedots, surfacedots, fr.x, &top_->atoms,
955 &top_->symtab, fr.ePBC, fr.box, bIncludeSolute_);
958 ah.startFrame(frnr, fr.time);
961 aah.startFrame(frnr, fr.time);
962 rah.startFrame(frnr, fr.time);
966 dgh.startFrame(frnr, fr.time);
969 ah.setPoint(0, totarea);
971 real totalArea, dgsolv;
972 if (bResAt || bDGsol)
974 computeAreas(surfaceSel, surfaceSel, frameData.atomAreas_, dgsFactor_,
975 &totalArea, &dgsolv, aah, rah, &frameData.res_a_);
978 dgh.setPoint(0, dgsolv);
981 for (size_t g = 0; g < outputSel.size(); ++g)
985 aah.selectDataSet(g + 1);
986 rah.selectDataSet(g + 1);
988 computeAreas(surfaceSel, outputSel[g], frameData.atomAreas_, dgsFactor_,
989 &totalArea, &dgsolv, aah, rah, &frameData.res_a_);
990 ah.setPoint(g + 1, totalArea);
993 dgh.setPoint(g + 1, dgsolv);
1011 for (int i = 0; i < surfaceSel.posCount(); ++i)
1013 totmass += surfaceSel.position(i).mass();
1015 const real density = totmass*AMU/(totvolume*NANO*NANO*NANO);
1016 vh.startFrame(frnr, fr.time);
1017 vh.setPoint(0, totvolume);
1018 vh.setPoint(1, density);
1024 Sasa::finishAnalysis(int /*nframes*/)
1028 // fp3 = ftp2FILE(efITP, nfile, fnm, "w");
1029 // fprintf(fp3, "[ position_restraints ]\n"
1030 // "#define FCX 1000\n"
1031 // "#define FCY 1000\n"
1032 // "#define FCZ 1000\n"
1033 // "; Atom Type fx fy fz\n");
1034 // for (i = 0; i < nx[0]; i++)
1036 // if (atom_area[i] > minarea)
1038 // fprintf(fp3, "%5d 1 FCX FCX FCZ\n", ii+1);
1054 const char SasaInfo::name[] = "sasa";
1055 const char SasaInfo::shortDescription[] =
1056 "Compute solvent accessible surface area";
1058 TrajectoryAnalysisModulePointer SasaInfo::create()
1060 return TrajectoryAnalysisModulePointer(new Sasa);
1063 } // namespace analysismodules