Split lines with many copyright years
[gromacs.git] / src / gromacs / gmxpreprocess / genion.cpp
blob96519dbfe4dffc873032ec02768472963f74e825
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 "genion.h"
42 #include <cctype>
43 #include <cmath>
44 #include <cstdlib>
45 #include <cstring>
47 #include <numeric>
48 #include <vector>
50 #include "gromacs/commandline/pargs.h"
51 #include "gromacs/fileio/confio.h"
52 #include "gromacs/math/units.h"
53 #include "gromacs/math/utilities.h"
54 #include "gromacs/math/vec.h"
55 #include "gromacs/mdlib/force.h"
56 #include "gromacs/pbcutil/pbc.h"
57 #include "gromacs/random/threefry.h"
58 #include "gromacs/random/uniformintdistribution.h"
59 #include "gromacs/topology/index.h"
60 #include "gromacs/topology/topology.h"
61 #include "gromacs/utility/arrayref.h"
62 #include "gromacs/utility/arraysize.h"
63 #include "gromacs/utility/basedefinitions.h"
64 #include "gromacs/utility/cstringutil.h"
65 #include "gromacs/utility/fatalerror.h"
66 #include "gromacs/utility/futil.h"
67 #include "gromacs/utility/smalloc.h"
70 /*! \brief Return whether any atoms of two groups are below minimum distance.
72 * \param[in] pbc the periodic boundary conditions
73 * \param[in] x the coordinates
74 * \param[in] smallerGroupIndices the atom indices of the first group to check
75 * \param[in] largerGroupIndices the atom indices of the second group to check
76 * \param[in] minimumDistance the minimum required distance betwenn any atom in
77 * the first and second group
78 * \returns true if any distance between an atom from group A and group B is
79 * smaller than a minimum distance.
81 static bool groupsCloserThanCutoffWithPbc(t_pbc* pbc,
82 rvec x[],
83 gmx::ArrayRef<const int> smallerGroupIndices,
84 gmx::ArrayRef<const int> largerGroupIndices,
85 real minimumDistance)
87 const real minimumDistance2 = minimumDistance * minimumDistance;
88 for (int aIndex : largerGroupIndices)
90 for (int bIndex : smallerGroupIndices)
92 rvec dx;
93 pbc_dx(pbc, x[aIndex], x[bIndex], dx);
94 if (norm2(dx) < minimumDistance2)
96 return true;
100 return false;
103 /*! \brief Calculate the solvent molecule atom indices from molecule number.
105 * \note the solvent group index has to be continuous
107 * \param[in] solventMoleculeNumber the number of the solvent molecule
108 * \param[in] numberAtomsPerSolventMolecule how many atoms each solvent molecule contains
109 * \param[in] solventGroupIndex continuous index of solvent atoms
111 * \returns atom indices of the specified solvent molecule
113 static std::vector<int> solventMoleculeIndices(int solventMoleculeNumber,
114 int numberAtomsPerSolventMolecule,
115 gmx::ArrayRef<const int> solventGroupIndex)
117 std::vector<int> indices(numberAtomsPerSolventMolecule);
118 for (int solventAtomNumber = 0; solventAtomNumber < numberAtomsPerSolventMolecule; ++solventAtomNumber)
120 indices[solventAtomNumber] =
121 solventGroupIndex[numberAtomsPerSolventMolecule * solventMoleculeNumber + solventAtomNumber];
123 return indices;
126 static void insert_ion(int nsa,
127 std::vector<int>* solventMoleculesForReplacement,
128 int repl[],
129 gmx::ArrayRef<const int> index,
130 rvec x[],
131 t_pbc* pbc,
132 int sign,
133 int q,
134 const char* ionname,
135 t_atoms* atoms,
136 real rmin,
137 std::vector<int>* notSolventGroup)
139 std::vector<int> solventMoleculeAtomsToBeReplaced =
140 solventMoleculeIndices(solventMoleculesForReplacement->back(), nsa, index);
142 if (rmin > 0.0)
144 // check for proximity to non-solvent
145 while (groupsCloserThanCutoffWithPbc(pbc, x, solventMoleculeAtomsToBeReplaced, *notSolventGroup, rmin)
146 && !solventMoleculesForReplacement->empty())
148 solventMoleculesForReplacement->pop_back();
149 solventMoleculeAtomsToBeReplaced =
150 solventMoleculeIndices(solventMoleculesForReplacement->back(), nsa, index);
154 if (solventMoleculesForReplacement->empty())
156 gmx_fatal(FARGS, "No more replaceable solvent!");
159 fprintf(stderr, "Replacing solvent molecule %d (atom %d) with %s\n",
160 solventMoleculesForReplacement->back(), solventMoleculeAtomsToBeReplaced[0], ionname);
162 /* Replace solvent molecule charges with ion charge */
163 notSolventGroup->push_back(solventMoleculeAtomsToBeReplaced[0]);
164 repl[solventMoleculesForReplacement->back()] = sign;
166 // The first solvent molecule atom is replaced with an ion and the respective
167 // charge while the rest of the solvent molecule atoms is set to 0 charge.
168 atoms->atom[solventMoleculeAtomsToBeReplaced.front()].q = q;
169 for (auto replacedMoleculeAtom = solventMoleculeAtomsToBeReplaced.begin() + 1;
170 replacedMoleculeAtom != solventMoleculeAtomsToBeReplaced.end(); ++replacedMoleculeAtom)
172 atoms->atom[*replacedMoleculeAtom].q = 0;
174 solventMoleculesForReplacement->pop_back();
178 static char* aname(const char* mname)
180 char* str;
181 int i;
183 str = gmx_strdup(mname);
184 i = std::strlen(str) - 1;
185 while (i > 1 && (std::isdigit(str[i]) || (str[i] == '+') || (str[i] == '-')))
187 str[i] = '\0';
188 i--;
191 return str;
194 static void sort_ions(int nsa,
195 int nw,
196 const int repl[],
197 gmx::ArrayRef<const int> index,
198 t_atoms* atoms,
199 rvec x[],
200 char** pptr,
201 char** nptr,
202 char** paptr,
203 char** naptr)
205 int i, j, k, r, np, nn, starta, startr, npi, nni;
206 rvec* xt;
208 snew(xt, atoms->nr);
210 /* Put all the solvent in front and count the added ions */
211 np = 0;
212 nn = 0;
213 j = index[0];
214 for (i = 0; i < nw; i++)
216 r = repl[i];
217 if (r == 0)
219 for (k = 0; k < nsa; k++)
221 copy_rvec(x[index[nsa * i + k]], xt[j++]);
224 else if (r > 0)
226 np++;
228 else if (r < 0)
230 nn++;
234 if (np + nn > 0)
236 /* Put the positive and negative ions at the end */
237 starta = index[nsa * (nw - np - nn)];
238 startr = atoms->atom[starta].resind;
240 npi = 0;
241 nni = 0;
242 for (i = 0; i < nw; i++)
244 r = repl[i];
245 if (r > 0)
247 j = starta + npi;
248 k = startr + npi;
249 copy_rvec(x[index[nsa * i]], xt[j]);
250 atoms->atomname[j] = paptr;
251 atoms->atom[j].resind = k;
252 atoms->resinfo[k].name = pptr;
253 npi++;
255 else if (r < 0)
257 j = starta + np + nni;
258 k = startr + np + nni;
259 copy_rvec(x[index[nsa * i]], xt[j]);
260 atoms->atomname[j] = naptr;
261 atoms->atom[j].resind = k;
262 atoms->resinfo[k].name = nptr;
263 nni++;
266 for (i = index[nsa * nw - 1] + 1; i < atoms->nr; i++)
268 j = i - (nsa - 1) * (np + nn);
269 atoms->atomname[j] = atoms->atomname[i];
270 atoms->atom[j] = atoms->atom[i];
271 copy_rvec(x[i], xt[j]);
273 atoms->nr -= (nsa - 1) * (np + nn);
275 /* Copy the new positions back */
276 for (i = index[0]; i < atoms->nr; i++)
278 copy_rvec(xt[i], x[i]);
280 sfree(xt);
284 static void update_topol(const char* topinout, int p_num, int n_num, const char* p_name, const char* n_name, char* grpname)
286 FILE * fpin, *fpout;
287 char buf[STRLEN], buf2[STRLEN], *temp, **mol_line = nullptr;
288 int line, i, nmol_line, sol_line, nsol_last;
289 gmx_bool bMolecules;
290 char temporary_filename[STRLEN];
292 printf("\nProcessing topology\n");
293 fpin = gmx_ffopen(topinout, "r");
294 std::strncpy(temporary_filename, "temp.topXXXXXX", STRLEN);
295 fpout = gmx_fopen_temporary(temporary_filename);
297 line = 0;
298 bMolecules = FALSE;
299 nmol_line = 0;
300 sol_line = -1;
301 nsol_last = -1;
302 while (fgets(buf, STRLEN, fpin))
304 line++;
305 std::strcpy(buf2, buf);
306 if ((temp = std::strchr(buf2, '\n')) != nullptr)
308 temp[0] = '\0';
310 ltrim(buf2);
311 if (buf2[0] == '[')
313 buf2[0] = ' ';
314 if ((temp = std::strchr(buf2, '\n')) != nullptr)
316 temp[0] = '\0';
318 rtrim(buf2);
319 if (buf2[std::strlen(buf2) - 1] == ']')
321 buf2[std::strlen(buf2) - 1] = '\0';
322 ltrim(buf2);
323 rtrim(buf2);
324 bMolecules = (gmx_strcasecmp(buf2, "molecules") == 0);
326 fprintf(fpout, "%s", buf);
328 else if (!bMolecules)
330 fprintf(fpout, "%s", buf);
332 else
334 /* Check if this is a line with solvent molecules */
335 sscanf(buf, "%s", buf2);
336 if (gmx_strcasecmp(buf2, grpname) == 0)
338 sol_line = nmol_line;
339 sscanf(buf, "%*s %d", &nsol_last);
341 /* Store this molecules section line */
342 srenew(mol_line, nmol_line + 1);
343 mol_line[nmol_line] = gmx_strdup(buf);
344 nmol_line++;
347 gmx_ffclose(fpin);
349 if (sol_line == -1)
351 gmx_ffclose(fpout);
352 gmx_fatal(FARGS,
353 "No line with moleculetype '%s' found the [ molecules ] section of file '%s'",
354 grpname, topinout);
356 if (nsol_last < p_num + n_num)
358 gmx_ffclose(fpout);
359 gmx_fatal(FARGS,
360 "The last entry for moleculetype '%s' in the [ molecules ] section of file '%s' "
361 "has less solvent molecules (%d) than were replaced (%d)",
362 grpname, topinout, nsol_last, p_num + n_num);
365 /* Print all the molecule entries */
366 for (i = 0; i < nmol_line; i++)
368 if (i != sol_line)
370 fprintf(fpout, "%s", mol_line[i]);
372 else
374 printf("Replacing %d solute molecules in topology file (%s) "
375 " by %d %s and %d %s ions.\n",
376 p_num + n_num, topinout, p_num, p_name, n_num, n_name);
377 nsol_last -= p_num + n_num;
378 if (nsol_last > 0)
380 fprintf(fpout, "%-10s %d\n", grpname, nsol_last);
382 if (p_num > 0)
384 fprintf(fpout, "%-15s %d\n", p_name, p_num);
386 if (n_num > 0)
388 fprintf(fpout, "%-15s %d\n", n_name, n_num);
392 gmx_ffclose(fpout);
393 make_backup(topinout);
394 gmx_file_rename(temporary_filename, topinout);
397 /*! \brief Return all atom indices that do not belong to an index group.
398 * \param[in] nrAtoms the total number of atoms
399 * \param[in] indexGroup the index group to be inverted. Note that a copy is
400 * made in order to sort the group indices
401 * \returns the inverted group indices
403 static std::vector<int> invertIndexGroup(int nrAtoms, std::vector<int> indexGroup)
405 // Add the indices -1 and nrAtoms, so that all intervals 0 .. firstofIndexGroup
406 // as well as lastOfIndexGroup .. nrAtoms are covered for the inverted indexgroup
407 indexGroup.push_back(-1);
408 indexGroup.push_back(nrAtoms);
409 std::sort(indexGroup.begin(), indexGroup.end());
411 // construct the inverted index group by adding all indicies between two
412 // indices of indexGroup
413 std::vector<int> invertedGroup;
414 for (auto indexGroupIt = std::begin(indexGroup); indexGroupIt != std::end(indexGroup) - 1; ++indexGroupIt)
416 const int firstToAddToInvertedGroup = *indexGroupIt + 1;
417 const int numIndicesToAdd = *(indexGroupIt + 1) - firstToAddToInvertedGroup;
418 if (numIndicesToAdd > 0)
420 invertedGroup.resize(invertedGroup.size() + numIndicesToAdd);
421 std::iota(std::end(invertedGroup) - numIndicesToAdd, std::end(invertedGroup),
422 firstToAddToInvertedGroup);
426 return invertedGroup;
429 int gmx_genion(int argc, char* argv[])
431 const char* desc[] = {
432 "[THISMODULE] randomly replaces solvent molecules with monoatomic ions.",
433 "The group of solvent molecules should be continuous and all molecules",
434 "should have the same number of atoms.",
435 "The user should add the ion molecules to the topology file or use",
436 "the [TT]-p[tt] option to automatically modify the topology.[PAR]",
437 "The ion molecule type, residue and atom names in all force fields",
438 "are the capitalized element names without sign. This molecule name",
439 "should be given with [TT]-pname[tt] or [TT]-nname[tt], and the",
440 "[TT][molecules][tt] section of your topology updated accordingly,",
441 "either by hand or with [TT]-p[tt]. Do not use an atom name instead!",
442 "[PAR]Ions which can have multiple charge states get the multiplicity",
443 "added, without sign, for the uncommon states only.[PAR]",
444 "For larger ions, e.g. sulfate we recommended using [gmx-insert-molecules]."
446 const char* bugs[] = {
447 "If you specify a salt concentration existing ions are not taken into "
448 "account. In effect you therefore specify the amount of salt to be added.",
450 int p_num = 0, n_num = 0, p_q = 1, n_q = -1;
451 const char *p_name = "NA", *n_name = "CL";
452 real rmin = 0.6, conc = 0;
453 int seed = 0;
454 gmx_bool bNeutral = FALSE;
455 t_pargs pa[] = {
456 { "-np", FALSE, etINT, { &p_num }, "Number of positive ions" },
457 { "-pname", FALSE, etSTR, { &p_name }, "Name of the positive ion" },
458 { "-pq", FALSE, etINT, { &p_q }, "Charge of the positive ion" },
459 { "-nn", FALSE, etINT, { &n_num }, "Number of negative ions" },
460 { "-nname", FALSE, etSTR, { &n_name }, "Name of the negative ion" },
461 { "-nq", FALSE, etINT, { &n_q }, "Charge of the negative ion" },
462 { "-rmin", FALSE, etREAL, { &rmin }, "Minimum distance between ions and non-solvent" },
463 { "-seed", FALSE, etINT, { &seed }, "Seed for random number generator (0 means generate)" },
464 { "-conc",
465 FALSE,
466 etREAL,
467 { &conc },
468 "Specify salt concentration (mol/liter). This will add sufficient ions to reach up to "
469 "the specified concentration as computed from the volume of the cell in the input "
470 "[REF].tpr[ref] file. Overrides the [TT]-np[tt] and [TT]-nn[tt] options." },
471 { "-neutral",
472 FALSE,
473 etBOOL,
474 { &bNeutral },
475 "This option will add enough ions to neutralize the system. These ions are added on top "
476 "of those specified with [TT]-np[tt]/[TT]-nn[tt] or [TT]-conc[tt]. " }
478 t_topology top;
479 rvec* x;
480 real vol;
481 matrix box;
482 t_atoms atoms;
483 t_pbc pbc;
484 int * repl, ePBC;
485 int nw, nsa, nsalt, iqtot;
486 gmx_output_env_t* oenv = nullptr;
487 t_filenm fnm[] = { { efTPR, nullptr, nullptr, ffREAD },
488 { efNDX, nullptr, nullptr, ffOPTRD },
489 { efSTO, "-o", nullptr, ffWRITE },
490 { efTOP, "-p", "topol", ffOPTRW } };
491 #define NFILE asize(fnm)
493 if (!parse_common_args(&argc, argv, 0, NFILE, fnm, asize(pa), pa, asize(desc), desc,
494 asize(bugs), bugs, &oenv))
496 if (oenv != nullptr)
498 output_env_done(oenv);
500 return 0;
503 /* Check input for something sensible */
504 if ((p_num < 0) || (n_num < 0))
506 gmx_fatal(FARGS, "Negative number of ions to add?");
509 if (conc > 0 && (p_num > 0 || n_num > 0))
511 fprintf(stderr, "WARNING: -conc specified, overriding -nn and -np.\n");
514 /* Read atom positions and charges */
515 read_tps_conf(ftp2fn(efTPR, NFILE, fnm), &top, &ePBC, &x, nullptr, box, FALSE);
516 atoms = top.atoms;
518 /* Compute total charge */
519 double qtot = 0;
520 for (int i = 0; (i < atoms.nr); i++)
522 qtot += atoms.atom[i].q;
524 iqtot = gmx::roundToInt(qtot);
527 if (conc > 0)
529 /* Compute number of ions to be added */
530 vol = det(box);
531 nsalt = gmx::roundToInt(conc * vol * AVOGADRO / 1e24);
532 p_num = abs(nsalt * n_q);
533 n_num = abs(nsalt * p_q);
535 if (bNeutral)
537 int qdelta = p_num * p_q + n_num * n_q + iqtot;
539 /* Check if the system is neutralizable
540 * is (qdelta == p_q*p_num + n_q*n_num) solvable for p_num and n_num? */
541 int gcd = gmx_greatest_common_divisor(n_q, p_q);
542 if ((qdelta % gcd) != 0)
544 gmx_fatal(FARGS,
545 "Can't neutralize this system using -nq %d and"
546 " -pq %d.\n",
547 n_q, p_q);
550 while (qdelta != 0)
552 while (qdelta < 0)
554 p_num++;
555 qdelta += p_q;
557 while (qdelta > 0)
559 n_num++;
560 qdelta += n_q;
565 char* pptr = gmx_strdup(p_name);
566 char* paptr = aname(p_name);
567 char* nptr = gmx_strdup(n_name);
568 char* naptr = aname(n_name);
570 if ((p_num == 0) && (n_num == 0))
572 fprintf(stderr, "No ions to add, will just copy input configuration.\n");
574 else
576 char* grpname = nullptr;
578 printf("Will try to add %d %s ions and %d %s ions.\n", p_num, p_name, n_num, n_name);
579 printf("Select a continuous group of solvent molecules\n");
581 std::vector<int> solventGroup;
583 int* index = nullptr;
584 int nwa;
585 get_index(&atoms, ftp2fn_null(efNDX, NFILE, fnm), 1, &nwa, &index, &grpname);
586 solventGroup.assign(index, index + nwa);
587 sfree(index);
590 for (gmx::index i = 1; i < gmx::ssize(solventGroup); i++)
592 if (solventGroup[i] != solventGroup[i - 1] + 1)
594 gmx_fatal(FARGS,
595 "The solvent group %s is not continuous: "
596 "index[%d]=%d, index[%d]=%d",
597 grpname, int(i), solventGroup[i - 1] + 1, int(i + 1), solventGroup[i] + 1);
600 nsa = 1;
601 while ((nsa < gmx::ssize(solventGroup))
602 && (atoms.atom[solventGroup[nsa]].resind == atoms.atom[solventGroup[nsa - 1]].resind))
604 nsa++;
606 if (solventGroup.size() % nsa != 0)
608 gmx_fatal(FARGS, "Your solvent group size (%td) is not a multiple of %d",
609 gmx::ssize(solventGroup), nsa);
611 nw = solventGroup.size() / nsa;
612 fprintf(stderr, "Number of (%d-atomic) solvent molecules: %d\n", nsa, nw);
613 if (p_num + n_num > nw)
615 gmx_fatal(FARGS, "Not enough solvent for adding ions");
618 if (opt2bSet("-p", NFILE, fnm))
620 update_topol(opt2fn("-p", NFILE, fnm), p_num, n_num, p_name, n_name, grpname);
623 snew(repl, nw);
624 set_pbc(&pbc, ePBC, box);
627 if (seed == 0)
629 // For now we make do with 32 bits to avoid changing the user input to 64 bit hex
630 seed = static_cast<int>(gmx::makeRandomSeed());
632 fprintf(stderr, "Using random seed %d.\n", seed);
635 std::vector<int> notSolventGroup = invertIndexGroup(atoms.nr, solventGroup);
637 std::vector<int> solventMoleculesForReplacement(nw);
638 std::iota(std::begin(solventMoleculesForReplacement), std::end(solventMoleculesForReplacement), 0);
640 // Randomly shuffle the solvent molecules that shall be replaced by ions
641 // then pick molecules from the back of the list as replacement candidates
642 gmx::DefaultRandomEngine rng(seed);
643 std::shuffle(std::begin(solventMoleculesForReplacement),
644 std::end(solventMoleculesForReplacement), rng);
646 /* Now loop over the ions that have to be placed */
647 while (p_num-- > 0)
649 insert_ion(nsa, &solventMoleculesForReplacement, repl, solventGroup, x, &pbc, 1, p_q,
650 p_name, &atoms, rmin, &notSolventGroup);
652 while (n_num-- > 0)
654 insert_ion(nsa, &solventMoleculesForReplacement, repl, solventGroup, x, &pbc, -1, n_q,
655 n_name, &atoms, rmin, &notSolventGroup);
657 fprintf(stderr, "\n");
659 if (nw)
661 sort_ions(nsa, nw, repl, solventGroup, &atoms, x, &pptr, &nptr, &paptr, &naptr);
664 sfree(repl);
665 sfree(grpname);
668 sfree(atoms.pdbinfo);
669 atoms.pdbinfo = nullptr;
670 write_sto_conf(ftp2fn(efSTO, NFILE, fnm), *top.name, &atoms, x, nullptr, ePBC, box);
672 sfree(pptr);
673 sfree(paptr);
674 sfree(nptr);
675 sfree(naptr);
677 sfree(x);
678 output_env_done(oenv);
679 done_top(&top);
681 return 0;