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