-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathamberprm.cpp
More file actions
5452 lines (4513 loc) · 184 KB
/
amberprm.cpp
File metadata and controls
5452 lines (4513 loc) · 184 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/********************************************\
*
* Sire - Molecular Simulation Framework
*
* Copyright (C) 2017 Christopher Woods
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* For full details of the license please see the COPYING file
* that should have come with this distribution.
*
* You can contact the authors at https://sire.openbiosim.org
*
\*********************************************/
#include <QDateTime>
#include <QElapsedTimer>
#include <QFile>
#include <QHash>
#include <QRegularExpression>
#include <QSet>
#include <QTextStream>
#include <tuple>
#include "SireIO/amberformat.h"
#include "SireIO/amberprm.h"
#include "SireIO/amberrst7.h"
#include "SireMol/element.h"
#include "SireMol/atomcharges.h"
#include "SireMol/atomcoords.h"
#include "SireMol/atomelements.h"
#include "SireMol/atommasses.h"
#include "SireMol/atomvelocities.h"
#include "SireMol/connectivity.h"
#include "SireMol/mgname.h"
#include "SireMol/selector.hpp"
#include "SireMol/atomcutting.h"
#include "SireMol/atomeditor.h"
#include "SireMol/atomidx.h"
#include "SireMol/cgatomidx.h"
#include "SireMol/molecule.h"
#include "SireMol/moleditor.h"
#include "SireMol/molidx.h"
#include "SireMol/reseditor.h"
#include "SireMol/residuecutting.h"
#include "SireMol/select.h"
#include "SireMol/trajectory.h"
#include "SireMol/amberparameters.h"
#include "SireCAS/trigfuncs.h"
#include "SireMM/amberparams.h"
#include "SireMM/atomljs.h"
#include "SireMM/cljnbpairs.h"
#include "SireMM/internalff.h"
#include "SireMM/ljparameter.h"
#include "SireMM/lj1264parameter.h"
#include "SireVol/cartesian.h"
#include "SireVol/periodicbox.h"
#include "SireVol/triclinicbox.h"
#include "SireMaths/maths.h"
#include "SireUnits/units.h"
#include "SireBase/findexe.h"
#include "SireBase/tempdir.h"
#include "SireBase/progressbar.h"
#include "SireBase/propertylist.h"
#include "SireIO/errors.h"
#include "SireMove/flexibility.h"
#include "SireMove/internalmove.h"
#include "SireBase/parallel.h"
#include "SireBase/stringproperty.h"
#include "SireBase/unittest.h"
#include "SireError/errors.h"
#include "SireSystem/system.h"
#include "SireStream/datastream.h"
#include "SireStream/shareddatastream.h"
#include <QDebug>
using namespace SireIO;
using namespace SireIO::detail;
using namespace SireMM;
using namespace SireMol;
using namespace SireMove;
using namespace SireMaths;
using namespace SireCAS;
using namespace SireSystem;
using namespace SireVol;
using namespace SireUnits;
using namespace SireStream;
using namespace SireBase;
static const RegisterMetaType<AmberPrm> r_parm;
const RegisterParser<AmberPrm> register_amberparm;
/** Serialise to a binary datastream */
QDataStream &operator<<(QDataStream &ds, const AmberPrm &parm)
{
writeHeader(ds, r_parm, 3);
SharedDataStream sds(ds);
sds << parm.flag_to_line << parm.int_data << parm.float_data << parm.string_data << parm.ffield
<< parm.lj_exceptions << parm.warns << parm.comb_rules
<< static_cast<const MoleculeParser &>(parm);
return ds;
}
/** Function called to rebuild the excluded atom lists */
void AmberPrm::rebuildExcludedAtoms()
{
const int nmols = this->nMolecules();
const int natoms = this->nAtoms();
if (nmols <= 0 or natoms <= 0)
return;
// number of excluded atoms per atom
const auto nnb_per_atom = this->intData("NUMBER_EXCLUDED_ATOMS");
// list of all excluded atoms
const auto inb = this->intData("EXCLUDED_ATOMS_LIST");
if (nnb_per_atom.count() != natoms)
{
throw SireIO::parse_error(QObject::tr("The number of excluded atoms per atom array is not equal to the "
"number of atoms! Should be %1, but is %2!")
.arg(natoms)
.arg(nnb_per_atom.count()),
CODELOC);
}
// get the number of atoms in each molecule
if (molnum_to_atomnums.count() != nmols + 1) // molnum_to_atomnums has a fake idx 0 value
{
throw SireIO::parse_error(QObject::tr("Disagreement of the number of molecules. %1 versus %2")
.arg(molnum_to_atomnums.count() - 1)
.arg(nmols),
CODELOC);
}
const auto nnb_per_atom_data = nnb_per_atom.constData();
const auto inb_data = inb.constData();
excl_atoms = QVector<QVector<QVector<int>>>(nmols);
auto excl_atoms_data = excl_atoms.data();
int nnbidx = 0;
// we need to loop through the excluded atoms in atomnum order, not molidx order.
// This is because a single molecule can be spread over several non-contiguous blocks
// of atom numbers!
QVector<QPair<int, int>> atomnum_to_molnum_atomidx(natoms + 1);
auto atomnum_to_molnum_atomidx_data = atomnum_to_molnum_atomidx.data();
const auto &const_molnum_to_atomnums = molnum_to_atomnums;
// get the molecule number and index of the atom in that molecule for every
// atom identified by its atom number
for (int molnum = 1; molnum <= nmols; ++molnum)
{
const auto &atomnums_in_mol = const_molnum_to_atomnums.at(molnum);
const auto atomnums_in_mol_data = atomnums_in_mol.constData();
for (int iat = 0; iat < atomnums_in_mol.count(); ++iat)
{
const int atomnum = atomnums_in_mol_data[iat];
atomnum_to_molnum_atomidx_data[atomnum] = QPair<int, int>(molnum, iat);
}
excl_atoms_data[molnum - 1] = QVector<QVector<int>>(atomnums_in_mol.count());
}
// now loop over the atoms in sequence, looking up their excluded atoms (as these
// are arranged in atomnum sequence. Extract the excluded atoms for each atom,
// placing the result into excl_atoms, which is indexed by molnum then atomidx in
// molecule
for (int atomnum = 1; atomnum <= natoms; ++atomnum)
{
int molnum = atomnum_to_molnum_atomidx_data[atomnum].first;
int atomidx = atomnum_to_molnum_atomidx_data[atomnum].second;
const int nnb = nnb_per_atom_data[atomnum - 1]; // atomnum is 1-indexed
QVector<int> ex(nnb);
auto ex_data = ex.data();
for (int k = 0; k < nnb; ++k)
{
if (nnbidx >= inb.count())
{
throw SireIO::parse_error(
QObject::tr("Disagreement of the number of excluded atom nb terms! %1 versus %2")
.arg(nnbidx)
.arg(inb.count()),
CODELOC);
}
ex_data[k] = inb_data[nnbidx];
nnbidx += 1;
}
excl_atoms_data[molnum - 1][atomidx] = ex; // molnum is 1-indexed
}
}
/** Internal function used to return the start index for the bonds of each
molecule, and the number of bonds in each molecule */
QVector<QVector<int>> indexBonds(const QVector<qint64> &bonds, const QVector<int> &atom_to_mol, const int nmols)
{
QVector<QVector<int>> molbonds(nmols);
auto molbonds_data = molbonds.data();
const auto bonds_data = bonds.constData();
const auto atom_to_mol_data = atom_to_mol.constData();
for (int i = 0; i < bonds.count(); i += 3)
{
// format is atom0-atom1-parameter
int mol0 = atom_to_mol_data[bonds_data[i] / 3]; // divide by three as index is
int mol1 = atom_to_mol_data[bonds_data[i + 1] / 3]; // into the coordinate array
if (mol0 != mol1)
throw SireIO::parse_error(QObject::tr("Something went wrong as there is a bond between two different "
"molecules! (%1, %2)")
.arg(mol0)
.arg(mol1),
CODELOC);
molbonds_data[mol0].append(i);
}
return molbonds;
}
/** Internal function used to return the start index for the angles of each
molecule, and the number of angles in each molecule */
QVector<QVector<int>> indexAngles(const QVector<qint64> &angs, const QVector<int> &atom_to_mol, const int nmols)
{
QVector<QVector<int>> molangs(nmols);
auto molangs_data = molangs.data();
const auto angs_data = angs.constData();
const auto atom_to_mol_data = atom_to_mol.constData();
for (int i = 0; i < angs.count(); i += 4)
{
// format is atom0-atom1-atom2-parameter
int mol0 = atom_to_mol_data[angs_data[i] / 3]; // divide by three as index is
int mol1 = atom_to_mol_data[angs_data[i + 1] / 3]; // into the coordinate array
int mol2 = atom_to_mol_data[angs_data[i + 2] / 3];
if (mol0 != mol1 or mol0 != mol2)
throw SireIO::parse_error(
QObject::tr("Something went wrong as there is a angle between more than one different "
"molecule! (%1, %2, %3)")
.arg(mol0)
.arg(mol1)
.arg(mol2),
CODELOC);
molangs_data[mol0].append(i);
}
return molangs;
}
/** Internal function used to return the start index for the dihedrals of each
molecule, and the number of dihedrals in each molecule */
QVector<QVector<int>> indexDihedrals(const QVector<qint64> &dihs, const QVector<int> &atom_to_mol, const int nmols)
{
QVector<QVector<int>> moldihs(nmols);
auto moldihs_data = moldihs.data();
const auto dihs_data = dihs.constData();
const auto atom_to_mol_data = atom_to_mol.constData();
for (int i = 0; i < dihs.count(); i += 5)
{
// format is atom0-atom1-atom2-atom3-parameter
int mol0 = atom_to_mol_data[dihs_data[i] / 3]; // divide by three as index is
int mol1 = atom_to_mol_data[dihs_data[i + 1] / 3]; // into the coordinate array
int mol2 = atom_to_mol_data[std::abs(dihs_data[i + 2] / 3)];
int mol3 = atom_to_mol_data[std::abs(dihs_data[i + 3] / 3)];
if (mol0 != mol1 or mol0 != mol2 or mol0 != mol3)
throw SireIO::parse_error(
QObject::tr("Something went wrong as there is a dihedral between more than one different "
"molecule! (%1, %2, %3, %4)")
.arg(mol0)
.arg(mol1)
.arg(mol2)
.arg(mol3),
CODELOC);
moldihs_data[mol0].append(i);
}
return moldihs;
}
/** Internal function used to return the start index for the cmaps of each
molecule, and the number of cmaps in each molecule */
QVector<QVector<int>> indexCMAPs(const QVector<qint64> &cmaps, const QVector<int> &atom_to_mol, const int nmols)
{
QVector<QVector<int>> molcmaps(nmols);
auto molcmaps_data = molcmaps.data();
const auto cmaps_data = cmaps.constData();
const auto atom_to_mol_data = atom_to_mol.constData();
for (int i = 0; i < cmaps.count(); i += 6)
{
// format is atom0-atom1-atom2-atom3-atom4-parameter
// CMAP indices are plain 1-based atom numbers (unlike bonds/angles/dihedrals
// which are 3×(atom_0based)). Subtract 1 to convert to the 0-based index
// used by atom_to_mol.
int mol0 = atom_to_mol_data[cmaps_data[i] - 1];
int mol1 = atom_to_mol_data[cmaps_data[i + 1] - 1];
int mol2 = atom_to_mol_data[cmaps_data[i + 2] - 1];
int mol3 = atom_to_mol_data[cmaps_data[i + 3] - 1];
int mol4 = atom_to_mol_data[cmaps_data[i + 4] - 1];
if (mol0 != mol1 or mol0 != mol2 or mol0 != mol3 or mol0 != mol4)
throw SireIO::parse_error(
QObject::tr("Something went wrong as there is a cmap between more than one different "
"molecule! (%1, %2, %3, %4, %5)")
.arg(mol0)
.arg(mol1)
.arg(mol2)
.arg(mol3)
.arg(mol4),
CODELOC);
molcmaps_data[mol0].append(i);
}
return molcmaps;
}
/** Function called to rebuild the Bond Angle and Dihedral indicies */
void AmberPrm::rebuildBADIndicies()
{
const int nmols = this->nMolecules();
const int natoms = this->nAtoms();
if (nmols <= 0 or natoms <= 0)
return;
// get the lookup table to go from atom index to molecule index
const auto atom_to_mol = this->getAtomIndexToMolIndex();
// now index the connectivity - find the start index and number of bonds/angles/dihedrals
// for each molecule
if (usesParallel())
{
tbb::parallel_invoke(
[&]()
{ bonds_inc_h = indexBonds(this->intData("BONDS_INC_HYDROGEN"), atom_to_mol, nmols); },
[&]()
{ bonds_exc_h = indexBonds(this->intData("BONDS_WITHOUT_HYDROGEN"), atom_to_mol, nmols); },
[&]()
{ angs_inc_h = indexAngles(this->intData("ANGLES_INC_HYDROGEN"), atom_to_mol, nmols); },
[&]()
{ angs_exc_h = indexAngles(this->intData("ANGLES_WITHOUT_HYDROGEN"), atom_to_mol, nmols); },
[&]()
{ dihs_inc_h = indexDihedrals(this->intData("DIHEDRALS_INC_HYDROGEN"), atom_to_mol, nmols); },
[&]()
{ dihs_exc_h = indexDihedrals(this->intData("DIHEDRALS_WITHOUT_HYDROGEN"), atom_to_mol, nmols); },
[&]()
{ cmap_idxs = indexCMAPs(this->intData("CMAP_INDEX") + this->intData("CHARMM_CMAP_INDEX"),
atom_to_mol, nmols); });
}
else
{
bonds_inc_h = indexBonds(this->intData("BONDS_INC_HYDROGEN"), atom_to_mol, nmols);
bonds_exc_h = indexBonds(this->intData("BONDS_WITHOUT_HYDROGEN"), atom_to_mol, nmols);
angs_inc_h = indexAngles(this->intData("ANGLES_INC_HYDROGEN"), atom_to_mol, nmols);
angs_exc_h = indexAngles(this->intData("ANGLES_WITHOUT_HYDROGEN"), atom_to_mol, nmols);
dihs_inc_h = indexDihedrals(this->intData("DIHEDRALS_INC_HYDROGEN"), atom_to_mol, nmols);
dihs_exc_h = indexDihedrals(this->intData("DIHEDRALS_WITHOUT_HYDROGEN"), atom_to_mol, nmols);
cmap_idxs = indexCMAPs(this->intData("CMAP_INDEX") + this->intData("CHARMM_CMAP_INDEX"), atom_to_mol, nmols);
}
}
/** Function called to rebuild all of the LJ parameters */
void AmberPrm::rebuildLJParameters()
{
lj_data.clear();
if (pointers.count() < 20)
return;
const auto pointers_data = pointers.constData();
const int ntypes = pointers_data[1]; // number of distinct atom types
if (ntypes <= 0)
return;
const int nphb = pointers_data[19]; // number of distinct 10-12 hydrogen bond pair types
lj_data = QVector<LJParameter>(ntypes);
auto lj_data_array = lj_data.data();
lj_exceptions.clear();
const auto acoeffs = float_data.value("LENNARD_JONES_ACOEF");
const auto bcoeffs = float_data.value("LENNARD_JONES_BCOEF");
const auto ccoeffs = float_data.value("LENNARD_JONES_CCOEF");
const auto hbond_acoeffs = float_data.value("HBOND_ACOEF");
const auto hbond_bcoeffs = float_data.value("HBOND_BCOEF");
const auto nb_parm_index = int_data.value("NONBONDED_PARM_INDEX");
if (acoeffs.count() != bcoeffs.count() or acoeffs.count() != (ntypes * (ntypes + 1)) / 2)
{
throw SireIO::parse_error(QObject::tr("Incorrect number of LJ coefficients for the number of specified "
"atom types! Should be %1 for %2 types, but actually have "
"%3 LJ A-coefficients, and %4 LJ B-coefficients")
.arg((ntypes * (ntypes + 1)) / 2)
.arg(ntypes)
.arg(acoeffs.count())
.arg(bcoeffs.count()),
CODELOC);
}
if (nb_parm_index.count() != ntypes * ntypes)
{
throw SireIO::parse_error(QObject::tr("Incorrect number of non-bonded parameter indicies. There should "
"be %1 indicies for %2 types, but actually have %3.")
.arg(ntypes * ntypes)
.arg(ntypes)
.arg(nb_parm_index.count()),
CODELOC);
}
if (hbond_acoeffs.count() != nphb or hbond_bcoeffs.count() != nphb)
{
throw SireIO::parse_error(QObject::tr("Incorrect number of HBond parameters. There should be "
"%1 such parameters, but the number of HBond A coefficients is "
"%2, and the number of B coefficients is %3.")
.arg(nphb)
.arg(hbond_acoeffs.count())
.arg(hbond_bcoeffs.count()),
CODELOC);
}
const auto acoeffs_data = acoeffs.constData();
const auto bcoeffs_data = bcoeffs.constData();
const auto ccoeffs_data = ccoeffs.constData();
const auto hbond_acoeffs_data = hbond_acoeffs.constData();
const auto hbond_bcoeffs_data = hbond_bcoeffs.constData();
const auto nb_parm_index_data = nb_parm_index.constData();
auto build_lj = [&](int i)
{
// amber stores the A and B coefficients as the product of all
// possible combinations. We need to find the values from the
// LJ_i * LJ_i values
int idx = nb_parm_index_data[ntypes * i + i];
if (idx < 0)
{
// this is a 10-12 parameter
throw SireError::unsupported(QObject::tr("Sire does not yet support Amber Parm files that "
"use 10-12 HBond parameters."),
CODELOC);
}
else
{
double acoeff = acoeffs_data[idx - 1];
double bcoeff = bcoeffs_data[idx - 1];
double sigma = 0;
double epsilon = 0;
// numeric imprecision means that any parameter with acoeff less
// than 1e-10 is really equal to 0
if (acoeff > 1e-10)
{
// convert a_coeff & b_coeff into angstroms and kcal/mol-1
sigma = std::pow(acoeff / bcoeff, 1 / 6.);
epsilon = pow_2(bcoeff) / (4 * acoeff);
}
lj_data_array[i] = LJParameter(sigma * angstrom, epsilon * kcal_per_mol);
}
};
if (usesParallel())
{
tbb::parallel_for(tbb::blocked_range<int>(0, ntypes), [&](tbb::blocked_range<int> r)
{
for (int i = r.begin(); i < r.end(); ++i)
{
build_lj(i);
} });
}
else
{
for (int i = 0; i < ntypes; ++i)
{
build_lj(i);
}
}
// The build_lj function above only considers diagonal elements of the
// NONBONDED_PARM_INDEX matrix. Here we loop over the off-diagonal elements
// to check for 10-12 parameters, which are currently unsupported.
// The matrix is symmetric, so perform a triangular loop over off-diagonal
// elements.
for (int i = 0; i < ntypes; ++i)
{
for (int j = i + 1; j < ntypes; ++j)
{
int idx = nb_parm_index_data[ntypes * i + j];
if (idx < 0)
{
auto a = hbond_acoeffs_data[1 - idx];
auto b = hbond_bcoeffs_data[1 - idx];
if ((a > 1e-6) and (b > 1e-6))
{
// this is a 10-12 parameter
throw SireError::unsupported(QObject::tr("Sire does not yet support Amber Parm files that "
"use 10-12 HBond parameters."),
CODELOC);
}
}
}
}
// While most LJ parameters use combining rules to form the i,j pairs,
// Amber parm files support custom (exception) LJ parameters for specific
// pairs of atoms. This is increasingly used by researchers to control
// interactions between molecules, or to support the 12-6-4 potential
// (which doesn't use combining rules?). Here, we loop over all pairs
// of parameters to find those that don't use combining rules, and
// so should be treated as exceptions.
const bool has_c_coeffs = not ccoeffs.isEmpty();
bool use_arithmetic_combining_rules = false;
bool use_geometric_combining_rules = false;
for (int i = 0; i < ntypes; ++i)
{
const auto &lj_i = lj_data_array[i];
// include i==j pair as we also need to check for c-coeffs
for (int j = i; j < ntypes; ++j)
{
const auto &lj_j = lj_data_array[j];
int idx = nb_parm_index_data[ntypes * i + j];
if (idx > 0)
{
auto a = acoeffs_data[idx - 1];
auto b = bcoeffs_data[idx - 1];
// convert A and B into sigma and epsilon
double sigma = 0;
double epsilon = 0;
// numeric imprecision means that any parameter with acoeff less
// than 1e-10 is really equal to 0
if (a > 1e-10)
{
// convert a_coeff & b_coeff into angstroms and kcal/mol-1
sigma = std::pow(a / b, 1 / 6.);
epsilon = pow_2(b) / (4 * a);
}
auto lj_ij = LJParameter(sigma * angstrom, epsilon * kcal_per_mol);
auto expect = lj_i.combine(lj_j, LJParameter::ARITHMETIC);
bool is_exception = false;
if (std::abs(epsilon) < 1e-6 and std::abs(expect.epsilon().value()) < 1e-6)
{
// this is a LJ pair that involves a ghost or dummy atom
// It should not impact exceptions or combining rules
}
else if (std::abs(lj_ij.epsilon().value() - expect.epsilon().value()) <= 1e-6)
{
if (std::abs(lj_ij.sigma().value() - expect.sigma().value()) > 1e-6)
{
// not arithmetic combining rules - try geometric
expect = lj_i.combine(lj_j, LJParameter::GEOMETRIC);
is_exception = std::abs(lj_ij.sigma().value() - expect.sigma().value()) > 1e-6;
if (not is_exception)
{
if (has_c_coeffs)
use_geometric_combining_rules = ccoeffs_data[idx - 1] != 0;
else
use_geometric_combining_rules = true;
}
}
else
{
use_arithmetic_combining_rules = true;
}
}
else
{
is_exception = true;
}
if (has_c_coeffs)
{
// we have C coefficients, so we need to check if this is an exception
// or not
if (std::abs(ccoeffs_data[idx - 1]) > 1e-6)
{
// this is an exception
is_exception = true;
}
}
if (is_exception)
{
// this is an exception parameter
double a = acoeffs_data[idx - 1];
double b = bcoeffs_data[idx - 1];
double c = 0;
if (has_c_coeffs)
{
c = ccoeffs_data[idx - 1];
}
// already in internal units (kcal mol-1, Angstroms)
auto lj_exception = LJException(LJ1264Parameter(a, b, c));
if (not lj_exceptions.contains(i))
{
lj_exceptions.insert(i, QList<LJException>());
}
if (not lj_exceptions.contains(j))
{
lj_exceptions.insert(j, QList<LJException>());
}
lj_exceptions[i].append(lj_exception);
lj_exceptions[j].append(lj_exception.getPair());
}
}
}
}
if (use_arithmetic_combining_rules and use_geometric_combining_rules)
{
warns.append(QObject::tr("The LJ parameters in this Amber Parm file use both arithmetic and geometric "
"combining rules. Sire will use arithmetic combining rules for all LJ "
"parameters."));
use_geometric_combining_rules = false;
}
if (use_geometric_combining_rules)
{
this->comb_rules = "geometric";
}
else
{
this->comb_rules = "arithmetic";
}
}
/** This function is called to build all of the CMAP terms from the data
* read in from the PRM file
*/
void AmberPrm::rebuildCMAPTerms()
{
cmap_data.clear();
// some confusion as both CHARMM_X and X names are used
const auto cmap_count = int_data.value("CMAP_COUNT") + int_data.value("CHARMM_CMAP_COUNT");
if (cmap_count.size() < 2)
return;
// the first value is the CMAP_TERM_COUNT. This is not read - and we don't need
// it because it is implied when we actually read in the CMAP terms
// const int cmap_term_count = cmap_count[0];
// the second is the CMAP_TYPE_COUNT
const int cmap_type_count = cmap_count[1];
const auto cmap_resolution = int_data.value("CMAP_RESOLUTION") + int_data.value("CHARMM_CMAP_RESOLUTION");
// this should have CMAP_TERM_COUNT entries
if (cmap_resolution.count() != cmap_type_count)
{
throw SireIO::parse_error(QObject::tr("The number of CMAP resolution terms is not equal to the number of "
"CMAP terms! Should be %1, but is %2!")
.arg(cmap_type_count)
.arg(cmap_resolution.count()),
CODELOC);
}
// there are CMAP_TYPE_COUNT data entries for the grids,
// called CMAP_PARAMETER_01 to CMAP_PARAMETER_{CMAP_TYPE_COUNT}
for (int i = 1; i <= cmap_type_count; ++i)
{
const auto resolution = cmap_resolution[i - 1];
const auto cmap_parameter = float_data.value(QString("CMAP_PARAMETER_%1").arg(i, 2, 10, QChar('0'))) +
float_data.value(QString("CHARMM_CMAP_PARAMETER_%1").arg(i, 2, 10, QChar('0')));
// the number of values should equal the resolution squared
if (cmap_parameter.count() != resolution * resolution)
{
throw SireIO::parse_error(QObject::tr("The number of CMAP parameters for type %1 is not equal to the "
"resolution squared! Should be %2, but is %3!")
.arg(i)
.arg(resolution * resolution)
.arg(cmap_parameter.count()),
CODELOC);
}
// the values are in column-major order
cmap_data.insert(i, CMAPParameter(Array2D<double>::fromColumnMajorVector(
cmap_parameter, resolution, resolution)));
}
}
/** This function finds all atoms that are bonded to the atom at index 'atom_idx'
(which is in molecule with index 'mol_idx', populating the hashe
'atom_to_mol' (the molecule containing the passed atom). This uses the bonding information
in 'bonded_atoms', which is the list of all atoms that are bonded to each atom */
static void findBondedAtoms(int atom_idx, int mol_idx, const QMultiHash<int, int> &bonded_atoms,
QHash<int, int> &atom_to_mol, QSet<int> &atoms_in_mol)
{
for (auto bonded_atom : bonded_atoms.values(atom_idx))
{
if (not atoms_in_mol.contains(bonded_atom))
{
// we have not walked along this atom before
atom_to_mol[bonded_atom] = mol_idx;
atoms_in_mol.insert(bonded_atom);
findBondedAtoms(bonded_atom, mol_idx, bonded_atoms, atom_to_mol, atoms_in_mol);
}
}
}
/** This function uses the bond information in 'bonds_inc_h' and 'bonds_exc_h'
to divide the passed atoms into molecules. This returns an array of
the number of atoms in each molecule (same format as ATOMS_PER_MOLECULE) */
static QVector<qint64> discoverMolecules(const QVector<qint64> &bonds_inc_h, const QVector<qint64> &bonds_exc_h,
int natoms)
{
// first, create a hash showing which atoms are bonded to each other
// NOTE: the atom numbers in the following arrays that describe bonds
// are coordinate array indexes for runtime speed. The true atom number
// equals the absolute value of the number divided by three, plus one.
//
//%FORMAT(10I8) (IBH(i),JBH(i),ICBH(i), i=1,NBONH)
// IBH : atom involved in bond "i", bond contains hydrogen
// JBH : atom involved in bond "i", bond contains hydrogen
// ICBH : index into parameter arrays RK and REQ
QMultiHash<int, int> bonded_atoms;
for (int j = 0; j < bonds_exc_h.count(); j = j + 3)
{
int atom0 = bonds_exc_h[j] / 3 + 1;
int atom1 = bonds_exc_h[j + 1] / 3 + 1;
bonded_atoms.insert(atom0, atom1);
bonded_atoms.insert(atom1, atom0);
}
for (int j = 0; j < bonds_inc_h.count(); j = j + 3)
{
int atom0 = bonds_inc_h[j] / 3 + 1;
int atom1 = bonds_inc_h[j + 1] / 3 + 1;
bonded_atoms.insert(atom0, atom1);
bonded_atoms.insert(atom1, atom0);
}
// Then recursively walk along each atom to find all the atoms that
// are in the same molecule
int nmols = 0;
QHash<int, int> atom_to_mol;
QList<qint64> atoms_per_mol;
for (int i = 1; i <= natoms; i++)
{
if (not atom_to_mol.contains(i))
{
QSet<int> atoms_in_mol;
nmols += 1;
atom_to_mol[i] = nmols;
atoms_in_mol.insert(i);
// Recursive walk
findBondedAtoms(i, nmols, bonded_atoms, atom_to_mol, atoms_in_mol);
// this has now found all of the atoms in this molecule. Add the
// number of atoms in the molecule to atoms_per_mol
atoms_per_mol.append(atoms_in_mol.count());
}
}
return atoms_per_mol.toVector();
}
/** Rebuild the arrays that show which atoms are in which molecules */
void AmberPrm::rebuildMolNumToAtomNums()
{
const int natoms = this->nAtoms();
const auto bonds_exc_h = this->intData("BONDS_WITHOUT_HYDROGEN");
const auto bonds_inc_h = this->intData("BONDS_INC_HYDROGEN");
const auto bonds_exc_h_data = bonds_exc_h.constData();
const auto bonds_inc_h_data = bonds_inc_h.constData();
// first, create a hash showing which atoms are bonded to each other
// NOTE: the atom numbers in the following arrays that describe bonds
// are coordinate array indexes for runtime speed. The true atom number
// equals the absolute value of the number divided by three, plus one.
//
//%FORMAT(10I8) (IBH(i),JBH(i),ICBH(i), i=1,NBONH)
// IBH : atom involved in bond "i", bond contains hydrogen
// JBH : atom involved in bond "i", bond contains hydrogen
// ICBH : index into parameter arrays RK and REQ
QMultiHash<int, int> bonded_atoms;
for (int j = 0; j < bonds_exc_h.count(); j = j + 3)
{
int atom0 = bonds_exc_h_data[j] / 3 + 1;
int atom1 = bonds_exc_h_data[j + 1] / 3 + 1;
bonded_atoms.insert(atom0, atom1);
bonded_atoms.insert(atom1, atom0);
}
for (int j = 0; j < bonds_inc_h.count(); j = j + 3)
{
int atom0 = bonds_inc_h_data[j] / 3 + 1;
int atom1 = bonds_inc_h_data[j + 1] / 3 + 1;
bonded_atoms.insert(atom0, atom1);
bonded_atoms.insert(atom1, atom0);
}
// Then recursively walk along each atom to find all the atoms that
// are in the same molecule
int nmols = 0;
QHash<int, int> atom_to_mol;
// remove the 0th index as Amber is 1-indexed
QVector<QVector<int>> new_molnum_to_atomnums;
new_molnum_to_atomnums.append(QVector<int>());
QList<qint64> atoms_per_mol;
// this is how many atoms the amber file says are in each molecule...
const auto a_per_m = int_data.value("ATOMS_PER_MOLECULE");
const auto a_per_m_data = a_per_m.constData();
for (int i = 1; i <= natoms; i++)
{
if (not atom_to_mol.contains(i))
{
QSet<int> atoms_in_mol;
nmols += 1;
if (nmols > a_per_m.count())
{
throw SireIO::parse_error(
QObject::tr("The files appears to contain more molecules than expected. The file "
"should only contain %1 molecules, but more than this have been "
"found based on the bonding of the molecules.")
.arg(nmols),
CODELOC);
}
// the number of atoms we should expect in this molecule...
int expected_nats = a_per_m_data[nmols - 1];
// this is the first atom in the new molecule
atom_to_mol[i] = nmols;
atoms_in_mol.insert(i);
// Recursive walk to find all of the other atoms
findBondedAtoms(i, nmols, bonded_atoms, atom_to_mol, atoms_in_mol);
// this has now found all of the atoms in this molecule. Check we have
// all of the molecules expected by the amber file...
int next_atom = i + 1;
while (atoms_in_mol.count() < expected_nats)
{
// find the next atom which is not yet in a molecule...
for (; next_atom <= natoms; ++next_atom)
{
if (not atom_to_mol.contains(next_atom))
{
break;
}
}
// add this, and all of its bonded atoms
atom_to_mol[next_atom] = nmols;
atoms_in_mol.insert(next_atom);
findBondedAtoms(next_atom, nmols, bonded_atoms, atom_to_mol, atoms_in_mol);
next_atom += 1;
if (next_atom > natoms)
break;
}
if (atoms_in_mol.count() != expected_nats)
{
throw SireIO::parse_error(
QObject::tr("Disagreement over the number of atoms in molecule %1. Looking at bonding "
"implies the number of atoms is %2, while the file itself claims the "
"number is %3.")
.arg(nmols)
.arg(atoms_in_mol.count())
.arg(expected_nats),
CODELOC);
}
// add the number of atoms in the molecule to atoms_per_mol
atoms_per_mol.append(atoms_in_mol.count());
auto atms = atoms_in_mol.values();
std::sort(atms.begin(), atms.end());
new_molnum_to_atomnums.append(atms.toVector());
}
}
molnum_to_atomnums = new_molnum_to_atomnums;
// now have all of the atomidxs (1-indexed) for molecule i (1-indexed)
// in the array molidx_to_atomidxs. Guaranteed to be sorted into AtomNum order
// in molnum_to_atomnums
}
/** Function called after loading the AmberPrm from a binary stream
to populate all of the calculated member data */
void AmberPrm::rebuildAfterReload()
{
pointers = this->intData("POINTERS");
if (pointers.isEmpty())
{
this->operator=(AmberPrm());
}
else if (pointers.count() < 31)
{
throw SireIO::parse_error(
QObject::tr("There was no, or an insufficient 'POINTERS' section in the file! (%1)").arg(pointers.count()),
CODELOC);
}
// first need to create the map of all of the atom numbers in each molecule
this->rebuildMolNumToAtomNums();