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.nonbonded;
39  
40  import edu.rit.pj.BarrierAction;
41  import edu.rit.pj.IntegerForLoop;
42  import edu.rit.pj.IntegerSchedule;
43  import edu.rit.pj.ParallelRegion;
44  import edu.rit.pj.ParallelTeam;
45  import edu.rit.pj.reduction.SharedBoolean;
46  import edu.rit.pj.reduction.SharedInteger;
47  import edu.rit.util.Range;
48  import ffx.crystal.Crystal;
49  import ffx.potential.MolecularAssembly;
50  import ffx.potential.bonded.Atom;
51  import ffx.potential.utils.PotentialsUtils;
52  
53  import java.io.File;
54  import java.util.ArrayList;
55  import java.util.Collections;
56  import java.util.HashMap;
57  import java.util.HashSet;
58  import java.util.List;
59  import java.util.Map;
60  import java.util.PriorityQueue;
61  import java.util.Set;
62  import java.util.logging.Level;
63  import java.util.logging.Logger;
64  
65  import static java.lang.String.format;
66  import static java.lang.System.arraycopy;
67  import static java.util.Arrays.copyOf;
68  import static java.util.Arrays.fill;
69  import static org.apache.commons.math3.util.FastMath.floor;
70  import static org.apache.commons.math3.util.FastMath.min;
71  import static org.apache.commons.math3.util.FastMath.sqrt;
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100 public class NeighborList extends ParallelRegion {
101 
102   
103 
104 
105   private static final Logger logger = Logger.getLogger(NeighborList.class.getName());
106 
107   private final DomainDecomposition domainDecomposition;
108   
109 
110 
111   private final double cutoff;
112   
113 
114 
115 
116   private final double buffer;
117   
118 
119 
120   private final double motion2;
121   
122 
123 
124   private final double cutoffPlusBuffer;
125   
126 
127 
128   private final double cutoffPlusBuffer2;
129   
130 
131 
132   private final ParallelTeam parallelTeam;
133   
134 
135 
136   private final int threadCount;
137   
138 
139 
140   private boolean forceRebuild = false;
141   
142 
143 
144   private boolean print = false;
145   
146 
147 
148 
149   private final SharedBoolean sharedMotion;
150   
151 
152 
153   private final MotionLoop[] motionLoops;
154   
155 
156 
157   private final ListInitBarrierAction listInitBarrierAction;
158   
159 
160 
161   private final AssignAtomsToCellsLoop[] assignAtomsToCellsLoops;
162   
163 
164 
165   private final VerletListLoop[] verletListLoop;
166   private final GroupVerletListLoop[] groupVerletListLoop;
167   
168 
169 
170   private long motionTime;
171   
172 
173 
174   private long initTime;
175   
176 
177 
178   private long assignAtomsToCellsTime;
179   
180 
181 
182   private long verletListTime;
183   
184 
185 
186   private long groupingTime;
187   
188 
189 
190   private final SharedInteger sharedCount;
191   
192 
193 
194   private final Range[] ranges;
195   
196 
197 
198 
199   private Crystal crystal;
200   
201 
202 
203   private int nSymm;
204   
205 
206 
207   private int nAtoms;
208   
209 
210 
211   private double[][] coordinates;
212   
213 
214 
215   private double[] previous;
216   
217 
218 
219   private int[][][] lists;
220   
221 
222 
223   private int M = -1;
224   
225 
226 
227   private int[][] mGroups;
228   
229 
230 
231   private int numGroups;
232   
233 
234 
235   private int[] mGroupID;
236   
237 
238 
239   private int[][][] groupLists;
240   
241 
242 
243   private int[] listCount;
244   
245 
246 
247   private PairwiseSchedule pairwiseSchedule;
248   
249 
250 
251   private int asymmetricUnitCount;
252   
253 
254 
255   private int symmetryMateCount;
256   
257 
258 
259   private int groupAsymmetricUnitCount;
260   
261 
262 
263 
264   private int groupSymmetryMateCount;
265   
266 
267 
268   private boolean[] use;
269   
270 
271 
272   private Atom[] atoms;
273   
274 
275 
276   private long time;
277   
278 
279 
280   private boolean intermolecular = true;
281   
282 
283 
284 
285 
286   private final boolean includeInactivePairs = true;
287   
288 
289 
290   private boolean disableUpdates = false;
291 
292   
293 
294 
295 
296 
297 
298 
299 
300 
301 
302   public NeighborList(Crystal crystal, Atom[] atoms, double cutoff,
303                       double buffer, ParallelTeam parallelTeam) {
304     this.crystal = crystal;
305     this.cutoff = cutoff;
306     this.buffer = buffer;
307     this.parallelTeam = new ParallelTeam(parallelTeam.getThreadCount());
308     this.atoms = atoms;
309     nAtoms = atoms.length;
310 
311     
312     cutoffPlusBuffer = cutoff + buffer;
313     cutoffPlusBuffer2 = cutoffPlusBuffer * cutoffPlusBuffer;
314     motion2 = (buffer / 2.0) * (buffer / 2.0);
315 
316     
317     threadCount = parallelTeam.getThreadCount();
318     sharedCount = new SharedInteger();
319     ranges = new Range[threadCount];
320 
321     sharedMotion = new SharedBoolean();
322     motionLoops = new MotionLoop[threadCount];
323     listInitBarrierAction = new ListInitBarrierAction();
324     assignAtomsToCellsLoops = new AssignAtomsToCellsLoop[threadCount];
325     verletListLoop = new VerletListLoop[threadCount];
326     groupVerletListLoop = new GroupVerletListLoop[threadCount];
327     for (int i = 0; i < threadCount; i++) {
328       motionLoops[i] = new MotionLoop();
329       verletListLoop[i] = new VerletListLoop();
330       assignAtomsToCellsLoops[i] = new AssignAtomsToCellsLoop();
331       groupVerletListLoop[i] = new GroupVerletListLoop();
332     }
333 
334     
335     boolean print = logger.isLoggable(Level.FINE);
336     domainDecomposition = new DomainDecomposition(nAtoms, crystal, cutoffPlusBuffer);
337     initNeighborList(print);
338   }
339 
340   
341 
342 
343 
344 
345 
346 
347 
348 
349 
350 
351   public void buildList(final double[][] coordinates, final int[][][] lists, boolean[] use,
352                         boolean forceRebuild, boolean print) {
353     if (disableUpdates) {
354       return;
355     }
356     this.coordinates = coordinates;
357     this.lists = lists;
358     this.use = use;
359     this.forceRebuild = forceRebuild;
360     this.print = print;
361 
362     try {
363       parallelTeam.execute(this);
364     } catch (Exception e) {
365       String message = "Fatal exception building neighbor list.\n";
366       logger.log(Level.SEVERE, message, e);
367     }
368   }
369 
370   
371 
372 
373 
374 
375 
376 
377 
378   public void setGroupSize(int M) {
379     this.M = M;
380   }
381 
382   
383 
384 
385 
386 
387   public void destroy() throws Exception {
388     parallelTeam.shutdown();
389   }
390 
391   
392 
393 
394 
395 
396 
397 
398   @Override
399   public void finish() {
400     if (disableUpdates) {
401       return;
402     }
403     if (!forceRebuild && !sharedMotion.get()) {
404       return;
405     }
406 
407     
408     int atomsWithIteractions = 0;
409     asymmetricUnitCount = 0;
410     symmetryMateCount = 0;
411     
412     int[][] list = lists[0];
413     for (int i = 0; i < nAtoms; i++) {
414       asymmetricUnitCount += list[i].length;
415       if (listCount[i] > 0) {
416         atomsWithIteractions++;
417       }
418     }
419     for (int iSymm = 1; iSymm < nSymm; iSymm++) {
420       list = lists[iSymm];
421       for (int i = 0; i < nAtoms; i++) {
422         symmetryMateCount += list[i].length;
423       }
424     }
425 
426     groupAsymmetricUnitCount = 0;
427     groupSymmetryMateCount = 0;
428     if (M > 1) {
429       
430       list = groupLists[0];
431       for (int i = 0; i < numGroups; i++) {
432         groupAsymmetricUnitCount += list[i].length;
433       }
434       
435       for (int iSymm = 1; iSymm < nSymm; iSymm++) {
436         list = groupLists[iSymm];
437         for (int i = 0; i < numGroups; i++) {
438           groupSymmetryMateCount += list[i].length;
439         }
440       }
441     }
442 
443     
444     if (print) {
445       print();
446     }
447 
448     
449     long scheduleTime = -System.nanoTime();
450     pairwiseSchedule.updateRanges(sharedCount.get(), atomsWithIteractions, listCount);
451     scheduleTime += System.nanoTime();
452 
453     if (logger.isLoggable(Level.FINE)) {
454       time = System.nanoTime() - time;
455       StringBuilder sb = new StringBuilder();
456       sb.append(format("   Motion Check:           %6.4f sec\n", motionTime * 1e-9));
457       sb.append(format("   List Initialization:    %6.4f sec\n", initTime * 1e-9));
458       sb.append(format("   Assign Atoms to Cells:  %6.4f sec\n", assignAtomsToCellsTime * 1e-9));
459       sb.append(format("   Create Vertlet Lists:   %6.4f sec\n", verletListTime * 1e-9));
460       sb.append(format("   Parallel Schedule:      %6.4f sec\n", scheduleTime * 1e-9));
461       if (M >= 1) {
462         sb.append(format("   Group List Total:       %6.4f sec\n", groupingTime * 1e-9));
463       }
464       sb.append(format("   Neighbor List Total:    %6.4f sec\n", time * 1e-9));
465       logger.fine(sb.toString());
466     }
467   }
468 
469   
470 
471 
472 
473 
474   public double getCutoff() {
475     return cutoff;
476   }
477 
478   
479 
480 
481 
482 
483 
484   public void setDisableUpdates(boolean disableUpdate) {
485     this.disableUpdates = disableUpdate;
486   }
487 
488   
489 
490 
491 
492 
493   public int[][][] getNeighborList() {
494     return lists;
495   }
496 
497   
498 
499 
500 
501 
502   public PairwiseSchedule getPairwiseSchedule() {
503     return pairwiseSchedule;
504   }
505 
506   
507 
508 
509 
510 
511 
512 
513   @Override
514   public void run() {
515     if (disableUpdates) {
516       return;
517     }
518 
519     int threadIndex = getThreadIndex();
520     try {
521       if (!forceRebuild) {
522         
523         if (threadIndex == 0) {
524           motionTime = -System.nanoTime();
525         }
526         execute(0, nAtoms - 1, motionLoops[threadIndex]);
527         if (threadIndex == 0) {
528           motionTime += System.nanoTime();
529         }
530       }
531       if (forceRebuild || sharedMotion.get()) {
532         
533         barrier(listInitBarrierAction);
534         
535         if (threadIndex == 0) {
536           assignAtomsToCellsTime = -System.nanoTime();
537         }
538         execute(0, nAtoms - 1, assignAtomsToCellsLoops[threadIndex]);
539         if (threadIndex == 0) {
540           assignAtomsToCellsTime += System.nanoTime();
541         }
542         
543         if (threadIndex == 0) {
544           verletListTime = -System.nanoTime();
545         }
546         execute(0, nAtoms - 1, verletListLoop[threadIndex]);
547         if (threadIndex == 0) {
548           verletListTime += System.nanoTime();
549         }
550         
551         if (M > 1) {
552           if (threadIndex == 0) {
553             groupingTime = -System.nanoTime();
554             
555             mGroups = buildGroups();
556             numGroups = mGroups.length;
557             mGroupID = assignGroupID(mGroups);
558             groupLists = new int[nSymm][numGroups][];
559           }
560           barrier();
561           execute(0, mGroups.length - 1, groupVerletListLoop[threadIndex]);
562           if (threadIndex == 0) {
563             groupingTime += System.nanoTime();
564           }
565         }
566       }
567     } catch (Exception e) {
568       String message =
569           "Fatal exception building neighbor list in thread: " + getThreadIndex() + "\n";
570       logger.log(Level.SEVERE, message, e);
571     }
572   }
573 
574   
575 
576 
577 
578 
579 
580   private int[][] buildGroups() {
581     
582     int nA = domainDecomposition.nA;
583     int nB = domainDecomposition.nB;
584     int nC = domainDecomposition.nC;
585 
586     
587     List<int[]> groupsList = new ArrayList<>();
588     int[] group = new int[M];
589     for (int i = 0; i < nA; i++) {
590       for (int j = 0; j < nB; j++) {
591         PriorityQueue<AtomIndex> queue = new PriorityQueue<>();
592         for (int k = 0; k < nC; k++) {
593           
594           Cell cell = domainDecomposition.getCell(i, j, k);
595           for (AtomIndex atomIndex : cell.list) {
596             
597             if (atomIndex.iSymm != 0) {
598               continue;
599             }
600             queue.add(atomIndex);
601             
602             while (queue.size() >= M) {
603               for (int m = 0; m < M; m++) {
604                 AtomIndex atom = queue.poll();
605                 group[m] = atom.i;
606               }
607               groupsList.add(group);
608               group = new int[M];
609             }
610           }
611         }
612         if (!queue.isEmpty()) {
613           
614           fill(group, -1);
615           for (int l = 0; l < M; l++) {
616             if (!queue.isEmpty()) {
617               AtomIndex atom = queue.poll();
618               group[l] = atom.i;
619             }
620           }
621           groupsList.add(group);
622           group = new int[M];
623         }
624       }
625     }
626 
627     int numGroups = groupsList.size();
628     int[][] groups = new int[groupsList.size()][];
629     for (int i = 0; i < numGroups; i++) {
630       groups[i] = groupsList.get(i);
631       
632     }
633     return groups;
634   }
635 
636   
637 
638 
639 
640 
641 
642   private int[] assignGroupID(int[][] groups) {
643     int nGroups = groups.length;
644     int[] groupID = new int[nAtoms];
645     for (int i = 0; i < nGroups; i++) {
646       int[] group = groups[i];
647       for (int atomIndex : group) {
648         
649         if (atomIndex >= 0) {
650           groupID[atomIndex] = i;
651         }
652       }
653     }
654     return groupID;
655   }
656 
657   
658 
659 
660 
661 
662   public void setAtoms(Atom[] atoms) {
663     this.atoms = atoms;
664     this.nAtoms = atoms.length;
665     initNeighborList(false);
666   }
667 
668   
669 
670 
671 
672 
673 
674   public void setCrystal(Crystal crystal) {
675     this.crystal = crystal;
676     initNeighborList(false);
677   }
678 
679   
680 
681 
682 
683 
684   public void setIntermolecular(boolean intermolecular) {
685     this.intermolecular = intermolecular;
686   }
687 
688   
689 
690 
691 
692 
693 
694 
695   @Override
696   public void start() {
697     if (disableUpdates) {
698       return;
699     }
700     time = System.nanoTime();
701     motionTime = 0;
702     initTime = 0;
703     assignAtomsToCellsTime = 0;
704     verletListTime = 0;
705     sharedMotion.set(false);
706   }
707 
708   private void initNeighborList(boolean print) {
709 
710     
711     int newNSymm = crystal.spaceGroup.symOps.size();
712     if (nSymm != newNSymm) {
713       nSymm = newNSymm;
714     }
715 
716     
717     if (previous == null || previous.length < 3 * nAtoms) {
718       previous = new double[3 * nAtoms];
719       listCount = new int[nAtoms];
720       pairwiseSchedule = new PairwiseSchedule(threadCount, nAtoms, ranges);
721     } else {
722       pairwiseSchedule.setAtoms(nAtoms);
723     }
724 
725     
726     final double sphere = min(min(crystal.interfacialRadiusA, crystal.interfacialRadiusB),
727         crystal.interfacialRadiusC);
728 
729     
730 
731 
732 
733     if (!crystal.aperiodic()) {
734       assert (sphere >= cutoffPlusBuffer);
735     }
736 
737     domainDecomposition.initDomainDecomposition(nAtoms, crystal);
738     if (print) {
739       domainDecomposition.log();
740     }
741   }
742 
743   private void print() {
744     StringBuilder sb = new StringBuilder(
745         format("   Buffer:                                %5.2f (A)\n", buffer));
746     sb.append(format("   Cut-off:                               %5.2f (A)\n", cutoff));
747     sb.append(format("   Total:                                 %5.2f (A)\n", cutoffPlusBuffer));
748     sb.append(format("   Neighbors in the asymmetric unit:%11d\n", asymmetricUnitCount));
749     if (nSymm > 1) {
750       sb.append(format("   Neighbors in symmetry mates:    %12d\n", symmetryMateCount));
751       int num = (asymmetricUnitCount + symmetryMateCount) * nSymm;
752       sb.append(format("   Neighbors in the unit cell:     %12d\n", num));
753     }
754 
755     if (M > 1) {
756       sb.append(format("   Number of groups:                  %9d\n", numGroups));
757       sb.append(format("   Group pairs in the asymmetric unit:%9d\n", groupAsymmetricUnitCount));
758       if (nSymm > 1) {
759         sb.append(format("   Group pairs in symmetry mates:  %12d\n", groupSymmetryMateCount));
760         int num = (groupAsymmetricUnitCount + groupSymmetryMateCount) * nSymm;
761         sb.append(format("   Group pairs in the unit cell:   %12d\n", num));
762       }
763     }
764 
765     logger.info(sb.toString());
766   }
767 
768   private static final int XX = 0;
769   private static final int YY = 1;
770   private static final int ZZ = 2;
771 
772   
773 
774 
775 
776 
777   private class MotionLoop extends IntegerForLoop {
778 
779     @Override
780     public void run(int lb, int ub) {
781       double[] current = coordinates[0];
782       for (int i = lb; i <= ub; i++) {
783         int i3 = i * 3;
784         int iX = i3 + XX;
785         int iY = i3 + YY;
786         int iZ = i3 + ZZ;
787         double dx = previous[iX] - current[iX];
788         double dy = previous[iY] - current[iY];
789         double dz = previous[iZ] - current[iZ];
790         double dr2 = crystal.image(dx, dy, dz);
791         if (dr2 > motion2) {
792           if (logger.isLoggable(Level.FINE)) {
793             logger.fine(format(" Motion detected for atom %d (%8.6f A).", i, sqrt(dr2)));
794           }
795           sharedMotion.set(true);
796         }
797       }
798     }
799   }
800 
801   
802 
803 
804   private class ListInitBarrierAction extends BarrierAction {
805 
806     @Override
807     public void run() {
808       initTime = -System.nanoTime();
809       
810       sharedCount.set(0);
811 
812       domainDecomposition.clear();
813 
814       
815       for (int iSymm = 0; iSymm < nSymm; iSymm++) {
816         if (lists[iSymm] == null || lists[iSymm].length < nAtoms) {
817           lists[iSymm] = new int[nAtoms][];
818         }
819       }
820       initTime += System.nanoTime();
821     }
822   }
823 
824   
825 
826 
827 
828 
829   private class AssignAtomsToCellsLoop extends IntegerForLoop {
830 
831     
832 
833 
834     private final double[] auCart = new double[3];
835     
836 
837 
838     private final double[] cart = new double[3];
839     
840 
841 
842     private final double[] frac = new double[3];
843 
844     @Override
845     public void run(int lb, int ub) {
846 
847       
848       double[] current = coordinates[0];
849       for (int i = lb; i <= ub; i++) {
850         int i3 = i * 3;
851         int iX = i3 + XX;
852         int iY = i3 + YY;
853         int iZ = i3 + ZZ;
854         previous[iX] = current[iX];
855         previous[iY] = current[iY];
856         previous[iZ] = current[iZ];
857       }
858 
859       final double[] auXYZ = coordinates[0];
860       final double spCutoff2 = crystal.getSpecialPositionCutoff2();
861       for (int iSymm = 0; iSymm < nSymm; iSymm++) {
862         final double[] xyz = coordinates[iSymm];
863         
864         for (int i = lb; i <= ub; i++) {
865           int i3 = i * 3;
866           cart[0] = xyz[i3 + XX];
867           cart[1] = xyz[i3 + YY];
868           cart[2] = xyz[i3 + ZZ];
869           if (iSymm != 0) {
870             auCart[0] = auXYZ[i3 + XX];
871             auCart[1] = auXYZ[i3 + YY];
872             auCart[2] = auXYZ[i3 + ZZ];
873             double dx = auCart[0] - cart[0];
874             double dy = auCart[1] - cart[1];
875             double dz = auCart[2] - cart[2];
876             double dr2 = crystal.image(dx, dy, dz);
877             if (dr2 < spCutoff2) {
878               int molecule = atoms[i].getMoleculeNumber();
879               if (atoms[i].isActive()) {
880                 logger.info(format("   Active Atom %s at Special Position in Molecule %d with SymOp %d (%8.6f A).",
881                     atoms[i], molecule, iSymm, sqrt(dr2)));
882               } else if (logger.isLoggable(Level.FINE)) {
883                 logger.fine(format("   Atom %s at Special Position in Molecule %d with SymOp %d (%8.6f A).",
884                     atoms[i], molecule, iSymm, sqrt(dr2)));
885               }
886               
887               domainDecomposition.addSpecialPositionExclusion(molecule, iSymm);
888             }
889           }
890           
891           crystal.toFractionalCoordinates(cart, frac);
892           domainDecomposition.addAtomToCell(i, iSymm, frac);
893         }
894       }
895     }
896   }
897 
898   
899 
900 
901 
902 
903 
904 
905   private class VerletListLoop extends IntegerForLoop {
906 
907     private final IntegerSchedule schedule;
908     private int n;
909     private int iSymm;
910     private int atomIndex;
911     private boolean iactive = true;
912     private int count;
913     private double[] xyz;
914     private int[] pairs;
915     private Cell cellForCurrentAtom;
916     private int[] pairCellAtoms;
917 
918     VerletListLoop() {
919       int len = 1000;
920       pairs = new int[len];
921       schedule = IntegerSchedule.dynamic(10);
922       pairCellAtoms = new int[len];
923     }
924 
925     @Override
926     public void finish() {
927       sharedCount.addAndGet(count);
928     }
929 
930     @Override
931     public void run(final int lb, final int ub) {
932       int nEdge = domainDecomposition.nEdge;
933       int nA = domainDecomposition.nA;
934       int nB = domainDecomposition.nB;
935       int nC = domainDecomposition.nC;
936       for (iSymm = 0; iSymm < nSymm; iSymm++) {
937         int[][] list = lists[iSymm];
938         
939         for (atomIndex = lb; atomIndex <= ub; atomIndex++) {
940           n = 0;
941 
942           if (iSymm == 0) {
943             listCount[atomIndex] = 0;
944             int molecule = atoms[atomIndex].getMoleculeNumber();
945             List<Integer> symOpList = domainDecomposition.getSpecialPositionSymOps(molecule);
946             atoms[atomIndex].setSpecialPositionSymOps(symOpList);
947           }
948 
949           if (use == null || use[atomIndex]) {
950 
951             iactive = atoms[atomIndex].isActive();
952 
953             cellForCurrentAtom = domainDecomposition.getCellForAtom(atomIndex);
954             int a = cellForCurrentAtom.a;
955             int b = cellForCurrentAtom.b;
956             int c = cellForCurrentAtom.c;
957             int a1 = a + 1;
958             int b1 = b + 1;
959             int c1 = c + 1;
960 
961             
962 
963 
964 
965 
966             int aStart = nA == 1 ? a : a - nEdge;
967             int aStop = nA == 1 ? a : a + nEdge;
968             int bStart = nB == 1 ? b : b - nEdge;
969             int bStop = nB == 1 ? b : b + nEdge;
970             int cStart = nC == 1 ? c : c - nEdge;
971             int cStop = nC == 1 ? c : c + nEdge;
972 
973             if (iSymm == 0) {
974               
975               atomCellPairs(cellForCurrentAtom);
976               
977               
978               for (int bi = b1; bi <= bStop; bi++) {
979                 atomCellPairs(domainDecomposition.image(a, bi, c));
980               }
981               
982               for (int bi = bStart; bi <= bStop; bi++) {
983                 for (int ci = c1; ci <= cStop; ci++) {
984                   atomCellPairs(domainDecomposition.image(a, bi, ci));
985                 }
986               }
987               
988               for (int bi = bStart; bi <= bStop; bi++) {
989                 for (int ci = cStart; ci <= cStop; ci++) {
990                   for (int ai = a1; ai <= aStop; ai++) {
991                     atomCellPairs(domainDecomposition.image(ai, bi, ci));
992                   }
993                 }
994               }
995             } else {
996               
997               for (int ai = aStart; ai <= aStop; ai++) {
998                 for (int bi = bStart; bi <= bStop; bi++) {
999                   for (int ci = cStart; ci <= cStop; ci++) {
1000                     atomCellPairs(domainDecomposition.image(ai, bi, ci));
1001                   }
1002                 }
1003               }
1004             }
1005           }
1006 
1007           list[atomIndex] = new int[n];
1008           listCount[atomIndex] += n;
1009           count += n;
1010           arraycopy(pairs, 0, list[atomIndex], 0, n);
1011         }
1012       }
1013     }
1014 
1015     @Override
1016     public IntegerSchedule schedule() {
1017       return schedule;
1018     }
1019 
1020     @Override
1021     public void start() {
1022       xyz = coordinates[0];
1023       count = 0;
1024     }
1025 
1026     private void atomCellPairs(final Cell cell) {
1027       if (pairCellAtoms.length < cell.getCount()) {
1028         pairCellAtoms = new int[cell.getCount()];
1029       }
1030 
1031       
1032       int num = cell.getSymOpAtoms(iSymm, pairCellAtoms);
1033       if (num == 0) {
1034         return;
1035       }
1036 
1037       final int moleculeIndex = atoms[atomIndex].getMoleculeNumber();
1038 
1039       boolean logClosePairs = logger.isLoggable(Level.FINE);
1040 
1041       final int i3 = atomIndex * 3;
1042       final double xi = xyz[i3 + XX];
1043       final double yi = xyz[i3 + YY];
1044       final double zi = xyz[i3 + ZZ];
1045       final double[] pair = coordinates[iSymm];
1046 
1047       
1048       for (int j = 0; j < num; j++) {
1049         final int aj = pairCellAtoms[j];
1050 
1051         
1052         if (iSymm == 0 && cell == cellForCurrentAtom) {
1053           
1054           if (aj <= atomIndex) {
1055             continue;
1056           }
1057         }
1058 
1059         if (use != null && !use[aj]) {
1060           continue;
1061         }
1062 
1063         if (!includeInactivePairs && !iactive) {
1064           if (!atoms[aj].isActive()) {
1065             continue;
1066           }
1067         }
1068 
1069         int moleculeIndexJ = atoms[aj].getMoleculeNumber();
1070         
1071         if (!intermolecular && (moleculeIndex != moleculeIndexJ)) {
1072           continue;
1073         }
1074 
1075         
1076         if (iSymm != 0 && (moleculeIndex == moleculeIndexJ)) {
1077           if (domainDecomposition.isSpecialPositionExclusion(moleculeIndex, iSymm)) {
1078             if (logger.isLoggable(Level.FINEST)) {
1079               logger.finest(
1080                   format("   Excluding Interaction for Atoms %d and %d in Molecule %d for SymOp %d.",
1081                       atomIndex, aj, moleculeIndex, iSymm));
1082             }
1083             continue;
1084           }
1085         }
1086 
1087         if (iSymm == 0 || aj >= atomIndex) {
1088           int aj3 = aj * 3;
1089           final double xj = pair[aj3 + XX];
1090           final double yj = pair[aj3 + YY];
1091           final double zj = pair[aj3 + ZZ];
1092           final double xr = xi - xj;
1093           final double yr = yi - yj;
1094           final double zr = zi - zj;
1095           final double d2 = crystal.image(xr, yr, zr);
1096           if (d2 <= cutoffPlusBuffer2) {
1097             if (logClosePairs && d2 < crystal.getSpecialPositionCutoff2()) {
1098               
1099               logger.fine(
1100                   format(" Close interaction (%6.3f) between atoms (iSymm = %d):\n %s\n %s\n",
1101                       sqrt(d2), iSymm, atoms[atomIndex].toString(), atoms[aj].toString()));
1102             }
1103 
1104             
1105             try {
1106               pairs[n++] = aj;
1107             } catch (Exception e) {
1108               n = pairs.length;
1109               pairs = copyOf(pairs, n + 100);
1110               pairs[n++] = aj;
1111             }
1112           }
1113         }
1114       }
1115     }
1116 
1117   }
1118 
1119   
1120 
1121 
1122   private class GroupVerletListLoop extends IntegerForLoop {
1123 
1124     private final IntegerSchedule schedule = IntegerSchedule.dynamic(10);
1125 
1126     @Override
1127     public IntegerSchedule schedule() {
1128       return schedule;
1129     }
1130 
1131     @Override
1132     public void run(final int lb, final int ub) {
1133       
1134       List<Integer> neighborGroups = new ArrayList<>();
1135       for (int iSymm = 0; iSymm < nSymm; iSymm++) {
1136         
1137         for (int groupIndex = lb; groupIndex <= ub; groupIndex++) {
1138           
1139           for (int a = 0; a < M; a++) {
1140             int atomIndex = mGroups[groupIndex][a];
1141             
1142             if (atomIndex == -1) {
1143               continue;
1144             }
1145             
1146             int[] neighborList = lists[iSymm][atomIndex];
1147             for (int value : neighborList) {
1148               
1149               int neighborGroupIndex = mGroupID[value];
1150               if (!neighborGroups.contains(neighborGroupIndex)) {
1151                 if (groupIndex <= neighborGroupIndex) {
1152                   
1153                   
1154                   neighborGroups.add(neighborGroupIndex);
1155                 } else if (neighborGroupIndex >= lb) {
1156                   
1157                   
1158                   int[] list = groupLists[iSymm][neighborGroupIndex];
1159                   boolean included = false;
1160                   for (int neighbor : list) {
1161                     
1162                     if (neighbor == groupIndex) {
1163                       included = true;
1164                       break;
1165                     }
1166                   }
1167                   if (!included) {
1168                     
1169                     neighborGroups.add(neighborGroupIndex);
1170                   }
1171                 } else if (!isIncludedInLowerIDList(iSymm, neighborGroupIndex, groupIndex)) {
1172                   
1173                   neighborGroups.add(neighborGroupIndex);
1174                 }
1175               }
1176             }
1177           }
1178 
1179           
1180           
1181           groupLists[iSymm][groupIndex] = neighborGroups.stream().mapToInt(Integer::intValue).toArray();
1182           
1183           neighborGroups.clear();
1184         }
1185       }
1186     }
1187   }
1188 
1189   
1190 
1191 
1192 
1193 
1194 
1195 
1196 
1197   private boolean isIncludedInLowerIDList(int symOp, int smallerGroupIndex, int secondGroup) {
1198     if (smallerGroupIndex >= secondGroup) {
1199       
1200       return false;
1201     }
1202     
1203     for (int a = 0; a < M; a++) {
1204       int atomID = mGroups[smallerGroupIndex][a];
1205       
1206       if (atomID == -1) {
1207         continue;
1208       }
1209       
1210       int[] neighborList = lists[symOp][atomID];
1211       for (int value : neighborList) {
1212         
1213         int neighborGroupID = mGroupID[value];
1214         if (neighborGroupID == secondGroup) {
1215           
1216           return true;
1217         }
1218       }
1219     }
1220     return false;
1221   }
1222 
1223   
1224 
1225 
1226   private static class DomainDecomposition {
1227 
1228     
1229 
1230 
1231     private Crystal crystal;
1232     
1233 
1234 
1235 
1236     private final double targetInterfacialRadius;
1237     
1238 
1239 
1240 
1241 
1242 
1243 
1244 
1245     private final int nEdge;
1246     
1247 
1248 
1249 
1250     private final int nSearch;
1251     
1252 
1253 
1254     private int nA;
1255     
1256 
1257 
1258     private int nB;
1259     
1260 
1261 
1262     private int nC;
1263     
1264 
1265 
1266     private int nAtoms;
1267     
1268 
1269 
1270     private int[] cellA;
1271     
1272 
1273 
1274     private int[] cellB;
1275     
1276 
1277 
1278     private int[] cellC;
1279     
1280 
1281 
1282     private Cell[][][] cells;
1283     
1284 
1285 
1286     private Map<Integer, List<Integer>> specialPositionExclusionMap;
1287 
1288 
1289     
1290 
1291 
1292 
1293 
1294 
1295 
1296     public DomainDecomposition(int nAtoms, Crystal crystal, double cutoffPlusBuffer) {
1297       this.nEdge = 2;
1298       this.nSearch = 2 * nEdge + 1;
1299       targetInterfacialRadius = cutoffPlusBuffer / (double) nEdge;
1300       initDomainDecomposition(nAtoms, crystal);
1301     }
1302 
1303     
1304 
1305 
1306     public void initDomainDecomposition(int nAtoms, Crystal crystal) {
1307       this.nAtoms = nAtoms;
1308       this.crystal = crystal;
1309 
1310       
1311       if (cellA == null || cellA.length < nAtoms) {
1312         cellA = new int[nAtoms];
1313         cellB = new int[nAtoms];
1314         cellC = new int[nAtoms];
1315       }
1316 
1317       
1318       int maxA = (int) floor(2 * crystal.interfacialRadiusA / targetInterfacialRadius);
1319       int maxB = (int) floor(2 * crystal.interfacialRadiusB / targetInterfacialRadius);
1320       int maxC = (int) floor(2 * crystal.interfacialRadiusC / targetInterfacialRadius);
1321 
1322       
1323 
1324 
1325 
1326       if (!crystal.aperiodic()) {
1327         assert (maxA >= nSearch - 1);
1328         assert (maxB >= nSearch - 1);
1329         assert (maxC >= nSearch - 1);
1330       }
1331 
1332       
1333 
1334 
1335 
1336       nA = maxA < nSearch ? 1 : maxA;
1337       nB = maxB < nSearch ? 1 : maxB;
1338       nC = maxC < nSearch ? 1 : maxC;
1339 
1340       
1341       if (cells == null || cells.length != nA || cells[0].length != nB || cells[0][0].length != nC) {
1342         cells = new Cell[nA][nB][nC];
1343         for (int i = 0; i < nA; i++) {
1344           for (int j = 0; j < nB; j++) {
1345             for (int k = 0; k < nC; k++) {
1346               cells[i][j][k] = new Cell(i, j, k);
1347             }
1348           }
1349         }
1350       } else {
1351         clear();
1352       }
1353 
1354     }
1355 
1356     
1357 
1358 
1359     public void clear() {
1360       
1361       for (int i = 0; i < nA; i++) {
1362         for (int j = 0; j < nB; j++) {
1363           for (int k = 0; k < nC; k++) {
1364             cells[i][j][k].clear();
1365           }
1366         }
1367       }
1368 
1369       
1370       
1371       
1372       
1373       
1374       
1375       
1376       
1377     }
1378 
1379     
1380 
1381 
1382 
1383 
1384 
1385     public void addSpecialPositionExclusion(int molecule, int symOpIndex) {
1386       
1387       if (specialPositionExclusionMap == null) {
1388         specialPositionExclusionMap = new HashMap<>();
1389       }
1390       
1391       List<Integer> list = specialPositionExclusionMap.get(molecule);
1392       if (list == null) {
1393         list = new ArrayList<>();
1394         specialPositionExclusionMap.put(molecule, list);
1395       }
1396       
1397       if (!list.contains(symOpIndex)) {
1398         list.add(symOpIndex);
1399       }
1400     }
1401 
1402     
1403 
1404 
1405 
1406 
1407 
1408 
1409     public boolean isSpecialPositionExclusion(int molecule, int symOpIndex) {
1410       if (specialPositionExclusionMap == null) {
1411         return false;
1412       }
1413       List<Integer> list = specialPositionExclusionMap.get(molecule);
1414       if (list == null) {
1415         return false;
1416       }
1417       return list.contains(symOpIndex);
1418     }
1419 
1420     
1421 
1422 
1423 
1424 
1425 
1426     public List<Integer> getSpecialPositionSymOps(int molecule) {
1427       if (specialPositionExclusionMap == null) {
1428         return null;
1429       }
1430       if (specialPositionExclusionMap.containsKey(molecule)) {
1431         List<Integer> list = specialPositionExclusionMap.get(molecule);
1432         if (list.isEmpty()) {
1433           return null;
1434         } else {
1435           return list;
1436         }
1437       }
1438       return null;
1439     }
1440 
1441     
1442 
1443 
1444     public void log() {
1445       int nCells = nA * nB * nC;
1446       int nSymm = crystal.spaceGroup.getNumberOfSymOps();
1447       StringBuilder sb = new StringBuilder("  Neighbor List Builder\n");
1448       sb.append(format("   Total Cells:          %8d\n", nCells));
1449       if (nCells > 1) {
1450         sb.append(format("   Interfacial radius    %8.3f A\n", targetInterfacialRadius));
1451         int searchA = nA == 1 ? 1 : nSearch;
1452         int searchB = nB == 1 ? 1 : nSearch;
1453         int searchC = nC == 1 ? 1 : nSearch;
1454         sb.append(format("   Domain Decomposition: %8d %4d %4d\n", nA, nB, nC));
1455         sb.append(format("   Neighbor Search:      %8d x%3d x%3d\n", searchA, searchB, searchC));
1456         sb.append(format("   Mean Atoms per Cell:  %8d", nAtoms * nSymm / nCells));
1457       }
1458       logger.info(sb.toString());
1459     }
1460 
1461     
1462 
1463 
1464 
1465 
1466 
1467 
1468 
1469 
1470 
1471     public Cell image(int i, int j, int k) {
1472       if (i >= nA) {
1473         i -= nA;
1474       } else if (i < 0) {
1475         i += nA;
1476       }
1477       if (j >= nB) {
1478         j -= nB;
1479       } else if (j < 0) {
1480         j += nB;
1481       }
1482       if (k >= nC) {
1483         k -= nC;
1484       } else if (k < 0) {
1485         k += nC;
1486       }
1487       return cells[i][j][k];
1488     }
1489 
1490     
1491 
1492 
1493 
1494 
1495 
1496 
1497     public void addAtomToCell(int i, int iSymm, double[] frac) {
1498       double xu = frac[0];
1499       double yu = frac[1];
1500       double zu = frac[2];
1501       
1502       while (xu < 0.0) {
1503         xu += 1.0;
1504       }
1505       while (xu >= 1.0) {
1506         xu -= 1.0;
1507       }
1508       while (yu < 0.0) {
1509         yu += 1.0;
1510       }
1511       while (yu >= 1.0) {
1512         yu -= 1.0;
1513       }
1514       while (zu < 0.0) {
1515         zu += 1.0;
1516       }
1517       while (zu >= 1.0) {
1518         zu -= 1.0;
1519       }
1520       
1521       final int a = (int) floor(xu * nA);
1522       final int b = (int) floor(yu * nB);
1523       final int c = (int) floor(zu * nC);
1524 
1525       if (iSymm == 0) {
1526         
1527         cellA[i] = a;
1528         cellB[i] = b;
1529         cellC[i] = c;
1530       }
1531       cells[a][b][c].add(i, iSymm, zu);
1532     }
1533 
1534     
1535 
1536 
1537 
1538 
1539 
1540     public Cell getCellForAtom(int i) {
1541       return cells[cellA[i]][cellB[i]][cellC[i]];
1542     }
1543 
1544     public Cell getCell(int a, int b, int c) {
1545       if (a < 0 || a >= nA || b < 0 || b >= nB || c < 0 || c >= nC) {
1546         return null;
1547       }
1548       return cells[a][b][c];
1549     }
1550   }
1551 
1552   
1553 
1554 
1555   public static class AtomIndex implements Comparable<AtomIndex> {
1556 
1557     public final int iSymm;
1558     public final int i;
1559     public final double z;
1560 
1561     public AtomIndex(int iSymm, int i, double z) {
1562       this.iSymm = iSymm;
1563       this.i = i;
1564       this.z = z;
1565     }
1566 
1567     @Override
1568     public int compareTo(AtomIndex o) {
1569       return Double.compare(this.z, o.z);
1570     }
1571   }
1572 
1573   
1574 
1575 
1576   public static class Cell {
1577 
1578     
1579 
1580 
1581     final int a;
1582     
1583 
1584 
1585     final int b;
1586     
1587 
1588 
1589     final int c;
1590 
1591     
1592 
1593 
1594     final List<AtomIndex> list;
1595     
1596 
1597 
1598     final Set<Integer> groupList;
1599 
1600     public Cell(int a, int b, int c) {
1601       this.a = a;
1602       this.b = b;
1603       this.c = c;
1604       list = Collections.synchronizedList(new ArrayList<>());
1605       groupList = Collections.synchronizedSet(new HashSet<>());
1606     }
1607 
1608     
1609 
1610 
1611 
1612 
1613 
1614     public void add(int atomIndex, int symOpIndex, double z) {
1615       list.add(new AtomIndex(symOpIndex, atomIndex, z));
1616     }
1617 
1618     public void groupAdd(int groupIndex) {
1619       groupList.add(groupIndex);
1620     }
1621 
1622     public AtomIndex get(int index) {
1623       if (index >= list.size()) {
1624         return null;
1625       }
1626       return list.get(index);
1627     }
1628 
1629     public int getCount() {
1630       return list.size();
1631     }
1632 
1633     public int getGroupCount() {
1634       return groupList.size();
1635     }
1636 
1637     
1638 
1639 
1640 
1641 
1642 
1643 
1644     public int getSymOpAtoms(int symOpIndex, int[] index) {
1645       int count = 0;
1646       for (AtomIndex atomIndex : list) {
1647         if (atomIndex.iSymm == symOpIndex) {
1648           if (count >= index.length) {
1649             throw new IndexOutOfBoundsException(
1650                 "Index out of bounds: count " + count + " " + index.length + " " + list.size());
1651           }
1652           index[count++] = atomIndex.i;
1653         }
1654       }
1655       return count;
1656     }
1657 
1658     
1659 
1660 
1661     public void clear() {
1662       list.clear();
1663     }
1664 
1665   }
1666 
1667   
1668 
1669 
1670 
1671 
1672   public static void main(String[] args) {
1673     PotentialsUtils potentialsUtils = new PotentialsUtils();
1674     File xyzFile = new File("./examples/waterbox.xyz");
1675     if (!xyzFile.exists()) {
1676       System.out.println("File does not exist");
1677     }
1678     MolecularAssembly molecularAssembly = potentialsUtils.open(xyzFile);
1679     molecularAssembly.getPotentialEnergy().energy(null, true);
1680   }
1681 }