1 package ffx.algorithms.optimize;
2
3 import com.google.common.collect.MinMaxPriorityQueue;
4 import ffx.algorithms.AlgorithmListener;
5 import ffx.potential.AssemblyState;
6 import ffx.potential.ForceFieldEnergy;
7 import ffx.potential.MolecularAssembly;
8 import ffx.potential.bonded.Atom;
9 import ffx.potential.bonded.Bond;
10 import ffx.potential.bonded.Molecule;
11 import ffx.potential.nonbonded.pme.Polarization;
12 import ffx.potential.bonded.RestrainDistance;
13 import ffx.potential.parameters.BondType;
14 import ffx.potential.parsers.XYZFilter;
15
16 import java.io.File;
17 import java.util.AbstractQueue;
18 import java.util.ArrayList;
19 import java.util.concurrent.ArrayBlockingQueue;
20 import java.util.logging.Logger;
21
22 import static ffx.potential.utils.Superpose.applyRotation;
23 import static java.lang.String.format;
24 import static org.apache.commons.math3.util.FastMath.*;
25
26
27
28
29
30
31
32
33
34
35
36
37
38 public class ConformationScan {
39
40 private static final Logger logger = Logger.getLogger(ConformationScan.class.getName());
41
42 private final MolecularAssembly mola;
43 private final ForceFieldEnergy forceFieldEnergy;
44 private final AssemblyState initState;
45 private double[] x;
46 private AlgorithmListener algorithmListener;
47
48 private final Molecule[] s1;
49 private final Molecule[] s2;
50 private final Atom[] s1Atoms;
51 private final Atom[] s2Atoms;
52 private final ArrayList<Atom> s1TargetAtoms;
53 private final ArrayList<Atom> s2TargetAtoms;
54
55 private AbstractQueue<StateContainer> statesQueue;
56
57 private final double eps;
58 private final int maxIter;
59 private double hBondDist;
60 private double flatBottomRadius;
61 private final boolean tScan;
62 private final boolean excludeH;
63 private final boolean minimize;
64
65 private double m1MinEnergy;
66 private double m2MinEnergy;
67 private double totalMonomerMinimizedEnergy;
68
69 private double minimumEnergy;
70 private double averageEnergy;
71 private double averageEnergyNoOutlier;
72 private double stdOfEnergies;
73 private double stdOfEnergiesNoOutlier;
74
75 public ConformationScan(
76 MolecularAssembly molecularAssembly,
77 Molecule[] systemOne,
78 Molecule[] systemTwo,
79 double minimizeEps,
80 int minimizeMaxIterations,
81 boolean staticTorsionScan,
82 boolean excludeExtraHydrogen,
83 boolean minimize
84 )
85 {
86 mola = molecularAssembly;
87 forceFieldEnergy = molecularAssembly.getPotentialEnergy();
88 initState = new AssemblyState(mola);
89 s1 = systemOne;
90 s2 = systemTwo;
91 eps = minimizeEps;
92 maxIter = minimizeMaxIterations;
93 tScan = staticTorsionScan;
94 excludeH = excludeExtraHydrogen;
95 this.minimize = minimize;
96 s1Atoms = getAtomsFromMoleculeArray(s1);
97 s2Atoms = getAtomsFromMoleculeArray(s2);
98 s1TargetAtoms = new ArrayList<>();
99 s2TargetAtoms = new ArrayList<>();
100 setTargetAtoms(mola.getAtomArray());
101 statesQueue = MinMaxPriorityQueue.maximumSize(s1TargetAtoms.size() * s2TargetAtoms.size())
102 .create();
103 if(!minimize) {
104 statesQueue = new ArrayBlockingQueue<>(s1TargetAtoms.size() * s2TargetAtoms.size() + 1);
105 }
106 }
107
108 public void scan(){
109 systemEnergies();
110 double[] zAxis = new double[]{0,0,1};
111
112 int loopCounter = 1;
113 for(Atom a: s1TargetAtoms){
114 for(Atom b: s2TargetAtoms){
115 zAxis[2] = 1;
116 initState.revertState();
117 alignSystemCOMtoAtomVecWithAxis(a, zAxis, s1Atoms);
118 zAxis[2] = -1;
119 logger.info("\n ----- Trial " + loopCounter + " out of " +
120 (s2TargetAtoms.size() * s1TargetAtoms.size()) + " -----");
121 loopCounter++;
122 alignSystemCOMtoAtomVecWithAxis(b, zAxis, s2Atoms);
123
124 hBondDist = 2.0;
125 double[] hBondVector = new double[]{0, 0, a.getZ() - b.getZ() + hBondDist};
126 logger.info(" Initial H-bond distance: " + hBondVector[2]);
127
128 forceFieldEnergy.getPmeNode().setPolarization(Polarization.NONE);
129 hBondVector[2] += minimizeVector(hBondVector, -15, 15)[2];
130 forceFieldEnergy.getPmeNode().setPolarization(Polarization.MUTUAL);
131 logger.info(" Best H-bond distance: " + hBondVector[2]);
132 hBondDist = hBondVector[2] - (a.getZ() - b.getZ());
133 flatBottomRadius = Math.abs(hBondDist / 2.0);
134 logger.info(" Flat bottom radius: " + flatBottomRadius);
135
136
137 try {
138 mola.update();
139 forceFieldEnergy.getCoordinates(x);
140 int status = 0;
141 if(minimize) {
142 status = minimizeSystem(a, b);
143 }
144 if(status != -1) {
145 forceFieldEnergy.getCoordinates(x);
146 double e = forceFieldEnergy.energy(x, true) - totalMonomerMinimizedEnergy;
147 logger.info("\n Binding energy of trial " + (loopCounter - 1) + ": " + e);
148 statesQueue.add(new StateContainer(new AssemblyState(mola), e));
149 } else {
150 logger.warning(" Minimization failed. No state will be saved.");
151
152 }
153 } catch (Exception ignored) {
154 logger.warning(" Minimization failed. No state will be saved.");
155
156
157 }
158 }
159 }
160 logger.info("\n ------------------------- End of Trials -------------------------");
161 if(statesQueue.peek() != null) {
162 minimumEnergy = statesQueue.peek().getEnergy();
163 } else{
164 logger.warning(" No states were saved. No minimum energy found.");
165 minimumEnergy = Double.NaN;
166 return;
167 }
168 calculateMeanStd();
169 }
170
171 public void logAllEnergyInformation(){
172 logger.info(format(" Minimum energy of monomer 1: %12.5f", m1MinEnergy));
173 logger.info(format(" Minimum energy of monomer 2: %12.5f", m2MinEnergy));
174 logger.info(format(" Sum of minimum monomers: %12.5f", totalMonomerMinimizedEnergy));
175 logger.info(format(" Minimum binding energy of system: %12.5f", minimumEnergy));
176 logger.info(format(" Average binding energy: %12.5f +- %6.3f", averageEnergy, stdOfEnergies));
177 logger.info(format(" Average binding energy (no outlier): %12.5f +- %6.3f", averageEnergyNoOutlier, stdOfEnergiesNoOutlier));
178 }
179 public boolean writeStructuresToXYZ(File outputFile){
180 XYZFilter xyzFilter = new XYZFilter(outputFile, mola, mola.getForceField(), mola.getForceField().getProperties());
181 logger.info("\n Writing structures to " + outputFile.getAbsolutePath());
182 int count = 1;
183 StateContainer[] states = new StateContainer[statesQueue.size()];
184 if(minimize){
185
186 int index = 0;
187 while(!statesQueue.isEmpty() && index < states.length){
188 states[index] = statesQueue.poll();
189 index++;
190 }
191 if(index < states.length){
192 logger.warning(" Not all states will be saved. ");
193 return false;
194 }
195 }
196 else{
197 statesQueue.toArray(states);
198 }
199 if(states.length == 0){
200 logger.warning(" No states were saved. No structures will be written.");
201 return false;
202 }
203 for(StateContainer s: states){
204 AssemblyState state = s.getState();
205 double energy = s.getEnergy();
206 logger.info(" Writing configuration #" + count + " with energy: " + energy);
207 state.revertState();
208 xyzFilter.writeFile(outputFile, count != 1);
209 count++;
210 }
211 return true;
212 }
213
214 public static ArrayList<Double> logBindingEnergyCalculation(ConformationScan m1, ConformationScan m2, ConformationScan dimer){
215 ArrayList<Double> bindingEnergies = new ArrayList<>();
216 logger.info("\n ------------------------- Binding Energy -------------------------");
217 logger.info(" ddE (of binding) = dE (of coformer dimer binding) - dE (average of both monomer dimer bindings summed)");
218 logger.info("\n Calculation using minimized energies: ");
219 double averageMonomerEnergy = (m1.getMinimumEnergy() + m2.getMinimumEnergy()) / 2;
220 double bindingE = dimer.getMinimumEnergy() - averageMonomerEnergy;
221 logger.info(format(" Minimal Structure ddE = %8.3f - %8.3f = %12.5f",
222 dimer.getMinimumEnergy(),
223 averageMonomerEnergy,
224 bindingE));
225 bindingEnergies.add(bindingE);
226 logger.info("\n Calculation using average energies: ");
227 averageMonomerEnergy = (m1.getAverageEnergy() + m2.getAverageEnergy()) / 2;
228 bindingE = dimer.getAverageEnergy() - averageMonomerEnergy;
229 logger.info(format(" Average ddE = %8.3f - %8.3f = %12.5f",
230 dimer.getAverageEnergy(),
231 averageMonomerEnergy,
232 bindingE));
233 bindingEnergies.add(bindingE);
234 logger.info("\n Calculation using average energies (no outliers): ");
235 averageMonomerEnergy = (m1.getAverageEnergyNoOutlier() + m2.getAverageEnergyNoOutlier()) / 2;
236 bindingE = dimer.getAverageEnergyNoOutlier() - averageMonomerEnergy;
237 logger.info(format(" Average (no outlier) ddE = %8.3f - %8.3f = %12.5f",
238 dimer.getAverageEnergyNoOutlier(),
239 averageMonomerEnergy,
240 bindingE));
241 bindingEnergies.add(bindingE);
242 logger.info("\n N.B. -- Monomer energies are calculated using the minimized structures in all cases.");
243 logger.info(" ------------------------- End of Binding Energy -------------------------\n");
244 return bindingEnergies;
245 }
246
247 public ArrayList<AssemblyState> getStates(){
248 ArrayList<AssemblyState> states = new ArrayList<>();
249 for(StateContainer s: statesQueue){
250 states.add(s.getState());
251 }
252 return states;
253 }
254 public ArrayList<AssemblyState> getStatesWithinEnergy(double energy){
255 ArrayList<AssemblyState> states = new ArrayList<>();
256 StateContainer minState = statesQueue.peek();
257 if(minState != null){
258 double minEnergy = minState.getEnergy();
259 for(StateContainer s: statesQueue){
260 if(s.getEnergy() - minEnergy < energy){
261 states.add(s.getState());
262 }
263 }
264 }
265 return states;
266 }
267 public ArrayList<AssemblyState> getStatesFilteredByRMSD(double rmsdCutoff){return null;}
268 public ArrayList<AssemblyState> getStatesAroundAverage(double averageE, double radialCutoff){return null;}
269 public ArrayList<Double> getEnergies(){
270 ArrayList<Double> energies = new ArrayList<>();
271 for(StateContainer s: statesQueue){
272 energies.add(s.getEnergy());
273 }
274 return energies;
275 }
276 public ArrayList<Double> getEnergiesWithinEnergy(double energy){
277 ArrayList<Double> energies = new ArrayList<>();
278 StateContainer minState = statesQueue.peek();
279 if(minState != null){
280 double minEnergy = minState.getEnergy();
281 for(StateContainer s: statesQueue){
282 if(s.getEnergy() - minEnergy < energy){
283 energies.add(s.getEnergy());
284 }
285 }
286 }
287 return energies;
288 }
289 public double getM1MinEnergy() {return m1MinEnergy;}
290 public double getM2MinEnergy() {return m2MinEnergy;}
291 public double getTotalMonomerMinimizedEnergy() {return totalMonomerMinimizedEnergy;}
292 public double getMinimumEnergy() {return minimumEnergy;}
293 public double getAverageEnergy() {return averageEnergy;}
294 public double getAverageEnergyNoOutlier() {return averageEnergyNoOutlier;}
295 public double getStdOfEnergies() {return stdOfEnergies;}
296 public double getStdOfEnergiesNoOutlier() {return stdOfEnergiesNoOutlier;}
297 private double[] minimizeVector(double[] hBondVector, int lowBound, int highBound) {
298 highBound += 1;
299
300 double[] coarsePotentialSurface = new double[Math.abs(highBound - lowBound)];
301 double[] zSearched = new double[Math.abs(highBound - lowBound)];
302 double[] coarseVector = new double[3];
303 int minIndex = -1;
304 double minE = Double.MAX_VALUE;
305 coarseVector[2] = hBondVector[2] + lowBound;
306 for(int i = 0; i < Math.abs(highBound - lowBound); i++){
307 zSearched[i] = i == 0 ? lowBound : zSearched[i - 1] + 1;
308
309 for(Atom a: s2Atoms){
310 a.move(coarseVector);
311 }
312
313 forceFieldEnergy.getCoordinates(x);
314 coarsePotentialSurface[i] = forceFieldEnergy.energy(x, false);
315 if(coarsePotentialSurface[i] < minE){
316 minE = coarsePotentialSurface[i];
317 minIndex = i;
318 }
319 coarseVector[2] = 1;
320 }
321
322
323 for (Atom s2Atom : s2Atoms) {
324 s2Atom.move(new double[]{0, 0, hBondVector[2] - zSearched[zSearched.length - 1]});
325 }
326
327
328
329
330 double[] refinedVector = new double[3];
331 double a;
332 double aPotential;
333 if (minIndex == 0) {
334 a = zSearched[minIndex + 1];
335 aPotential = coarsePotentialSurface[minIndex + 1];
336 } else if (minIndex == coarsePotentialSurface.length - 1) {
337 a = zSearched[minIndex - 1];
338 aPotential = coarsePotentialSurface[minIndex - 1];
339 } else {
340 a = coarsePotentialSurface[minIndex - 1] < coarsePotentialSurface[minIndex + 1] ?
341 zSearched[minIndex - 1] : zSearched[minIndex + 1];
342 aPotential = Math.min(coarsePotentialSurface[minIndex - 1], coarsePotentialSurface[minIndex + 1]);
343 }
344 double b = zSearched[minIndex];
345 double bPotential = coarsePotentialSurface[minIndex];
346 double c = 0;
347 double convergence = 1e-5;
348 while(Math.abs(aPotential - bPotential) > convergence){
349 refinedVector[2] = (a + b) / 2 - c;
350
351 for(Atom a1: s2Atoms){
352 a1.move(refinedVector);
353 }
354
355 forceFieldEnergy.getCoordinates(x);
356 double e = forceFieldEnergy.energy(x, false);
357
358 minE = Math.min(e, minE);
359 if(aPotential > bPotential && e < aPotential){
360 a = (a+b)/2;
361 aPotential = e;
362 c = a;
363 } else if(e < bPotential){
364 b = (a+b)/2;
365 bPotential = e;
366 c = b;
367 } else{
368
369 c = (a + b) / 2;
370 if(aPotential < bPotential){
371 refinedVector[2] = a - c;
372 for(Atom a1: s2Atoms){
373 a1.move(refinedVector);
374 }
375 } else{
376 refinedVector[2] = b - c;
377 for(Atom a1: s2Atoms){
378 a1.move(refinedVector);
379 }
380 }
381 break;
382 }
383 }
384 refinedVector[2] = aPotential < bPotential ? a : b;
385
386
387 return refinedVector;
388 }
389
390
391 private void systemEnergies(){
392
393 for(Atom a: s2Atoms){ a.setUse(false); }
394 logger.info("\n --------- System 1 Energy Breakdown --------- ");
395 double monomerEnergy = forceFieldEnergy.energy(x, true);
396 for(Atom a: s2Atoms){ a.setUse(true); }
397
398
399 for(Atom a: s1Atoms){ a.setUse(false); }
400 logger.info("\n --------- System 2 Energy Breakdown --------- ");
401 double monomerEnergy2 = forceFieldEnergy.energy(x, true);
402 for(Atom a: s1Atoms){ a.setUse(true); }
403
404
405 logger.info(format("\n %-29s%12.7f kcal/mol", "System energy 1:", monomerEnergy));
406 logger.info(format(" %-29s%12.7f kcal/mol", "System energy 2:", monomerEnergy2));
407
408 m1MinEnergy = monomerEnergy;
409 m2MinEnergy = monomerEnergy2;
410 totalMonomerMinimizedEnergy = monomerEnergy + monomerEnergy2;
411 }
412
413
414
415
416
417
418 private int minimizeMolecule(Molecule m){
419 if (this.tScan){
420 staticScanMolecule(m);
421 }
422 Minimize minimize = new Minimize(this.mola, this.forceFieldEnergy, this.algorithmListener);
423 minimize.minimize(this.eps, this.maxIter).getCoordinates(this.x);
424 return minimize.getStatus();
425 }
426
427 private void staticScanMolecule(Molecule m){
428 TorsionSearch torsionSearch = new TorsionSearch(this.mola, m, 32, 1);
429 torsionSearch.staticAnalysis(0, 100);
430 if(!torsionSearch.getStates().isEmpty()){
431 AssemblyState minState = torsionSearch.getStates().get(0);
432 minState.revertState();
433 }
434 }
435 private int minimizeSystem(Atom a, Atom b) throws Exception{
436 if(tScan){
437
438 logger.info("\n --------- System 1 Static Torsion Scan --------- ");
439 forceFieldEnergy.getCoordinates(x);
440 double tscanE = forceFieldEnergy.energy(x, false);
441 for(Molecule m: s1) {
442 staticScanMolecule(m);
443 }
444 forceFieldEnergy.getCoordinates(x);
445 double tscanEAfter = forceFieldEnergy.energy(x, false);
446 logger.info("\n Energy before static torsion scan of system 1: " + tscanE);
447 logger.info(" Energy after static torsion scan of system 1: " + tscanEAfter);
448
449 logger.info("\n --------- System 2 Static Torsion Scan --------- ");
450 forceFieldEnergy.getCoordinates(x);
451 tscanE = forceFieldEnergy.energy(x, false);
452 for(Molecule m: s2) {
453 staticScanMolecule(m);
454 }
455 forceFieldEnergy.getCoordinates(x);
456 tscanEAfter = forceFieldEnergy.energy(x, false);
457 logger.info("\n Energy before static torsion scan of system 2: " + tscanE);
458 logger.info(" Energy after static torsion scan of system 2: " + tscanEAfter);
459 }
460 forceFieldEnergy.getCoordinates(x);
461 double e = forceFieldEnergy.energy(x, true);
462 RestrainDistance restrainDistance = getRestraintBond(a, b, e);
463 Minimize minEngine = new Minimize(mola, forceFieldEnergy, null);
464 try {
465 minEngine.minimize(this.eps, this.maxIter);
466 } catch (Exception ex){
467
468 a.getBonds().remove(restrainDistance);
469 b.getBonds().remove(restrainDistance);
470 a.update();
471 b.update();
472 mola.getBondList().remove(restrainDistance);
473 mola.update();
474 return -1;
475 }
476
477 a.getBonds().remove(restrainDistance);
478 b.getBonds().remove(restrainDistance);
479 a.update();
480 b.update();
481 mola.getBondList().remove(restrainDistance);
482 mola.update();
483 return minEngine.getStatus();
484 }
485
486 private RestrainDistance getRestraintBond(Atom a, Atom b, double e) throws Exception {
487 if (e > 1000000){
488 throw new Exception(" Energy too high to minimize.");
489 }
490
491 BondType restraint = new BondType(new int[]{a.getAtomicNumber(), b.getAtomicNumber()},
492 100.0,
493 this.hBondDist,
494 BondType.BondFunction.FLAT_BOTTOM_QUARTIC,
495 this.flatBottomRadius);
496 RestrainDistance restrainDistance = new RestrainDistance(a, b,
497 null,
498 false,
499 0.0, 0.0,
500 null);
501 restrainDistance.setBondType(restraint);
502 return restrainDistance;
503 }
504
505 private void setTargetAtoms(Atom[] atoms){
506 for(Atom a: atoms){
507 if(a.getAtomType().atomicNumber == 7|| a.getAtomType().atomicNumber == 8
508 || a.getAtomType().atomicNumber == 9 || a.getAtomType().atomicNumber == 15
509 || a.getAtomType().atomicNumber == 16 || a.getAtomType().atomicNumber == 17){
510 if(a.getMoleculeNumber() == mola.getMoleculeNumbers()[0]) {
511 s1TargetAtoms.add(a);
512 } else{
513 s2TargetAtoms.add(a);
514 }
515
516
517 if(excludeH) {
518 for (Bond b : a.getBonds()) {
519 int num = b.get1_2(a).getAtomType().atomicNumber;
520 if (num == 1) {
521 if (a.getMoleculeNumber() == mola.getMoleculeNumbers()[0]) {
522 s1TargetAtoms.add(a);
523 } else {
524 s2TargetAtoms.add(a);
525 }
526 }
527 }
528 }
529 }
530 else if(a.getAtomType().atomicNumber == 1 && !excludeH){
531 if(a.getMoleculeNumber() == mola.getMoleculeNumbers()[0]) {
532 s1TargetAtoms.add(a);
533 } else{
534 s2TargetAtoms.add(a);
535 }
536 }
537 }
538
539 }
540 private void calculateMeanStd(){
541 ArrayList<Double> energies = this.getEnergies();
542 double highOutlierCutoff = Double.MAX_VALUE;
543 double lowOutlierCutoff = Double.MIN_VALUE;
544
545 if(energies.size() > 4) {
546 double iqr = energies.get(energies.size() / 4) - energies.get(3 * energies.size() / 4);
547 double q3 = energies.get(3 * energies.size() / 4);
548 double q1 = energies.get(energies.size() / 4);
549 highOutlierCutoff = q3 + 1.5 * iqr;
550 lowOutlierCutoff = q1 - 1.5 * iqr;
551 }
552
553
554 int count = 0;
555 double sum = 0.0;
556 double sumNoOutlier = 0.0;
557 for(double e: energies){
558 if(e < highOutlierCutoff && e > lowOutlierCutoff){
559 sumNoOutlier += e;
560 count++;
561 }
562 sum += e;
563 }
564 averageEnergy = sum / energies.size();
565 averageEnergyNoOutlier = sumNoOutlier / count;
566
567
568 double sumOfSquares = 0.0;
569 double sumOfSquaresNoOutlier = 0.0;
570 for(double e: energies){
571 sumOfSquares += (e - averageEnergy) * (e - averageEnergy);
572 if(e < highOutlierCutoff && e > lowOutlierCutoff){
573 sumOfSquaresNoOutlier += (e - averageEnergyNoOutlier) * (e - averageEnergyNoOutlier);
574 }
575 }
576 stdOfEnergies = Math.sqrt(sumOfSquares / energies.size());
577 stdOfEnergiesNoOutlier = Math.sqrt(sumOfSquaresNoOutlier / count);
578 }
579
580 private static void alignSystemCOMtoAtomVecWithAxis(Atom a, double[] axis, Atom[] mAtoms){
581
582 double[] moleculeOneCOM = getCOM(mAtoms);
583 for (Atom mAtom : mAtoms) {
584 mAtom.move(new double[]{-moleculeOneCOM[0], -moleculeOneCOM[1], -moleculeOneCOM[2]});
585 }
586
587 double[] aCoords = a.getXYZ().copy().get();
588
589 double[][] rotation = getRotationBetween(aCoords, axis);
590
591 double[] moleculeAtomicPositions = new double[mAtoms.length * 3];
592 for(int i = 0; i < mAtoms.length; i++){
593 moleculeAtomicPositions[i*3] = mAtoms[i].getX();
594 moleculeAtomicPositions[i*3 + 1] = mAtoms[i].getY();
595 moleculeAtomicPositions[i*3 + 2] = mAtoms[i].getZ();
596 }
597 applyRotation(moleculeAtomicPositions, rotation);
598
599 for(int i = 0; i < mAtoms.length; i++){
600 mAtoms[i].setXYZ(new double[] {moleculeAtomicPositions[i*3],
601 moleculeAtomicPositions[i*3 + 1],
602 moleculeAtomicPositions[i*3 + 2]});
603 }
604 }
605
606
607
608
609
610
611 private static double[] getCOM(Atom[] atoms){
612
613 double[] COM = new double[3];
614 double totalMass = 0.0;
615 for(Atom s: atoms){
616 double[] pos = s.getXYZ().get();
617 COM[0] += pos[0] * s.getMass();
618 COM[1] += pos[1] * s.getMass();
619 COM[2] += pos[2] * s.getMass();
620 totalMass += s.getMass();
621 }
622 totalMass = 1 / totalMass;
623 COM[0] *= totalMass;
624 COM[1] *= totalMass;
625 COM[2] *= totalMass;
626
627 return COM;
628 }
629
630
631
632
633
634
635
636 private static double[][] getRotationBetween(double[] v1, double[] v2){
637
638 double v1Norm = 1/ Math.sqrt(v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2]);
639 double v2Norm = 1 / Math.sqrt(v2[0] * v2[0] + v2[1] * v2[1] + v2[2] * v2[2]);
640 for(int i = 0; i < 3; i++){
641 v1[i] *= v1Norm;
642 v2[i] *= v2Norm;
643 }
644
645 double[] crossProduct = new double[3];
646 crossProduct[0] = v1[1] * v2[2] - v1[2] * v2[1];
647 crossProduct[1] = v1[2] * v2[0] - v1[0] * v2[2];
648 crossProduct[2] = v1[0] * v2[1] - v1[1] * v2[0];
649
650 double crossProductNorm = 1 / Math.sqrt(crossProduct[0] * crossProduct[0] + crossProduct[1] * crossProduct[1] + crossProduct[2] * crossProduct[2]);
651 for(int i = 0; i < 3; i++){
652 crossProduct[i] *= crossProductNorm;
653 }
654
655 double dotProduct = v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
656
657 double theta = Math.acos(dotProduct);
658
659 double[] quaternion = new double[4];
660 quaternion[0] = cos(theta/2);
661 quaternion[1] = crossProduct[0] * sin(theta/2);
662 quaternion[2] = crossProduct[1] * sin(theta/2);
663 quaternion[3] = crossProduct[2] * sin(theta/2);
664
665 double quaternionNorm = 1 / Math.sqrt(quaternion[0] * quaternion[0] + quaternion[1] * quaternion[1]
666 + quaternion[2] * quaternion[2] + quaternion[3] * quaternion[3]);
667 for(int i = 0; i < 4; i++){ quaternion[i] *= quaternionNorm; }
668
669 double q1q1 = quaternion[1] * quaternion[1];
670 double q2q2 = quaternion[2] * quaternion[2];
671 double q3q3 = quaternion[3] * quaternion[3];
672 double q0q1 = quaternion[0] * quaternion[1];
673 double q0q2 = quaternion[0] * quaternion[2];
674 double q0q3 = quaternion[0] * quaternion[3];
675 double q1q2 = quaternion[1] * quaternion[2];
676 double q1q3 = quaternion[1] * quaternion[3];
677 double q2q3 = quaternion[2] * quaternion[3];
678
679 double[][] rotation = new double[3][3];
680 rotation[0][0] = 1 - 2 * (q2q2 + q3q3);
681 rotation[0][1] = 2 * (q1q2 - q0q3);
682 rotation[0][2] = 2 * (q1q3 + q0q2);
683 rotation[1][0] = 2 * (q1q2 + q0q3);
684 rotation[1][1] = 1 - 2 * (q1q1 + q3q3);
685 rotation[1][2] = 2 * (q2q3 - q0q1);
686 rotation[2][0] = 2 * (q1q3 - q0q2);
687 rotation[2][1] = 2 * (q2q3 + q0q1);
688 rotation[2][2] = 1 - 2 * (q1q1 + q2q2);
689
690 return rotation;
691 }
692
693 private static Atom[] getAtomsFromMoleculeArray(Molecule[] system){
694 ArrayList<Atom> atoms = new ArrayList<>();
695 for(Molecule m: system){
696 atoms.addAll(m.getAtomList());
697 }
698 return atoms.toArray(new Atom[0]);
699 }
700
701
702
703
704
705 private record StateContainer(AssemblyState state, double e) implements Comparable<StateContainer>
706 {
707 AssemblyState getState() {
708 return state;
709 }
710
711 double getEnergy() {
712 return e;
713 }
714
715 @Override
716 public int compareTo(StateContainer o) {
717 return Double.compare(e, o.getEnergy());
718 }
719 }
720 }