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 package ffx.algorithms.optimize;
40
41 import com.google.common.collect.Lists;
42 import com.google.common.collect.MinMaxPriorityQueue;
43 import ffx.numerics.math.HilbertCurveTransforms;
44 import ffx.potential.AssemblyState;
45 import ffx.potential.ForceFieldEnergy;
46 import ffx.potential.MolecularAssembly;
47 import ffx.potential.bonded.Atom;
48 import ffx.potential.bonded.Bond;
49 import ffx.potential.bonded.Molecule;
50 import org.apache.commons.math3.util.FastMath;
51
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.Collections;
55 import java.util.List;
56 import java.util.logging.Logger;
57
58 import static ffx.potential.utils.Superpose.applyRotation;
59 import static java.lang.String.format;
60 import static org.apache.commons.math3.util.FastMath.cos;
61 import static org.apache.commons.math3.util.FastMath.sin;
62 import static org.apache.commons.math3.util.FastMath.toRadians;
63
64
65
66
67
68
69
70
71
72
73
74 public class TorsionSearch {
75 private static final Logger logger = Logger.getLogger(TorsionSearch.class.getName());
76
77 private final MolecularAssembly molecularAssembly;
78 private final ForceFieldEnergy forceFieldEnergy;
79 private final double[] x;
80 private final boolean run;
81
82 private final List<Bond> torsionalBonds;
83 private final List<Atom[]> atomGroups;
84
85 private final int nTorsionsPerBond;
86 private final int nBits;
87 private int nTorsionalBonds;
88 private final int returnedStates;
89 private long numConfigs;
90 private long numIndices;
91 private long end;
92 private double minEnergy = Double.MAX_VALUE;
93 private final List<Double> energies = new ArrayList<>();
94 private final List<Long> hilbertIndices = new ArrayList<>();
95 private final List<AssemblyState> states = new ArrayList<>();
96 private MinMaxPriorityQueue<StateContainer> queue = null;
97
98 private long[] workerAssignments;
99 private long indicesPerAssignment;
100 private boolean runWorker = false;
101
102 public TorsionSearch(MolecularAssembly molecularAssembly,
103 Molecule molecule,
104 int nTorsionsPerBond,
105 int returnedStates
106 ) {
107 this.molecularAssembly = molecularAssembly;
108 if (Arrays.stream(molecularAssembly.getMoleculeArray()).anyMatch(mol -> mol == molecule)) {
109 run = true;
110 } else {
111 logger.warning("Molecule is not part of the assembly. Torsion scan will not run.");
112 run = false;
113 }
114 this.forceFieldEnergy = molecularAssembly.getPotentialEnergy();
115 this.x = new double[forceFieldEnergy.getNumberOfVariables()];
116 this.nTorsionsPerBond = nTorsionsPerBond;
117 this.nBits = (int) FastMath.ceil(FastMath.log(2, nTorsionsPerBond));
118 this.torsionalBonds = getTorsionalBonds(molecule);
119 this.atomGroups = getRotationGroups(torsionalBonds);
120 this.nTorsionalBonds = torsionalBonds.size();
121 this.numConfigs = (long) FastMath.pow(this.nTorsionsPerBond, torsionalBonds.size());
122 this.numIndices = (long) FastMath.pow(2, this.nBits * torsionalBonds.size());
123 if (returnedStates != -1) {
124 this.returnedStates = returnedStates;
125 } else {
126 this.returnedStates = (int) numConfigs;
127 }
128 this.end = numIndices;
129 }
130
131
132
133
134
135
136
137
138 public void spinTorsions() {
139 logger.info("\n ----------------- Starting torsion scan -----------------");
140 logger.info(format(" Number of configurations: %d", numConfigs));
141 logger.info(format(" Number of indices: %d", numIndices));
142 this.spinTorsions(0, this.end);
143 }
144
145
146
147
148
149 public void spinTorsions(long start, long end) {
150 logger.info("\n ----------------- Starting torsion scan -----------------");
151 logger.info(format(" Number of configurations: %d", numConfigs));
152 logger.info(format(" Number of indices: %d", numIndices));
153 this.spinTorsions(start, end, true);
154 }
155
156
157
158
159
160
161
162 private void spinTorsions(long start, long end, boolean updateLists) {
163 if (!run) {
164 logger.warning("Torsion spin returning early since molecule not part of molecular assembly.");
165 return;
166 }
167
168
169 long[] currentState = new long[nTorsionalBonds];
170
171
172 if (queue == null) {
173 queue = MinMaxPriorityQueue
174 .maximumSize(returnedStates)
175 .create();
176 }
177
178 int progress = 0;
179 while (start <= end && progress < numConfigs) {
180 long[] newState = HilbertCurveTransforms.hilbertIndexToCoordinates(nTorsionalBonds, nBits, start);
181 boolean exit = false;
182 for (long ind : newState) {
183 if (ind >= nTorsionsPerBond) {
184 exit = true;
185 break;
186 }
187 }
188 if (exit) {
189
190 start++;
191 continue;
192 }
193
194
195 changeState(currentState, newState, nTorsionsPerBond, torsionalBonds, atomGroups);
196
197 forceFieldEnergy.getCoordinates(x);
198
199 double energy = forceFieldEnergy.energy(x);
200
201 if (energy < minEnergy) {
202 minEnergy = energy;
203 logger.info(format("\n New minimum energy: %12.5f", minEnergy));
204 logger.info(format(" Hilbert Index: %d; Coordinate State: " + Arrays.toString(newState), start));
205 }
206
207 queue.add(new StateContainer(new AssemblyState(molecularAssembly), energy, start));
208 currentState = newState;
209 start++;
210 progress++;
211 }
212
213 changeState(currentState, new long[nTorsionalBonds], nTorsionsPerBond, torsionalBonds, atomGroups);
214 if (progress == numConfigs) {
215 logger.info("\n Completed all configurations before end index.");
216 }
217 if (updateLists) {
218 this.updateInfoLists();
219 }
220 }
221
222
223
224
225
226
227
228
229 public void staticAnalysis(int numRemove, double eliminationThreshold) {
230 if (!run) {
231 logger.warning("Static analysis returning early since molecule not part of molecular assembly.");
232 return;
233 }
234 if (queue == null) {
235 queue = MinMaxPriorityQueue
236 .maximumSize(returnedStates)
237 .create();
238 }
239 eliminationThreshold = FastMath.abs(eliminationThreshold);
240
241 forceFieldEnergy.getCoordinates(x);
242
243 double initialE = forceFieldEnergy.energy(x);
244 ArrayList<Integer> remove = new ArrayList<>();
245 long[] state = new long[nTorsionalBonds];
246 long[] oldState = new long[nTorsionalBonds];
247
248 for (int i = 0; i < nTorsionalBonds; i++) {
249 for (int j = i == 0 ? 0 : 1; j < nTorsionsPerBond; j++) {
250 state[i] = j;
251 changeState(oldState, state, nTorsionsPerBond, torsionalBonds, atomGroups);
252
253 forceFieldEnergy.getCoordinates(x);
254
255 double newEnergy = forceFieldEnergy.energy(x);
256 if (newEnergy - initialE > eliminationThreshold && !remove.contains(i)) {
257 remove.add(i);
258 } else {
259 queue.add(new StateContainer(new AssemblyState(molecularAssembly), newEnergy, -1));
260 }
261 changeState(state, oldState, nTorsionsPerBond, torsionalBonds, atomGroups);
262 }
263 state[i] = 0;
264 }
265 remove.sort(Collections.reverseOrder());
266 logger.info("\n " + remove.size() + " bonds that cause large energy increase: " + remove);
267 if (remove.size() > numRemove) {
268 remove = Lists.newArrayList(remove.subList(0, numRemove));
269 }
270 for (int i = remove.size() - 1; i >= 0; i--) {
271 logger.info(" Removing bond: " + torsionalBonds.get(remove.get(i)));
272 logger.info(" Bond index: " + remove.get(i));
273 torsionalBonds.set(remove.get(i), null);
274 atomGroups.set(remove.get(i), null);
275 }
276 torsionalBonds.removeAll(Collections.singleton(null));
277 atomGroups.removeAll(Collections.singleton(null));
278 this.nTorsionalBonds = torsionalBonds.size();
279
280 this.end = (long) FastMath.pow(2, nBits * nTorsionalBonds);
281 this.numConfigs = (long) FastMath.pow(nTorsionsPerBond, nTorsionalBonds);
282 this.numIndices = this.end;
283 logger.info(" Finished static analysis.");
284 logger.info(format(" Number of configurations after elimination: %d", numConfigs));
285 logger.info(format(" Number of indices after elimination: %d", numIndices));
286 this.updateInfoLists();
287 }
288
289
290
291
292
293
294
295
296 public boolean buildWorker(int rank, int worldSize) {
297 if (!run) {
298 logger.warning("Build worker returning early since molecule not part of molecular assembly.");
299 return false;
300 }
301 if (rank >= worldSize) {
302 logger.warning(" Rank is greater than world size.");
303 return false;
304 }
305 runWorker = true;
306
307 this.workerAssignments = new long[worldSize];
308 long jobsPerWorker = numIndices / worldSize;
309 this.indicesPerAssignment = jobsPerWorker / worldSize;
310 logger.info("\n Jobs per worker: " + jobsPerWorker);
311 logger.info(" Jobs per worker split: " + indicesPerAssignment);
312
313 for (int i = 0; i < worldSize; i++) {
314 workerAssignments[i] = i * jobsPerWorker + rank * indicesPerAssignment;
315 }
316 logger.info(" Worker " + rank + " assigned indices: " + Arrays.toString(workerAssignments));
317 return true;
318 }
319
320
321
322
323 public void runWorker() {
324 if (!run) {
325 logger.warning("Worker returning early since molecule not part of molecular assembly.");
326 return;
327 } else if (!runWorker) {
328 logger.warning("Worker returning early since worker not built or is invalid.");
329 return;
330 }
331 logger.info("\n ----------------- Starting torsion scan -----------------");
332 logger.info(format("\n Number of configurations before worker starts: %d", numConfigs));
333 logger.info(format(" Number of indices before worker starts: %d", numIndices));
334 for (int i = 0; i < workerAssignments.length; i++) {
335 logger.info(format(" Worker torsion assignment %3d of %3d: %12d to %12d.", i + 1, workerAssignments.length,
336 workerAssignments[i], workerAssignments[i] + indicesPerAssignment - 1));
337
338 this.spinTorsions(workerAssignments[i], workerAssignments[i] + indicesPerAssignment - 1,
339 i == workerAssignments.length - 1);
340 }
341 }
342
343
344
345
346 public List<Double> getEnergies() {
347 return energies;
348 }
349
350
351
352
353 public List<Long> getHilbertIndices() {
354 return hilbertIndices;
355 }
356
357
358
359
360 public List<AssemblyState> getStates() {
361 return states;
362 }
363
364
365
366
367 public long getEnd() {
368 return end;
369 }
370
371
372
373
374 private void updateInfoLists() {
375 if (!states.isEmpty()) {
376
377 for (int i = 0; i < states.size(); i++) {
378 queue.add(new StateContainer(states.get(i), energies.get(i), hilbertIndices.get(i)));
379 }
380
381 states.clear();
382 energies.clear();
383 hilbertIndices.clear();
384 }
385
386 while (!queue.isEmpty()) {
387 StateContainer toBeSaved = queue.removeFirst();
388 states.add(toBeSaved.getState());
389 energies.add(toBeSaved.getEnergy());
390 hilbertIndices.add(toBeSaved.getIndex());
391 }
392 }
393
394
395
396
397
398
399
400 private static List<Bond> getTorsionalBonds(Molecule molecule) {
401 List<Bond> torsionalBonds = new ArrayList<>();
402 for (Bond bond : molecule.getBondList()) {
403 Atom a1 = bond.getAtom(0);
404 Atom a2 = bond.getAtom(1);
405 List<Bond> bond1 = a1.getBonds();
406 int b1 = bond1.size();
407 List<Bond> bond2 = a2.getBonds();
408 int b2 = bond2.size();
409
410 if (b1 > 1 && b2 > 1) {
411
412 if (a1.getAtomicNumber() == 6 && a1.getNumberOfBondedHydrogen() == 3 || a2.getAtomicNumber() == 6 && a2.getNumberOfBondedHydrogen() == 3) {
413 continue;
414 }
415
416 if (a1.isRing(a2)) {
417 continue;
418 }
419 torsionalBonds.add(bond);
420 logger.info(" Bond " + bond + " is a torsional bond.");
421 }
422 }
423 return torsionalBonds;
424 }
425
426
427
428
429
430
431
432 private static List<Atom[]> getRotationGroups(List<Bond> bonds) {
433 List<Atom[]> rotationGroups = new ArrayList<>();
434 for (Bond bond : bonds) {
435 Atom a1 = bond.getAtom(0);
436 Atom a2 = bond.getAtom(1);
437
438 List<Atom> a1List = new ArrayList<>();
439 List<Atom> a2List = new ArrayList<>();
440
441 searchTorsions(a1, a1List, a2);
442 searchTorsions(a2, a2List, a1);
443
444 Atom[] a1Array = new Atom[a1List.size()];
445 Atom[] a2Array = new Atom[a2List.size()];
446 a1List.toArray(a1Array);
447 a2List.toArray(a2Array);
448
449 if (a1List.size() > a2List.size()) {
450 rotationGroups.add(a2Array);
451 } else {
452 rotationGroups.add(a1Array);
453 }
454 }
455 return rotationGroups;
456 }
457
458
459
460
461
462
463
464
465 private static void rotateAbout(double[] u, Atom a2, double theta) {
466
467 theta = toRadians(theta);
468
469
470 double[] quaternion = new double[4];
471 quaternion[0] = cos(theta / 2);
472 quaternion[1] = u[0] * sin(theta / 2);
473 quaternion[2] = u[1] * sin(theta / 2);
474 quaternion[3] = u[2] * sin(theta / 2);
475
476 double quaternionNorm = 1 / Math.sqrt(quaternion[0] * quaternion[0] + quaternion[1] * quaternion[1]
477 + quaternion[2] * quaternion[2] + quaternion[3] * quaternion[3]);
478 for (int i = 0; i < 4; i++) {
479 quaternion[i] *= quaternionNorm;
480 }
481
482 double q1q1 = quaternion[1] * quaternion[1];
483 double q2q2 = quaternion[2] * quaternion[2];
484 double q3q3 = quaternion[3] * quaternion[3];
485 double q0q1 = quaternion[0] * quaternion[1];
486 double q0q2 = quaternion[0] * quaternion[2];
487 double q0q3 = quaternion[0] * quaternion[3];
488 double q1q2 = quaternion[1] * quaternion[2];
489 double q1q3 = quaternion[1] * quaternion[3];
490 double q2q3 = quaternion[2] * quaternion[3];
491
492 double[][] rotation2 = new double[3][3];
493 rotation2[0][0] = 1 - 2 * (q2q2 + q3q3);
494 rotation2[0][1] = 2 * (q1q2 - q0q3);
495 rotation2[0][2] = 2 * (q1q3 + q0q2);
496 rotation2[1][0] = 2 * (q1q2 + q0q3);
497 rotation2[1][1] = 1 - 2 * (q1q1 + q3q3);
498 rotation2[1][2] = 2 * (q2q3 - q0q1);
499 rotation2[2][0] = 2 * (q1q3 - q0q2);
500 rotation2[2][1] = 2 * (q2q3 + q0q1);
501 rotation2[2][2] = 1 - 2 * (q1q1 + q2q2);
502
503 double[] a2XYZ = new double[3];
504 a2.getXYZ(a2XYZ);
505 applyRotation(a2XYZ, rotation2);
506 a2.setXYZ(a2XYZ);
507 }
508
509
510
511
512
513
514
515
516 private static void searchTorsions(Atom seed, List<Atom> atoms, Atom notAtom) {
517 if (seed == null) {
518 return;
519 }
520 atoms.add(seed);
521 for (Bond b : seed.getBonds()) {
522 Atom nextAtom = b.get1_2(seed);
523 if (nextAtom == notAtom || atoms.contains(nextAtom)) {
524 continue;
525 }
526 searchTorsions(nextAtom, atoms, notAtom);
527 }
528 }
529
530
531
532
533
534
535
536
537
538
539 private static void changeState(long[] oldState, long[] newState, int nTorsions,
540 List<Bond> bonds, List<Atom[]> atoms) {
541 for (int i = 0; i < oldState.length; i++) {
542 if (oldState[i] != newState[i]) {
543
544 int change = (int) (newState[i] - oldState[i]);
545
546 int turnDegrees = change * (360 / nTorsions);
547
548 double[] u = new double[3];
549 double[] translation = new double[3];
550 double[] a1 = bonds.get(i).getAtom(0).getXYZ(new double[3]);
551 double[] a2 = bonds.get(i).getAtom(1).getXYZ(new double[3]);
552 if (atoms.get(i)[0] == bonds.get(i).getAtom(0)) {
553 for (int j = 0; j < 3; j++) {
554
555 u[j] = a1[j] - a2[j];
556 translation[j] = -a1[j];
557 }
558
559 double sqrt = Math.sqrt(u[0] * u[0] + u[1] * u[1] + u[2] * u[2]);
560 double unit = 1.0 / sqrt;
561 for (int j = 0; j < 3; j++) {
562 u[j] *= unit;
563 }
564 for (int j = 0; j < atoms.get(i).length; j++) {
565 atoms.get(i)[j].move(translation);
566 rotateAbout(u, atoms.get(i)[j], turnDegrees);
567 atoms.get(i)[j].move(a1);
568 }
569 } else {
570 for (int j = 0; j < 3; j++) {
571 u[j] = a2[j] - a1[j];
572 translation[j] = -a2[j];
573 }
574 double sqrt = Math.sqrt(u[0] * u[0] + u[1] * u[1] + u[2] * u[2]);
575 double unit = 1.0 / sqrt;
576 for (int j = 0; j < 3; j++) {
577 u[j] *= unit;
578 }
579 for (int j = 0; j < atoms.get(i).length; j++) {
580 atoms.get(i)[j].move(translation);
581 rotateAbout(u, atoms.get(i)[j], turnDegrees);
582 atoms.get(i)[j].move(a2);
583 }
584 }
585 }
586 }
587 }
588
589
590
591
592 private record StateContainer(AssemblyState state, double e,
593 long hilbertIndex) implements Comparable<StateContainer> {
594 AssemblyState getState() {
595 return state;
596 }
597
598 double getEnergy() {
599 return e;
600 }
601
602 long getIndex() {
603 return hilbertIndex;
604 }
605
606 @Override
607 public int compareTo(StateContainer o) {
608 return Double.compare(e, o.getEnergy());
609 }
610 }
611 }