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