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-2004, The GROMACS development team.
6 * Copyright (c) 2013,2014,2015,2016,2017,2018,2019, 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.
47 #include "gromacs/commandline/pargs.h"
48 #include "gromacs/fileio/confio.h"
49 #include "gromacs/fileio/pdbio.h"
50 #include "gromacs/gmxlib/conformation_utilities.h"
51 #include "gromacs/gmxpreprocess/makeexclusiondistances.h"
52 #include "gromacs/math/functions.h"
53 #include "gromacs/math/units.h"
54 #include "gromacs/math/vec.h"
55 #include "gromacs/pbcutil/boxutilities.h"
56 #include "gromacs/pbcutil/pbc.h"
57 #include "gromacs/selection/nbsearch.h"
58 #include "gromacs/topology/atomprop.h"
59 #include "gromacs/topology/atoms.h"
60 #include "gromacs/topology/atomsbuilder.h"
61 #include "gromacs/topology/mtop_util.h"
62 #include "gromacs/topology/topology.h"
63 #include "gromacs/utility/arraysize.h"
64 #include "gromacs/utility/cstringutil.h"
65 #include "gromacs/utility/fatalerror.h"
66 #include "gromacs/utility/futil.h"
67 #include "gromacs/utility/gmxassert.h"
68 #include "gromacs/utility/smalloc.h"
72 /*! \brief Describes a molecule type, and keeps track of the number of these molecules
74 * Used for sorting coordinate file data after solvation
80 //! number of atoms in the molecule
82 //! number of occurences of molecule
86 static void sort_molecule(t_atoms** atoms_solvt, t_atoms** newatoms, std::vector<RVec>* x, std::vector<RVec>* v)
89 fprintf(stderr, "Sorting configuration\n");
90 t_atoms* atoms = *atoms_solvt;
92 /* copy each residue from *atoms to a molecule in *molecule */
93 std::vector<MoleculeType> molTypes;
94 for (int i = 0; i < atoms->nr; i++)
96 if ((i == 0) || (atoms->atom[i].resind != atoms->atom[i - 1].resind))
98 /* see if this was a molecule type we haven't had yet: */
99 auto matchingMolType = std::find_if(
100 molTypes.begin(), molTypes.end(), [atoms, i](const MoleculeType& molecule) {
101 return molecule.name == *atoms->resinfo[atoms->atom[i].resind].name;
103 if (matchingMolType == molTypes.end())
105 int numAtomsInMolType = 0;
106 while ((i + numAtomsInMolType < atoms->nr)
107 && (atoms->atom[i].resind == atoms->atom[i + numAtomsInMolType].resind))
111 molTypes.emplace_back(MoleculeType{ *atoms->resinfo[atoms->atom[i].resind].name,
112 numAtomsInMolType, 1 });
116 matchingMolType->numMolecules++;
121 fprintf(stderr, "Found %zu%s molecule type%s:\n", molTypes.size(),
122 molTypes.size() == 1 ? "" : " different", molTypes.size() == 1 ? "" : "s");
123 for (const auto& molType : molTypes)
125 fprintf(stderr, "%7s (%4d atoms): %5d residues\n", molType.name.c_str(), molType.numAtoms,
126 molType.numMolecules);
129 /* if we have only 1 moleculetype, we don't have to sort */
130 if (molTypes.size() > 1)
132 /* now put them there: */
134 init_t_atoms(*newatoms, atoms->nr, FALSE);
135 (*newatoms)->nres = atoms->nres;
136 srenew((*newatoms)->resinfo, atoms->nres);
137 std::vector<RVec> newx(x->size());
138 std::vector<RVec> newv(v->size());
142 for (const auto& moleculeType : molTypes)
145 while (i < atoms->nr)
147 int residOfCurrAtom = atoms->atom[i].resind;
148 if (moleculeType.name == *atoms->resinfo[residOfCurrAtom].name)
150 /* Copy the residue info */
151 (*newatoms)->resinfo[residIndex] = atoms->resinfo[residOfCurrAtom];
152 // Residue numbering starts from 1, so +1 from the index
153 (*newatoms)->resinfo[residIndex].nr = residIndex + 1;
154 /* Copy the atom info */
157 (*newatoms)->atom[atomIndex] = atoms->atom[i];
158 (*newatoms)->atomname[atomIndex] = atoms->atomname[i];
159 (*newatoms)->atom[atomIndex].resind = residIndex;
160 copy_rvec((*x)[i], newx[atomIndex]);
163 copy_rvec((*v)[i], newv[atomIndex]);
167 } while (i < atoms->nr && atoms->atom[i].resind == residOfCurrAtom);
168 /* Increase the new residue counters */
173 /* Skip this residue */
177 } while (i < atoms->nr && atoms->atom[i].resind == residOfCurrAtom);
182 /* put them back into the original arrays and throw away temporary arrays */
184 *atoms_solvt = (*newatoms);
190 static void rm_res_pbc(const t_atoms* atoms, std::vector<RVec>* x, matrix box)
198 for (int n = 0; n < atoms->nr; n++)
200 if (!is_hydrogen(*(atoms->atomname[n])))
203 rvec_inc(xcg, (*x)[n]);
205 if ((n + 1 == atoms->nr) || (atoms->atom[n + 1].resind != atoms->atom[n].resind))
207 /* if nat==0 we have only hydrogens in the solvent,
208 we take last coordinate as cg */
212 copy_rvec((*x)[n], xcg);
214 svmul(1.0 / nat, xcg, xcg);
215 for (int d = 0; d < DIM; d++)
219 for (int i = start; i <= n; i++)
221 (*x)[i][d] += box[d][d];
225 while (xcg[d] >= box[d][d])
227 for (int i = start; i <= n; i++)
229 (*x)[i][d] -= box[d][d];
242 * Generates a solvent configuration of desired size by stacking solvent boxes.
244 * \param[in,out] atoms Solvent atoms.
245 * \param[in,out] x Solvent positions.
246 * \param[in,out] v Solvent velocities (`*v` can be NULL).
247 * \param[in,out] r Solvent exclusion radii.
248 * \param[in] box Initial solvent box.
249 * \param[in] boxTarget Target box size.
251 * The solvent box of desired size is created by stacking the initial box in
252 * the smallest k*l*m array that covers the box, and then removing any residue
253 * where all atoms are outside the target box (with a small margin).
254 * This function does not remove overlap between solvent atoms across the
257 * Note that the input configuration should be in the rectangular unit cell and
258 * have whole residues.
260 static void replicateSolventBox(t_atoms* atoms,
261 std::vector<RVec>* x,
262 std::vector<RVec>* v,
263 std::vector<real>* r,
265 const matrix boxTarget)
267 // Calculate the box multiplication factors.
270 for (int i = 0; i < DIM; ++i)
273 while (n_box[i] * box[i][i] < boxTarget[i][i])
279 fprintf(stderr, "Will generate new solvent configuration of %dx%dx%d boxes\n", n_box[XX],
280 n_box[YY], n_box[ZZ]);
282 // Create arrays for storing the generated system (cannot be done in-place
283 // in case the target box is smaller than the original in one dimension,
286 init_t_atoms(&newAtoms, 0, FALSE);
287 gmx::AtomsBuilder builder(&newAtoms, nullptr);
288 builder.reserve(atoms->nr * nmol, atoms->nres * nmol);
289 std::vector<RVec> newX(atoms->nr * nmol);
290 std::vector<RVec> newV(!v->empty() ? atoms->nr * nmol : 0);
291 std::vector<real> newR(atoms->nr * nmol);
293 const real maxRadius = *std::max_element(r->begin(), r->end());
295 for (int i = 0; i < DIM; ++i)
297 // The code below is only interested about the box diagonal.
298 boxWithMargin[i] = boxTarget[i][i] + 3 * maxRadius;
301 for (int ix = 0; ix < n_box[XX]; ++ix)
304 delta[XX] = ix * box[XX][XX];
305 for (int iy = 0; iy < n_box[YY]; ++iy)
307 delta[YY] = iy * box[YY][YY];
308 for (int iz = 0; iz < n_box[ZZ]; ++iz)
310 delta[ZZ] = iz * box[ZZ][ZZ];
311 bool bKeepResidue = false;
312 for (int i = 0; i < atoms->nr; ++i)
314 const int newIndex = builder.currentAtomCount();
315 bool bKeepAtom = true;
316 for (int m = 0; m < DIM; ++m)
318 const real newCoord = delta[m] + (*x)[i][m];
319 bKeepAtom = bKeepAtom && (newCoord < boxWithMargin[m]);
320 newX[newIndex][m] = newCoord;
322 bKeepResidue = bKeepResidue || bKeepAtom;
325 copy_rvec((*v)[i], newV[newIndex]);
327 newR[newIndex] = (*r)[i];
328 builder.addAtom(*atoms, i);
329 if (i == atoms->nr - 1 || atoms->atom[i + 1].resind != atoms->atom[i].resind)
333 builder.finishResidue(atoms->resinfo[atoms->atom[i].resind]);
337 builder.discardCurrentResidue();
339 // Reset state for the next residue.
340 bKeepResidue = false;
347 sfree(atoms->atomname);
348 sfree(atoms->resinfo);
349 atoms->nr = newAtoms.nr;
350 atoms->nres = newAtoms.nres;
351 atoms->atom = newAtoms.atom;
352 atoms->atomname = newAtoms.atomname;
353 atoms->resinfo = newAtoms.resinfo;
355 newX.resize(atoms->nr);
359 newV.resize(atoms->nr);
362 newR.resize(atoms->nr);
365 fprintf(stderr, "Solvent box contains %d atoms in %d residues\n", atoms->nr, atoms->nres);
369 * Removes overlap of solvent atoms across the edges.
371 * \param[in,out] atoms Solvent atoms.
372 * \param[in,out] x Solvent positions.
373 * \param[in,out] v Solvent velocities (can be empty).
374 * \param[in,out] r Solvent exclusion radii.
375 * \param[in] pbc PBC information.
377 * Solvent residues that lay on the edges that do not touch the origin are
378 * removed if they overlap with other solvent atoms across the PBC.
379 * This is done in this way as the assumption is that the input solvent
380 * configuration is already equilibrated, and so does not contain any
381 * undesirable overlap. The only overlap that should be removed is caused by
382 * cutting the box in half in replicateSolventBox() and leaving a margin of
383 * solvent outside those box edges; these atoms can then overlap with those on
384 * the opposite box edge in a way that is not part of the pre-equilibrated
387 static void removeSolventBoxOverlap(t_atoms* atoms,
388 std::vector<RVec>* x,
389 std::vector<RVec>* v,
390 std::vector<real>* r,
393 gmx::AtomsRemover remover(*atoms);
395 // TODO: We could limit the amount of pairs searched significantly,
396 // since we are only interested in pairs where the positions are on
398 const real maxRadius = *std::max_element(r->begin(), r->end());
399 gmx::AnalysisNeighborhood nb;
400 nb.setCutoff(2 * maxRadius);
401 gmx::AnalysisNeighborhoodPositions pos(*x);
402 gmx::AnalysisNeighborhoodSearch search = nb.initSearch(&pbc, pos);
403 gmx::AnalysisNeighborhoodPairSearch pairSearch = search.startPairSearch(pos);
404 gmx::AnalysisNeighborhoodPair pair;
405 while (pairSearch.findNextPair(&pair))
407 const int i1 = pair.refIndex();
408 const int i2 = pair.testIndex();
409 if (remover.isMarked(i2))
411 pairSearch.skipRemainingPairsForTestPosition();
414 if (remover.isMarked(i1) || atoms->atom[i1].resind == atoms->atom[i2].resind)
418 if (pair.distance2() < gmx::square((*r)[i1] + (*r)[i2]))
421 rvec_sub((*x)[i2], (*x)[i1], dx);
422 bool bCandidate1 = false, bCandidate2 = false;
423 // To satisfy Clang static analyzer.
424 GMX_ASSERT(pbc.ndim_ePBC <= DIM, "Too many periodic dimensions");
425 for (int d = 0; d < pbc.ndim_ePBC; ++d)
427 // If the distance in some dimension is larger than the
428 // cutoff, then it means that the distance has been computed
429 // over the PBC. Mark the position with a larger coordinate
430 // for potential removal.
431 if (dx[d] > maxRadius)
435 else if (dx[d] < -maxRadius)
440 // Only mark one of the positions for removal if both were
442 if (bCandidate2 && (!bCandidate1 || i2 > i1))
444 remover.markResidue(*atoms, i2, true);
445 pairSearch.skipRemainingPairsForTestPosition();
447 else if (bCandidate1)
449 remover.markResidue(*atoms, i1, true);
454 remover.removeMarkedElements(x);
457 remover.removeMarkedElements(v);
459 remover.removeMarkedElements(r);
460 const int originalAtomCount = atoms->nr;
461 remover.removeMarkedAtoms(atoms);
462 fprintf(stderr, "Removed %d solvent atoms due to solvent-solvent overlap\n",
463 originalAtomCount - atoms->nr);
467 * Remove all solvent molecules outside a give radius from the solute.
469 * \param[in,out] atoms Solvent atoms.
470 * \param[in,out] x_solvent Solvent positions.
471 * \param[in,out] v_solvent Solvent velocities.
472 * \param[in,out] r Atomic exclusion radii.
473 * \param[in] pbc PBC information.
474 * \param[in] x_solute Solute positions.
475 * \param[in] rshell The radius outside the solute molecule.
477 static void removeSolventOutsideShell(t_atoms* atoms,
478 std::vector<RVec>* x_solvent,
479 std::vector<RVec>* v_solvent,
480 std::vector<real>* r,
482 const std::vector<RVec>& x_solute,
485 gmx::AtomsRemover remover(*atoms);
486 gmx::AnalysisNeighborhood nb;
487 nb.setCutoff(rshell);
488 gmx::AnalysisNeighborhoodPositions posSolute(x_solute);
489 gmx::AnalysisNeighborhoodSearch search = nb.initSearch(&pbc, posSolute);
490 gmx::AnalysisNeighborhoodPositions pos(*x_solvent);
491 gmx::AnalysisNeighborhoodPairSearch pairSearch = search.startPairSearch(pos);
492 gmx::AnalysisNeighborhoodPair pair;
496 // Now put back those within the shell without checking for overlap
497 while (pairSearch.findNextPair(&pair))
499 remover.markResidue(*atoms, pair.testIndex(), false);
500 pairSearch.skipRemainingPairsForTestPosition();
502 remover.removeMarkedElements(x_solvent);
503 if (!v_solvent->empty())
505 remover.removeMarkedElements(v_solvent);
507 remover.removeMarkedElements(r);
508 const int originalAtomCount = atoms->nr;
509 remover.removeMarkedAtoms(atoms);
510 fprintf(stderr, "Removed %d solvent atoms more than %f nm from solute.\n",
511 originalAtomCount - atoms->nr, rshell);
515 * Removes solvent molecules that overlap with the solute.
517 * \param[in,out] atoms Solvent atoms.
518 * \param[in,out] x Solvent positions.
519 * \param[in,out] v Solvent velocities (can be empty).
520 * \param[in,out] r Solvent exclusion radii.
521 * \param[in] pbc PBC information.
522 * \param[in] x_solute Solute positions.
523 * \param[in] r_solute Solute exclusion radii.
525 static void removeSolventOverlappingWithSolute(t_atoms* atoms,
526 std::vector<RVec>* x,
527 std::vector<RVec>* v,
528 std::vector<real>* r,
530 const std::vector<RVec>& x_solute,
531 const std::vector<real>& r_solute)
533 gmx::AtomsRemover remover(*atoms);
534 const real maxRadius1 = *std::max_element(r->begin(), r->end());
535 const real maxRadius2 = *std::max_element(r_solute.begin(), r_solute.end());
537 // Now check for overlap.
538 gmx::AnalysisNeighborhood nb;
539 gmx::AnalysisNeighborhoodPair pair;
540 nb.setCutoff(maxRadius1 + maxRadius2);
541 gmx::AnalysisNeighborhoodPositions posSolute(x_solute);
542 gmx::AnalysisNeighborhoodSearch search = nb.initSearch(&pbc, posSolute);
543 gmx::AnalysisNeighborhoodPositions pos(*x);
544 gmx::AnalysisNeighborhoodPairSearch pairSearch = search.startPairSearch(pos);
545 while (pairSearch.findNextPair(&pair))
547 if (remover.isMarked(pair.testIndex()))
549 pairSearch.skipRemainingPairsForTestPosition();
552 const real r1 = r_solute[pair.refIndex()];
553 const real r2 = (*r)[pair.testIndex()];
554 const bool bRemove = (pair.distance2() < gmx::square(r1 + r2));
555 remover.markResidue(*atoms, pair.testIndex(), bRemove);
558 remover.removeMarkedElements(x);
561 remover.removeMarkedElements(v);
563 remover.removeMarkedElements(r);
564 const int originalAtomCount = atoms->nr;
565 remover.removeMarkedAtoms(atoms);
566 fprintf(stderr, "Removed %d solvent atoms due to solute-solvent overlap\n",
567 originalAtomCount - atoms->nr);
571 * Removes a given number of solvent residues.
573 * \param[in,out] atoms Solvent atoms.
574 * \param[in,out] x Solvent positions.
575 * \param[in,out] v Solvent velocities (can be empty).
576 * \param[in] numberToRemove Number of residues to remove.
578 * This function is called last in the process of creating the solvent box,
579 * so it does not operate on the exclusion radii, as no code after this needs
582 static void removeExtraSolventMolecules(t_atoms* atoms, std::vector<RVec>* x, std::vector<RVec>* v, int numberToRemove)
584 gmx::AtomsRemover remover(*atoms);
585 std::random_device rd;
586 std::mt19937 randomNumberGenerator(rd());
587 std::uniform_int_distribution<> randomDistribution(0, atoms->nr - 1);
588 while (numberToRemove > 0)
590 int atomIndex = randomDistribution(randomNumberGenerator);
591 if (!remover.isMarked(atomIndex))
593 remover.markResidue(*atoms, atomIndex, true);
597 remover.removeMarkedElements(x);
600 remover.removeMarkedElements(v);
602 remover.removeMarkedAtoms(atoms);
605 static void add_solv(const char* filename,
608 std::vector<RVec>* x,
609 std::vector<RVec>* v,
613 real defaultDistance,
618 gmx_mtop_t topSolvent;
619 std::vector<RVec> xSolvent, vSolvent;
620 matrix boxSolvent = { { 0 } };
623 fprintf(stderr, "Reading solvent configuration\n");
624 bool bTprFileWasRead;
625 rvec *temporaryX = nullptr, *temporaryV = nullptr;
626 readConfAndTopology(gmx::findLibraryFile(filename).c_str(), &bTprFileWasRead, &topSolvent,
627 &ePBCSolvent, &temporaryX, &temporaryV, boxSolvent);
628 t_atoms* atomsSolvent;
629 snew(atomsSolvent, 1);
630 *atomsSolvent = gmx_mtop_global_atoms(&topSolvent);
631 xSolvent.assign(temporaryX, temporaryX + topSolvent.natoms);
633 vSolvent.assign(temporaryV, temporaryV + topSolvent.natoms);
635 if (gmx::boxIsZero(boxSolvent))
638 "No box information for solvent in %s, please use a properly formatted file\n",
641 if (0 == atomsSolvent->nr)
643 gmx_fatal(FARGS, "No solvent in %s, please check your input\n", filename);
645 fprintf(stderr, "\n");
647 /* initialise distance arrays for solvent configuration */
648 fprintf(stderr, "Initialising inter-atomic distances...\n");
649 const std::vector<real> exclusionDistances(
650 makeExclusionDistances(atoms, aps, defaultDistance, scaleFactor));
651 std::vector<real> exclusionDistances_solvt(
652 makeExclusionDistances(atomsSolvent, aps, defaultDistance, scaleFactor));
654 /* generate a new solvent configuration */
655 fprintf(stderr, "Generating solvent configuration\n");
657 set_pbc(&pbc, ePBC, box);
658 if (!gmx::boxesAreEqual(boxSolvent, box))
660 if (TRICLINIC(boxSolvent))
663 "Generating from non-rectangular solvent boxes is currently not supported.\n"
664 "You can try to pass the same box for -cp and -cs.");
666 /* apply pbc for solvent configuration for whole molecules */
667 rm_res_pbc(atomsSolvent, &xSolvent, boxSolvent);
668 replicateSolventBox(atomsSolvent, &xSolvent, &vSolvent, &exclusionDistances_solvt, boxSolvent, box);
669 if (ePBC != epbcNONE)
671 removeSolventBoxOverlap(atomsSolvent, &xSolvent, &vSolvent, &exclusionDistances_solvt, pbc);
678 removeSolventOutsideShell(atomsSolvent, &xSolvent, &vSolvent, &exclusionDistances_solvt,
681 removeSolventOverlappingWithSolute(atomsSolvent, &xSolvent, &vSolvent,
682 &exclusionDistances_solvt, pbc, *x, exclusionDistances);
685 if (max_sol > 0 && atomsSolvent->nres > max_sol)
687 const int numberToRemove = atomsSolvent->nres - max_sol;
688 removeExtraSolventMolecules(atomsSolvent, &xSolvent, &vSolvent, numberToRemove);
691 /* Sort the solvent mixture, not the protein... */
692 t_atoms* newatoms = nullptr;
693 // The sort_molecule function does something creative with the
694 // t_atoms pointers. We need to make sure we neither leak, nor
695 // double-free, so make a shallow pointer that is fine for it to
697 t_atoms* sortedAtomsSolvent = atomsSolvent;
698 sort_molecule(&sortedAtomsSolvent, &newatoms, &xSolvent, &vSolvent);
700 // Merge the two configurations.
701 x->insert(x->end(), xSolvent.begin(), xSolvent.end());
704 v->insert(v->end(), vSolvent.begin(), vSolvent.end());
707 gmx::AtomsBuilder builder(atoms, symtab);
708 builder.mergeAtoms(*sortedAtomsSolvent);
710 fprintf(stderr, "Generated solvent containing %d atoms in %d residues\n", atomsSolvent->nr,
720 done_atom(atomsSolvent);
725 static void update_top(t_atoms* atoms,
726 int firstSolventResidueIndex,
733 char buf[STRLEN * 2], buf2[STRLEN], *temp;
734 const char* topinout;
741 int nsol = atoms->nres - firstSolventResidueIndex;
744 for (i = 0; (i < atoms->nr); i++)
746 aps->setAtomProperty(epropMass, std::string(*atoms->resinfo[atoms->atom[i].resind].name),
747 std::string(*atoms->atomname[i]), &mm);
753 fprintf(stderr, "Volume : %10g (nm^3)\n", vol);
754 fprintf(stderr, "Density : %10g (g/l)\n", (mtot * 1e24) / (AVOGADRO * vol));
755 fprintf(stderr, "Number of solvent molecules: %5d \n\n", nsol);
757 /* open topology file and append sol molecules */
758 topinout = ftp2fn(efTOP, NFILE, fnm);
759 if (ftp2bSet(efTOP, NFILE, fnm))
761 char temporary_filename[STRLEN];
762 strncpy(temporary_filename, "temp.topXXXXXX", STRLEN);
764 fprintf(stderr, "Processing topology\n");
765 fpin = gmx_ffopen(topinout, "r");
766 fpout = gmx_fopen_temporary(temporary_filename);
769 while (fgets(buf, STRLEN, fpin))
773 if ((temp = strchr(buf2, '\n')) != nullptr)
781 if ((temp = strchr(buf2, '\n')) != nullptr)
786 if (buf2[strlen(buf2) - 1] == ']')
788 buf2[strlen(buf2) - 1] = '\0';
791 bSystem = (gmx_strcasecmp(buf2, "system") == 0);
794 else if (bSystem && nsol && (buf[0] != ';'))
796 /* if sol present, append "in water" to system name */
798 if (buf2[0] && (!strstr(buf2, " water")))
800 sprintf(buf, "%s in water\n", buf2);
804 fprintf(fpout, "%s", buf);
808 // Add new solvent molecules to the topology
811 std::string currRes = *atoms->resinfo[firstSolventResidueIndex].name;
814 // Iterate through solvent molecules and increment a count until new resname found
815 for (int i = firstSolventResidueIndex; i < atoms->nres; i++)
817 if ((currRes == *atoms->resinfo[i].name))
823 // Change topology and restart count
825 "Adding line for %d solvent molecules with resname (%s) to "
826 "topology file (%s)\n",
827 resCount, currRes.c_str(), topinout);
828 fprintf(fpout, "%-15s %5d\n", currRes.c_str(), resCount);
829 currRes = *atoms->resinfo[i].name;
833 // One more print needed for last residue type
835 "Adding line for %d solvent molecules with resname (%s) to "
836 "topology file (%s)\n",
837 resCount, currRes.c_str(), topinout);
838 fprintf(fpout, "%-15s %5d\n", currRes.c_str(), resCount);
841 make_backup(topinout);
842 gmx_file_rename(temporary_filename, topinout);
846 int gmx_solvate(int argc, char* argv[])
848 const char* desc[] = {
849 "[THISMODULE] can do one of 2 things:[PAR]",
851 "1) Generate a box of solvent. Specify [TT]-cs[tt] and [TT]-box[tt].",
852 "Or specify [TT]-cs[tt] and [TT]-cp[tt] with a structure file with",
853 "a box, but without atoms.[PAR]",
855 "2) Solvate a solute configuration, e.g. a protein, in a bath of solvent ",
856 "molecules. Specify [TT]-cp[tt] (solute) and [TT]-cs[tt] (solvent). ",
857 "The box specified in the solute coordinate file ([TT]-cp[tt]) is used,",
858 "unless [TT]-box[tt] is set.",
859 "If you want the solute to be centered in the box,",
860 "the program [gmx-editconf] has sophisticated options",
861 "to change the box dimensions and center the solute.",
862 "Solvent molecules are removed from the box where the ",
863 "distance between any atom of the solute molecule(s) and any atom of ",
864 "the solvent molecule is less than the sum of the scaled van der Waals",
865 "radii of both atoms. A database ([TT]vdwradii.dat[tt]) of van der",
866 "Waals radii is read by the program, and the resulting radii scaled",
867 "by [TT]-scale[tt]. If radii are not found in the database, those",
868 "atoms are assigned the (pre-scaled) distance [TT]-radius[tt].",
869 "Note that the usefulness of those radii depends on the atom names,",
870 "and thus varies widely with force field.",
872 "The default solvent is Simple Point Charge water (SPC), with coordinates ",
873 "from [TT]$GMXLIB/spc216.gro[tt]. These coordinates can also be used",
874 "for other 3-site water models, since a short equibilibration will remove",
875 "the small differences between the models.",
876 "Other solvents are also supported, as well as mixed solvents. The",
877 "only restriction to solvent types is that a solvent molecule consists",
878 "of exactly one residue. The residue information in the coordinate",
879 "files is used, and should therefore be more or less consistent.",
880 "In practice this means that two subsequent solvent molecules in the ",
881 "solvent coordinate file should have different residue number.",
882 "The box of solute is built by stacking the coordinates read from",
883 "the coordinate file. This means that these coordinates should be ",
884 "equlibrated in periodic boundary conditions to ensure a good",
885 "alignment of molecules on the stacking interfaces.",
886 "The [TT]-maxsol[tt] option simply adds only the first [TT]-maxsol[tt]",
887 "solvent molecules and leaves out the rest that would have fitted",
888 "into the box. This can create a void that can cause problems later.",
889 "Choose your volume wisely.[PAR]",
891 "Setting [TT]-shell[tt] larger than zero will place a layer of water of",
892 "the specified thickness (nm) around the solute. Hint: it is a good",
893 "idea to put the protein in the center of a box first (using [gmx-editconf]).",
896 "Finally, [THISMODULE] will optionally remove lines from your topology file in ",
897 "which a number of solvent molecules is already added, and adds a ",
898 "line with the total number of solvent molecules in your coordinate file."
901 const char* bugs[] = {
902 "Molecules must be whole in the initial configurations.",
906 gmx_bool bProt, bBox;
907 const char *conf_prot, *confout;
910 { efSTX, "-cp", "protein", ffOPTRD },
911 { efSTX, "-cs", "spc216", ffLIBRD },
912 { efSTO, nullptr, nullptr, ffWRITE },
913 { efTOP, nullptr, nullptr, ffOPTRW },
915 #define NFILE asize(fnm)
917 real defaultDistance = 0.105, r_shell = 0, scaleFactor = 0.57;
918 rvec new_box = { 0.0, 0.0, 0.0 };
919 gmx_bool bReadV = FALSE;
921 int firstSolventResidueIndex = 0;
922 gmx_output_env_t* oenv;
924 { "-box", FALSE, etRVEC, { new_box }, "Box size (in nm)" },
925 { "-radius", FALSE, etREAL, { &defaultDistance }, "Default van der Waals distance" },
930 "Scale factor to multiply Van der Waals radii from the database in "
931 "share/gromacs/top/vdwradii.dat. The default value of 0.57 yields density close to 1000 "
932 "g/l for proteins in water." },
933 { "-shell", FALSE, etREAL, { &r_shell }, "Thickness of optional water layer around solute" },
938 "Maximum number of solvent molecules to add if they fit in the box. If zero (default) "
940 { "-vel", FALSE, etBOOL, { &bReadV }, "Keep velocities from input solute and solvent" },
943 if (!parse_common_args(&argc, argv, 0, NFILE, fnm, asize(pa), pa, asize(desc), desc,
944 asize(bugs), bugs, &oenv))
949 const char* solventFileName = opt2fn("-cs", NFILE, fnm);
950 bProt = opt2bSet("-cp", NFILE, fnm);
951 bBox = opt2parg_bSet("-box", asize(pa), pa);
957 "When no solute (-cp) is specified, "
958 "a box size (-box) must be specified");
963 /* solute configuration data */
965 std::vector<RVec> x, v;
966 matrix box = { { 0 } };
972 /* Generate a solute configuration */
973 conf_prot = opt2fn("-cp", NFILE, fnm);
974 fprintf(stderr, "Reading solute configuration%s\n", bReadV ? " and velocities" : "");
975 bool bTprFileWasRead;
976 rvec *temporaryX = nullptr, *temporaryV = nullptr;
977 readConfAndTopology(conf_prot, &bTprFileWasRead, &top, &ePBC, &temporaryX,
978 bReadV ? &temporaryV : nullptr, box);
979 *atoms = gmx_mtop_global_atoms(&top);
980 x.assign(temporaryX, temporaryX + top.natoms);
984 v.assign(temporaryV, temporaryV + top.natoms);
989 fprintf(stderr, "Note: no velocities found\n");
993 fprintf(stderr, "Note: no atoms in %s\n", conf_prot);
998 firstSolventResidueIndex = atoms->nres;
1001 int ePBCForOutput = ePBC;
1004 ePBCForOutput = epbcXYZ;
1006 box[XX][XX] = new_box[XX];
1007 box[YY][YY] = new_box[YY];
1008 box[ZZ][ZZ] = new_box[ZZ];
1013 "Undefined solute box.\nCreate one with gmx editconf "
1014 "or give explicit -box command line option");
1017 add_solv(solventFileName, atoms, &top.symtab, &x, &v, ePBCForOutput, box, &aps, defaultDistance,
1018 scaleFactor, r_shell, max_sol);
1020 /* write new configuration 1 to file confout */
1021 confout = ftp2fn(efSTO, NFILE, fnm);
1022 fprintf(stderr, "Writing generated configuration to %s\n", confout);
1023 const char* outputTitle = (bProt ? *top.name : "Generated by gmx solvate");
1024 write_sto_conf(confout, outputTitle, atoms, as_rvec_array(x.data()),
1025 !v.empty() ? as_rvec_array(v.data()) : nullptr, ePBCForOutput, box);
1027 /* print size of generated configuration */
1028 fprintf(stderr, "\nOutput configuration contains %d atoms in %d residues\n", atoms->nr, atoms->nres);
1029 update_top(atoms, firstSolventResidueIndex, box, NFILE, fnm, &aps);
1033 output_env_done(oenv);