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 package ffx.potential;
39
40 import edu.rit.pj.ParallelRegion;
41 import edu.rit.pj.ParallelSection;
42 import edu.rit.pj.ParallelTeam;
43 import ffx.crystal.Crystal;
44 import ffx.crystal.CrystalPotential;
45 import ffx.crystal.SymOp;
46 import ffx.numerics.Potential;
47 import ffx.numerics.switching.UnivariateSwitchingFunction;
48 import ffx.potential.bonded.Atom;
49 import ffx.potential.bonded.LambdaInterface;
50 import ffx.potential.openmm.OpenMMDualTopologyEnergy;
51 import ffx.potential.openmm.OpenMMEnergy;
52 import ffx.potential.parameters.ForceField;
53 import ffx.potential.utils.EnergyException;
54
55 import javax.annotation.Nullable;
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.List;
59 import java.util.logging.Level;
60 import java.util.logging.Logger;
61
62 import static ffx.crystal.SymOp.applyCartesianSymOp;
63 import static ffx.crystal.SymOp.applyCartesianSymRot;
64 import static ffx.crystal.SymOp.invertSymOp;
65 import static ffx.potential.parameters.ForceField.toEnumForm;
66 import static ffx.potential.utils.Superpose.rmsd;
67 import static ffx.utilities.StringUtils.parseAtomRanges;
68 import static ffx.utilities.StringUtils.writeAtomRanges;
69 import static java.lang.Double.parseDouble;
70 import static java.lang.String.format;
71 import static java.util.Arrays.fill;
72 import static org.apache.commons.math3.util.FastMath.sqrt;
73
74
75
76
77
78
79
80 public class DualTopologyEnergy implements CrystalPotential, LambdaInterface {
81
82
83
84
85 private static final Logger logger = Logger.getLogger(DualTopologyEnergy.class.getName());
86
87
88
89 private final int nShared;
90
91
92
93 private final int nVariables;
94
95
96
97 private final EnergyRegion energyRegion;
98
99
100
101 private final double[] mass;
102
103
104
105 private final VARIABLE_TYPE[] variableTypes;
106
107
108
109 private final double[] x1;
110
111
112
113 private final double[] x2;
114
115
116
117 private final double[] g1;
118
119
120
121 private final double[] g2;
122
123
124
125 private final double[] rg1;
126
127
128
129 private final double[] rg2;
130
131
132
133 private final double[] gl1;
134
135
136
137 private final double[] gl2;
138
139
140
141
142 private final double[] rgl1;
143
144
145
146
147 private final double[] rgl2;
148
149
150
151 private final CrystalPotential potential1;
152
153
154
155 private final CrystalPotential potential2;
156
157
158
159 private final LambdaInterface lambdaInterface1;
160
161
162
163 private final LambdaInterface lambdaInterface2;
164
165
166
167 private final ForceFieldEnergy forceFieldEnergy1;
168
169
170
171 private final ForceFieldEnergy forceFieldEnergy2;
172
173
174
175 private final int nActive1;
176
177
178
179 private final int nActive2;
180
181
182
183 private final Atom[] activeAtoms1;
184
185
186
187 private final Atom[] activeAtoms2;
188
189
190
191 private final boolean[] sharedAtoms1;
192
193
194
195 private final boolean[] sharedAtoms2;
196
197
198
199
200 private final int[] system1AtomIndex;
201
202
203
204
205 private final int[] system2AtomIndex;
206
207
208
209
210
211 private final Atom[] dualTopologyAtoms;
212
213
214
215
216
217 private final Atom[] dualTopologyAtoms2;
218
219
220
221 private final UnivariateSwitchingFunction switchFunction;
222
223
224
225 private double energy1 = 0;
226
227
228
229 private double energy2 = 0;
230
231
232
233 private ParallelTeam parallelTeam;
234
235
236
237 private final boolean doValenceRestraint1;
238
239
240
241 private final boolean doValenceRestraint2;
242
243
244
245 private final boolean useFirstSystemBondedEnergy;
246
247
248
249 private double restraintEnergy1 = 0;
250
251
252
253 private double restraintEnergy2 = 0;
254
255
256
257 private double dEdL_1 = 0;
258
259
260
261 private double dEdL_2 = 0;
262
263
264
265 private double restraintdEdL_1 = 0;
266
267
268
269 private double restraintdEdL_2 = 0;
270
271
272
273 private double d2EdL2_1 = 0;
274
275
276
277 private double d2EdL2_2 = 0;
278
279
280
281 private double restraintd2EdL2_1 = 0;
282
283
284
285 private double restraintd2EdL2_2 = 0;
286
287
288
289 private double totalEnergy = 0;
290
291
292
293 private double lambda = 1.0;
294
295
296
297 private double f1L = 1.0;
298
299
300
301 private double f2L = 0.0;
302
303
304
305 private double dF1dL = 0.0;
306
307
308
309 private double dF2dL = 0.0;
310
311
312
313 private double d2F1dL2 = 0.0;
314
315
316
317 private double d2F2dL2 = 0.0;
318
319
320
321 private double[] scaling = null;
322
323
324
325 private STATE state = STATE.BOTH;
326
327
328
329 private SymOp[] symOp;
330
331
332
333 private SymOp[] inverse;
334
335
336
337 private final ArrayList<List<Integer>> symOpAtoms = new ArrayList<>();
338
339
340
341 private boolean[][] mask = null;
342
343
344
345 private boolean useSymOp = false;
346
347
348
349
350
351
352
353
354 public DualTopologyEnergy(
355 MolecularAssembly topology1,
356 MolecularAssembly topology2,
357 UnivariateSwitchingFunction switchFunction) {
358 forceFieldEnergy1 = topology1.getPotentialEnergy();
359 forceFieldEnergy2 = topology2.getPotentialEnergy();
360 potential1 = forceFieldEnergy1;
361 potential2 = forceFieldEnergy2;
362 lambdaInterface1 = forceFieldEnergy1;
363 lambdaInterface2 = forceFieldEnergy2;
364
365
366 Atom[] atoms1 = topology1.getAtomArray();
367
368 Atom[] atoms2 = topology2.getAtomArray();
369
370 ForceField forceField1 = topology1.getForceField();
371 doValenceRestraint1 = forceField1.getBoolean("LAMBDA_VALENCE_RESTRAINTS", true);
372 ForceField forceField2 = topology2.getForceField();
373 doValenceRestraint2 = forceField2.getBoolean("LAMBDA_VALENCE_RESTRAINTS", true);
374
375 useFirstSystemBondedEnergy = forceField2.getBoolean("USE_FIRST_SYSTEM_BONDED_ENERGY", false);
376
377
378 int shared1 = 0;
379 int shared2 = 0;
380 int activeCount1 = 0;
381 int activeCount2 = 0;
382 for (Atom a1 : atoms1) {
383 if (a1.isActive()) {
384 activeCount1++;
385 if (!a1.applyLambda()) {
386 shared1++;
387 }
388 }
389 }
390 for (Atom a2 : atoms2) {
391 if (a2.isActive()) {
392 activeCount2++;
393 if (!a2.applyLambda()) {
394 shared2++;
395 }
396 }
397 }
398
399 if (shared1 != shared2) {
400 logger.severe(" Shared active atoms are not equal between topologies:\n"
401 + " Topology 1 has " + shared1 + " shared atoms.\n"
402 + " Topology 2 has " + shared2 + " shared atoms.\n"
403 + " Please check the input files or force field parameters.");
404 }
405
406 assert (shared1 == shared2);
407 nActive1 = activeCount1;
408 nActive2 = activeCount2;
409 activeAtoms1 = new Atom[nActive1];
410 activeAtoms2 = new Atom[nActive2];
411 sharedAtoms1 = new boolean[nActive1];
412 sharedAtoms2 = new boolean[nActive2];
413 Arrays.fill(sharedAtoms1, true);
414 Arrays.fill(sharedAtoms2, true);
415
416
417 int nAlchemical1 = 0;
418 int nShared1 = 0;
419 int nAlchemical2 = 0;
420 int nShared2 = 0;
421 for (Atom a : atoms1) {
422 if (a.applyLambda()) {
423 nAlchemical1++;
424 } else {
425 nShared1++;
426 }
427 }
428 for (Atom a : atoms2) {
429 if (a.applyLambda()) {
430 nAlchemical2++;
431 } else {
432 nShared2++;
433 }
434 }
435 if (nShared1 != nShared2) {
436 logger.severe(" Shared atoms are not equal between topologies:\n"
437 + " Topology 1 has " + nShared1 + " shared atoms.\n"
438 + " Topology 2 has " + nShared2 + " shared atoms.\n"
439 + " Please check the input files or force field parameters.");
440 }
441 dualTopologyAtoms = new Atom[nShared1 + nAlchemical1 + nAlchemical2];
442 dualTopologyAtoms2 = new Atom[nShared1 + nAlchemical1 + nAlchemical2];
443 int indexShared = 0;
444 int indexAlchemical = nShared1;
445 int index1 = 0;
446 system1AtomIndex = new int[atoms1.length];
447 for (Atom a : atoms1) {
448 a.setTopologyIndex(0);
449 if (a.applyLambda()) {
450 system1AtomIndex[index1] = indexAlchemical;
451 a.setTopologyAtomIndex(indexAlchemical);
452 dualTopologyAtoms[indexAlchemical] = a;
453 dualTopologyAtoms2[indexAlchemical] = a;
454 indexAlchemical++;
455 } else {
456 system1AtomIndex[index1] = indexShared;
457 a.setTopologyAtomIndex(indexShared);
458 dualTopologyAtoms[indexShared] = a;
459 indexShared++;
460 }
461 index1++;
462 }
463
464 indexShared = 0;
465 int index2 = 0;
466 system2AtomIndex = new int[atoms2.length];
467 for (Atom a : atoms2) {
468 a.setTopologyIndex(1);
469 if (a.applyLambda()) {
470 system2AtomIndex[index2] = indexAlchemical;
471 a.setTopologyAtomIndex(indexAlchemical);
472 dualTopologyAtoms[indexAlchemical] = a;
473 dualTopologyAtoms2[indexAlchemical] = a;
474 indexAlchemical++;
475 } else {
476 system2AtomIndex[index2] = indexShared;
477 a.setTopologyAtomIndex(indexShared);
478 dualTopologyAtoms2[indexShared] = a;
479 indexShared++;
480 }
481 index2++;
482 }
483
484
485 int index = 0;
486 for (Atom a1 : atoms1) {
487 if (a1.isActive()) {
488 activeAtoms1[index] = a1;
489 if (a1.applyLambda()) {
490
491 sharedAtoms1[index] = false;
492 }
493 index++;
494 }
495 }
496 index = 0;
497 for (Atom a2 : atoms2) {
498 if (a2.isActive()) {
499 activeAtoms2[index] = a2;
500 if (a2.applyLambda()) {
501
502 sharedAtoms2[index] = false;
503 }
504 index++;
505 }
506 }
507
508 nShared = shared1;
509
510 int nSoftCore1 = nActive1 - nShared;
511
512 int nSoftCore2 = nActive2 - nShared;
513
514 int nTotal = nShared + nSoftCore1 + nSoftCore2;
515 nVariables = 3 * nTotal;
516
517
518 x1 = new double[nActive1 * 3];
519 x2 = new double[nActive2 * 3];
520 g1 = new double[nActive1 * 3];
521 g2 = new double[nActive2 * 3];
522 rg1 = new double[nActive1 * 3];
523 rg2 = new double[nActive2 * 3];
524 gl1 = new double[nActive1 * 3];
525 gl2 = new double[nActive2 * 3];
526 rgl1 = new double[nActive1 * 3];
527 rgl2 = new double[nActive2 * 3];
528
529
530 index = 0;
531 variableTypes = new VARIABLE_TYPE[nVariables];
532 for (int i = 0; i < nTotal; i++) {
533 variableTypes[index++] = VARIABLE_TYPE.X;
534 variableTypes[index++] = VARIABLE_TYPE.Y;
535 variableTypes[index++] = VARIABLE_TYPE.Z;
536 }
537
538
539 int commonIndex = 0;
540 int softcoreIndex = 3 * nShared;
541 mass = new double[nVariables];
542 for (int i = 0; i < nActive1; i++) {
543 Atom a = activeAtoms1[i];
544 double m = a.getMass();
545 if (sharedAtoms1[i]) {
546 mass[commonIndex++] = m;
547 mass[commonIndex++] = m;
548 mass[commonIndex++] = m;
549 } else {
550 mass[softcoreIndex++] = m;
551 mass[softcoreIndex++] = m;
552 mass[softcoreIndex++] = m;
553 }
554 }
555 commonIndex = 0;
556 for (int i = 0; i < nActive2; i++) {
557 Atom a = activeAtoms2[i];
558 double m = a.getMass();
559 if (sharedAtoms2[i]) {
560 for (int j = 0; j < 3; j++) {
561 double massCommon = mass[commonIndex];
562 massCommon = Math.max(massCommon, m);
563 mass[commonIndex++] = massCommon;
564 }
565 } else {
566 mass[softcoreIndex++] = m;
567 mass[softcoreIndex++] = m;
568 mass[softcoreIndex++] = m;
569 }
570 }
571 energyRegion = new EnergyRegion();
572 parallelTeam = new ParallelTeam(1);
573
574 this.switchFunction = switchFunction;
575 logger.info(format("\n Dual topology using switching function:\n %s", switchFunction));
576 logger.info(format(" Shared atoms: %d (1: %d of %d; 2: %d of %d)\n ", nShared, shared1, nActive1, shared2, nActive2));
577 String symOpString = forceField2.getString("symop", null);
578 if (symOpString != null) {
579 readSymOp(symOpString);
580 } else {
581
582 potential2.setCrystal(potential1.getCrystal());
583 useSymOp = false;
584 symOp = null;
585 inverse = null;
586 }
587
588
589 int i1 = 0;
590 int i2 = 0;
591
592 if (!useSymOp) {
593 for (int i = 0; i < nShared; i++) {
594 Atom a1 = atoms1[i1++];
595 while (a1.applyLambda()) {
596 a1 = atoms1[i1++];
597 }
598 Atom a2 = atoms2[i2++];
599 while (a2.applyLambda()) {
600 a2 = atoms2[i2++];
601 }
602 assert (a1.getX() == a2.getX());
603 assert (a1.getY() == a2.getY());
604 assert (a1.getZ() == a2.getZ());
605 reconcileAtoms(a1, a2, Level.INFO);
606 }
607 }
608 }
609
610
611
612
613
614
615
616
617
618 @SuppressWarnings("fallthrough")
619 public static DualTopologyEnergy energyFactory(MolecularAssembly molecularAssembly1,
620 MolecularAssembly molecularAssembly2,
621 UnivariateSwitchingFunction switchFunction) {
622 ForceField forceField = molecularAssembly1.getForceField();
623 String platformString = toEnumForm(forceField.getString("PLATFORM-DT", "FFX"));
624 try {
625 Platform platform = Platform.valueOf(platformString);
626 switch (platform) {
627 case OMM, OMM_REF, OMM_CUDA, OMM_OPENCL:
628 try {
629 return new OpenMMDualTopologyEnergy(molecularAssembly1, molecularAssembly2, switchFunction, platform);
630 } catch (Exception ex) {
631 logger.warning(format(" Exception creating OpenMMDualTopologyEnergy: %s", ex));
632 return new DualTopologyEnergy(molecularAssembly1, molecularAssembly2, switchFunction);
633 }
634 case OMM_CPU:
635 logger.warning(format(" Platform %s not supported; defaulting to FFX", platform));
636 default:
637 return new DualTopologyEnergy(molecularAssembly1, molecularAssembly2, switchFunction);
638 }
639 } catch (IllegalArgumentException | NullPointerException ex) {
640 logger.warning(
641 format(" String %s did not match a known energy implementation", platformString));
642 return new DualTopologyEnergy(molecularAssembly1, molecularAssembly2, switchFunction);
643 }
644 }
645
646
647
648
649
650
651
652 public int getNumberOfAtoms() {
653 return dualTopologyAtoms.length;
654 }
655
656
657
658
659
660
661
662 public Atom[] getDualTopologyAtoms(int topology) {
663 if (topology == 0) {
664 return dualTopologyAtoms;
665 } else if (topology == 1) {
666 return dualTopologyAtoms2;
667 } else {
668 throw new IllegalArgumentException(" Invalid topology index: " + topology);
669 }
670 }
671
672
673
674
675
676
677
678
679 public Atom getDualTopologyAtom(int topology, int index) {
680 if (topology == 0) {
681 return dualTopologyAtoms[index];
682 } else if (topology == 1) {
683 return dualTopologyAtoms2[index];
684 } else {
685 throw new IllegalArgumentException(" Invalid topology index: " + topology);
686 }
687 }
688
689
690
691
692
693
694
695
696 public int mapToDualTopologyIndex(int topology, int index) {
697 if (topology == 0) {
698 return system1AtomIndex[index];
699 } else if (topology == 1) {
700 return system2AtomIndex[index];
701 } else {
702 throw new IllegalArgumentException(" Invalid topology index: " + topology);
703 }
704 }
705
706
707
708
709
710
711 public ForceFieldEnergy getForceFieldEnergy1() {
712 return forceFieldEnergy1;
713 }
714
715
716
717
718
719
720 public ForceFieldEnergy getForceFieldEnergy2() {
721 return forceFieldEnergy2;
722 }
723
724
725
726
727
728
729 public int[] getDualTopologyIndex1() {
730 return system1AtomIndex;
731 }
732
733
734
735
736
737
738 public int[] getDualTopologyIndex2() {
739 return system2AtomIndex;
740 }
741
742
743
744
745
746
747
748 public double getTopologyScale(int topology) {
749 if (topology == 0) {
750 return f1L;
751 } else if (topology == 1) {
752 return f2L;
753 } else {
754 throw new IllegalArgumentException(" Invalid topology index: " + topology);
755 }
756 }
757
758
759
760
761
762
763 private void readSymOp(String symOpString) {
764 int numSymOps;
765 try {
766 String[] tokens = symOpString.split(" +");
767 int numTokens = tokens.length;
768
769 if (numTokens == 12) {
770 numSymOps = 1;
771 symOp = new SymOp[numSymOps];
772 inverse = new SymOp[numSymOps];
773
774 symOpAtoms.add(parseAtomRanges("symmetryAtoms", "1-N", nActive2));
775
776 symOp[0] = new SymOp(new double[][]{
777 {parseDouble(tokens[0]), parseDouble(tokens[1]), parseDouble(tokens[2])},
778 {parseDouble(tokens[3]), parseDouble(tokens[4]), parseDouble(tokens[5])},
779 {parseDouble(tokens[6]), parseDouble(tokens[7]), parseDouble(tokens[8])}},
780 new double[]{parseDouble(tokens[9]), parseDouble(tokens[10]), parseDouble(tokens[11])});
781 } else if (numTokens % 14 == 0) {
782 numSymOps = numTokens / 14;
783 symOp = new SymOp[numSymOps];
784 inverse = new SymOp[numSymOps];
785 if (logger.isLoggable(Level.FINER)) {
786 logger.finer(format(" Number of Tokens: %3d Number of Sym Ops: %2d", numTokens, numSymOps));
787 }
788 for (int i = 0; i < numSymOps; i++) {
789 int j = i * 14;
790
791 symOpAtoms.add(parseAtomRanges("symmetryAtoms", tokens[j], nActive2));
792 symOp[i] = new SymOp(new double[][]{
793 {parseDouble(tokens[j + 2]), parseDouble(tokens[j + 3]), parseDouble(tokens[j + 4])},
794 {parseDouble(tokens[j + 5]), parseDouble(tokens[j + 6]), parseDouble(tokens[j + 7])},
795 {parseDouble(tokens[j + 8]), parseDouble(tokens[j + 9]), parseDouble(tokens[j + 10])}},
796 new double[]{parseDouble(tokens[j + 11]), parseDouble(tokens[j + 12]), parseDouble(tokens[j + 13])});
797 }
798 } else {
799 logger.warning(format(" Provided symmetry operator is formatted incorrectly. Should have 12 or 14 entries, but found %3d.", numTokens));
800 return;
801 }
802 useSymOp = true;
803
804 mask = new boolean[numSymOps][nActive2];
805
806 boolean[][] sharedMask = new boolean[numSymOps][nShared];
807 for (int i = 0; i < numSymOps; i++) {
808 List<Integer> symAtoms = symOpAtoms.get(i);
809 for (int atom : symAtoms) {
810 if (sharedAtoms2[atom]) {
811 mask[i][atom] = true;
812 }
813 }
814
815 int sharedIndex = 0;
816 for (int j = 0; j < nActive2; j++) {
817 if (sharedAtoms2[j]) {
818 if (mask[i][j]) {
819 sharedMask[i][sharedIndex] = true;
820 }
821 sharedIndex++;
822 }
823 }
824 }
825
826
827 potential1.getCoordinates(x1);
828 potential2.getCoordinates(x2);
829
830
831 double[] x1Shared = new double[nShared * 3];
832 double[] x2Shared = new double[nShared * 3];
833 int sharedIndex = 0;
834 for (int i = 0; i < nActive1; i++) {
835 if (sharedAtoms1[i]) {
836 int index = i * 3;
837 x1Shared[sharedIndex++] = x1[index];
838 x1Shared[sharedIndex++] = x1[index + 1];
839 x1Shared[sharedIndex++] = x1[index + 2];
840 }
841 }
842 sharedIndex = 0;
843 for (int i = 0; i < nActive2; i++) {
844 if (sharedAtoms2[i]) {
845 int index = i * 3;
846 x2Shared[sharedIndex++] = x2[index];
847 x2Shared[sharedIndex++] = x2[index + 1];
848 x2Shared[sharedIndex++] = x2[index + 2];
849 }
850 }
851
852 double origRMSD = rmsd(x1Shared, x2Shared, mass);
853 logger.info("\n RMSD to topology 2 coordinates from input file:");
854 logger.info(format(" RMSD: %12.3f A", origRMSD));
855
856
857 double[] origX1 = Arrays.copyOf(x1Shared, nShared * 3);
858 double[] origX2 = Arrays.copyOf(x2Shared, nShared * 3);
859
860
861
862 for (int i = 0; i < numSymOps; i++) {
863
864 inverse[i] = invertSymOp(symOp[i]);
865
866
867 applyCartesianSymOp(origX1, origX1, symOp[i], sharedMask[i]);
868 logger.info(format("\n SymOp %3d of %3d between topologies:\n Applied to atoms: %s\n %s\n Inverse SymOp:\n %s",
869 i + 1, numSymOps, writeAtomRanges(symOpAtoms.get(i).stream().mapToInt(j -> j).toArray()), symOp[i].toString(), inverse[i].toString()));
870 }
871 double symOpRMSD = rmsd(origX1, origX2, mass);
872 logger.info("\n RMSD to topology 2 coordinates via loaded symop:");
873 logger.info(format(" RMSD: %12.3f A", symOpRMSD));
874
875
876 logger.info("\n Checking alchemical atoms for system 1:");
877 validateAlchemicalAtoms(nActive1, sharedAtoms1, activeAtoms1);
878 logger.info(" Checking alchemical atoms for system 2:");
879 validateAlchemicalAtoms(nActive2, sharedAtoms2, activeAtoms2);
880
881 if (logger.isLoggable(Level.FINE)) {
882 for (int i = 0; i < nShared; i++) {
883 int i3 = i * 3;
884 double rmsd = rmsd(new double[]{origX1[i3], origX1[i3 + 1], origX1[i3 + 2]}, new double[]{origX2[i3], origX2[i3 + 1], origX2[i3 + 2]}, new double[]{mass[i]});
885 if (rmsd > 0.5) {
886 logger.fine(format(" Shared atom %3d has a distance of %8.3f", i + 1, rmsd));
887 }
888 }
889 }
890 } catch (Exception ex) {
891 logger.severe(" Error parsing SymOp for Dual Topology:\n (" + symOpString + ")" + Utilities.stackTraceToString(ex));
892 }
893 }
894
895
896
897
898
899
900
901 @Override
902 public boolean dEdLZeroAtEnds() {
903 if (!forceFieldEnergy1.dEdLZeroAtEnds() || !forceFieldEnergy2.dEdLZeroAtEnds()) {
904 return false;
905 }
906 return (switchFunction.highestOrderZeroDerivativeAtZeroBound() > 0);
907 }
908
909
910
911
912 @Override
913 public boolean destroy() {
914 boolean ffe1Destroy = forceFieldEnergy1.destroy();
915 boolean ffe2Destroy = forceFieldEnergy2.destroy();
916 try {
917 if (parallelTeam != null) {
918 parallelTeam.shutdown();
919 }
920 return ffe1Destroy && ffe2Destroy;
921 } catch (Exception ex) {
922 logger.warning(format(" Exception in shutting down DualTopologyEnergy: %s", ex));
923 logger.info(Utilities.stackTraceToString(ex));
924 return false;
925 }
926 }
927
928
929
930
931 @Override
932 public double energy(double[] x) {
933 return energy(x, false);
934 }
935
936
937
938
939 @Override
940 public double energy(double[] x, boolean verbose) {
941 try {
942 energyRegion.setX(x);
943 energyRegion.setVerbose(verbose);
944 parallelTeam.execute(energyRegion);
945 } catch (Exception ex) {
946 throw new EnergyException(format(" Exception in calculating dual-topology energy: %s", ex));
947 }
948 return totalEnergy;
949 }
950
951
952
953
954
955
956 @Override
957 public double energyAndGradient(double[] x, double[] g) {
958 return energyAndGradient(x, g, false);
959 }
960
961
962
963
964
965
966 @Override
967 public double energyAndGradient(double[] x, double[] g, boolean verbose) {
968 assert Arrays.stream(x).allMatch(Double::isFinite);
969 try {
970 energyRegion.setX(x);
971 energyRegion.setG(g);
972 energyRegion.setVerbose(verbose);
973 parallelTeam.execute(energyRegion);
974 } catch (Exception ex) {
975 throw new EnergyException(format(" Exception in calculating dual-topology energy: %s", ex));
976 }
977 return totalEnergy;
978 }
979
980
981
982
983 @Override
984 public double[] getAcceleration(double[] acceleration) {
985 if (acceleration == null || acceleration.length < nVariables) {
986 acceleration = new double[nVariables];
987 }
988 int indexCommon = 0;
989 int indexUnique = nShared * 3;
990 double[] accel = new double[3];
991 for (int i = 0; i < nActive1; i++) {
992 Atom atom = activeAtoms1[i];
993 atom.getAcceleration(accel);
994 if (sharedAtoms1[i]) {
995 acceleration[indexCommon++] = accel[0];
996 acceleration[indexCommon++] = accel[1];
997 acceleration[indexCommon++] = accel[2];
998 } else {
999 acceleration[indexUnique++] = accel[0];
1000 acceleration[indexUnique++] = accel[1];
1001 acceleration[indexUnique++] = accel[2];
1002 }
1003 }
1004 for (int i = 0; i < nActive2; i++) {
1005 if (!sharedAtoms2[i]) {
1006 Atom atom = activeAtoms2[i];
1007 atom.getAcceleration(accel);
1008 acceleration[indexUnique++] = accel[0];
1009 acceleration[indexUnique++] = accel[1];
1010 acceleration[indexUnique++] = accel[2];
1011 }
1012 }
1013
1014 return acceleration;
1015 }
1016
1017
1018
1019
1020 @Override
1021 public double[] getCoordinates(double[] x) {
1022 if (x == null) {
1023 x = new double[nVariables];
1024 }
1025 int indexCommon = 0;
1026 int indexUnique = nShared * 3;
1027 for (int i = 0; i < nActive1; i++) {
1028 Atom a = activeAtoms1[i];
1029 if (sharedAtoms1[i]) {
1030 x[indexCommon++] = a.getX();
1031 x[indexCommon++] = a.getY();
1032 x[indexCommon++] = a.getZ();
1033 } else {
1034 x[indexUnique++] = a.getX();
1035 x[indexUnique++] = a.getY();
1036 x[indexUnique++] = a.getZ();
1037 }
1038 }
1039 for (int i = 0; i < nActive2; i++) {
1040 if (!sharedAtoms2[i]) {
1041 Atom a = activeAtoms2[i];
1042 x[indexUnique++] = a.getX();
1043 x[indexUnique++] = a.getY();
1044 x[indexUnique++] = a.getZ();
1045 }
1046 }
1047 return x;
1048 }
1049
1050
1051
1052
1053 @Override
1054 public void setCoordinates(@Nullable double[] x) {
1055 int indexCommon = 0;
1056 int indexUnique = nShared * 3;
1057 double[] xyz = new double[3];
1058 for (int i = 0; i < nActive1; i++) {
1059 if (sharedAtoms1[i]) {
1060 xyz[0] = x[indexCommon++];
1061 xyz[1] = x[indexCommon++];
1062 xyz[2] = x[indexCommon++];
1063 } else {
1064 xyz[0] = x[indexUnique++];
1065 xyz[1] = x[indexUnique++];
1066 xyz[2] = x[indexUnique++];
1067 }
1068 Atom a = activeAtoms1[i];
1069 a.setXYZ(xyz);
1070 }
1071
1072 indexCommon = 0;
1073 for (int i = 0; i < nActive2; i++) {
1074 if (sharedAtoms2[i]) {
1075 xyz[0] = x[indexCommon++];
1076 xyz[1] = x[indexCommon++];
1077 xyz[2] = x[indexCommon++];
1078 } else {
1079 xyz[0] = x[indexUnique++];
1080 xyz[1] = x[indexUnique++];
1081 xyz[2] = x[indexUnique++];
1082 }
1083 Atom a = activeAtoms2[i];
1084 a.setXYZ(xyz);
1085 }
1086 }
1087
1088
1089
1090
1091 @Override
1092 public Crystal getCrystal() {
1093
1094 if (useSymOp && logger.isLoggable(Level.FINE)) {
1095 logger.fine(" Get method only returned first crystal.");
1096 }
1097 return potential1.getCrystal();
1098 }
1099
1100
1101
1102
1103 @Override
1104 public void setCrystal(Crystal crystal) {
1105
1106 if (useSymOp && logger.isLoggable(Level.FINE)) {
1107 logger.fine(" Both systems set to the same crystal.");
1108 }
1109 potential1.setCrystal(crystal);
1110 potential2.setCrystal(crystal);
1111 }
1112
1113
1114
1115
1116 @Override
1117 public STATE getEnergyTermState() {
1118 return state;
1119 }
1120
1121
1122
1123
1124 @Override
1125 public void setEnergyTermState(STATE state) {
1126 this.state = state;
1127 potential1.setEnergyTermState(state);
1128 potential2.setEnergyTermState(state);
1129 }
1130
1131
1132
1133
1134 @Override
1135 public double getLambda() {
1136 return lambda;
1137 }
1138
1139
1140
1141
1142 @Override
1143 public void setLambda(double lambda) {
1144 if (lambda <= 1.0 && lambda >= 0.0) {
1145 this.lambda = lambda;
1146 double oneMinusLambda = 1.0 - lambda;
1147 lambdaInterface1.setLambda(lambda);
1148 lambdaInterface2.setLambda(oneMinusLambda);
1149
1150 f1L = switchFunction.valueAt(lambda);
1151 dF1dL = switchFunction.firstDerivative(lambda);
1152 d2F1dL2 = switchFunction.secondDerivative(lambda);
1153
1154 f2L = switchFunction.valueAt(oneMinusLambda);
1155 dF2dL = -1.0 * switchFunction.firstDerivative(oneMinusLambda);
1156 d2F2dL2 = switchFunction.secondDerivative(oneMinusLambda);
1157 } else {
1158 String message = format("Lambda value %8.3f is not in the range [0..1].", lambda);
1159 throw new IllegalArgumentException(message);
1160 }
1161 }
1162
1163
1164
1165
1166 @Override
1167 public double[] getMass() {
1168 return mass;
1169 }
1170
1171
1172
1173
1174
1175
1176 public int getNumSharedVariables() {
1177 return 3 * nShared;
1178 }
1179
1180
1181
1182
1183 @Override
1184 public int getNumberOfVariables() {
1185 return nVariables;
1186 }
1187
1188
1189
1190
1191 @Override
1192 public double[] getPreviousAcceleration(double[] previousAcceleration) {
1193 if (previousAcceleration == null || previousAcceleration.length < nVariables) {
1194 previousAcceleration = new double[nVariables];
1195 }
1196 int indexCommon = 0;
1197 int indexUnique = nShared * 3;
1198 double[] prev = new double[3];
1199 for (int i = 0; i < nActive1; i++) {
1200 Atom atom = activeAtoms1[i];
1201 atom.getPreviousAcceleration(prev);
1202 if (sharedAtoms1[i]) {
1203 previousAcceleration[indexCommon++] = prev[0];
1204 previousAcceleration[indexCommon++] = prev[1];
1205 previousAcceleration[indexCommon++] = prev[2];
1206 } else {
1207 previousAcceleration[indexUnique++] = prev[0];
1208 previousAcceleration[indexUnique++] = prev[1];
1209 previousAcceleration[indexUnique++] = prev[2];
1210 }
1211 }
1212 for (int i = 0; i < nActive2; i++) {
1213 Atom atom = activeAtoms2[i];
1214 if (!sharedAtoms2[i]) {
1215 atom.getPreviousAcceleration(prev);
1216 previousAcceleration[indexUnique++] = prev[0];
1217 previousAcceleration[indexUnique++] = prev[1];
1218 previousAcceleration[indexUnique++] = prev[2];
1219 }
1220 }
1221
1222 return previousAcceleration;
1223 }
1224
1225
1226
1227
1228 @Override
1229 public double[] getScaling() {
1230 return scaling;
1231 }
1232
1233
1234
1235
1236 @Override
1237 public void setScaling(double[] scaling) {
1238 this.scaling = scaling;
1239 }
1240
1241
1242
1243
1244
1245
1246
1247 public UnivariateSwitchingFunction getSwitch() {
1248 return switchFunction;
1249 }
1250
1251
1252
1253
1254 @Override
1255 public double getTotalEnergy() {
1256 return totalEnergy;
1257 }
1258
1259 @Override
1260 public List<Potential> getUnderlyingPotentials() {
1261 List<Potential> under = new ArrayList<>(2);
1262 under.add(forceFieldEnergy1);
1263 under.add(forceFieldEnergy2);
1264 under.addAll(forceFieldEnergy1.getUnderlyingPotentials());
1265 under.addAll(forceFieldEnergy2.getUnderlyingPotentials());
1266 return under;
1267 }
1268
1269
1270
1271
1272 @Override
1273 public VARIABLE_TYPE[] getVariableTypes() {
1274 return variableTypes;
1275 }
1276
1277
1278
1279
1280 @Override
1281 public double[] getVelocity(double[] velocity) {
1282 if (velocity == null || velocity.length < nVariables) {
1283 velocity = new double[nVariables];
1284 }
1285 int indexCommon = 0;
1286 int indexUnique = nShared * 3;
1287 double[] vel = new double[3];
1288 for (int i = 0; i < nActive1; i++) {
1289 Atom atom = activeAtoms1[i];
1290 atom.getVelocity(vel);
1291 if (sharedAtoms1[i]) {
1292 velocity[indexCommon++] = vel[0];
1293 velocity[indexCommon++] = vel[1];
1294 velocity[indexCommon++] = vel[2];
1295 } else {
1296 velocity[indexUnique++] = vel[0];
1297 velocity[indexUnique++] = vel[1];
1298 velocity[indexUnique++] = vel[2];
1299 }
1300 }
1301 for (int i = 0; i < nActive2; i++) {
1302 if (!sharedAtoms2[i]) {
1303 Atom atom = activeAtoms2[i];
1304 atom.getVelocity(vel);
1305 velocity[indexUnique++] = vel[0];
1306 velocity[indexUnique++] = vel[1];
1307 velocity[indexUnique++] = vel[2];
1308 }
1309 }
1310
1311 return velocity;
1312 }
1313
1314
1315
1316
1317 @Override
1318 public double getd2EdL2() {
1319 double e1 =
1320 f1L * d2EdL2_1
1321 + 2.0 * dF1dL * dEdL_1
1322 + d2F1dL2 * energy1
1323 + f2L * restraintd2EdL2_1
1324 + 2.0 * dF2dL * restraintdEdL_1
1325 + d2F2dL2 * restraintEnergy1;
1326 double e2;
1327 if (!useFirstSystemBondedEnergy) {
1328 e2 = f2L * d2EdL2_2
1329 + 2.0 * dF2dL * dEdL_2
1330 + d2F2dL2 * energy2
1331 + f1L * restraintd2EdL2_2
1332 + 2.0 * dF1dL * restraintdEdL_2
1333 + d2F1dL2 * restraintEnergy2;
1334 } else {
1335 e2 = f2L * d2EdL2_2
1336 + 2.0 * dF2dL * dEdL_2
1337 + d2F2dL2 * energy2
1338 - f2L * restraintd2EdL2_2
1339 - 2.0 * dF2dL * restraintdEdL_2
1340 - d2F2dL2 * restraintEnergy2;
1341 }
1342 return e1 + e2;
1343 }
1344
1345
1346
1347
1348 @Override
1349 public double getdEdL() {
1350
1351
1352 double e1 = f1L * dEdL_1 + dF1dL * energy1 + f2L * restraintdEdL_1 + dF2dL * restraintEnergy1;
1353 double e2;
1354 if (!useFirstSystemBondedEnergy) {
1355 e2 = f2L * dEdL_2 + dF2dL * energy2 + f1L * restraintdEdL_2 + dF1dL * restraintEnergy2;
1356 } else {
1357 e2 = f2L * dEdL_2 + dF2dL * energy2 - f2L * restraintdEdL_2 - dF2dL * restraintEnergy2;
1358 }
1359 return e1 + e2;
1360 }
1361
1362
1363
1364
1365 @Override
1366 public void getdEdXdL(double[] g) {
1367 if (g == null) {
1368 g = new double[nVariables];
1369 }
1370
1371 int index = 0;
1372 int indexCommon = 0;
1373 int indexUnique = nShared * 3;
1374
1375 for (int i = 0; i < nActive1; i++) {
1376 if (sharedAtoms1[i]) {
1377 g[indexCommon++] =
1378 f1L * gl1[index] + dF1dL * g1[index] + f2L * rgl1[index] + dF2dL * rg1[index++];
1379 g[indexCommon++] =
1380 f1L * gl1[index] + dF1dL * g1[index] + f2L * rgl1[index] + dF2dL * rg1[index++];
1381 g[indexCommon++] =
1382 f1L * gl1[index] + dF1dL * g1[index] + f2L * rgl1[index] + dF2dL * rg1[index++];
1383 } else {
1384 g[indexUnique++] =
1385 f1L * gl1[index] + dF1dL * g1[index] + f2L * rgl1[index] + dF2dL * rg1[index++];
1386 g[indexUnique++] =
1387 f1L * gl1[index] + dF1dL * g1[index] + f2L * rgl1[index] + dF2dL * rg1[index++];
1388 g[indexUnique++] =
1389 f1L * gl1[index] + dF1dL * g1[index] + f2L * rgl1[index] + dF2dL * rg1[index++];
1390 }
1391 }
1392
1393
1394 if (!useFirstSystemBondedEnergy) {
1395 index = 0;
1396 indexCommon = 0;
1397 for (int i = 0; i < nActive2; i++) {
1398 if (sharedAtoms2[i]) {
1399 g[indexCommon++] += (-f2L * gl2[index] + dF2dL * g2[index] - f1L * rgl2[index]
1400 + dF1dL * rg2[index++]);
1401 g[indexCommon++] += (-f2L * gl2[index] + dF2dL * g2[index] - f1L * rgl2[index]
1402 + dF1dL * rg2[index++]);
1403 g[indexCommon++] += (-f2L * gl2[index] + dF2dL * g2[index] - f1L * rgl2[index]
1404 + dF1dL * rg2[index++]);
1405 } else {
1406 g[indexUnique++] = (-f2L * gl2[index] + dF2dL * g2[index] - f1L * rgl2[index]
1407 + dF1dL * rg2[index++]);
1408 g[indexUnique++] = (-f2L * gl2[index] + dF2dL * g2[index] - f1L * rgl2[index]
1409 + dF1dL * rg2[index++]);
1410 g[indexUnique++] = (-f2L * gl2[index] + dF2dL * g2[index] - f1L * rgl2[index]
1411 + dF1dL * rg2[index++]);
1412 }
1413 }
1414 } else {
1415 index = 0;
1416 indexCommon = 0;
1417 for (int i = 0; i < nActive2; i++) {
1418 if (sharedAtoms2[i]) {
1419 g[indexCommon++] += (-f2L * gl2[index] + dF2dL * g2[index] + f2L * rgl2[index]
1420 - dF2dL * rg2[index++]);
1421 g[indexCommon++] += (-f2L * gl2[index] + dF2dL * g2[index] + f2L * rgl2[index]
1422 - dF2dL * rg2[index++]);
1423 g[indexCommon++] += (-f2L * gl2[index] + dF2dL * g2[index] + f2L * rgl2[index]
1424 - dF2dL * rg2[index++]);
1425 } else {
1426 g[indexUnique++] = (-f2L * gl2[index] + dF2dL * g2[index] + f2L * rgl2[index]
1427 - dF2dL * rg2[index++]);
1428 g[indexUnique++] = (-f2L * gl2[index] + dF2dL * g2[index] + f2L * rgl2[index]
1429 - dF2dL * rg2[index++]);
1430 g[indexUnique++] = (-f2L * gl2[index] + dF2dL * g2[index] + f2L * rgl2[index]
1431 - dF2dL * rg2[index++]);
1432 }
1433 }
1434
1435 }
1436 }
1437
1438
1439
1440
1441 @Override
1442 public void setAcceleration(double[] acceleration) {
1443 double[] accel = new double[3];
1444 int indexCommon = 0;
1445 int indexUnique = 3 * nShared;
1446 for (int i = 0; i < nActive1; i++) {
1447 Atom atom = activeAtoms1[i];
1448 if (sharedAtoms1[i]) {
1449 accel[0] = acceleration[indexCommon++];
1450 accel[1] = acceleration[indexCommon++];
1451 accel[2] = acceleration[indexCommon++];
1452 } else {
1453 accel[0] = acceleration[indexUnique++];
1454 accel[1] = acceleration[indexUnique++];
1455 accel[2] = acceleration[indexUnique++];
1456 }
1457 atom.setAcceleration(accel);
1458 }
1459 indexCommon = 0;
1460 for (int i = 0; i < nActive2; i++) {
1461 Atom atom = activeAtoms2[i];
1462 if (sharedAtoms2[i]) {
1463 accel[0] = acceleration[indexCommon++];
1464 accel[1] = acceleration[indexCommon++];
1465 accel[2] = acceleration[indexCommon++];
1466 } else {
1467 accel[0] = acceleration[indexUnique++];
1468 accel[1] = acceleration[indexUnique++];
1469 accel[2] = acceleration[indexUnique++];
1470 }
1471 atom.setAcceleration(accel);
1472 }
1473 }
1474
1475
1476
1477
1478
1479
1480 public void setParallel(boolean parallel) {
1481 if (parallelTeam != null) {
1482 try {
1483 parallelTeam.shutdown();
1484 } catch (Exception e) {
1485 logger.severe(format(" Exception in shutting down old ParallelTeam for DualTopologyEnergy: %s", e));
1486 }
1487 }
1488 parallelTeam = parallel ? new ParallelTeam(2) : new ParallelTeam(1);
1489 }
1490
1491
1492
1493
1494 @Override
1495 public void setPreviousAcceleration(double[] previousAcceleration) {
1496 double[] prev = new double[3];
1497 int indexCommon = 0;
1498 int indexUnique = 3 * nShared;
1499 for (int i = 0; i < nActive1; i++) {
1500 Atom atom = activeAtoms1[i];
1501 if (sharedAtoms1[i]) {
1502 prev[0] = previousAcceleration[indexCommon++];
1503 prev[1] = previousAcceleration[indexCommon++];
1504 prev[2] = previousAcceleration[indexCommon++];
1505 } else {
1506 prev[0] = previousAcceleration[indexUnique++];
1507 prev[1] = previousAcceleration[indexUnique++];
1508 prev[2] = previousAcceleration[indexUnique++];
1509 }
1510 atom.setPreviousAcceleration(prev);
1511 }
1512 indexCommon = 0;
1513 for (int i = 0; i < nActive2; i++) {
1514 Atom atom = activeAtoms2[i];
1515 if (sharedAtoms2[i]) {
1516 prev[0] = previousAcceleration[indexCommon++];
1517 prev[1] = previousAcceleration[indexCommon++];
1518 prev[2] = previousAcceleration[indexCommon++];
1519 } else {
1520 prev[0] = previousAcceleration[indexUnique++];
1521 prev[1] = previousAcceleration[indexUnique++];
1522 prev[2] = previousAcceleration[indexUnique++];
1523 }
1524 atom.setPreviousAcceleration(prev);
1525 }
1526 }
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538 public void setPrintOnFailure(boolean onFail, boolean override) {
1539 forceFieldEnergy1.setPrintOnFailure(onFail, override);
1540 forceFieldEnergy2.setPrintOnFailure(onFail, override);
1541 }
1542
1543
1544
1545
1546 @Override
1547 public void setVelocity(double[] velocity) {
1548 double[] vel = new double[3];
1549 int indexCommon = 0;
1550 int indexUnique = 3 * nShared;
1551 for (int i = 0; i < nActive1; i++) {
1552 Atom atom = activeAtoms1[i];
1553 if (sharedAtoms1[i]) {
1554 vel[0] = velocity[indexCommon++];
1555 vel[1] = velocity[indexCommon++];
1556 vel[2] = velocity[indexCommon++];
1557 } else {
1558 vel[0] = velocity[indexUnique++];
1559 vel[1] = velocity[indexUnique++];
1560 vel[2] = velocity[indexUnique++];
1561 }
1562 atom.setVelocity(vel);
1563 }
1564
1565 indexCommon = 0;
1566 for (int i = 0; i < nActive2; i++) {
1567 Atom atom = activeAtoms2[i];
1568 if (sharedAtoms2[i]) {
1569 vel[0] = velocity[indexCommon++];
1570 vel[1] = velocity[indexCommon++];
1571 vel[2] = velocity[indexCommon++];
1572 } else {
1573 vel[0] = velocity[indexUnique++];
1574 vel[1] = velocity[indexUnique++];
1575 vel[2] = velocity[indexUnique++];
1576 }
1577 atom.setVelocity(vel);
1578 }
1579 }
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589 private void reconcileAtoms(Atom a1, Atom a2, Level warnlev) {
1590 double dist = 0;
1591 double[] xyz1 = a1.getXYZ(null);
1592 double[] xyz2 = a2.getXYZ(null);
1593 double[] xyzAv = new double[3];
1594 for (int i = 0; i < 3; i++) {
1595 double dx = xyz1[i] - xyz2[i];
1596 dist += (dx * dx);
1597 xyzAv[i] = xyz1[i] + (0.5 * dx);
1598 }
1599
1600
1601 double maxDist2 = 0.09;
1602
1603
1604
1605
1606
1607 double minDistWarn2 = 0.00001;
1608
1609 if (dist > maxDist2) {
1610 logger.log(
1611 Level.SEVERE,
1612 format(
1613 " Distance between atoms %s " + "and %s is %7.4f >> maximum allowed %7.4f",
1614 a1, a2, sqrt(dist), sqrt(maxDist2)));
1615 } else if (dist > minDistWarn2) {
1616 logger.log(
1617 warnlev,
1618 format(
1619 " Distance between atoms %s " + "and %s is %7.4f; moving atoms together.",
1620 a1, a2, sqrt(dist)));
1621 a1.setXYZ(xyzAv);
1622 a2.setXYZ(xyzAv);
1623 } else if (dist > 0) {
1624
1625 a1.setXYZ(xyzAv);
1626 a2.setXYZ(xyzAv);
1627 }
1628 }
1629
1630 private void packGradient(double[] x, double[] g) {
1631 if (g == null) {
1632 g = new double[nVariables];
1633 }
1634 int indexCommon = 0;
1635 int indexUnique = nShared * 3;
1636
1637
1638 int index = 0;
1639 for (int i = 0; i < nActive1; i++) {
1640 if (sharedAtoms1[i]) {
1641 g[indexCommon++] = f1L * g1[index] + f2L * rg1[index++];
1642 g[indexCommon++] = f1L * g1[index] + f2L * rg1[index++];
1643 g[indexCommon++] = f1L * g1[index] + f2L * rg1[index++];
1644 } else {
1645 g[indexUnique++] = f1L * g1[index] + f2L * rg1[index++];
1646 g[indexUnique++] = f1L * g1[index] + f2L * rg1[index++];
1647 g[indexUnique++] = f1L * g1[index] + f2L * rg1[index++];
1648 }
1649 }
1650
1651 if (!useFirstSystemBondedEnergy) {
1652
1653 indexCommon = 0;
1654 index = 0;
1655 for (int i = 0; i < nActive2; i++) {
1656 if (sharedAtoms2[i]) {
1657 g[indexCommon++] += f2L * g2[index] + f1L * rg2[index++];
1658 g[indexCommon++] += f2L * g2[index] + f1L * rg2[index++];
1659 g[indexCommon++] += f2L * g2[index] + f1L * rg2[index++];
1660 } else {
1661 g[indexUnique++] = f2L * g2[index] + f1L * rg2[index++];
1662 g[indexUnique++] = f2L * g2[index] + f1L * rg2[index++];
1663 g[indexUnique++] = f2L * g2[index] + f1L * rg2[index++];
1664 }
1665 }
1666 } else {
1667
1668 indexCommon = 0;
1669 index = 0;
1670 for (int i = 0; i < nActive2; i++) {
1671 if (sharedAtoms2[i]) {
1672 g[indexCommon++] += f2L * g2[index] - f2L * rg2[index++];
1673 g[indexCommon++] += f2L * g2[index] - f2L * rg2[index++];
1674 g[indexCommon++] += f2L * g2[index] - f2L * rg2[index++];
1675 } else {
1676 g[indexUnique++] = f2L * g2[index] - f2L * rg2[index++];
1677 g[indexUnique++] = f2L * g2[index] - f2L * rg2[index++];
1678 g[indexUnique++] = f2L * g2[index] - f2L * rg2[index++];
1679 }
1680 }
1681 }
1682
1683 scaleCoordinatesAndGradient(x, g);
1684 }
1685
1686 private void unpackCoordinates(double[] x) {
1687
1688
1689 unscaleCoordinates(x);
1690
1691 int index = 0;
1692 int indexCommon = 0;
1693 int indexUnique = 3 * nShared;
1694 for (int i = 0; i < nActive1; i++) {
1695 if (sharedAtoms1[i]) {
1696 x1[index++] = x[indexCommon++];
1697 x1[index++] = x[indexCommon++];
1698 x1[index++] = x[indexCommon++];
1699 } else {
1700 x1[index++] = x[indexUnique++];
1701 x1[index++] = x[indexUnique++];
1702 x1[index++] = x[indexUnique++];
1703 }
1704 }
1705
1706 index = 0;
1707 indexCommon = 0;
1708 for (int i = 0; i < nActive2; i++) {
1709 if (sharedAtoms2[i]) {
1710 x2[index++] = x[indexCommon++];
1711 x2[index++] = x[indexCommon++];
1712 x2[index++] = x[indexCommon++];
1713 } else {
1714 x2[index++] = x[indexUnique++];
1715 x2[index++] = x[indexUnique++];
1716 x2[index++] = x[indexUnique++];
1717 }
1718 }
1719 }
1720
1721
1722
1723
1724
1725
1726
1727
1728 void reloadCommonMasses(boolean secondTopology) {
1729 int commonIndex = 0;
1730 if (secondTopology) {
1731 for (int i = 0; i < nActive2; i++) {
1732 Atom a = activeAtoms2[i];
1733 double m = a.getMass();
1734 if (sharedAtoms2[i]) {
1735 mass[commonIndex++] = m;
1736 mass[commonIndex++] = m;
1737 mass[commonIndex++] = m;
1738 }
1739 }
1740 } else {
1741 for (int i = 0; i < nActive1; i++) {
1742 Atom a = activeAtoms1[i];
1743 double m = a.getMass();
1744 if (sharedAtoms1[i]) {
1745 mass[commonIndex++] = m;
1746 mass[commonIndex++] = m;
1747 mass[commonIndex++] = m;
1748 }
1749 }
1750 }
1751 }
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761 private void validateAlchemicalAtoms(int nActive, boolean[] sharedAtoms, Atom[] activeAtoms) {
1762 int numSymOps = symOpAtoms.size();
1763
1764 for (int i = 0; i < nActive; i++) {
1765
1766 if (!sharedAtoms[i]) {
1767 Atom alchAtom = activeAtoms[i];
1768 int symOpForAlchemicalAtom = -1;
1769 for (int j = 0; j < numSymOps; j++) {
1770 List<Integer> symOpAtomGroup = symOpAtoms.get(j);
1771 if (symOpAtomGroup.contains(i)) {
1772 symOpForAlchemicalAtom = j;
1773 break;
1774 }
1775 }
1776
1777 StringBuilder sb = new StringBuilder();
1778 sb.append(format("\n Alchemical atom %s\n Symmetry group %s:\n", alchAtom, symOpForAlchemicalAtom + 1));
1779 List<Integer> symOpAtomGroup = symOpAtoms.get(symOpForAlchemicalAtom);
1780 for (Integer j : symOpAtomGroup) {
1781 if (j == i) {
1782 continue;
1783 }
1784 sb.append(format(" %d %s\n", j + 1, activeAtoms[j]));
1785 }
1786 sb.append(format("\n 1-2, 1-3 or 1-4 atoms in other symmetry groups:\n"));
1787
1788
1789 ArrayList<Integer> bondedAtoms = new ArrayList<>();
1790 addUniqueBondedIndices(bondedAtoms, alchAtom.get12List());
1791 addUniqueBondedIndices(bondedAtoms, alchAtom.get13List());
1792 addUniqueBondedIndices(bondedAtoms, alchAtom.get14List());
1793
1794
1795 boolean conflict = false;
1796 for (int j = 0; j < numSymOps; j++) {
1797
1798 if (j == symOpForAlchemicalAtom) {
1799 continue;
1800 }
1801 symOpAtomGroup = symOpAtoms.get(j);
1802 for (Integer integer : bondedAtoms) {
1803 if (symOpAtomGroup.contains(integer)) {
1804 conflict = true;
1805 sb.append(format(" %d %s uses symmetry group %2d.\n", integer + 1, activeAtoms[integer], j + 1));
1806 }
1807 }
1808 }
1809 if (conflict) {
1810 logger.info(sb.toString());
1811 }
1812 }
1813 }
1814 }
1815
1816
1817
1818
1819
1820
1821
1822 private void addUniqueBondedIndices(ArrayList<Integer> uniqueIndices, List<Atom> bondedAtoms) {
1823
1824 for (Atom a : bondedAtoms) {
1825 int index = a.getIndex() - 1;
1826
1827 if (!uniqueIndices.contains(index)) {
1828
1829 uniqueIndices.add(index);
1830 }
1831 }
1832 }
1833
1834 private class EnergyRegion extends ParallelRegion {
1835
1836 private final Energy1Section e1sect;
1837 private final Energy2Section e2sect;
1838 private double[] x;
1839 private double[] g;
1840 private boolean gradient = false;
1841 private boolean verbose = false;
1842
1843 EnergyRegion() {
1844 e1sect = new Energy1Section();
1845 e2sect = new Energy2Section();
1846 }
1847
1848 @Override
1849 public void finish() {
1850
1851 if (!useFirstSystemBondedEnergy) {
1852 totalEnergy =
1853 f1L * energy1 + f2L * restraintEnergy1 + f2L * energy2 + f1L * restraintEnergy2;
1854 } else {
1855 totalEnergy =
1856 f1L * energy1 + f2L * restraintEnergy1 + f2L * energy2 - f2L * restraintEnergy2;
1857 }
1858
1859 if (gradient) {
1860 packGradient(x, g);
1861 } else {
1862 scaleCoordinates(x);
1863 }
1864
1865 if (verbose) {
1866 logger.info(format(" Total dual-topology energy: %12.4f", totalEnergy));
1867 }
1868 setVerbose(false);
1869 setGradient(false);
1870 }
1871
1872 @Override
1873 public void run() throws Exception {
1874 execute(e1sect, e2sect);
1875 }
1876
1877 public void setG(double[] g) {
1878 this.g = g;
1879 setGradient(true);
1880 }
1881
1882 public void setGradient(boolean grad) {
1883 this.gradient = grad;
1884 e1sect.setGradient(grad);
1885 e2sect.setGradient(grad);
1886 }
1887
1888 public void setVerbose(boolean verb) {
1889 this.verbose = verb;
1890 e1sect.setVerbose(verb);
1891 e2sect.setVerbose(verb);
1892 }
1893
1894 public void setX(double[] x) {
1895 this.x = x;
1896 }
1897
1898 @Override
1899 public void start() {
1900 unpackCoordinates(x);
1901 }
1902 }
1903
1904 private class Energy1Section extends ParallelSection {
1905
1906 private boolean gradient = false;
1907 private boolean verbose = false;
1908
1909 @Override
1910 public void run() {
1911 if (gradient) {
1912 fill(gl1, 0.0);
1913 fill(rgl1, 0.0);
1914 energy1 = potential1.energyAndGradient(x1, g1, verbose);
1915 dEdL_1 = lambdaInterface1.getdEdL();
1916 d2EdL2_1 = lambdaInterface1.getd2EdL2();
1917 lambdaInterface1.getdEdXdL(gl1);
1918 if (doValenceRestraint1) {
1919 if (verbose) {
1920 logger.info(" Calculating lambda bonded terms for topology 1");
1921 }
1922 forceFieldEnergy1.setLambdaBondedTerms(true, useFirstSystemBondedEnergy);
1923 if (forceFieldEnergy1 instanceof OpenMMEnergy openMMEnergy) {
1924
1925 restraintEnergy1 = openMMEnergy.energyAndGradientFFX(x1, rg1, verbose);
1926 } else {
1927 restraintEnergy1 = forceFieldEnergy1.energyAndGradient(x1, rg1, verbose);
1928 }
1929 restraintdEdL_1 = forceFieldEnergy1.getdEdL();
1930 restraintd2EdL2_1 = forceFieldEnergy1.getd2EdL2();
1931 forceFieldEnergy1.getdEdXdL(rgl1);
1932 forceFieldEnergy1.setLambdaBondedTerms(false, false);
1933 } else {
1934 restraintEnergy1 = 0.0;
1935 restraintdEdL_1 = 0.0;
1936 restraintd2EdL2_1 = 0.0;
1937 }
1938 if (logger.isLoggable(Level.FINE)) {
1939 logger.fine(format(" Topology 1 Energy & Restraints: %15.8f %15.8f\n", f1L * energy1, f2L * restraintEnergy1));
1940 logger.fine(format(" Topology 1: %15.8f * (%.2f)", energy1, f1L));
1941 logger.fine(format(" T1 Restraints: %15.8f * (%.2f)", restraintEnergy1, f2L));
1942 }
1943 } else {
1944 energy1 = potential1.energy(x1, verbose);
1945 if (doValenceRestraint1) {
1946 if (verbose) {
1947 logger.info(" Calculating lambda bonded terms for topology 1");
1948 }
1949 forceFieldEnergy1.setLambdaBondedTerms(true, useFirstSystemBondedEnergy);
1950 if (forceFieldEnergy1 instanceof OpenMMEnergy openMMEnergy) {
1951
1952 restraintEnergy1 = openMMEnergy.energyFFX(x1, verbose);
1953 } else {
1954 restraintEnergy1 = potential1.energy(x1, verbose);
1955 }
1956 forceFieldEnergy1.setLambdaBondedTerms(false, false);
1957 } else {
1958 restraintEnergy1 = 0.0;
1959 }
1960 if (logger.isLoggable(Level.FINE)) {
1961 logger.fine(format(" Topology 1 Energy & Restraints: %15.8f %15.8f\n", f1L * energy1, f2L * restraintEnergy1));
1962 }
1963 }
1964 }
1965
1966 public void setGradient(boolean grad) {
1967 this.gradient = grad;
1968 }
1969
1970 public void setVerbose(boolean verb) {
1971 this.verbose = verb;
1972 }
1973 }
1974
1975 private class Energy2Section extends ParallelSection {
1976
1977 private boolean gradient = false;
1978 private boolean verbose = false;
1979
1980 @Override
1981 public void run() {
1982
1983 int numSymOps = 0;
1984 if (useSymOp) {
1985 numSymOps = symOpAtoms.size();
1986 for (int i = 0; i < numSymOps; i++) {
1987
1988 applyCartesianSymOp(x2, x2, symOp[i], mask[i]);
1989 }
1990 }
1991
1992 if (gradient) {
1993 fill(gl2, 0.0);
1994 fill(rgl2, 0.0);
1995
1996
1997 energy2 = potential2.energyAndGradient(x2, g2, verbose);
1998 dEdL_2 = -lambdaInterface2.getdEdL();
1999 d2EdL2_2 = lambdaInterface2.getd2EdL2();
2000 lambdaInterface2.getdEdXdL(gl2);
2001 if (useSymOp) {
2002
2003 for (int i = 0; i < numSymOps; i++) {
2004 applyCartesianSymRot(g2, g2, inverse[i], mask[i]);
2005 applyCartesianSymRot(gl2, gl2, inverse[i], mask[i]);
2006 }
2007 }
2008 if (doValenceRestraint2) {
2009 if (verbose) {
2010 logger.info(" Calculating lambda bonded terms for topology 2");
2011 }
2012 forceFieldEnergy2.setLambdaBondedTerms(true, useFirstSystemBondedEnergy);
2013 if (forceFieldEnergy2 instanceof OpenMMEnergy openMMEnergy) {
2014
2015 restraintEnergy2 = openMMEnergy.energyAndGradientFFX(x2, rg2, verbose);
2016 } else {
2017 restraintEnergy2 = forceFieldEnergy2.energyAndGradient(x2, rg2, verbose);
2018 }
2019 restraintdEdL_2 = -forceFieldEnergy2.getdEdL();
2020 restraintd2EdL2_2 = forceFieldEnergy2.getd2EdL2();
2021 forceFieldEnergy2.getdEdXdL(rgl2);
2022 if (useSymOp) {
2023
2024 for (int i = 0; i < numSymOps; i++) {
2025 applyCartesianSymRot(rg2, rg2, inverse[i], mask[i]);
2026 applyCartesianSymRot(rgl2, rgl2, inverse[i], mask[i]);
2027 }
2028 }
2029 forceFieldEnergy2.setLambdaBondedTerms(false, false);
2030 } else {
2031 restraintEnergy2 = 0.0;
2032 restraintdEdL_2 = 0.0;
2033 restraintd2EdL2_2 = 0.0;
2034 }
2035 if (logger.isLoggable(Level.FINE)) {
2036 logger.fine(format(" Topology 2 Energy & Restraints: %15.8f %15.8f", f2L * energy2, f1L * restraintEnergy2));
2037 logger.fine(format(" Topology 2: %15.8f * (%.2f)", energy2, f2L));
2038 logger.fine(format(" T2 Restraints: %15.8f * (%.2f)", restraintEnergy2, f1L));
2039 }
2040 } else {
2041 energy2 = potential2.energy(x2, verbose);
2042 if (doValenceRestraint2) {
2043 if (verbose) {
2044 logger.info(" Calculating lambda bonded terms for topology 2");
2045 }
2046 forceFieldEnergy2.setLambdaBondedTerms(true, useFirstSystemBondedEnergy);
2047 if (forceFieldEnergy2 instanceof OpenMMEnergy openMMEnergy) {
2048
2049 restraintEnergy2 = openMMEnergy.energyFFX(x2, verbose);
2050 } else {
2051 restraintEnergy2 = potential2.energy(x2, verbose);
2052 }
2053 forceFieldEnergy2.setLambdaBondedTerms(false, false);
2054 } else {
2055 restraintEnergy2 = 0.0;
2056 }
2057 if (logger.isLoggable(Level.FINE)) {
2058 logger.fine(format(" Topology 2 Energy & Restraints: %15.8f %15.8f", f2L * energy2, f1L * restraintEnergy2));
2059 }
2060 }
2061 }
2062
2063 public void setGradient(boolean grad) {
2064 this.gradient = grad;
2065 }
2066
2067 public void setVerbose(boolean verb) {
2068 this.verbose = verb;
2069 }
2070 }
2071 }