baoabmd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BaoabMD.js +928 -0
- package/CL/cl.h +1979 -0
- package/CL/cl_d3d10.h +268 -0
- package/CL/cl_d3d11.h +270 -0
- package/CL/cl_dx9_media_sharing.h +386 -0
- package/CL/cl_dx9_media_sharing_intel.h +18 -0
- package/CL/cl_egl.h +187 -0
- package/CL/cl_ext.h +4738 -0
- package/CL/cl_ext_intel.h +19 -0
- package/CL/cl_function_types.h +1199 -0
- package/CL/cl_gl.h +421 -0
- package/CL/cl_gl_ext.h +18 -0
- package/CL/cl_half.h +440 -0
- package/CL/cl_icd.h +349 -0
- package/CL/cl_layer.h +163 -0
- package/CL/cl_platform.h +1414 -0
- package/CL/cl_va_api_media_sharing_intel.h +220 -0
- package/CL/cl_version.h +85 -0
- package/CL/opencl.h +32 -0
- package/INSTRUCTIONS.txt +13 -0
- package/OpenCL.lib +0 -0
- package/binding.gyp +24 -0
- package/build/Release/forces.exp +0 -0
- package/build/Release/forces.iobj +0 -0
- package/build/Release/forces.ipdb +0 -0
- package/build/Release/forces.lib +0 -0
- package/build/Release/forces.node +0 -0
- package/build/Release/forces.pdb +0 -0
- package/build/Release/nothing.lib +0 -0
- package/build/Release/obj/forces/forces.node.recipe +11 -0
- package/build/Release/obj/forces/forces.obj +0 -0
- package/build/Release/obj/forces/forces.tlog/CL.command.1.tlog +0 -0
- package/build/Release/obj/forces/forces.tlog/CL.read.1.tlog +0 -0
- package/build/Release/obj/forces/forces.tlog/CL.write.1.tlog +0 -0
- package/build/Release/obj/forces/forces.tlog/Cl.items.tlog +2 -0
- package/build/Release/obj/forces/forces.tlog/forces.lastbuildstate +2 -0
- package/build/Release/obj/forces/forces.tlog/link.command.1.tlog +0 -0
- package/build/Release/obj/forces/forces.tlog/link.read.1.tlog +0 -0
- package/build/Release/obj/forces/forces.tlog/link.secondary.1.tlog +5 -0
- package/build/Release/obj/forces/forces.tlog/link.write.1.tlog +0 -0
- package/build/Release/obj/forces/win_delay_load_hook.obj +0 -0
- package/build/binding.sln +39 -0
- package/build/forces.vcxproj +156 -0
- package/build/forces.vcxproj.filters +46 -0
- package/extra xyz files/traj_GB1-10000s.xyz +451900 -0
- package/forces.cpp +194 -0
- package/package.json +26 -0
- package/pdb/1UBQ.pdb +970 -0
- package/pdb/1YRF.pdb +1467 -0
- package/pdb/input.pdb +4521 -0
package/BaoabMD.js
ADDED
|
@@ -0,0 +1,928 @@
|
|
|
1
|
+
/*
|
|
2
|
+
version: 1.06
|
|
3
|
+
time working: 102 hours
|
|
4
|
+
|
|
5
|
+
UPDATES(m/d/y):
|
|
6
|
+
9/6/2026 - added terminal inputs
|
|
7
|
+
9/6/2026 - performance fix, replaced object vectors with inline math inside loops
|
|
8
|
+
9/6/2026 - added angle forces
|
|
9
|
+
9/6/2026 - fixed issue with temperatures starting at over 1000k
|
|
10
|
+
9/7/2026 - fixed memory issues
|
|
11
|
+
9/7/2026 - added multi-threading
|
|
12
|
+
9/7/2026 - added amd gpu support
|
|
13
|
+
|
|
14
|
+
ELECTROSTATICS:
|
|
15
|
+
- Periodic orthorhombic systems use a CIC particle-mesh Ewald reciprocal solve
|
|
16
|
+
plus Ewald real space. Triclinic cells are not supported.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const readline = require('readline');
|
|
21
|
+
const os = require('os');
|
|
22
|
+
|
|
23
|
+
// system units
|
|
24
|
+
// CHARMM standard
|
|
25
|
+
// UPDATE!!(THERMOSTAT RELAXATION RATE ADDED)
|
|
26
|
+
const config = {
|
|
27
|
+
// Constrained X-H bonds permit a 1 fs step; do not raise this without a
|
|
28
|
+
// validated constraint/force-field combination.
|
|
29
|
+
dt: 1.0, // time in femtoseconds
|
|
30
|
+
thermostatRelaxationRate: 100.0, // relaxation rate tau (fs)
|
|
31
|
+
gamma: 0.01, // friction (1/fs) as 1/thermostatRelaxationRate
|
|
32
|
+
targetTemp: 300, // kelvin
|
|
33
|
+
kB: 0.0019872041, // kcal/(mol*k)
|
|
34
|
+
fta: 4.184e-4, // kcal/(mol*A*amu) to A/fs^2
|
|
35
|
+
coulomb: 332.06371, // kcal*A/(mol*e^2)
|
|
36
|
+
cutoff: 10.0,
|
|
37
|
+
switchDistance: 8.0,
|
|
38
|
+
neighborSkin: 2.0,
|
|
39
|
+
oneFourLJScale: 0.5,
|
|
40
|
+
oneFourCoulombScale: 1 / 1.2,
|
|
41
|
+
constraintTolerance: 1e-6,
|
|
42
|
+
constraintIterations: 100,
|
|
43
|
+
pme: true,
|
|
44
|
+
pmeGridSpacing: 1.0, // Å; mesh dimensions are rounded up to powers of two
|
|
45
|
+
ewaldAlpha: 0.35 // Å^-1; erfc(alpha * cutoff) should be negligible
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const masses = {
|
|
49
|
+
H: 1.008, He: 4.0026, Li: 6.94, Be: 9.0122, B: 10.81, C: 12.011, N: 14.007, O: 15.999, F: 18.998, Ne: 20.180,
|
|
50
|
+
Na: 22.990, Mg: 24.305, Al: 26.982, Si: 28.085, P: 30.974, S: 32.06, Cl: 35.45, Ar: 39.95, K: 39.098, Ca: 40.078,
|
|
51
|
+
Sc: 44.956, Ti: 47.867, V: 50.942, Cr: 51.996, Mn: 54.938, Fe: 55.845, Co: 58.933, Ni: 58.693, Cu: 63.546, Zn: 65.38,
|
|
52
|
+
Ga: 69.723, Ge: 72.630, As: 74.922, Se: 78.971, Br: 79.904, Kr: 83.798, Rb: 85.468, Sr: 87.62, Y: 88.906, Zr: 91.224,
|
|
53
|
+
Nb: 92.906, Mo: 95.95, Tc: 98, Ru: 101.07, Rh: 102.91, Pd: 106.42, Ag: 107.87, Cd: 112.41, In: 114.82, Sn: 118.71,
|
|
54
|
+
Sb: 121.76, Te: 127.60, I: 126.90, Xe: 131.29, Cs: 132.91, Ba: 137.33, La: 138.91, Ce: 140.12, Pr: 140.91, Nd: 144.24,
|
|
55
|
+
Pm: 145, Sm: 150.36, Eu: 151.96, Gd: 157.25, Tb: 158.93, Dy: 162.50, Ho: 164.93, Er: 167.26, Tm: 168.93, Yb: 173.05,
|
|
56
|
+
Lu: 174.97, Hf: 178.49, Ta: 180.95, W: 183.84, Re: 186.21, Os: 190.23, Ir: 192.22, Pt: 195.08, Au: 196.97, Hg: 200.59,
|
|
57
|
+
Tl: 204.38, Pb: 207.2, Bi: 208.98, Po: 209, At: 210, Rn: 222, Fr: 223, Ra: 226, Ac: 227, Th: 232.04,
|
|
58
|
+
Pa: 231.04, U: 238.03, Np: 237, Pu: 244, Am: 243, Cm: 247, Bk: 247, Cf: 251, Es: 252, Fm: 257,
|
|
59
|
+
Md: 258, No: 259, Lr: 262, Rf: 267, Db: 270, Sg: 271, Bh: 270, Hs: 277, Mt: 276, Ds: 281,
|
|
60
|
+
Rg: 282, Cn: 285, Nh: 286, Fl: 289, Mc: 290, Lv: 293, Ts: 294, Og: 294,
|
|
61
|
+
undefDef: 12.0
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const ff = {
|
|
65
|
+
lj: {
|
|
66
|
+
H: {eps:0.0300,sig:2.50 },
|
|
67
|
+
C: {eps:0.0660,sig:3.50 },
|
|
68
|
+
N: {eps:0.1700,sig:3.25 },
|
|
69
|
+
O: {eps:0.1521,sig:3.15061 },
|
|
70
|
+
S: {eps:0.2500,sig:3.60 },
|
|
71
|
+
P: {eps:0.2000,sig:3.74 },
|
|
72
|
+
F: {eps:0.0610,sig:3.12 },
|
|
73
|
+
Cl: {eps:0.2270,sig:3.47 },
|
|
74
|
+
Br: {eps:0.3200,sig:3.68 },
|
|
75
|
+
I: {eps:0.4000,sig:4.00 },
|
|
76
|
+
Na: {eps:0.0028,sig:3.33 },
|
|
77
|
+
K: {eps:0.0003,sig:4.73 },
|
|
78
|
+
Mg: {eps:0.8750,sig:1.41 },
|
|
79
|
+
Ca: {eps:0.1200,sig:2.87 },
|
|
80
|
+
undefDef: {eps:0.1,sig:3.0 }
|
|
81
|
+
},
|
|
82
|
+
bonds: {
|
|
83
|
+
'C-H': { k: 340.0, r0: 1.090 },
|
|
84
|
+
'H-O': { k: 450.0, r0: 0.9572 },
|
|
85
|
+
'C-C': { k: 310.0, r0: 1.526 },
|
|
86
|
+
'C-O': { k: 320.0, r0: 1.410 },
|
|
87
|
+
'H-N': { k: 434.0, r0: 1.010 },
|
|
88
|
+
'C-N': { k: 320.0, r0: 1.470 },
|
|
89
|
+
'C-S': { k: 250.0, r0: 1.810 },
|
|
90
|
+
'S-S': { k: 166.0, r0: 2.040 },
|
|
91
|
+
'S-H': { k: 274.0, r0: 1.336 },
|
|
92
|
+
'C-F': { k: 367.0, r0: 1.350 },
|
|
93
|
+
'C-Cl':{ k: 300.0, r0: 1.770 },
|
|
94
|
+
'C-Br':{ k: 280.0, r0: 1.940 },
|
|
95
|
+
'C-I': { k: 230.0, r0: 2.140 },
|
|
96
|
+
'P-O': { k: 400.0, r0: 1.610 },
|
|
97
|
+
undefDef: { k: 300, r0: 1.5 }
|
|
98
|
+
},
|
|
99
|
+
angles: {
|
|
100
|
+
'H-C-H': { k: 39.5, t0: 109.5 },
|
|
101
|
+
'H-O-H': { k: 55.0, t0: 104.52 },
|
|
102
|
+
'H-C-C': { k: 50.0, t0: 109.5 },
|
|
103
|
+
'C-C-C': { k: 40.0, t0: 109.5 },
|
|
104
|
+
'C-C-O': { k: 50.0, t0: 109.5 },
|
|
105
|
+
'C-C-N': { k: 50.0, t0: 109.5 },
|
|
106
|
+
'C-N-C': { k: 50.0, t0: 109.5 },
|
|
107
|
+
'C-O-C': { k: 60.0, t0: 109.5 },
|
|
108
|
+
'O-P-O': { k: 100.0, t0: 109.5 },
|
|
109
|
+
'C-S-C': { k: 62.0, t0: 98.9 },
|
|
110
|
+
undefDef: { k: 50, t0: 109.5 }
|
|
111
|
+
},
|
|
112
|
+
dihedrals: {
|
|
113
|
+
undefDef: { k: 1.0, n: 1, delta: 0.0 }
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const backboneBondPairs = [['N','CA'],['CA','C'],['C','O'],['C','OXT']];
|
|
118
|
+
|
|
119
|
+
const residueBondPairs = {
|
|
120
|
+
ALA: [['CA','CB']],
|
|
121
|
+
ARG: [['CA','CB'],['CB','CG'],['CG','CD'],['CD','NE'],['NE','CZ'],['CZ','NH1'],['CZ','NH2']],
|
|
122
|
+
ASN: [['CA','CB'],['CB','CG'],['CG','OD1'],['CG','ND2']],
|
|
123
|
+
ASP: [['CA','CB'],['CB','CG'],['CG','OD1'],['CG','OD2']],
|
|
124
|
+
CYS: [['CA','CB'],['CB','SG']],
|
|
125
|
+
GLN: [['CA','CB'],['CB','CG'],['CG','CD'],['CD','OE1'],['CD','NE2']],
|
|
126
|
+
GLU: [['CA','CB'],['CB','CG'],['CG','CD'],['CD','OE1'],['CD','OE2']],
|
|
127
|
+
GLY: [],
|
|
128
|
+
HIS: [['CA','CB'],['CB','CG'],['CG','ND1'],['ND1','CE1'],['CE1','NE2'],['NE2','CD2'],['CD2','CG']],
|
|
129
|
+
ILE: [['CA','CB'],['CB','CG1'],['CB','CG2'],['CG1','CD1']],
|
|
130
|
+
LEU: [['CA','CB'],['CB','CG'],['CG','CD1'],['CG','CD2']],
|
|
131
|
+
LYS: [['CA','CB'],['CB','CG'],['CG','CD'],['CD','CE'],['CE','NZ']],
|
|
132
|
+
MET: [['CA','CB'],['CB','CG'],['CG','SD'],['SD','CE']],
|
|
133
|
+
PHE: [['CA','CB'],['CB','CG'],['CG','CD1'],['CD1','CE1'],['CE1','CZ'],['CZ','CE2'],['CE2','CD2'],['CD2','CG']],
|
|
134
|
+
PRO: [['CA','CB'],['CB','CG'],['CG','CD'],['CD','N']],
|
|
135
|
+
SER: [['CA','CB'],['CB','OG']],
|
|
136
|
+
THR: [['CA','CB'],['CB','OG1'],['CB','CG2']],
|
|
137
|
+
TRP: [['CA','CB'],['CB','CG'],['CG','CD1'],['CD1','NE1'],['NE1','CE2'],['CE2','CD2'],['CD2','CG'],['CE2','CZ2'],['CZ2','CH2'],['CH2','CZ3'],['CZ3','CE3'],['CE3','CD2']],
|
|
138
|
+
TYR: [['CA','CB'],['CB','CG'],['CG','CD1'],['CD1','CE1'],['CE1','CZ'],['CZ','CE2'],['CE2','CD2'],['CD2','CG'],['CZ','OH']],
|
|
139
|
+
VAL: [['CA','CB'],['CB','CG1'],['CB','CG2']]
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
class Vec3 {
|
|
143
|
+
constructor(x = 0, y = 0, z = 0) {
|
|
144
|
+
this.x = x; this.y = y; this.z = z;
|
|
145
|
+
}
|
|
146
|
+
add(v) { this.x += v.x; this.y += v.y; this.z += v.z; return this; }
|
|
147
|
+
sub(v) { return new Vec3(this.x-v.x, this.y-v.y, this.z-v.z); }
|
|
148
|
+
scale(s) { this.x *= s; this.y *= s; this.z *= s; return this; }
|
|
149
|
+
magSq() { return this.x*this.x+this.y*this.y+this.z*this.z; }
|
|
150
|
+
mag() { return Math.sqrt(this.magSq()); }
|
|
151
|
+
dot(v) { return this.x*v.x+this.y*v.y+this.z*v.z; }
|
|
152
|
+
copy() { return new Vec3(this.x, this.y, this.z); }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
class Atom {
|
|
156
|
+
constructor(element, pos, q = 0, serial = 0, type = null) {
|
|
157
|
+
this.element = element;
|
|
158
|
+
this.mass = masses[element] || masses.undefDef;
|
|
159
|
+
this.pos = pos;
|
|
160
|
+
this.vel = new Vec3();
|
|
161
|
+
this.q = q;
|
|
162
|
+
this.serial = serial;
|
|
163
|
+
this.type = type || element;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
class System {
|
|
168
|
+
constructor(atoms, bonds, box = null) {
|
|
169
|
+
this.atoms = atoms;
|
|
170
|
+
this.box = box;
|
|
171
|
+
this.bonds = bonds.map(([i, j]) => this._createBond(i, j));
|
|
172
|
+
this.angles = this._ba(bonds);
|
|
173
|
+
this.dihedrals = this._buildDihedrals(bonds);
|
|
174
|
+
this.pairClasses = this._buildPairClasses();
|
|
175
|
+
this.forces = atoms.map(() => new Vec3());
|
|
176
|
+
this.neighborList = new NeighborList(this);
|
|
177
|
+
this.pme = config.pme && this.box ? new PMESolver(this.box) : null;
|
|
178
|
+
}
|
|
179
|
+
_getPairKey(a, b) {
|
|
180
|
+
return [a, b].sort().join('-');
|
|
181
|
+
}
|
|
182
|
+
_createBond(i, j) {
|
|
183
|
+
const typePair = this._getPairKey(this.atoms[i].type, this.atoms[j].type);
|
|
184
|
+
const elementPair = this._getPairKey(this.atoms[i].element, this.atoms[j].element);
|
|
185
|
+
const params = ff.bonds[typePair] || ff.bonds[elementPair] || ff.bonds.undefDef;
|
|
186
|
+
return { i, j, k: params.k, r0: params.r0 };
|
|
187
|
+
}
|
|
188
|
+
_ba(bonds) {
|
|
189
|
+
const adj = Array.from({ length: this.atoms.length }, () => []);
|
|
190
|
+
for (const [i, j] of bonds) {
|
|
191
|
+
adj[i].push(j);
|
|
192
|
+
adj[j].push(i);
|
|
193
|
+
}
|
|
194
|
+
const angles = [];
|
|
195
|
+
adj.forEach((neighbors, j) => {
|
|
196
|
+
for (let a = 0; a < neighbors.length; a++) {
|
|
197
|
+
for (let b = a+1; b < neighbors.length; b++) {
|
|
198
|
+
const i = neighbors[a];
|
|
199
|
+
const k = neighbors[b];
|
|
200
|
+
const typeKey = `${[this.atoms[i].type, this.atoms[k].type].sort().join('-')}-${this.atoms[j].type}`;
|
|
201
|
+
const elementKey = `${[this.atoms[i].element, this.atoms[k].element].sort().join('-')}-${this.atoms[j].element}`;
|
|
202
|
+
const params = ff.angles[typeKey] || ff.angles[elementKey] || ff.angles.undefDef;
|
|
203
|
+
angles.push({
|
|
204
|
+
i, j, k,
|
|
205
|
+
kTheta: params.k,
|
|
206
|
+
theta0: (params.t0*Math.PI)/180
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
return angles;
|
|
212
|
+
}
|
|
213
|
+
_buildDihedrals(bonds) {
|
|
214
|
+
const adj = Array.from({ length: this.atoms.length }, () => []);
|
|
215
|
+
for (const [i, j] of bonds) {
|
|
216
|
+
adj[i].push(j);
|
|
217
|
+
adj[j].push(i);
|
|
218
|
+
}
|
|
219
|
+
const dihedrals = [];
|
|
220
|
+
for (const [j, k] of bonds) {
|
|
221
|
+
for (const i of adj[j]) {
|
|
222
|
+
if (i === k) continue;
|
|
223
|
+
for (const l of adj[k]) {
|
|
224
|
+
if (l === j || l === i) continue;
|
|
225
|
+
const params = ff.dihedrals.undefDef;
|
|
226
|
+
dihedrals.push({
|
|
227
|
+
i, j, k, l,
|
|
228
|
+
k: params.k,
|
|
229
|
+
n: params.n,
|
|
230
|
+
delta: params.delta
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return dihedrals;
|
|
236
|
+
}
|
|
237
|
+
_buildPairClasses() {
|
|
238
|
+
// 1 = excluded (1-2/1-3); 2 = scaled 1-4. Sparse storage avoids N^2 memory.
|
|
239
|
+
const pairClasses = new Map();
|
|
240
|
+
const setClass = (i, j, value) => {
|
|
241
|
+
const min = Math.min(i, j);
|
|
242
|
+
const max = Math.max(i, j);
|
|
243
|
+
pairClasses.set(`${min}:${max}`, value);
|
|
244
|
+
};
|
|
245
|
+
this.bonds.forEach(b => setClass(b.i, b.j, 1));
|
|
246
|
+
this.angles.forEach(a => setClass(a.i, a.k, 1));
|
|
247
|
+
this.dihedrals.forEach(d => {
|
|
248
|
+
const key = `${Math.min(d.i, d.l)}:${Math.max(d.i, d.l)}`;
|
|
249
|
+
if (!pairClasses.has(key)) setClass(d.i, d.l, 2);
|
|
250
|
+
});
|
|
251
|
+
return pairClasses;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
class NeighborList {
|
|
256
|
+
constructor(sys) {
|
|
257
|
+
this.sys = sys;
|
|
258
|
+
this.pairs = [];
|
|
259
|
+
this.reference = [];
|
|
260
|
+
this.valid = false;
|
|
261
|
+
}
|
|
262
|
+
needsRebuild() {
|
|
263
|
+
if (!this.valid) return true;
|
|
264
|
+
const limit2 = (config.neighborSkin * 0.5) ** 2;
|
|
265
|
+
for (let i = 0; i < this.sys.atoms.length; i++) {
|
|
266
|
+
const p = this.sys.atoms[i].pos, r = this.reference[i];
|
|
267
|
+
const d = pbcDistance(p.x - r.x, p.y - r.y, p.z - r.z, this.sys.box);
|
|
268
|
+
if (d.dx * d.dx + d.dy * d.dy + d.dz * d.dz > limit2) return true;
|
|
269
|
+
}
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
rebuild() {
|
|
273
|
+
const atoms = this.sys.atoms;
|
|
274
|
+
const cellSize = config.cutoff + config.neighborSkin;
|
|
275
|
+
const cells = new Map();
|
|
276
|
+
const key = (x, y, z) => `${x},${y},${z}`;
|
|
277
|
+
const dims = this.sys.box ? [
|
|
278
|
+
Math.max(1, Math.ceil(this.sys.box.x / cellSize)),
|
|
279
|
+
Math.max(1, Math.ceil(this.sys.box.y / cellSize)),
|
|
280
|
+
Math.max(1, Math.ceil(this.sys.box.z / cellSize))
|
|
281
|
+
] : null;
|
|
282
|
+
for (let i = 0; i < atoms.length; i++) {
|
|
283
|
+
const p = atoms[i].pos;
|
|
284
|
+
const cx = Math.floor(p.x / cellSize), cy = Math.floor(p.y / cellSize), cz = Math.floor(p.z / cellSize);
|
|
285
|
+
const k = key(cx, cy, cz);
|
|
286
|
+
if (!cells.has(k)) cells.set(k, []);
|
|
287
|
+
cells.get(k).push(i);
|
|
288
|
+
}
|
|
289
|
+
const pairs = [];
|
|
290
|
+
const maxR2 = cellSize * cellSize;
|
|
291
|
+
for (const [cellKey, members] of cells) {
|
|
292
|
+
const [cx, cy, cz] = cellKey.split(',').map(Number);
|
|
293
|
+
const neighborKeys = new Set();
|
|
294
|
+
for (let dx = -1; dx <= 1; dx++) for (let dy = -1; dy <= 1; dy++) for (let dz = -1; dz <= 1; dz++) {
|
|
295
|
+
let nx = cx + dx, ny = cy + dy, nz = cz + dz;
|
|
296
|
+
if (dims) { nx = (nx + dims[0]) % dims[0]; ny = (ny + dims[1]) % dims[1]; nz = (nz + dims[2]) % dims[2]; }
|
|
297
|
+
neighborKeys.add(key(nx, ny, nz));
|
|
298
|
+
}
|
|
299
|
+
for (const neighborKey of neighborKeys) {
|
|
300
|
+
const neighbors = cells.get(neighborKey);
|
|
301
|
+
if (!neighbors) continue;
|
|
302
|
+
for (const i of members) for (const j of neighbors) {
|
|
303
|
+
if (j <= i) continue;
|
|
304
|
+
const a = atoms[i].pos, b = atoms[j].pos;
|
|
305
|
+
const d = pbcDistance(a.x-b.x, a.y-b.y, a.z-b.z, this.sys.box);
|
|
306
|
+
if (d.dx*d.dx + d.dy*d.dy + d.dz*d.dz <= maxR2) pairs.push([i, j]);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
this.pairs = pairs;
|
|
311
|
+
this.reference = atoms.map(a => a.pos.copy());
|
|
312
|
+
this.valid = true;
|
|
313
|
+
}
|
|
314
|
+
getPairs() { if (this.needsRebuild()) this.rebuild(); return this.pairs; }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function nextPowerOfTwo(value) { let n=1; while (n < value) n <<= 1; return n; }
|
|
318
|
+
// Numerical Recipes approximation; absolute error is below 1.5e-7.
|
|
319
|
+
function erfc(x) {
|
|
320
|
+
const z=Math.abs(x), t=1/(1+0.5*z);
|
|
321
|
+
const ans=t*Math.exp(-z*z-1.26551223+t*(1.00002368+t*(0.37409196+t*(0.09678418+t*(-0.18628806+t*(0.27886807+t*(-1.13520398+t*(1.48851587+t*(-0.82215223+t*0.17087277)))))))));
|
|
322
|
+
return x >= 0 ? ans : 2-ans;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function fft1d(re, im, inverse) {
|
|
326
|
+
const n=re.length;
|
|
327
|
+
for (let i=1,j=0;i<n;i++) { let bit=n>>1; for (; j&bit; bit>>=1) j^=bit; j^=bit; if (i<j) { [re[i],re[j]]=[re[j],re[i]]; [im[i],im[j]]=[im[j],im[i]]; } }
|
|
328
|
+
for (let len=2;len<=n;len<<=1) {
|
|
329
|
+
const angle=(inverse ? 2 : -2)*Math.PI/len, wrStep=Math.cos(angle), wiStep=Math.sin(angle);
|
|
330
|
+
for (let start=0;start<n;start+=len) { let wr=1, wi=0; const half=len>>1;
|
|
331
|
+
for (let k=0;k<half;k++) { const even=start+k, odd=even+half, tr=re[odd]*wr-im[odd]*wi, ti=re[odd]*wi+im[odd]*wr; re[odd]=re[even]-tr; im[odd]=im[even]-ti; re[even]+=tr; im[even]+=ti; const nw=wr*wrStep-wi*wiStep; wi=wr*wiStep+wi*wrStep; wr=nw; }
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (inverse) for (let i=0;i<n;i++) { re[i]/=n; im[i]/=n; }
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
class PMESolver {
|
|
338
|
+
constructor(box) {
|
|
339
|
+
this.box=box;
|
|
340
|
+
this.nx=nextPowerOfTwo(Math.max(8, Math.ceil(box.x/config.pmeGridSpacing)));
|
|
341
|
+
this.ny=nextPowerOfTwo(Math.max(8, Math.ceil(box.y/config.pmeGridSpacing)));
|
|
342
|
+
this.nz=nextPowerOfTwo(Math.max(8, Math.ceil(box.z/config.pmeGridSpacing)));
|
|
343
|
+
this.size=this.nx*this.ny*this.nz;
|
|
344
|
+
}
|
|
345
|
+
index(x,y,z) { return (x*this.ny+y)*this.nz+z; }
|
|
346
|
+
transform(re, im, inverse) {
|
|
347
|
+
const runLines=(count, lineLength, getter) => { const lr=new Float64Array(lineLength), li=new Float64Array(lineLength); for(let line=0;line<count;line++){ for(let k=0;k<lineLength;k++){const idx=getter(line,k);lr[k]=re[idx];li[k]=im[idx];} fft1d(lr,li,inverse); for(let k=0;k<lineLength;k++){const idx=getter(line,k);re[idx]=lr[k];im[idx]=li[k];} } };
|
|
348
|
+
runLines(this.nx*this.ny,this.nz,(line,k)=>line*this.nz+k);
|
|
349
|
+
runLines(this.nx*this.nz,this.ny,(line,k)=>this.index(Math.floor(line/this.nz),k,line%this.nz));
|
|
350
|
+
runLines(this.ny*this.nz,this.nx,(line,k)=>this.index(k,Math.floor(line/this.nz),line%this.nz));
|
|
351
|
+
}
|
|
352
|
+
compute(atoms) {
|
|
353
|
+
const rhoR=new Float64Array(this.size), rhoI=new Float64Array(this.size);
|
|
354
|
+
const weights=[];
|
|
355
|
+
for (const atom of atoms) {
|
|
356
|
+
const gx=atom.pos.x/this.box.x*this.nx, gy=atom.pos.y/this.box.y*this.ny, gz=atom.pos.z/this.box.z*this.nz;
|
|
357
|
+
const ix=Math.floor(gx), iy=Math.floor(gy), iz=Math.floor(gz), tx=gx-ix, ty=gy-iy, tz=gz-iz;
|
|
358
|
+
const sites=[];
|
|
359
|
+
for(let dx=0;dx<=1;dx++) for(let dy=0;dy<=1;dy++) for(let dz=0;dz<=1;dz++) { const w=(dx?tx:1-tx)*(dy?ty:1-ty)*(dz?tz:1-tz); const idx=this.index((ix+dx)%this.nx,(iy+dy)%this.ny,(iz+dz)%this.nz); rhoR[idx]+=atom.q*w; sites.push([idx,w]); }
|
|
360
|
+
weights.push(sites);
|
|
361
|
+
}
|
|
362
|
+
this.transform(rhoR,rhoI,false);
|
|
363
|
+
const phiR=new Float64Array(this.size), phiI=new Float64Array(this.size), exR=new Float64Array(this.size), exI=new Float64Array(this.size), eyR=new Float64Array(this.size), eyI=new Float64Array(this.size), ezR=new Float64Array(this.size), ezI=new Float64Array(this.size);
|
|
364
|
+
const volume=this.box.x*this.box.y*this.box.z, alpha=config.ewaldAlpha, mesh=this.size;
|
|
365
|
+
for(let x=0;x<this.nx;x++) for(let y=0;y<this.ny;y++) for(let z=0;z<this.nz;z++) { const idx=this.index(x,y,z); const kx=2*Math.PI*(x<=this.nx/2?x:x-this.nx)/this.box.x, ky=2*Math.PI*(y<=this.ny/2?y:y-this.ny)/this.box.y, kz=2*Math.PI*(z<=this.nz/2?z:z-this.nz)/this.box.z, k2=kx*kx+ky*ky+kz*kz; if(k2===0) continue; const g=mesh*4*Math.PI/volume*Math.exp(-k2/(4*alpha*alpha))/k2; phiR[idx]=g*rhoR[idx]; phiI[idx]=g*rhoI[idx]; exR[idx]=kx*phiI[idx]; exI[idx]=-kx*phiR[idx]; eyR[idx]=ky*phiI[idx]; eyI[idx]=-ky*phiR[idx]; ezR[idx]=kz*phiI[idx]; ezI[idx]=-kz*phiR[idx]; }
|
|
366
|
+
this.transform(phiR,phiI,true); this.transform(exR,exI,true); this.transform(eyR,eyI,true); this.transform(ezR,ezI,true);
|
|
367
|
+
const forces=atoms.map(()=>new Vec3()); let energy=0, q2=0;
|
|
368
|
+
for(let i=0;i<atoms.length;i++) { let phi=0, ex=0, ey=0, ez=0; for(const [idx,w] of weights[i]) {phi+=w*phiR[idx];ex+=w*exR[idx];ey+=w*eyR[idx];ez+=w*ezR[idx];} const q=atoms[i].q; energy+=0.5*q*phi; q2+=q*q; forces[i].x=config.coulomb*q*ex; forces[i].y=config.coulomb*q*ey; forces[i].z=config.coulomb*q*ez; }
|
|
369
|
+
return { forces, energy:config.coulomb*(energy-alpha*q2/Math.sqrt(Math.PI)) };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function pbcDistance(dx, dy, dz, box) {
|
|
374
|
+
if (!box) return { dx, dy, dz };
|
|
375
|
+
dx -= Math.round(dx / box.x) * box.x;
|
|
376
|
+
dy -= Math.round(dy / box.y) * box.y;
|
|
377
|
+
dz -= Math.round(dz / box.z) * box.z;
|
|
378
|
+
return { dx, dy, dz };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function applyPBC(sys) {
|
|
382
|
+
if (!sys.box) return;
|
|
383
|
+
for (const a of sys.atoms) {
|
|
384
|
+
a.pos.x -= Math.floor(a.pos.x / sys.box.x) * sys.box.x;
|
|
385
|
+
a.pos.y -= Math.floor(a.pos.y / sys.box.y) * sys.box.y;
|
|
386
|
+
a.pos.z -= Math.floor(a.pos.z / sys.box.z) * sys.box.z;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function randGaussian() {
|
|
391
|
+
let u = 0, v = 0;
|
|
392
|
+
while (u === 0) u = Math.random();
|
|
393
|
+
while (v === 0) v = Math.random();
|
|
394
|
+
return Math.sqrt(-2.0*Math.log(u))*Math.cos(2.0*Math.PI*v);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function constrainHydrogenBonds(sys) {
|
|
398
|
+
const constrained = sys.bonds.filter(b => sys.atoms[b.i].element === 'H' || sys.atoms[b.j].element === 'H');
|
|
399
|
+
for (let iteration = 0; iteration < config.constraintIterations; iteration++) {
|
|
400
|
+
let largest = 0;
|
|
401
|
+
for (const b of constrained) {
|
|
402
|
+
const ai=sys.atoms[b.i], aj=sys.atoms[b.j];
|
|
403
|
+
const d=pbcDistance(ai.pos.x-aj.pos.x, ai.pos.y-aj.pos.y, ai.pos.z-aj.pos.z, sys.box);
|
|
404
|
+
const r2=d.dx*d.dx+d.dy*d.dy+d.dz*d.dz, target2=b.r0*b.r0, error=r2-target2;
|
|
405
|
+
largest=Math.max(largest, Math.abs(error));
|
|
406
|
+
if (Math.abs(error) < config.constraintTolerance) continue;
|
|
407
|
+
const invMass=1/ai.mass+1/aj.mass, lambda=-error/(2*invMass*(r2+1e-16));
|
|
408
|
+
ai.pos.x += lambda*d.dx/ai.mass; ai.pos.y += lambda*d.dy/ai.mass; ai.pos.z += lambda*d.dz/ai.mass;
|
|
409
|
+
aj.pos.x -= lambda*d.dx/aj.mass; aj.pos.y -= lambda*d.dy/aj.mass; aj.pos.z -= lambda*d.dz/aj.mass;
|
|
410
|
+
}
|
|
411
|
+
if (largest < config.constraintTolerance) return;
|
|
412
|
+
}
|
|
413
|
+
throw new Error('SHAKE failed to converge; reduce dt or minimize the starting structure.');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function rattleHydrogenBonds(sys) {
|
|
417
|
+
for (const b of sys.bonds) {
|
|
418
|
+
const ai=sys.atoms[b.i], aj=sys.atoms[b.j];
|
|
419
|
+
if (ai.element !== 'H' && aj.element !== 'H') continue;
|
|
420
|
+
const d=pbcDistance(ai.pos.x-aj.pos.x, ai.pos.y-aj.pos.y, ai.pos.z-aj.pos.z, sys.box);
|
|
421
|
+
const dvx=ai.vel.x-aj.vel.x, dvy=ai.vel.y-aj.vel.y, dvz=ai.vel.z-aj.vel.z;
|
|
422
|
+
const lambda=(d.dx*dvx+d.dy*dvy+d.dz*dvz)/((d.dx*d.dx+d.dy*d.dy+d.dz*d.dz)*(1/ai.mass+1/aj.mass));
|
|
423
|
+
ai.vel.x-=lambda*d.dx/ai.mass; ai.vel.y-=lambda*d.dy/ai.mass; ai.vel.z-=lambda*d.dz/ai.mass;
|
|
424
|
+
aj.vel.x+=lambda*d.dx/aj.mass; aj.vel.y+=lambda*d.dy/aj.mass; aj.vel.z+=lambda*d.dz/aj.mass;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function dihedralEnergy(dih, atoms) {
|
|
429
|
+
const a = atoms[dih.i].pos, b = atoms[dih.j].pos, c = atoms[dih.k].pos, d = atoms[dih.l].pos;
|
|
430
|
+
const ab = pbcDistance(b.x-a.x, b.y-a.y, b.z-a.z, null);
|
|
431
|
+
const bc = pbcDistance(c.x-b.x, c.y-b.y, c.z-b.z, null);
|
|
432
|
+
const cd = pbcDistance(d.x-c.x, d.y-c.y, d.z-c.z, null);
|
|
433
|
+
const n1x = ab.dy*bc.dz-ab.dz*bc.dy, n1y = ab.dz*bc.dx-ab.dx*bc.dz, n1z = ab.dx*bc.dy-ab.dy*bc.dx;
|
|
434
|
+
const n2x = bc.dy*cd.dz-bc.dz*cd.dy, n2y = bc.dz*cd.dx-bc.dx*cd.dz, n2z = bc.dx*cd.dy-bc.dy*cd.dx;
|
|
435
|
+
const n1 = Math.hypot(n1x,n1y,n1z), n2 = Math.hypot(n2x,n2y,n2z), bn = Math.hypot(bc.dx,bc.dy,bc.dz);
|
|
436
|
+
if (n1 < 1e-10 || n2 < 1e-10 || bn < 1e-10) return 0;
|
|
437
|
+
const x = (n1x*n2x+n1y*n2y+n1z*n2z)/(n1*n2);
|
|
438
|
+
const mx = n1y*bc.dz-n1z*bc.dy, my = n1z*bc.dx-n1x*bc.dz, mz = n1x*bc.dy-n1y*bc.dx;
|
|
439
|
+
const y = (mx*n2x+my*n2y+mz*n2z)/(n1*n2*bn);
|
|
440
|
+
const phi = Math.atan2(y, Math.max(-1, Math.min(1, x)));
|
|
441
|
+
return dih.k * (1 + Math.cos(dih.n * phi - dih.delta));
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function initVelocities(atoms) {
|
|
445
|
+
for (const a of atoms) {
|
|
446
|
+
const std = Math.sqrt((config.kB*config.targetTemp*config.fta)/a.mass);
|
|
447
|
+
a.vel.x = std*randGaussian();
|
|
448
|
+
a.vel.y = std*randGaussian();
|
|
449
|
+
a.vel.z = std*randGaussian();
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function minimizeGeometry(sys, steps = 250) {
|
|
454
|
+
console.log("Minimizing geometry with L-BFGS...");
|
|
455
|
+
const n = sys.atoms.length * 3, memory = 8;
|
|
456
|
+
const dot = (a, b) => a.reduce((s, v, i) => s + v*b[i], 0);
|
|
457
|
+
const coordinates = () => sys.atoms.flatMap(a => [a.pos.x, a.pos.y, a.pos.z]);
|
|
458
|
+
const setCoordinates = x => sys.atoms.forEach((a, i) => { a.pos.x=x[3*i]; a.pos.y=x[3*i+1]; a.pos.z=x[3*i+2]; });
|
|
459
|
+
const gradient = f => f.flatMap(v => [-v.x, -v.y, -v.z]);
|
|
460
|
+
let state = await cf(sys), x = coordinates(), g = gradient(state.forces), history = [];
|
|
461
|
+
for (let iter = 0; iter < steps; iter++) {
|
|
462
|
+
const gNorm = Math.sqrt(dot(g,g)/n);
|
|
463
|
+
if (gNorm < 1e-3) break;
|
|
464
|
+
let q = g.slice(), alpha = [];
|
|
465
|
+
for (let h = history.length-1; h >= 0; h--) { alpha[h] = history[h].rho*dot(history[h].s,q); q=q.map((v,k)=>v-alpha[h]*history[h].y[k]); }
|
|
466
|
+
const last = history[history.length-1];
|
|
467
|
+
const scale = last ? dot(last.s,last.y)/dot(last.y,last.y) : 1;
|
|
468
|
+
let direction = q.map(v => -scale*v);
|
|
469
|
+
for (let h = 0; h < history.length; h++) { const beta=history[h].rho*dot(history[h].y,direction); direction=direction.map((v,k)=>v+history[h].s[k]*(alpha[h]-beta)); }
|
|
470
|
+
if (dot(direction,g) >= 0) direction = g.map(v => -v);
|
|
471
|
+
const baseEnergy = state.energy.total, slope = dot(g,direction);
|
|
472
|
+
let step = 0.02, next = null, nextX = null;
|
|
473
|
+
for (let ls = 0; ls < 20; ls++) {
|
|
474
|
+
nextX = x.map((v,k) => v + step*direction[k]); setCoordinates(nextX); applyPBC(sys); nextX = coordinates();
|
|
475
|
+
next = await cf(sys);
|
|
476
|
+
if (next.energy.total <= baseEnergy + 1e-4*step*slope) break;
|
|
477
|
+
step *= 0.5;
|
|
478
|
+
}
|
|
479
|
+
if (!next || next.energy.total > baseEnergy) { setCoordinates(x); break; }
|
|
480
|
+
const nextG = gradient(next.forces), s = nextX.map((v,k)=>v-x[k]), y=nextG.map((v,k)=>v-g[k]), sy=dot(s,y);
|
|
481
|
+
if (sy > 1e-10) { history.push({s,y,rho:1/sy}); if (history.length > memory) history.shift(); }
|
|
482
|
+
x=nextX; g=nextG; state=next;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
async function cf(sys) {
|
|
487
|
+
const forces = sys.forces;
|
|
488
|
+
for (let i = 0; i < forces.length; i++) {
|
|
489
|
+
forces[i].x = 0; forces[i].y = 0; forces[i].z = 0;
|
|
490
|
+
}
|
|
491
|
+
const energy = { bond: 0, angle: 0, dihedral: 0, lj: 0, coulomb: 0, total: 0 };
|
|
492
|
+
|
|
493
|
+
for (const b of sys.bonds) {
|
|
494
|
+
const a1 = sys.atoms[b.i];
|
|
495
|
+
const a2 = sys.atoms[b.j];
|
|
496
|
+
let delta = pbcDistance(a1.pos.x-a2.pos.x, a1.pos.y-a2.pos.y, a1.pos.z-a2.pos.z, sys.box);
|
|
497
|
+
const dx = delta.dx;
|
|
498
|
+
const dy = delta.dy;
|
|
499
|
+
const dz = delta.dz;
|
|
500
|
+
const r = Math.sqrt(dx*dx+dy*dy+dz*dz)+1e-9;
|
|
501
|
+
const stretch = r-b.r0;
|
|
502
|
+
const s = (-b.k*stretch)/r;
|
|
503
|
+
forces[b.i].x += dx*s;
|
|
504
|
+
forces[b.i].y += dy*s;
|
|
505
|
+
forces[b.i].z += dz*s;
|
|
506
|
+
forces[b.j].x -= dx*s;
|
|
507
|
+
forces[b.j].y -= dy*s;
|
|
508
|
+
forces[b.j].z -= dz*s;
|
|
509
|
+
energy.bond += 0.5*b.k*stretch*stretch;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
for (const ang of sys.angles) {
|
|
513
|
+
const a1 = sys.atoms[ang.i];
|
|
514
|
+
const a2 = sys.atoms[ang.j];
|
|
515
|
+
const a3 = sys.atoms[ang.k];
|
|
516
|
+
let d1 = pbcDistance(a1.pos.x-a2.pos.x, a1.pos.y-a2.pos.y, a1.pos.z-a2.pos.z, sys.box);
|
|
517
|
+
let d2 = pbcDistance(a3.pos.x-a2.pos.x, a3.pos.y-a2.pos.y, a3.pos.z-a2.pos.z, sys.box);
|
|
518
|
+
const dx1 = d1.dx; const dy1 = d1.dy; const dz1 = d1.dz;
|
|
519
|
+
const r1 = Math.sqrt(dx1*dx1+dy1*dy1+dz1*dz1)+1e-9;
|
|
520
|
+
const dx2 = d2.dx; const dy2 = d2.dy; const dz2 = d2.dz;
|
|
521
|
+
const r2 = Math.sqrt(dx2*dx2+dy2*dy2+dz2*dz2)+1e-9;
|
|
522
|
+
let cosTheta = (dx1*dx2+dy1*dy2+dz1*dz2)/(r1*r2);
|
|
523
|
+
cosTheta = Math.max(-1.0, Math.min(1.0, cosTheta));
|
|
524
|
+
const theta = Math.acos(cosTheta);
|
|
525
|
+
const dTheta = theta-ang.theta0;
|
|
526
|
+
energy.angle += 0.5*ang.kTheta*dTheta*dTheta;
|
|
527
|
+
let sinTheta = Math.sin(theta);
|
|
528
|
+
if (Math.abs(sinTheta) < 1e-6) sinTheta = 1e-6;
|
|
529
|
+
const factor = (-ang.kTheta*dTheta)/sinTheta;
|
|
530
|
+
const f1x = factor*(cosTheta*(dx1/r1)-(dx2/r2))/r1;
|
|
531
|
+
const f1y = factor*(cosTheta*(dy1/r1)-(dy2/r2))/r1;
|
|
532
|
+
const f1z = factor*(cosTheta*(dz1/r1)-(dz2/r2))/r1;
|
|
533
|
+
const f3x = factor*(cosTheta*(dx2/r2)-(dx1/r1))/r2;
|
|
534
|
+
const f3y = factor*(cosTheta*(dy2/r2)-(dy1/r1))/r2;
|
|
535
|
+
const f3z = factor*(cosTheta*(dz2/r2)-(dz1/r1))/r2;
|
|
536
|
+
forces[ang.i].x += f1x;
|
|
537
|
+
forces[ang.i].y += f1y;
|
|
538
|
+
forces[ang.i].z += f1z;
|
|
539
|
+
forces[ang.k].x += f3x;
|
|
540
|
+
forces[ang.k].y += f3y;
|
|
541
|
+
forces[ang.k].z += f3z;
|
|
542
|
+
forces[ang.j].x -= (f1x+f3x);
|
|
543
|
+
forces[ang.j].y -= (f1y+f3y);
|
|
544
|
+
forces[ang.j].z -= (f1z+f3z);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
for (const dih of sys.dihedrals) {
|
|
548
|
+
energy.dihedral += dihedralEnergy(dih, sys.atoms);
|
|
549
|
+
// Central differences are robust for all periodic multiplicities and make the
|
|
550
|
+
// previously energy-only torsion contribute a conservative force.
|
|
551
|
+
const h = 1e-5;
|
|
552
|
+
for (const atomIndex of [dih.i, dih.j, dih.k, dih.l]) {
|
|
553
|
+
const p = sys.atoms[atomIndex].pos;
|
|
554
|
+
for (const axis of ['x', 'y', 'z']) {
|
|
555
|
+
p[axis] += h; const plus = dihedralEnergy(dih, sys.atoms);
|
|
556
|
+
p[axis] -= 2*h; const minus = dihedralEnergy(dih, sys.atoms);
|
|
557
|
+
p[axis] += h;
|
|
558
|
+
forces[atomIndex][axis] -= (plus - minus) / (2*h);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
for (const [i, j] of sys.neighborList.getPairs()) {
|
|
564
|
+
const pairClass = sys.pairClasses.get(`${i}:${j}`) || 0;
|
|
565
|
+
const ai = sys.atoms[i], aj = sys.atoms[j];
|
|
566
|
+
const d = pbcDistance(ai.pos.x-aj.pos.x, ai.pos.y-aj.pos.y, ai.pos.z-aj.pos.z, sys.box);
|
|
567
|
+
const r2 = d.dx*d.dx+d.dy*d.dy+d.dz*d.dz;
|
|
568
|
+
if (r2 >= config.cutoff*config.cutoff || r2 < 1e-12) continue;
|
|
569
|
+
const r = Math.sqrt(r2);
|
|
570
|
+
const t = Math.max(0, Math.min(1, (r-config.switchDistance)/(config.cutoff-config.switchDistance)));
|
|
571
|
+
const sw = r <= config.switchDistance ? 1 : 1 - (10*t**3 - 15*t**4 + 6*t**5);
|
|
572
|
+
const dSw = r <= config.switchDistance ? 0 : -(30*t*t - 60*t**3 + 30*t**4)/(config.cutoff-config.switchDistance);
|
|
573
|
+
const li = ff.lj[ai.type] || ff.lj[ai.element] || ff.lj.undefDef;
|
|
574
|
+
const lj = ff.lj[aj.type] || ff.lj[aj.element] || ff.lj.undefDef;
|
|
575
|
+
const sig = 0.5*(li.sig+lj.sig), eps = Math.sqrt(li.eps*lj.eps);
|
|
576
|
+
const sr2 = (sig*sig)/r2, sr6 = sr2**3, sr12 = sr6*sr6;
|
|
577
|
+
const ljScale = pairClass === 2 ? config.oneFourLJScale : 1;
|
|
578
|
+
const coulScale = pairClass === 2 ? config.oneFourCoulombScale : 1;
|
|
579
|
+
const rawLJ = pairClass === 1 ? 0 : 4*eps*(sr12-sr6)*ljScale;
|
|
580
|
+
const rawLJForce = pairClass === 1 ? 0 : (24*eps*(2*sr12-sr6)/r2)*ljScale;
|
|
581
|
+
const qq=config.coulomb*ai.q*aj.q;
|
|
582
|
+
const erfcTerm=erfc(config.ewaldAlpha*r), ewaldReal=qq*erfcTerm/r;
|
|
583
|
+
const ewaldRealForce=qq*(erfcTerm/(r2*r)+(2*config.ewaldAlpha/Math.sqrt(Math.PI))*Math.exp(-((config.ewaldAlpha*r)**2))/r2);
|
|
584
|
+
const rawCoul = sys.pme ? (pairClass === 0 ? ewaldReal : 0) : (pairClass === 1 ? 0 : qq/r*coulScale);
|
|
585
|
+
const rawCoulForce = sys.pme ? (pairClass === 0 ? ewaldRealForce : 0) : (pairClass === 1 ? 0 : qq/(r2*r)*coulScale);
|
|
586
|
+
const forceOverR = sw*rawLJForce - rawLJ*dSw/r + (sys.pme ? rawCoulForce : sw*rawCoulForce-rawCoul*dSw/r);
|
|
587
|
+
const fx = d.dx*forceOverR, fy = d.dy*forceOverR, fz = d.dz*forceOverR;
|
|
588
|
+
forces[i].x += fx; forces[i].y += fy; forces[i].z += fz;
|
|
589
|
+
forces[j].x -= fx; forces[j].y -= fy; forces[j].z -= fz;
|
|
590
|
+
energy.lj += sw*rawLJ;
|
|
591
|
+
energy.coulomb += sys.pme ? rawCoul : sw*rawCoul;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (sys.pme) {
|
|
595
|
+
const reciprocal=sys.pme.compute(sys.atoms);
|
|
596
|
+
energy.coulomb += reciprocal.energy;
|
|
597
|
+
for (let i=0;i<sys.atoms.length;i++) forces[i].add(reciprocal.forces[i]);
|
|
598
|
+
// PME's reciprocal term contains all charge pairs. Restore the intended
|
|
599
|
+
// excluded/scaled topology using the complementary erf(alpha r)/r piece.
|
|
600
|
+
for (const [key, pairClass] of sys.pairClasses) {
|
|
601
|
+
const [i,j]=key.split(':').map(Number), ai=sys.atoms[i], aj=sys.atoms[j];
|
|
602
|
+
const d=pbcDistance(ai.pos.x-aj.pos.x,ai.pos.y-aj.pos.y,ai.pos.z-aj.pos.z,sys.box), r2=d.dx*d.dx+d.dy*d.dy+d.dz*d.dz;
|
|
603
|
+
if(r2 < 1e-12) continue;
|
|
604
|
+
const r=Math.sqrt(r2), qq=config.coulomb*ai.q*aj.q, erfTerm=1-erfc(config.ewaldAlpha*r);
|
|
605
|
+
const fErf=qq*(erfTerm/(r2*r)-(2*config.ewaldAlpha/Math.sqrt(Math.PI))*Math.exp(-((config.ewaldAlpha*r)**2))/r2);
|
|
606
|
+
const scale=pairClass===1 ? 0 : config.oneFourCoulombScale;
|
|
607
|
+
const correction=qq*(scale/r-erfTerm/r), fCorrection=scale*qq/(r2*r)-fErf;
|
|
608
|
+
const fx=d.dx*fCorrection, fy=d.dy*fCorrection, fz=d.dz*fCorrection;
|
|
609
|
+
forces[i].x+=fx;forces[i].y+=fy;forces[i].z+=fz;forces[j].x-=fx;forces[j].y-=fy;forces[j].z-=fz;
|
|
610
|
+
energy.coulomb+=correction;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
energy.total = energy.bond+energy.angle+energy.dihedral+energy.lj+energy.coulomb;
|
|
615
|
+
return { forces, energy };
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
async function stepBAOAB(sys, forces) {
|
|
619
|
+
const dt = config.dt;
|
|
620
|
+
const halfDt = 0.5*dt;
|
|
621
|
+
const decay = Math.exp(-config.gamma*dt);
|
|
622
|
+
for (let i = 0; i < sys.atoms.length; i++) {
|
|
623
|
+
const a = sys.atoms[i];
|
|
624
|
+
const mult = config.fta/a.mass*halfDt;
|
|
625
|
+
a.vel.x += forces[i].x*mult;
|
|
626
|
+
a.vel.y += forces[i].y*mult;
|
|
627
|
+
a.vel.z += forces[i].z*mult;
|
|
628
|
+
a.pos.x += a.vel.x*halfDt;
|
|
629
|
+
a.pos.y += a.vel.y*halfDt;
|
|
630
|
+
a.pos.z += a.vel.z*halfDt;
|
|
631
|
+
}
|
|
632
|
+
constrainHydrogenBonds(sys);
|
|
633
|
+
for (const a of sys.atoms) {
|
|
634
|
+
const std = Math.sqrt((1-decay*decay)*config.kB*config.targetTemp*config.fta/a.mass);
|
|
635
|
+
a.vel.x = decay*a.vel.x+std*randGaussian();
|
|
636
|
+
a.vel.y = decay*a.vel.y+std*randGaussian();
|
|
637
|
+
a.vel.z = decay*a.vel.z+std*randGaussian();
|
|
638
|
+
a.pos.x += a.vel.x*halfDt;
|
|
639
|
+
a.pos.y += a.vel.y*halfDt;
|
|
640
|
+
a.pos.z += a.vel.z*halfDt;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
applyPBC(sys);
|
|
644
|
+
constrainHydrogenBonds(sys);
|
|
645
|
+
|
|
646
|
+
const next = await cf(sys);
|
|
647
|
+
for (let i = 0; i < sys.atoms.length; i++) {
|
|
648
|
+
const a = sys.atoms[i];
|
|
649
|
+
const mult = config.fta/a.mass*halfDt;
|
|
650
|
+
a.vel.x += next.forces[i].x*mult;
|
|
651
|
+
a.vel.y += next.forces[i].y*mult;
|
|
652
|
+
a.vel.z += next.forces[i].z*mult;
|
|
653
|
+
}
|
|
654
|
+
rattleHydrogenBonds(sys);
|
|
655
|
+
return next;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function calcTemp(atoms) {
|
|
659
|
+
let ke = 0;
|
|
660
|
+
for (const a of atoms) {
|
|
661
|
+
ke += 0.5*a.mass*a.vel.magSq();
|
|
662
|
+
}
|
|
663
|
+
return (2*(ke/config.fta))/(3*atoms.length*config.kB);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const elementSymbols = new Set(Object.keys(masses).filter(k => k !== 'undefDef'));
|
|
667
|
+
const proteinAtomTypes = {
|
|
668
|
+
N:'N_AMIDE', H:'H_AMIDE', CA:'C_ALPHA', C:'C_CARBONYL', O:'O_CARBONYL', OXT:'O_CARBONYL',
|
|
669
|
+
CB:'C_ALIPHATIC', CG:'C_ALIPHATIC', CG1:'C_ALIPHATIC', CG2:'C_ALIPHATIC', CD:'C_ALIPHATIC', CD1:'C_ALIPHATIC', CD2:'C_ALIPHATIC', CE:'C_ALIPHATIC', CE1:'C_ALIPHATIC', CE2:'C_ALIPHATIC', CE3:'C_ALIPHATIC', CZ:'C_AROMATIC',
|
|
670
|
+
ND1:'N_AROMATIC', ND2:'N_AMIDE', NE:'N_AMINE', NE1:'N_AROMATIC', NE2:'N_AMIDE', NZ:'N_AMINE', NH1:'N_AMINE', NH2:'N_AMINE',
|
|
671
|
+
OG:'O_ALCOHOL', OG1:'O_ALCOHOL', OD1:'O_CARBONYL', OD2:'O_CARBONYL', OE1:'O_CARBONYL', OE2:'O_CARBONYL', OH:'O_ALCOHOL', SG:'S_THIOL', SD:'S_THIOETHER'
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
// Atom types, rather than bare elements, are the primary non-bonded keys. The
|
|
675
|
+
// values are deliberately explicit so a real parameter set can replace them
|
|
676
|
+
// without changing topology or force code.
|
|
677
|
+
Object.assign(ff.lj, {
|
|
678
|
+
N_AMIDE: {eps:0.17, sig:3.25}, N_AMINE:{eps:0.17, sig:3.25}, N_AROMATIC:{eps:0.17, sig:3.25},
|
|
679
|
+
H_AMIDE: {eps:0.015, sig:1.10}, C_ALPHA:{eps:0.066, sig:3.50}, C_ALIPHATIC:{eps:0.066, sig:3.50}, C_AROMATIC:{eps:0.070, sig:3.55}, C_CARBONYL:{eps:0.066, sig:3.50},
|
|
680
|
+
O_CARBONYL:{eps:0.1521, sig:3.15061}, O_ALCOHOL:{eps:0.1521, sig:3.15061}, S_THIOL:{eps:0.25, sig:3.60}, S_THIOETHER:{eps:0.25, sig:3.60}
|
|
681
|
+
});
|
|
682
|
+
const atomCharges = { N:-0.47, H:0.31, CA:0.07, HA:0.09, C:0.51, O:-0.51, OXT:-0.51, NZ:-0.30, NH1:-0.30, NH2:-0.30, NE:-0.30, ND2:-0.50, NE2:-0.50, OD1:-0.55, OD2:-0.55, OE1:-0.55, OE2:-0.55, OG:-0.42, OG1:-0.42, OH:-0.42, SG:-0.20, SD:-0.10 };
|
|
683
|
+
function inferElement(atomName, rawElement, record) {
|
|
684
|
+
const specified = rawElement.trim();
|
|
685
|
+
if (specified) { const e=specified[0].toUpperCase()+specified.slice(1).toLowerCase(); if (elementSymbols.has(e)) return e; }
|
|
686
|
+
const letters = atomName.replace(/[^A-Za-z]/g, '');
|
|
687
|
+
// PDB alignment distinguishes protein " CA " (carbon alpha) from "CA " (calcium).
|
|
688
|
+
if (record === 'ATOM' && proteinAtomTypes[atomName]) return atomName[0] === 'H' ? 'H' : ({N:'N',O:'O',S:'S'}[atomName[0]] || 'C');
|
|
689
|
+
const two = letters.slice(0,2); const candidate = two[0]?.toUpperCase()+two.slice(1).toLowerCase();
|
|
690
|
+
if (elementSymbols.has(candidate)) return candidate;
|
|
691
|
+
const one = letters[0]?.toUpperCase();
|
|
692
|
+
return elementSymbols.has(one) ? one : 'C';
|
|
693
|
+
}
|
|
694
|
+
function inferAtomType(atomName, resName, element) { return proteinAtomTypes[atomName] || `${resName}:${atomName}` || element; }
|
|
695
|
+
function inferCharge(atomName, resName, element) {
|
|
696
|
+
if (atomCharges[atomName] !== undefined) return atomCharges[atomName];
|
|
697
|
+
if (atomName.startsWith('H')) return 0.09;
|
|
698
|
+
if (element === 'O') return -0.35;
|
|
699
|
+
if (element === 'N') return -0.20;
|
|
700
|
+
return 0.0;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function loadPDB(filePath) {
|
|
704
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
705
|
+
const lines = content.split('\n');
|
|
706
|
+
const atoms = [];
|
|
707
|
+
const bonds = [];
|
|
708
|
+
const bondSet = new Set();
|
|
709
|
+
const addBond = (i, j) => {
|
|
710
|
+
const min = Math.min(i, j);
|
|
711
|
+
const max = Math.max(i, j);
|
|
712
|
+
const key = `${min}-${max}`;
|
|
713
|
+
if (min !== max && !bondSet.has(key)) {
|
|
714
|
+
bondSet.add(key);
|
|
715
|
+
bonds.push([min, max]);
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
const serialToIndex = new Map();
|
|
719
|
+
const resGroups = [];
|
|
720
|
+
let currentGroup = null;
|
|
721
|
+
let box = null;
|
|
722
|
+
|
|
723
|
+
for (const line of lines) {
|
|
724
|
+
if (line.startsWith('CRYST1')) {
|
|
725
|
+
const a = parseFloat(line.substring(6, 15).trim());
|
|
726
|
+
const b = parseFloat(line.substring(15, 24).trim());
|
|
727
|
+
const c = parseFloat(line.substring(24, 33).trim());
|
|
728
|
+
if (!isNaN(a) && !isNaN(b) && !isNaN(c) && a > 0 && b > 0 && c > 0) {
|
|
729
|
+
box = new Vec3(a, b, c);
|
|
730
|
+
}
|
|
731
|
+
} else if (line.startsWith('ATOM') || line.startsWith('HETATM')) {
|
|
732
|
+
const record = line.substring(0, 6).trim();
|
|
733
|
+
const serial = parseInt(line.substring(6, 11).trim(), 10);
|
|
734
|
+
const atomName = line.substring(12, 16).trim();
|
|
735
|
+
const resName = line.substring(17, 20).trim();
|
|
736
|
+
const chainID = line.substring(21, 22).trim();
|
|
737
|
+
const resSeq = parseInt(line.substring(22, 26).trim(), 10);
|
|
738
|
+
const element = inferElement(atomName, line.substring(76, 78), record);
|
|
739
|
+
const x = parseFloat(line.substring(30, 38).trim());
|
|
740
|
+
const y = parseFloat(line.substring(38, 46).trim());
|
|
741
|
+
const z = parseFloat(line.substring(46, 54).trim());
|
|
742
|
+
const q = inferCharge(atomName, resName, element);
|
|
743
|
+
const atom = new Atom(element, new Vec3(x, y, z), q, serial, inferAtomType(atomName, resName, element));
|
|
744
|
+
const idx = atoms.length;
|
|
745
|
+
serialToIndex.set(serial, idx);
|
|
746
|
+
atoms.push(atom);
|
|
747
|
+
if (!currentGroup || currentGroup.chainID !== chainID || currentGroup.resSeq !== resSeq) {
|
|
748
|
+
currentGroup = { chainID, resSeq, resName, atomIdx: {} };
|
|
749
|
+
resGroups.push(currentGroup);
|
|
750
|
+
}
|
|
751
|
+
currentGroup.atomIdx[atomName] = idx;
|
|
752
|
+
} else if (line.startsWith('CONECT')) {
|
|
753
|
+
const parts = line.substring(6).trim().split(/\s+/).map(v => parseInt(v, 10)).filter(v => !isNaN(v));
|
|
754
|
+
if (parts.length > 1) {
|
|
755
|
+
const srcSerial = parts[0];
|
|
756
|
+
const srcIdx = serialToIndex.get(srcSerial);
|
|
757
|
+
if (srcIdx !== undefined) {
|
|
758
|
+
for (let i = 1; i < parts.length; i++) {
|
|
759
|
+
const tgtIdx = serialToIndex.get(parts[i]);
|
|
760
|
+
if (tgtIdx !== undefined) {
|
|
761
|
+
addBond(srcIdx, tgtIdx);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
for (const group of resGroups) {
|
|
770
|
+
for (const [a1, a2] of backboneBondPairs) {
|
|
771
|
+
const i = group.atomIdx[a1];
|
|
772
|
+
const j = group.atomIdx[a2];
|
|
773
|
+
if (i !== undefined && j !== undefined) addBond(i, j);
|
|
774
|
+
}
|
|
775
|
+
const template = residueBondPairs[group.resName];
|
|
776
|
+
if (template) {
|
|
777
|
+
for (const [a1, a2] of template) {
|
|
778
|
+
const i = group.atomIdx[a1];
|
|
779
|
+
const j = group.atomIdx[a2];
|
|
780
|
+
if (i !== undefined && j !== undefined) addBond(i, j);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
const names = Object.keys(group.atomIdx);
|
|
784
|
+
const heavyNames = names.filter(n => atoms[group.atomIdx[n]].element !== 'H');
|
|
785
|
+
const hNames = names.filter(n => atoms[group.atomIdx[n]].element === 'H');
|
|
786
|
+
for (const hName of hNames) {
|
|
787
|
+
const hBase = hName.slice(1);
|
|
788
|
+
let bestName = null;
|
|
789
|
+
let bestLen = -1;
|
|
790
|
+
if (heavyNames.length === 1) {
|
|
791
|
+
bestName = heavyNames[0];
|
|
792
|
+
} else {
|
|
793
|
+
for (const heavyName of heavyNames) {
|
|
794
|
+
const heavyBase = heavyName.slice(1);
|
|
795
|
+
if (heavyBase.length > 0 && hBase.startsWith(heavyBase) && heavyBase.length > bestLen) {
|
|
796
|
+
bestName = heavyName;
|
|
797
|
+
bestLen = heavyBase.length;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (bestName === null && heavyNames.includes('N')) {
|
|
801
|
+
bestName = 'N';
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (bestName !== null) {
|
|
805
|
+
addBond(group.atomIdx[hName], group.atomIdx[bestName]);
|
|
806
|
+
} else {
|
|
807
|
+
// PDB hydrogen names may start with a digit (e.g. 1HH3). When a
|
|
808
|
+
// residue template does not identify their parent, use the chemically
|
|
809
|
+
// conservative nearest-heavy-atom fallback rather than silently omit
|
|
810
|
+
// the bond.
|
|
811
|
+
const h = atoms[group.atomIdx[hName]];
|
|
812
|
+
let nearest = null, nearestR2 = 1.25 * 1.25;
|
|
813
|
+
for (const heavyName of heavyNames) {
|
|
814
|
+
const heavy = atoms[group.atomIdx[heavyName]];
|
|
815
|
+
const dx=h.pos.x-heavy.pos.x, dy=h.pos.y-heavy.pos.y, dz=h.pos.z-heavy.pos.z;
|
|
816
|
+
const r2=dx*dx+dy*dy+dz*dz;
|
|
817
|
+
if (r2 < nearestR2) { nearest=heavyName; nearestR2=r2; }
|
|
818
|
+
}
|
|
819
|
+
if (nearest !== null) addBond(group.atomIdx[hName], group.atomIdx[nearest]);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
for (let g = 1; g < resGroups.length; g++) {
|
|
824
|
+
const prev = resGroups[g-1];
|
|
825
|
+
const curr = resGroups[g];
|
|
826
|
+
if (prev.chainID === curr.chainID && curr.resSeq === prev.resSeq+1) {
|
|
827
|
+
const i = prev.atomIdx['C'];
|
|
828
|
+
const j = curr.atomIdx['N'];
|
|
829
|
+
if (i !== undefined && j !== undefined) addBond(i, j);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return { atoms, bonds, box };
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function writeTrajectoryFrame(fd, atoms, step) {
|
|
837
|
+
let frameData = `${atoms.length}\nFrame ${step}\n`;
|
|
838
|
+
for (const a of atoms) {
|
|
839
|
+
frameData += `${a.element.padEnd(2)} ${a.pos.x.toFixed(5)} ${a.pos.y.toFixed(5)} ${a.pos.z.toFixed(5)}\n`;
|
|
840
|
+
}
|
|
841
|
+
fs.writeSync(fd, frameData);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
async function run(pdbPath, totalSteps, logInterval) {
|
|
845
|
+
if (!fs.existsSync(pdbPath)) {
|
|
846
|
+
console.error(`Error: PDB file not found at "${pdbPath}"`);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
console.log(`\nLoading structure from ${pdbPath}...`);
|
|
850
|
+
const { atoms, bonds, box } = loadPDB(pdbPath);
|
|
851
|
+
console.log(`Loaded ${atoms.length} atoms.`);
|
|
852
|
+
|
|
853
|
+
initVelocities(atoms);
|
|
854
|
+
console.log(`Loaded ${bonds.length} bonds from topology/PDB.`);
|
|
855
|
+
const sys = new System(atoms, bonds, box);
|
|
856
|
+
await minimizeGeometry(sys, 250);
|
|
857
|
+
const trajectoryFile = fs.openSync('traj.xyz', 'w');
|
|
858
|
+
let { forces } = await cf(sys);
|
|
859
|
+
const startTime = Date.now();
|
|
860
|
+
|
|
861
|
+
function calcRg(atoms) {
|
|
862
|
+
let totalMass = 0;
|
|
863
|
+
let cx = 0, cy = 0, cz = 0;
|
|
864
|
+
for (let i = 0; i < atoms.length; i++) {
|
|
865
|
+
const a = atoms[i];
|
|
866
|
+
totalMass += a.mass;
|
|
867
|
+
cx += a.pos.x*a.mass;
|
|
868
|
+
cy += a.pos.y*a.mass;
|
|
869
|
+
cz += a.pos.z*a.mass;
|
|
870
|
+
}
|
|
871
|
+
cx /= totalMass; cy /= totalMass; cz /= totalMass;
|
|
872
|
+
|
|
873
|
+
let sumSq = 0;
|
|
874
|
+
for (let i = 0; i < atoms.length; i++) {
|
|
875
|
+
const a = atoms[i];
|
|
876
|
+
const dx = a.pos.x-cx;
|
|
877
|
+
const dy = a.pos.y-cy;
|
|
878
|
+
const dz = a.pos.z-cz;
|
|
879
|
+
sumSq += a.mass*(dx*dx+dy*dy+dz*dz);
|
|
880
|
+
}
|
|
881
|
+
return Math.sqrt(sumSq/totalMass);
|
|
882
|
+
}
|
|
883
|
+
for (let s = 0; s < totalSteps; s++) {
|
|
884
|
+
const res = await stepBAOAB(sys, forces);
|
|
885
|
+
forces = res.forces;
|
|
886
|
+
if (s % logInterval === 0) {
|
|
887
|
+
writeTrajectoryFrame(trajectoryFile, sys.atoms, s);
|
|
888
|
+
const elapsedSec = ((Date.now()-startTime)/1000).toFixed(1);
|
|
889
|
+
const estRemainSec = s > 0 ? (((Date.now()-startTime)/s)*(totalSteps-s)/1000).toFixed(1) : "Calibrating...";
|
|
890
|
+
const rg = calcRg(sys.atoms).toFixed(2);
|
|
891
|
+
|
|
892
|
+
console.log(`Step ${s} | T: ${calcTemp(sys.atoms).toFixed(1)}K | E_tot: ${res.energy.total.toFixed(2)} | Rg: ${rg}Å | Elapsed: ${elapsedSec}s | ETA: ${estRemainSec}s`);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
fs.closeSync(trajectoryFile);
|
|
897
|
+
console.log('\nSimulation complete. Trajectory saved to traj.xyz');
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
async function promptUser() {
|
|
901
|
+
const rl = readline.createInterface({
|
|
902
|
+
input: process.stdin,
|
|
903
|
+
output: process.stdout
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
const ask = (query) => new Promise((resolve) => rl.question(query, resolve));
|
|
907
|
+
|
|
908
|
+
const pdbPath = (await ask('PDB file name (put in same folder as script): [default: input.pdb]: ')).trim() || 'input.pdb';
|
|
909
|
+
const stepsInput = (await ask('total simulation steps [default: 100]: ')).trim();
|
|
910
|
+
const logInput = (await ask('logged frame per steps [default: 1]: ')).trim();
|
|
911
|
+
|
|
912
|
+
rl.close();
|
|
913
|
+
|
|
914
|
+
const totalSteps = parseInt(stepsInput, 10) || 100;
|
|
915
|
+
const logInterval = parseInt(logInput, 10) || 1;
|
|
916
|
+
|
|
917
|
+
run(pdbPath, totalSteps, logInterval);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (require.main === module) promptUser();
|
|
921
|
+
module.exports = { Atom, Vec3, System, loadPDB, cf, minimizeGeometry, stepBAOAB, config };
|
|
922
|
+
|
|
923
|
+
/* THANK YOUS
|
|
924
|
+
https://www.compchems.com
|
|
925
|
+
https://galileo.phys.virginia.edu
|
|
926
|
+
https://stackexchange.com/
|
|
927
|
+
https://www.reddit.com/
|
|
928
|
+
*/
|