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,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.
44 #include "gromacs/fileio/confio.h"
45 #include "gromacs/gmxlib/network.h"
46 #include "gromacs/gmxpreprocess/calch.h"
47 #include "gromacs/gmxpreprocess/h_db.h"
48 #include "gromacs/gmxpreprocess/notset.h"
49 #include "gromacs/gmxpreprocess/pgutil.h"
50 #include "gromacs/gmxpreprocess/ter_db.h"
51 #include "gromacs/math/vec.h"
52 #include "gromacs/topology/atoms.h"
53 #include "gromacs/topology/symtab.h"
54 #include "gromacs/utility/cstringutil.h"
55 #include "gromacs/utility/exceptions.h"
56 #include "gromacs/utility/fatalerror.h"
57 #include "gromacs/utility/futil.h"
58 #include "gromacs/utility/smalloc.h"
60 #include "hackblock.h"
63 static void copy_atom(const t_atoms
*atoms1
, int a1
, t_atoms
*atoms2
, int a2
, t_symtab
*symtab
)
65 atoms2
->atom
[a2
] = atoms1
->atom
[a1
];
66 atoms2
->atomname
[a2
] = put_symtab(symtab
, *atoms1
->atomname
[a1
]);
69 static int pdbasearch_atom(const char *name
, int resind
, const t_atoms
*pdba
,
70 const char *searchtype
, bool bAllowMissing
)
74 for (i
= 0; (i
< pdba
->nr
) && (pdba
->atom
[i
].resind
!= resind
); i
++)
79 return search_atom(name
, i
, pdba
,
80 searchtype
, bAllowMissing
);
83 static void hacksearch_atom(int *ii
, int *jj
, const char *name
,
84 gmx::ArrayRef
< const std::vector
< MoleculePatch
>> patches
,
85 int resind
, const t_atoms
*pdba
)
95 for (i
= 0; (i
< pdba
->nr
) && (pdba
->atom
[i
].resind
!= resind
); i
++)
99 for (; (i
< pdba
->nr
) && (pdba
->atom
[i
].resind
== resind
) && (*ii
< 0); i
++)
102 for (const auto &patch
: patches
[i
])
104 if (patch
.nname
== name
)
115 static std::vector
<MoleculePatchDatabase
>
116 getMoleculePatchDatabases(const t_atoms
*pdba
,
117 gmx::ArrayRef
<MoleculePatchDatabase
> globalPatches
,
119 gmx::ArrayRef
<MoleculePatchDatabase
*> ntdb
,
120 gmx::ArrayRef
<MoleculePatchDatabase
*> ctdb
,
121 gmx::ArrayRef
<const int> rN
,
122 gmx::ArrayRef
<const int> rC
)
124 std::vector
<MoleculePatchDatabase
> modBlock(pdba
->nres
);
126 /* first the termini */
127 for (int i
= 0; i
< nterpairs
; i
++)
129 if (ntdb
[i
] != nullptr)
131 copyModificationBlocks(*ntdb
[i
], &modBlock
[rN
[i
]]);
133 if (ctdb
[i
] != nullptr)
135 mergeAtomAndBondModifications(*ctdb
[i
], &modBlock
[rC
[i
]]);
138 /* then the whole hdb */
139 for (int rnr
= 0; rnr
< pdba
->nres
; rnr
++)
141 auto ahptr
= search_h_db(globalPatches
, *pdba
->resinfo
[rnr
].rtp
);
142 if (ahptr
!= globalPatches
.end())
144 if (globalPatches
[rnr
].name
.empty())
146 globalPatches
[rnr
].name
= ahptr
->name
;
148 mergeAtomModifications(*ahptr
, &modBlock
[rnr
]);
154 static void expand_hackblocks_one(const MoleculePatchDatabase
&newPatch
,
155 const std::string localAtomName
, //NOLINT(performance-unnecessary-value-param)
156 std::vector
<MoleculePatch
> *globalPatches
,
159 /* we'll recursively add atoms to atoms */
161 for (auto &singlePatch
: newPatch
.hack
)
163 /* first check if we're in the N- or C-terminus, then we should ignore
164 all hacks involving atoms from resp. previous or next residue
165 (i.e. which name begins with '-' (N) or '+' (C) */
166 bool bIgnore
= false;
167 if (bN
) /* N-terminus: ignore '-' */
169 for (int k
= 0; k
< 4 && !singlePatch
.a
[k
].empty() && !bIgnore
; k
++)
171 bIgnore
= singlePatch
.a
[k
][0] == '-';
174 if (bC
) /* C-terminus: ignore '+' */
176 for (int k
= 0; k
< 4 && !singlePatch
.a
[k
].empty() && !bIgnore
; k
++)
178 bIgnore
= singlePatch
.a
[k
][0] == '+';
181 /* must be either hdb entry (tp>0) or add from tdb (oname==NULL)
182 and first control aton (AI) matches this atom or
183 delete/replace from tdb (oname!=NULL) and oname matches this atom */
186 ( ( ( singlePatch
.tp
> 0 || singlePatch
.oname
.empty() ) &&
187 singlePatch
.a
[0] == localAtomName
) ||
188 ( singlePatch
.oname
== localAtomName
) ) )
190 /* now expand all hacks for this atom */
191 for (int k
= 0; k
< singlePatch
.nr
; k
++)
193 globalPatches
->push_back(singlePatch
);
194 MoleculePatch
*patch
= &globalPatches
->back();
195 patch
->bXSet
= false;
196 /* if we're adding (oname==NULL) and don't have a new name (nname)
197 yet, build it from localAtomName */
198 if (patch
->nname
.empty())
200 if (patch
->oname
.empty())
202 patch
->nname
= localAtomName
;
203 patch
->nname
[0] = 'H';
210 fprintf(debug
, "Hack '%s' %d, replacing nname '%s' with '%s' (old name '%s')\n",
211 localAtomName
.c_str(), pos
,
212 patch
->nname
.c_str(), singlePatch
.nname
.c_str(),
213 patch
->oname
.empty() ? "" : patch
->oname
.c_str());
215 patch
->nname
= singlePatch
.nname
;
218 if (singlePatch
.tp
== 10 && k
== 2)
220 /* This is a water virtual site, not a hydrogen */
221 /* Ugly hardcoded name hack */
222 patch
->nname
.assign("M");
224 else if (singlePatch
.tp
== 11 && k
>= 2)
226 /* This is a water lone pair, not a hydrogen */
227 /* Ugly hardcoded name hack */
228 patch
->nname
.assign(gmx::formatString("LP%d", 1+k
-2));
230 else if (singlePatch
.nr
> 1)
232 /* adding more than one atom, number them */
233 patch
->nname
.append(gmx::formatString("%d", 1+k
));
237 /* add hacks to atoms we've just added */
238 if (singlePatch
.tp
> 0 || singlePatch
.oname
.empty())
240 for (int k
= 0; k
< singlePatch
.nr
; k
++)
242 expand_hackblocks_one(newPatch
,
244 globalPatches
->size() -
247 globalPatches
, bN
, bC
);
255 static void expand_hackblocks(const t_atoms
*pdba
,
256 gmx::ArrayRef
<const MoleculePatchDatabase
> hb
,
257 gmx::ArrayRef
< std::vector
< MoleculePatch
>> patches
,
259 gmx::ArrayRef
<const int> rN
,
260 gmx::ArrayRef
<const int> rC
)
262 for (int i
= 0; i
< pdba
->nr
; i
++)
265 for (int j
= 0; j
< nterpairs
&& !bN
; j
++)
267 bN
= pdba
->atom
[i
].resind
== rN
[j
];
270 for (int j
= 0; j
< nterpairs
&& !bC
; j
++)
272 bC
= pdba
->atom
[i
].resind
== rC
[j
];
275 /* add hacks to this atom */
276 expand_hackblocks_one(hb
[pdba
->atom
[i
].resind
], *pdba
->atomname
[i
],
277 &patches
[i
], bN
, bC
);
281 static int check_atoms_present(const t_atoms
*pdba
,
282 gmx::ArrayRef
< std::vector
< MoleculePatch
>> patches
)
285 for (int i
= 0; i
< pdba
->nr
; i
++)
287 int rnr
= pdba
->atom
[i
].resind
;
288 for (auto patch
= patches
[i
].begin();
289 patch
!= patches
[i
].end();
292 switch (patch
->type())
294 case MoleculePatchType::Add
:
297 /* check if the atom is already present */
298 int k
= pdbasearch_atom(patch
->nname
.c_str(), rnr
, pdba
, "check", TRUE
);
301 /* We found the added atom. */
302 patch
->bAlreadyPresent
= true;
306 patch
->bAlreadyPresent
= false;
307 /* count how many atoms we'll add */
312 case MoleculePatchType::Delete
:
318 case MoleculePatchType::Replace
:
324 GMX_THROW(gmx::InternalError("Case not handled"));
332 static void calc_all_pos(const t_atoms
*pdba
,
333 gmx::ArrayRef
<const gmx::RVec
> x
,
334 gmx::ArrayRef
< std::vector
< MoleculePatch
>> patches
,
339 rvec xa
[4]; /* control atoms for calc_h_pos */
340 rvec xh
[MAXH
]; /* hydrogen positions from calc_h_pos */
344 for (int i
= 0; i
< pdba
->nr
; i
++)
346 int rnr
= pdba
->atom
[i
].resind
;
347 for (auto patch
= patches
[i
].begin(); patch
!= patches
[i
].end();
350 GMX_RELEASE_ASSERT(patch
< patches
[i
].end(),
351 "The number of patches in the last patch can not exceed the total number of patches");
352 /* check if we're adding: */
353 if (patch
->type() == MoleculePatchType::Add
&& patch
->tp
> 0)
355 bool bFoundAll
= true;
356 for (int m
= 0; (m
< patch
->nctl
&& bFoundAll
); m
++)
358 int ia
= pdbasearch_atom(patch
->a
[m
].c_str(), rnr
, pdba
,
359 bCheckMissing
? "atom" : "check",
363 /* not found in original atoms, might still be in
364 * the patch Instructions (patches) */
365 hacksearch_atom(&ii
, &jj
, patch
->a
[m
].c_str(), patches
, rnr
, pdba
);
368 copy_rvec(patches
[ii
][jj
].newx
, xa
[m
]);
375 gmx_fatal(FARGS
, "Atom %s not found in residue %s %d"
377 " while adding hydrogens",
379 *pdba
->resinfo
[rnr
].name
,
380 pdba
->resinfo
[rnr
].nr
,
381 *pdba
->resinfo
[rnr
].rtp
);
387 copy_rvec(x
[ia
], xa
[m
]);
392 for (int m
= 0; (m
< MAXH
); m
++)
394 for (int d
= 0; d
< DIM
; d
++)
406 calc_h_pos(patch
->tp
, xa
, xh
, &l
);
407 for (int m
= 0; m
< patch
->nr
; m
++)
409 auto next
= patch
+ m
;
410 copy_rvec(xh
[m
], next
->newx
);
419 static int add_h_low(t_atoms
**initialAtoms
,
420 t_atoms
**modifiedAtoms
,
421 std::vector
<gmx::RVec
> *xptr
,
422 gmx::ArrayRef
<MoleculePatchDatabase
> globalPatches
,
425 std::vector
<MoleculePatchDatabase
*> ntdb
,
426 std::vector
<MoleculePatchDatabase
*> ctdb
,
427 gmx::ArrayRef
<int> rN
,
428 gmx::ArrayRef
<int> rC
,
432 int newi
, natoms
, nalreadypresent
;
433 std::vector
< std::vector
< MoleculePatch
>> patches
;
434 std::vector
<gmx::RVec
> xn
;
436 t_atoms
*pdba
= *initialAtoms
;
438 /* set flags for adding hydrogens (according to hdb) */
442 /* We'll have to do all the hard work */
443 /* first get all the hackblocks for each residue: */
444 std::vector
<MoleculePatchDatabase
> hb
=
445 getMoleculePatchDatabases(pdba
, globalPatches
, nterpairs
, ntdb
, ctdb
, rN
, rC
);
447 /* expand the hackblocks to atom level */
448 patches
.resize(natoms
);
449 expand_hackblocks(pdba
, hb
, patches
, nterpairs
, rN
, rC
);
452 /* Now calc the positions */
453 calc_all_pos(pdba
, *xptr
, patches
, bCheckMissing
);
455 /* we don't have to add atoms that are already present in initialAtoms,
456 so we will remove them from the patches (MoleculePatch) */
457 nadd
= check_atoms_present(pdba
, patches
);
459 /* Copy old atoms, making space for new ones */
462 srenew(*modifiedAtoms
, 1);
463 init_t_atoms(*modifiedAtoms
, natoms
+nadd
, FALSE
);
464 (*modifiedAtoms
)->nres
= pdba
->nres
;
465 srenew((*modifiedAtoms
)->resinfo
, pdba
->nres
);
466 std::copy(pdba
->resinfo
, pdba
->resinfo
+ pdba
->nres
, (*modifiedAtoms
)->resinfo
);
473 xn
.resize(natoms
+nadd
);
475 for (int i
= 0; (i
< natoms
); i
++)
477 /* check if this atom wasn't scheduled for deletion */
478 if (patches
[i
].empty() || (!patches
[i
][0].nname
.empty()) )
480 if (newi
>= natoms
+nadd
)
482 /*gmx_fatal(FARGS,"Not enough space for adding atoms");*/
484 xn
.resize(natoms
+nadd
);
485 srenew((*modifiedAtoms
)->atom
, natoms
+nadd
);
486 srenew((*modifiedAtoms
)->atomname
, natoms
+nadd
);
488 copy_atom(pdba
, i
, (*modifiedAtoms
), newi
, symtab
);
489 copy_rvec((*xptr
)[i
], xn
[newi
]);
490 /* process the hacks for this atom */
492 for (auto patch
= patches
[i
].begin();
493 patch
!= patches
[i
].end();
496 if (patch
->type() == MoleculePatchType::Add
) /* add */
499 if (newi
>= natoms
+nadd
)
501 /* gmx_fatal(FARGS,"Not enough space for adding atoms");*/
503 xn
.resize(natoms
+nadd
);
504 srenew((*modifiedAtoms
)->atom
, natoms
+nadd
);
505 srenew((*modifiedAtoms
)->atomname
, natoms
+nadd
);
507 (*modifiedAtoms
)->atom
[newi
].resind
= pdba
->atom
[i
].resind
;
509 if (!patch
->nname
.empty() &&
510 (patch
->oname
.empty() ||
511 patch
->oname
== *(*modifiedAtoms
)->atomname
[newi
]))
514 if (patch
->type() == MoleculePatchType::Add
&& patch
->bAlreadyPresent
)
516 /* This atom is already present, copy it from the input. */
518 copy_atom(pdba
, i
+nalreadypresent
, (*modifiedAtoms
), newi
, symtab
);
519 copy_rvec((*xptr
)[i
+nalreadypresent
], xn
[newi
]);
525 fprintf(debug
, "Replacing %d '%s' with (old name '%s') %s\n",
527 ((*modifiedAtoms
)->atomname
[newi
] &&
528 *(*modifiedAtoms
)->atomname
[newi
]) ?
529 *(*modifiedAtoms
)->atomname
[newi
] : "",
530 patch
->oname
.empty() ? "" : patch
->oname
.c_str(),
531 patch
->nname
.c_str());
533 (*modifiedAtoms
)->atomname
[newi
] = put_symtab(symtab
, patch
->nname
.c_str());
536 copy_rvec(patch
->newx
, xn
[newi
]);
541 fprintf(debug
, " %s %g %g", *(*modifiedAtoms
)->atomname
[newi
],
542 (*modifiedAtoms
)->atom
[newi
].m
, (*modifiedAtoms
)->atom
[newi
].q
);
547 i
+= nalreadypresent
;
550 (*modifiedAtoms
)->nr
= newi
;
553 *initialAtoms
= *modifiedAtoms
;
560 int add_h(t_atoms
**initialAtoms
,
561 t_atoms
**localAtoms
,
562 std::vector
<gmx::RVec
> *xptr
,
563 gmx::ArrayRef
<MoleculePatchDatabase
> globalPatches
,
566 const std::vector
<MoleculePatchDatabase
*> &ntdb
,
567 const std::vector
<MoleculePatchDatabase
*> &ctdb
,
568 gmx::ArrayRef
<int> rN
,
569 gmx::ArrayRef
<int> rC
,
572 int nold
, nnew
, niter
;
574 /* Here we loop to be able to add atoms to added atoms.
575 * We should not check for missing atoms here.
582 nnew
= add_h_low(initialAtoms
, localAtoms
, xptr
, globalPatches
, symtab
, nterpairs
, ntdb
, ctdb
, rN
, rC
, FALSE
);
586 gmx_fatal(FARGS
, "More than 100 iterations of add_h. Maybe you are trying to replace an added atom (this is not supported)?");
593 /* Call add_h_low once more, now only for the missing atoms check */
594 add_h_low(initialAtoms
, localAtoms
, xptr
, globalPatches
, symtab
, nterpairs
, ntdb
, ctdb
, rN
, rC
, TRUE
);