Update instructions in containers.rst
[gromacs.git] / src / gromacs / gmxpreprocess / solvate.cpp
blob054d5b223ac1f3dff596ecd101c6de159c960903
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5 * Copyright (c) 2001-2004, The GROMACS development team.
6 * Copyright (c) 2013,2014,2015,2016,2017 by the GROMACS development team.
7 * Copyright (c) 2018,2019,2020, by the GROMACS development team, led by
8 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
9 * and including many others, as listed in the AUTHORS file in the
10 * top-level source directory and at http://www.gromacs.org.
12 * GROMACS is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 2.1
15 * of the License, or (at your option) any later version.
17 * GROMACS is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with GROMACS; if not, see
24 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
25 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 * If you want to redistribute modifications to GROMACS, please
28 * consider that scientific software is very special. Version
29 * control is crucial - bugs must be traceable. We will be happy to
30 * consider code for inclusion in the official distribution, but
31 * derived work must not be called official GROMACS. Details are found
32 * in the README & COPYING files - if they are missing, get the
33 * official version at http://www.gromacs.org.
35 * To help us fund GROMACS development, we humbly ask that you cite
36 * the research papers on the package. Check out http://www.gromacs.org.
38 #include "gmxpre.h"
40 #include "solvate.h"
42 #include <cstring>
44 #include <algorithm>
45 #include <random>
46 #include <vector>
48 #include "gromacs/commandline/pargs.h"
49 #include "gromacs/fileio/confio.h"
50 #include "gromacs/fileio/pdbio.h"
51 #include "gromacs/gmxlib/conformation_utilities.h"
52 #include "gromacs/gmxpreprocess/makeexclusiondistances.h"
53 #include "gromacs/math/functions.h"
54 #include "gromacs/math/units.h"
55 #include "gromacs/math/vec.h"
56 #include "gromacs/pbcutil/boxutilities.h"
57 #include "gromacs/pbcutil/pbc.h"
58 #include "gromacs/selection/nbsearch.h"
59 #include "gromacs/topology/atomprop.h"
60 #include "gromacs/topology/atoms.h"
61 #include "gromacs/topology/atomsbuilder.h"
62 #include "gromacs/topology/mtop_util.h"
63 #include "gromacs/topology/topology.h"
64 #include "gromacs/utility/arraysize.h"
65 #include "gromacs/utility/cstringutil.h"
66 #include "gromacs/utility/fatalerror.h"
67 #include "gromacs/utility/futil.h"
68 #include "gromacs/utility/gmxassert.h"
69 #include "gromacs/utility/smalloc.h"
71 using gmx::RVec;
73 /*! \brief Describes a molecule type, and keeps track of the number of these molecules
75 * Used for sorting coordinate file data after solvation
77 struct MoleculeType
79 //! molecule name
80 std::string name;
81 //! number of atoms in the molecule
82 int numAtoms = 0;
83 //! number of occurences of molecule
84 int numMolecules = 0;
87 static void sort_molecule(t_atoms** atoms_solvt, t_atoms** newatoms, std::vector<RVec>* x, std::vector<RVec>* v)
90 fprintf(stderr, "Sorting configuration\n");
91 t_atoms* atoms = *atoms_solvt;
93 /* copy each residue from *atoms to a molecule in *molecule */
94 std::vector<MoleculeType> molTypes;
95 for (int i = 0; i < atoms->nr; i++)
97 if ((i == 0) || (atoms->atom[i].resind != atoms->atom[i - 1].resind))
99 /* see if this was a molecule type we haven't had yet: */
100 auto matchingMolType = std::find_if(
101 molTypes.begin(), molTypes.end(), [atoms, i](const MoleculeType& molecule) {
102 return molecule.name == *atoms->resinfo[atoms->atom[i].resind].name;
104 if (matchingMolType == molTypes.end())
106 int numAtomsInMolType = 0;
107 while ((i + numAtomsInMolType < atoms->nr)
108 && (atoms->atom[i].resind == atoms->atom[i + numAtomsInMolType].resind))
110 numAtomsInMolType++;
112 molTypes.emplace_back(MoleculeType{ *atoms->resinfo[atoms->atom[i].resind].name,
113 numAtomsInMolType, 1 });
115 else
117 matchingMolType->numMolecules++;
122 fprintf(stderr, "Found %zu%s molecule type%s:\n", molTypes.size(),
123 molTypes.size() == 1 ? "" : " different", molTypes.size() == 1 ? "" : "s");
124 for (const auto& molType : molTypes)
126 fprintf(stderr, "%7s (%4d atoms): %5d residues\n", molType.name.c_str(), molType.numAtoms,
127 molType.numMolecules);
130 /* if we have only 1 moleculetype, we don't have to sort */
131 if (molTypes.size() > 1)
133 /* now put them there: */
134 snew(*newatoms, 1);
135 init_t_atoms(*newatoms, atoms->nr, FALSE);
136 (*newatoms)->nres = atoms->nres;
137 srenew((*newatoms)->resinfo, atoms->nres);
138 std::vector<RVec> newx(x->size());
139 std::vector<RVec> newv(v->size());
141 int residIndex = 0;
142 int atomIndex = 0;
143 for (const auto& moleculeType : molTypes)
145 int i = 0;
146 while (i < atoms->nr)
148 int residOfCurrAtom = atoms->atom[i].resind;
149 if (moleculeType.name == *atoms->resinfo[residOfCurrAtom].name)
151 /* Copy the residue info */
152 (*newatoms)->resinfo[residIndex] = atoms->resinfo[residOfCurrAtom];
153 // Residue numbering starts from 1, so +1 from the index
154 (*newatoms)->resinfo[residIndex].nr = residIndex + 1;
155 /* Copy the atom info */
158 (*newatoms)->atom[atomIndex] = atoms->atom[i];
159 (*newatoms)->atomname[atomIndex] = atoms->atomname[i];
160 (*newatoms)->atom[atomIndex].resind = residIndex;
161 copy_rvec((*x)[i], newx[atomIndex]);
162 if (!v->empty())
164 copy_rvec((*v)[i], newv[atomIndex]);
166 i++;
167 atomIndex++;
168 } while (i < atoms->nr && atoms->atom[i].resind == residOfCurrAtom);
169 /* Increase the new residue counters */
170 residIndex++;
172 else
174 /* Skip this residue */
177 i++;
178 } while (i < atoms->nr && atoms->atom[i].resind == residOfCurrAtom);
183 /* put them back into the original arrays and throw away temporary arrays */
184 done_atom(atoms);
185 *atoms_solvt = (*newatoms);
186 std::swap(*x, newx);
187 std::swap(*v, newv);
191 static void rm_res_pbc(const t_atoms* atoms, std::vector<RVec>* x, matrix box)
193 int start, nat;
194 rvec xcg;
196 start = 0;
197 nat = 0;
198 clear_rvec(xcg);
199 for (int n = 0; n < atoms->nr; n++)
201 if (!is_hydrogen(*(atoms->atomname[n])))
203 nat++;
204 rvec_inc(xcg, (*x)[n]);
206 if ((n + 1 == atoms->nr) || (atoms->atom[n + 1].resind != atoms->atom[n].resind))
208 /* if nat==0 we have only hydrogens in the solvent,
209 we take last coordinate as cg */
210 if (nat == 0)
212 nat = 1;
213 copy_rvec((*x)[n], xcg);
215 svmul(1.0 / nat, xcg, xcg);
216 for (int d = 0; d < DIM; d++)
218 while (xcg[d] < 0)
220 for (int i = start; i <= n; i++)
222 (*x)[i][d] += box[d][d];
224 xcg[d] += box[d][d];
226 while (xcg[d] >= box[d][d])
228 for (int i = start; i <= n; i++)
230 (*x)[i][d] -= box[d][d];
232 xcg[d] -= box[d][d];
235 start = n + 1;
236 nat = 0;
237 clear_rvec(xcg);
242 /*! \brief
243 * Generates a solvent configuration of desired size by stacking solvent boxes.
245 * \param[in,out] atoms Solvent atoms.
246 * \param[in,out] x Solvent positions.
247 * \param[in,out] v Solvent velocities (`*v` can be NULL).
248 * \param[in,out] r Solvent exclusion radii.
249 * \param[in] box Initial solvent box.
250 * \param[in] boxTarget Target box size.
252 * The solvent box of desired size is created by stacking the initial box in
253 * the smallest k*l*m array that covers the box, and then removing any residue
254 * where all atoms are outside the target box (with a small margin).
255 * This function does not remove overlap between solvent atoms across the
256 * edges.
258 * Note that the input configuration should be in the rectangular unit cell and
259 * have whole residues.
261 static void replicateSolventBox(t_atoms* atoms,
262 std::vector<RVec>* x,
263 std::vector<RVec>* v,
264 std::vector<real>* r,
265 const matrix box,
266 const matrix boxTarget)
268 // Calculate the box multiplication factors.
269 ivec n_box;
270 int nmol = 1;
271 for (int i = 0; i < DIM; ++i)
273 n_box[i] = 1;
274 while (n_box[i] * box[i][i] < boxTarget[i][i])
276 n_box[i]++;
278 nmol *= n_box[i];
280 fprintf(stderr, "Will generate new solvent configuration of %dx%dx%d boxes\n", n_box[XX],
281 n_box[YY], n_box[ZZ]);
283 // Create arrays for storing the generated system (cannot be done in-place
284 // in case the target box is smaller than the original in one dimension,
285 // but not in all).
286 t_atoms newAtoms;
287 init_t_atoms(&newAtoms, 0, FALSE);
288 gmx::AtomsBuilder builder(&newAtoms, nullptr);
289 builder.reserve(atoms->nr * nmol, atoms->nres * nmol);
290 std::vector<RVec> newX(atoms->nr * nmol);
291 std::vector<RVec> newV(!v->empty() ? atoms->nr * nmol : 0);
292 std::vector<real> newR(atoms->nr * nmol);
294 const real maxRadius = *std::max_element(r->begin(), r->end());
295 rvec boxWithMargin;
296 for (int i = 0; i < DIM; ++i)
298 // The code below is only interested about the box diagonal.
299 boxWithMargin[i] = boxTarget[i][i] + 3 * maxRadius;
302 for (int ix = 0; ix < n_box[XX]; ++ix)
304 rvec delta;
305 delta[XX] = ix * box[XX][XX];
306 for (int iy = 0; iy < n_box[YY]; ++iy)
308 delta[YY] = iy * box[YY][YY];
309 for (int iz = 0; iz < n_box[ZZ]; ++iz)
311 delta[ZZ] = iz * box[ZZ][ZZ];
312 bool bKeepResidue = false;
313 for (int i = 0; i < atoms->nr; ++i)
315 const int newIndex = builder.currentAtomCount();
316 bool bKeepAtom = true;
317 for (int m = 0; m < DIM; ++m)
319 const real newCoord = delta[m] + (*x)[i][m];
320 bKeepAtom = bKeepAtom && (newCoord < boxWithMargin[m]);
321 newX[newIndex][m] = newCoord;
323 bKeepResidue = bKeepResidue || bKeepAtom;
324 if (!v->empty())
326 copy_rvec((*v)[i], newV[newIndex]);
328 newR[newIndex] = (*r)[i];
329 builder.addAtom(*atoms, i);
330 if (i == atoms->nr - 1 || atoms->atom[i + 1].resind != atoms->atom[i].resind)
332 if (bKeepResidue)
334 builder.finishResidue(atoms->resinfo[atoms->atom[i].resind]);
336 else
338 builder.discardCurrentResidue();
340 // Reset state for the next residue.
341 bKeepResidue = false;
347 sfree(atoms->atom);
348 sfree(atoms->atomname);
349 sfree(atoms->resinfo);
350 atoms->nr = newAtoms.nr;
351 atoms->nres = newAtoms.nres;
352 atoms->atom = newAtoms.atom;
353 atoms->atomname = newAtoms.atomname;
354 atoms->resinfo = newAtoms.resinfo;
356 newX.resize(atoms->nr);
357 std::swap(*x, newX);
358 if (!v->empty())
360 newV.resize(atoms->nr);
361 std::swap(*v, newV);
363 newR.resize(atoms->nr);
364 std::swap(*r, newR);
366 fprintf(stderr, "Solvent box contains %d atoms in %d residues\n", atoms->nr, atoms->nres);
369 /*! \brief
370 * Removes overlap of solvent atoms across the edges.
372 * \param[in,out] atoms Solvent atoms.
373 * \param[in,out] x Solvent positions.
374 * \param[in,out] v Solvent velocities (can be empty).
375 * \param[in,out] r Solvent exclusion radii.
376 * \param[in] pbc PBC information.
378 * Solvent residues that lay on the edges that do not touch the origin are
379 * removed if they overlap with other solvent atoms across the PBC.
380 * This is done in this way as the assumption is that the input solvent
381 * configuration is already equilibrated, and so does not contain any
382 * undesirable overlap. The only overlap that should be removed is caused by
383 * cutting the box in half in replicateSolventBox() and leaving a margin of
384 * solvent outside those box edges; these atoms can then overlap with those on
385 * the opposite box edge in a way that is not part of the pre-equilibrated
386 * configuration.
388 static void removeSolventBoxOverlap(t_atoms* atoms,
389 std::vector<RVec>* x,
390 std::vector<RVec>* v,
391 std::vector<real>* r,
392 const t_pbc& pbc)
394 gmx::AtomsRemover remover(*atoms);
396 // TODO: We could limit the amount of pairs searched significantly,
397 // since we are only interested in pairs where the positions are on
398 // opposite edges.
399 const real maxRadius = *std::max_element(r->begin(), r->end());
400 gmx::AnalysisNeighborhood nb;
401 nb.setCutoff(2 * maxRadius);
402 gmx::AnalysisNeighborhoodPositions pos(*x);
403 gmx::AnalysisNeighborhoodSearch search = nb.initSearch(&pbc, pos);
404 gmx::AnalysisNeighborhoodPairSearch pairSearch = search.startPairSearch(pos);
405 gmx::AnalysisNeighborhoodPair pair;
406 while (pairSearch.findNextPair(&pair))
408 const int i1 = pair.refIndex();
409 const int i2 = pair.testIndex();
410 if (remover.isMarked(i2))
412 pairSearch.skipRemainingPairsForTestPosition();
413 continue;
415 if (remover.isMarked(i1) || atoms->atom[i1].resind == atoms->atom[i2].resind)
417 continue;
419 if (pair.distance2() < gmx::square((*r)[i1] + (*r)[i2]))
421 rvec dx;
422 rvec_sub((*x)[i2], (*x)[i1], dx);
423 bool bCandidate1 = false, bCandidate2 = false;
424 // To satisfy Clang static analyzer.
425 GMX_ASSERT(pbc.ndim_ePBC <= DIM, "Too many periodic dimensions");
426 for (int d = 0; d < pbc.ndim_ePBC; ++d)
428 // If the distance in some dimension is larger than the
429 // cutoff, then it means that the distance has been computed
430 // over the PBC. Mark the position with a larger coordinate
431 // for potential removal.
432 if (dx[d] > maxRadius)
434 bCandidate2 = true;
436 else if (dx[d] < -maxRadius)
438 bCandidate1 = true;
441 // Only mark one of the positions for removal if both were
442 // candidates.
443 if (bCandidate2 && (!bCandidate1 || i2 > i1))
445 remover.markResidue(*atoms, i2, true);
446 pairSearch.skipRemainingPairsForTestPosition();
448 else if (bCandidate1)
450 remover.markResidue(*atoms, i1, true);
455 remover.removeMarkedElements(x);
456 if (!v->empty())
458 remover.removeMarkedElements(v);
460 remover.removeMarkedElements(r);
461 const int originalAtomCount = atoms->nr;
462 remover.removeMarkedAtoms(atoms);
463 fprintf(stderr, "Removed %d solvent atoms due to solvent-solvent overlap\n",
464 originalAtomCount - atoms->nr);
467 /*! \brief
468 * Remove all solvent molecules outside a give radius from the solute.
470 * \param[in,out] atoms Solvent atoms.
471 * \param[in,out] x_solvent Solvent positions.
472 * \param[in,out] v_solvent Solvent velocities.
473 * \param[in,out] r Atomic exclusion radii.
474 * \param[in] pbc PBC information.
475 * \param[in] x_solute Solute positions.
476 * \param[in] rshell The radius outside the solute molecule.
478 static void removeSolventOutsideShell(t_atoms* atoms,
479 std::vector<RVec>* x_solvent,
480 std::vector<RVec>* v_solvent,
481 std::vector<real>* r,
482 const t_pbc& pbc,
483 const std::vector<RVec>& x_solute,
484 real rshell)
486 gmx::AtomsRemover remover(*atoms);
487 gmx::AnalysisNeighborhood nb;
488 nb.setCutoff(rshell);
489 gmx::AnalysisNeighborhoodPositions posSolute(x_solute);
490 gmx::AnalysisNeighborhoodSearch search = nb.initSearch(&pbc, posSolute);
491 gmx::AnalysisNeighborhoodPositions pos(*x_solvent);
492 gmx::AnalysisNeighborhoodPairSearch pairSearch = search.startPairSearch(pos);
493 gmx::AnalysisNeighborhoodPair pair;
495 // Remove everything
496 remover.markAll();
497 // Now put back those within the shell without checking for overlap
498 while (pairSearch.findNextPair(&pair))
500 remover.markResidue(*atoms, pair.testIndex(), false);
501 pairSearch.skipRemainingPairsForTestPosition();
503 remover.removeMarkedElements(x_solvent);
504 if (!v_solvent->empty())
506 remover.removeMarkedElements(v_solvent);
508 remover.removeMarkedElements(r);
509 const int originalAtomCount = atoms->nr;
510 remover.removeMarkedAtoms(atoms);
511 fprintf(stderr, "Removed %d solvent atoms more than %f nm from solute.\n",
512 originalAtomCount - atoms->nr, rshell);
515 /*! \brief
516 * Removes solvent molecules that overlap with the solute.
518 * \param[in,out] atoms Solvent atoms.
519 * \param[in,out] x Solvent positions.
520 * \param[in,out] v Solvent velocities (can be empty).
521 * \param[in,out] r Solvent exclusion radii.
522 * \param[in] pbc PBC information.
523 * \param[in] x_solute Solute positions.
524 * \param[in] r_solute Solute exclusion radii.
526 static void removeSolventOverlappingWithSolute(t_atoms* atoms,
527 std::vector<RVec>* x,
528 std::vector<RVec>* v,
529 std::vector<real>* r,
530 const t_pbc& pbc,
531 const std::vector<RVec>& x_solute,
532 const std::vector<real>& r_solute)
534 gmx::AtomsRemover remover(*atoms);
535 const real maxRadius1 = *std::max_element(r->begin(), r->end());
536 const real maxRadius2 = *std::max_element(r_solute.begin(), r_solute.end());
538 // Now check for overlap.
539 gmx::AnalysisNeighborhood nb;
540 gmx::AnalysisNeighborhoodPair pair;
541 nb.setCutoff(maxRadius1 + maxRadius2);
542 gmx::AnalysisNeighborhoodPositions posSolute(x_solute);
543 gmx::AnalysisNeighborhoodSearch search = nb.initSearch(&pbc, posSolute);
544 gmx::AnalysisNeighborhoodPositions pos(*x);
545 gmx::AnalysisNeighborhoodPairSearch pairSearch = search.startPairSearch(pos);
546 while (pairSearch.findNextPair(&pair))
548 if (remover.isMarked(pair.testIndex()))
550 pairSearch.skipRemainingPairsForTestPosition();
551 continue;
553 const real r1 = r_solute[pair.refIndex()];
554 const real r2 = (*r)[pair.testIndex()];
555 const bool bRemove = (pair.distance2() < gmx::square(r1 + r2));
556 remover.markResidue(*atoms, pair.testIndex(), bRemove);
559 remover.removeMarkedElements(x);
560 if (!v->empty())
562 remover.removeMarkedElements(v);
564 remover.removeMarkedElements(r);
565 const int originalAtomCount = atoms->nr;
566 remover.removeMarkedAtoms(atoms);
567 fprintf(stderr, "Removed %d solvent atoms due to solute-solvent overlap\n",
568 originalAtomCount - atoms->nr);
571 /*! \brief
572 * Removes a given number of solvent residues.
574 * \param[in,out] atoms Solvent atoms.
575 * \param[in,out] x Solvent positions.
576 * \param[in,out] v Solvent velocities (can be empty).
577 * \param[in] numberToRemove Number of residues to remove.
579 * This function is called last in the process of creating the solvent box,
580 * so it does not operate on the exclusion radii, as no code after this needs
581 * them.
583 static void removeExtraSolventMolecules(t_atoms* atoms, std::vector<RVec>* x, std::vector<RVec>* v, int numberToRemove)
585 gmx::AtomsRemover remover(*atoms);
586 std::random_device rd;
587 std::mt19937 randomNumberGenerator(rd());
588 std::uniform_int_distribution<> randomDistribution(0, atoms->nr - 1);
589 while (numberToRemove > 0)
591 int atomIndex = randomDistribution(randomNumberGenerator);
592 if (!remover.isMarked(atomIndex))
594 remover.markResidue(*atoms, atomIndex, true);
595 numberToRemove--;
598 remover.removeMarkedElements(x);
599 if (!v->empty())
601 remover.removeMarkedElements(v);
603 remover.removeMarkedAtoms(atoms);
606 static void add_solv(const char* filename,
607 t_atoms* atoms,
608 t_symtab* symtab,
609 std::vector<RVec>* x,
610 std::vector<RVec>* v,
611 PbcType pbcType,
612 matrix box,
613 AtomProperties* aps,
614 real defaultDistance,
615 real scaleFactor,
616 real rshell,
617 int max_sol)
619 gmx_mtop_t topSolvent;
620 std::vector<RVec> xSolvent, vSolvent;
621 matrix boxSolvent = { { 0 } };
622 PbcType pbcTypeSolvent;
624 fprintf(stderr, "Reading solvent configuration\n");
625 bool bTprFileWasRead;
626 rvec *temporaryX = nullptr, *temporaryV = nullptr;
627 readConfAndTopology(gmx::findLibraryFile(filename).c_str(), &bTprFileWasRead, &topSolvent,
628 &pbcTypeSolvent, &temporaryX, &temporaryV, boxSolvent);
629 t_atoms* atomsSolvent;
630 snew(atomsSolvent, 1);
631 *atomsSolvent = gmx_mtop_global_atoms(&topSolvent);
632 xSolvent.assign(temporaryX, temporaryX + topSolvent.natoms);
633 sfree(temporaryX);
634 vSolvent.assign(temporaryV, temporaryV + topSolvent.natoms);
635 sfree(temporaryV);
636 if (gmx::boxIsZero(boxSolvent))
638 gmx_fatal(FARGS,
639 "No box information for solvent in %s, please use a properly formatted file\n",
640 filename);
642 if (0 == atomsSolvent->nr)
644 gmx_fatal(FARGS, "No solvent in %s, please check your input\n", filename);
646 fprintf(stderr, "\n");
648 /* initialise distance arrays for solvent configuration */
649 fprintf(stderr, "Initialising inter-atomic distances...\n");
650 const std::vector<real> exclusionDistances(
651 makeExclusionDistances(atoms, aps, defaultDistance, scaleFactor));
652 std::vector<real> exclusionDistances_solvt(
653 makeExclusionDistances(atomsSolvent, aps, defaultDistance, scaleFactor));
655 /* generate a new solvent configuration */
656 fprintf(stderr, "Generating solvent configuration\n");
657 t_pbc pbc;
658 set_pbc(&pbc, pbcType, box);
659 if (!gmx::boxesAreEqual(boxSolvent, box))
661 if (TRICLINIC(boxSolvent))
663 gmx_fatal(FARGS,
664 "Generating from non-rectangular solvent boxes is currently not supported.\n"
665 "You can try to pass the same box for -cp and -cs.");
667 /* apply pbc for solvent configuration for whole molecules */
668 rm_res_pbc(atomsSolvent, &xSolvent, boxSolvent);
669 replicateSolventBox(atomsSolvent, &xSolvent, &vSolvent, &exclusionDistances_solvt, boxSolvent, box);
670 if (pbcType != PbcType::No)
672 removeSolventBoxOverlap(atomsSolvent, &xSolvent, &vSolvent, &exclusionDistances_solvt, pbc);
675 if (atoms->nr > 0)
677 if (rshell > 0.0)
679 removeSolventOutsideShell(atomsSolvent, &xSolvent, &vSolvent, &exclusionDistances_solvt,
680 pbc, *x, rshell);
682 removeSolventOverlappingWithSolute(atomsSolvent, &xSolvent, &vSolvent,
683 &exclusionDistances_solvt, pbc, *x, exclusionDistances);
686 if (max_sol > 0 && atomsSolvent->nres > max_sol)
688 const int numberToRemove = atomsSolvent->nres - max_sol;
689 removeExtraSolventMolecules(atomsSolvent, &xSolvent, &vSolvent, numberToRemove);
692 /* Sort the solvent mixture, not the protein... */
693 t_atoms* newatoms = nullptr;
694 // The sort_molecule function does something creative with the
695 // t_atoms pointers. We need to make sure we neither leak, nor
696 // double-free, so make a shallow pointer that is fine for it to
697 // change.
698 t_atoms* sortedAtomsSolvent = atomsSolvent;
699 sort_molecule(&sortedAtomsSolvent, &newatoms, &xSolvent, &vSolvent);
701 // Merge the two configurations.
702 x->insert(x->end(), xSolvent.begin(), xSolvent.end());
703 if (!v->empty())
705 v->insert(v->end(), vSolvent.begin(), vSolvent.end());
708 gmx::AtomsBuilder builder(atoms, symtab);
709 builder.mergeAtoms(*sortedAtomsSolvent);
711 fprintf(stderr, "Generated solvent containing %d atoms in %d residues\n", atomsSolvent->nr,
712 atomsSolvent->nres);
714 if (newatoms)
716 done_atom(newatoms);
717 sfree(newatoms);
719 if (atomsSolvent)
721 done_atom(atomsSolvent);
722 sfree(atomsSolvent);
726 static void update_top(t_atoms* atoms,
727 int firstSolventResidueIndex,
728 matrix box,
729 int NFILE,
730 t_filenm fnm[],
731 AtomProperties* aps)
733 FILE * fpin, *fpout;
734 char buf[STRLEN * 2], buf2[STRLEN], *temp;
735 const char* topinout;
736 int line;
737 bool bSystem;
738 int i;
739 double mtot;
740 real vol, mm;
742 int nsol = atoms->nres - firstSolventResidueIndex;
744 mtot = 0;
745 for (i = 0; (i < atoms->nr); i++)
747 aps->setAtomProperty(epropMass, std::string(*atoms->resinfo[atoms->atom[i].resind].name),
748 std::string(*atoms->atomname[i]), &mm);
749 mtot += mm;
752 vol = det(box);
754 fprintf(stderr, "Volume : %10g (nm^3)\n", vol);
755 fprintf(stderr, "Density : %10g (g/l)\n", (mtot * 1e24) / (AVOGADRO * vol));
756 fprintf(stderr, "Number of solvent molecules: %5d \n\n", nsol);
758 /* open topology file and append sol molecules */
759 topinout = ftp2fn(efTOP, NFILE, fnm);
760 if (ftp2bSet(efTOP, NFILE, fnm))
762 char temporary_filename[STRLEN];
763 strncpy(temporary_filename, "temp.topXXXXXX", STRLEN);
765 fprintf(stderr, "Processing topology\n");
766 fpin = gmx_ffopen(topinout, "r");
767 fpout = gmx_fopen_temporary(temporary_filename);
768 line = 0;
769 bSystem = false;
770 while (fgets(buf, STRLEN, fpin))
772 line++;
773 strcpy(buf2, buf);
774 if ((temp = strchr(buf2, '\n')) != nullptr)
776 temp[0] = '\0';
778 ltrim(buf2);
779 if (buf2[0] == '[')
781 buf2[0] = ' ';
782 if ((temp = strchr(buf2, '\n')) != nullptr)
784 temp[0] = '\0';
786 rtrim(buf2);
787 if (buf2[strlen(buf2) - 1] == ']')
789 buf2[strlen(buf2) - 1] = '\0';
790 ltrim(buf2);
791 rtrim(buf2);
792 bSystem = (gmx_strcasecmp(buf2, "system") == 0);
795 else if (bSystem && nsol && (buf[0] != ';'))
797 /* if sol present, append "in water" to system name */
798 rtrim(buf2);
799 if (buf2[0] && (!strstr(buf2, " water")))
801 sprintf(buf, "%s in water\n", buf2);
802 bSystem = false;
805 fprintf(fpout, "%s", buf);
807 gmx_ffclose(fpin);
809 // Add new solvent molecules to the topology
810 if (nsol > 0)
812 std::string currRes = *atoms->resinfo[firstSolventResidueIndex].name;
813 int resCount = 0;
815 // Iterate through solvent molecules and increment a count until new resname found
816 for (int i = firstSolventResidueIndex; i < atoms->nres; i++)
818 if ((currRes == *atoms->resinfo[i].name))
820 resCount += 1;
822 else
824 // Change topology and restart count
825 fprintf(stdout,
826 "Adding line for %d solvent molecules with resname (%s) to "
827 "topology file (%s)\n",
828 resCount, currRes.c_str(), topinout);
829 fprintf(fpout, "%-15s %5d\n", currRes.c_str(), resCount);
830 currRes = *atoms->resinfo[i].name;
831 resCount = 1;
834 // One more print needed for last residue type
835 fprintf(stdout,
836 "Adding line for %d solvent molecules with resname (%s) to "
837 "topology file (%s)\n",
838 resCount, currRes.c_str(), topinout);
839 fprintf(fpout, "%-15s %5d\n", currRes.c_str(), resCount);
841 gmx_ffclose(fpout);
842 make_backup(topinout);
843 gmx_file_rename(temporary_filename, topinout);
847 int gmx_solvate(int argc, char* argv[])
849 const char* desc[] = {
850 "[THISMODULE] can do one of 2 things:[PAR]",
852 "1) Generate a box of solvent. Specify [TT]-cs[tt] and [TT]-box[tt].",
853 "Or specify [TT]-cs[tt] and [TT]-cp[tt] with a structure file with",
854 "a box, but without atoms.[PAR]",
856 "2) Solvate a solute configuration, e.g. a protein, in a bath of solvent ",
857 "molecules. Specify [TT]-cp[tt] (solute) and [TT]-cs[tt] (solvent). ",
858 "The box specified in the solute coordinate file ([TT]-cp[tt]) is used,",
859 "unless [TT]-box[tt] is set.",
860 "If you want the solute to be centered in the box,",
861 "the program [gmx-editconf] has sophisticated options",
862 "to change the box dimensions and center the solute.",
863 "Solvent molecules are removed from the box where the ",
864 "distance between any atom of the solute molecule(s) and any atom of ",
865 "the solvent molecule is less than the sum of the scaled van der Waals",
866 "radii of both atoms. A database ([TT]vdwradii.dat[tt]) of van der",
867 "Waals radii is read by the program, and the resulting radii scaled",
868 "by [TT]-scale[tt]. If radii are not found in the database, those",
869 "atoms are assigned the (pre-scaled) distance [TT]-radius[tt].",
870 "Note that the usefulness of those radii depends on the atom names,",
871 "and thus varies widely with force field.",
873 "The default solvent is Simple Point Charge water (SPC), with coordinates ",
874 "from [TT]$GMXLIB/spc216.gro[tt]. These coordinates can also be used",
875 "for other 3-site water models, since a short equibilibration will remove",
876 "the small differences between the models.",
877 "Other solvents are also supported, as well as mixed solvents. The",
878 "only restriction to solvent types is that a solvent molecule consists",
879 "of exactly one residue. The residue information in the coordinate",
880 "files is used, and should therefore be more or less consistent.",
881 "In practice this means that two subsequent solvent molecules in the ",
882 "solvent coordinate file should have different residue number.",
883 "The box of solute is built by stacking the coordinates read from",
884 "the coordinate file. This means that these coordinates should be ",
885 "equlibrated in periodic boundary conditions to ensure a good",
886 "alignment of molecules on the stacking interfaces.",
887 "The [TT]-maxsol[tt] option simply adds only the first [TT]-maxsol[tt]",
888 "solvent molecules and leaves out the rest that would have fitted",
889 "into the box. This can create a void that can cause problems later.",
890 "Choose your volume wisely.[PAR]",
892 "Setting [TT]-shell[tt] larger than zero will place a layer of water of",
893 "the specified thickness (nm) around the solute. Hint: it is a good",
894 "idea to put the protein in the center of a box first (using [gmx-editconf]).",
895 "[PAR]",
897 "Finally, [THISMODULE] will optionally remove lines from your topology file in ",
898 "which a number of solvent molecules is already added, and adds a ",
899 "line with the total number of solvent molecules in your coordinate file."
902 const char* bugs[] = {
903 "Molecules must be whole in the initial configurations.",
906 /* parameter data */
907 gmx_bool bProt, bBox;
908 const char *conf_prot, *confout;
910 t_filenm fnm[] = {
911 { efSTX, "-cp", "protein", ffOPTRD },
912 { efSTX, "-cs", "spc216", ffLIBRD },
913 { efSTO, nullptr, nullptr, ffWRITE },
914 { efTOP, nullptr, nullptr, ffOPTRW },
916 #define NFILE asize(fnm)
918 real defaultDistance = 0.105, r_shell = 0, scaleFactor = 0.57;
919 rvec new_box = { 0.0, 0.0, 0.0 };
920 gmx_bool bReadV = FALSE;
921 int max_sol = 0;
922 int firstSolventResidueIndex = 0;
923 gmx_output_env_t* oenv;
924 t_pargs pa[] = {
925 { "-box", FALSE, etRVEC, { new_box }, "Box size (in nm)" },
926 { "-radius", FALSE, etREAL, { &defaultDistance }, "Default van der Waals distance" },
927 { "-scale",
928 FALSE,
929 etREAL,
930 { &scaleFactor },
931 "Scale factor to multiply Van der Waals radii from the database in "
932 "share/gromacs/top/vdwradii.dat. The default value of 0.57 yields density close to 1000 "
933 "g/l for proteins in water." },
934 { "-shell", FALSE, etREAL, { &r_shell }, "Thickness of optional water layer around solute" },
935 { "-maxsol",
936 FALSE,
937 etINT,
938 { &max_sol },
939 "Maximum number of solvent molecules to add if they fit in the box. If zero (default) "
940 "this is ignored" },
941 { "-vel", FALSE, etBOOL, { &bReadV }, "Keep velocities from input solute and solvent" },
944 if (!parse_common_args(&argc, argv, 0, NFILE, fnm, asize(pa), pa, asize(desc), desc,
945 asize(bugs), bugs, &oenv))
947 return 0;
950 const char* solventFileName = opt2fn("-cs", NFILE, fnm);
951 bProt = opt2bSet("-cp", NFILE, fnm);
952 bBox = opt2parg_bSet("-box", asize(pa), pa);
954 /* check input */
955 if (!bProt && !bBox)
957 gmx_fatal(FARGS,
958 "When no solute (-cp) is specified, "
959 "a box size (-box) must be specified");
962 AtomProperties aps;
964 /* solute configuration data */
965 gmx_mtop_t top;
966 std::vector<RVec> x, v;
967 matrix box = { { 0 } };
968 PbcType pbcType = PbcType::Unset;
969 t_atoms* atoms;
970 snew(atoms, 1);
971 if (bProt)
973 /* Generate a solute configuration */
974 conf_prot = opt2fn("-cp", NFILE, fnm);
975 fprintf(stderr, "Reading solute configuration%s\n", bReadV ? " and velocities" : "");
976 bool bTprFileWasRead;
977 rvec *temporaryX = nullptr, *temporaryV = nullptr;
978 readConfAndTopology(conf_prot, &bTprFileWasRead, &top, &pbcType, &temporaryX,
979 bReadV ? &temporaryV : nullptr, box);
980 *atoms = gmx_mtop_global_atoms(&top);
981 x.assign(temporaryX, temporaryX + top.natoms);
982 sfree(temporaryX);
983 if (temporaryV)
985 v.assign(temporaryV, temporaryV + top.natoms);
986 sfree(temporaryV);
988 else if (bReadV)
990 fprintf(stderr, "Note: no velocities found\n");
992 if (atoms->nr == 0)
994 fprintf(stderr, "Note: no atoms in %s\n", conf_prot);
995 bProt = FALSE;
997 else
999 firstSolventResidueIndex = atoms->nres;
1002 PbcType pbcTypeForOutput = pbcType;
1003 if (bBox)
1005 pbcTypeForOutput = PbcType::Xyz;
1006 clear_mat(box);
1007 box[XX][XX] = new_box[XX];
1008 box[YY][YY] = new_box[YY];
1009 box[ZZ][ZZ] = new_box[ZZ];
1011 if (det(box) == 0)
1013 gmx_fatal(FARGS,
1014 "Undefined solute box.\nCreate one with gmx editconf "
1015 "or give explicit -box command line option");
1018 add_solv(solventFileName, atoms, &top.symtab, &x, &v, pbcTypeForOutput, box, &aps,
1019 defaultDistance, scaleFactor, r_shell, max_sol);
1021 /* write new configuration 1 to file confout */
1022 confout = ftp2fn(efSTO, NFILE, fnm);
1023 fprintf(stderr, "Writing generated configuration to %s\n", confout);
1024 const char* outputTitle = (bProt ? *top.name : "Generated by gmx solvate");
1025 write_sto_conf(confout, outputTitle, atoms, as_rvec_array(x.data()),
1026 !v.empty() ? as_rvec_array(v.data()) : nullptr, pbcTypeForOutput, box);
1028 /* print size of generated configuration */
1029 fprintf(stderr, "\nOutput configuration contains %d atoms in %d residues\n", atoms->nr, atoms->nres);
1030 update_top(atoms, firstSolventResidueIndex, box, NFILE, fnm, &aps);
1032 done_atom(atoms);
1033 sfree(atoms);
1034 output_env_done(oenv);
1036 return 0;