View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2025.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
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.*;
55  import java.util.logging.Level;
56  import java.util.logging.Logger;
57  
58  import static java.lang.Math.max;
59  import static java.lang.String.format;
60  import static java.lang.System.arraycopy;
61  import static java.util.Arrays.*;
62  import static org.apache.commons.math3.util.FastMath.floor;
63  import static org.apache.commons.math3.util.FastMath.min;
64  import static org.apache.commons.math3.util.FastMath.sqrt;
65  
66  /**
67   * The NeighborList class builds Verlet lists in parallel via a spatial decomposition. <br>
68   *
69   * <ol>
70   *   <li>The unit cell is partitioned into <code>nA * nB * nC</code> smaller axis-aligned cells,
71   *       where {nA, nB, nC} are chosen as large as possible subject to the criteria that the length
72   *       of each side of a sub-volume (rCellA, rCellB, rCellC) multiplied by nEdge
73   *       must be greater than the cutoff distance <code>Rcut</code> plus a
74   *       buffer distance <code>delta</code>: <br>
75   *       <code>rCellA * nEdge .GE. (Rcut + delta)</code> <br>
76   *       <code>rCellB * nEdge .GE. (Rcut + delta)</code> <br>
77   *       <code>rCellC * nEdge .GE. (Rcut + delta)</code> <br>
78   *       All neighbors of an atom are in a block of (2*nEdge+1)^3 neighborCells.
79   *   <li>Interactions between an atom and neighbors in the asymmetric unit require only half the
80   *       neighboring cells to be searched to avoid double counting. However, enumeration of
81   *       interactions between an atom in the asymmetric unit and its neighbors in a symmetry mate
82   *       require all cells to be searched.
83   *   <li>Verlet lists from the search are stored, which reduces the number of neighbors whose
84   *       distances must be calculated by a factor of approximately: <br>
85   *       <code>(4/3*Pi*Rcut^3)/(neighborCells*Vcell)</code>
86   *       About 1/3 as many interactions are contained in the Verlet lists compared to
87   *       the total amount in the neighboring cells.
88   * </ol>
89   *
90   * @author Michael J. Schnieders
91   * @since 1.0
92   */
93  public class NeighborList extends ParallelRegion {
94  
95    /** The logger. */
96    private static final Logger logger = Logger.getLogger(NeighborList.class.getName());
97  
98    private final DomainDecomposition domainDecomposition;
99    /** The masking rules to apply when building the neighbor list. */
100   private final MaskingInterface maskingRules;
101   /** The cutoff beyond which the pairwise energy is zero. */
102   private final double cutoff;
103   /**
104    * A buffer, which is added to the cutoff distance, such that the Verlet lists do not need to be
105    * calculated for all coordinate changes.
106    */
107   private final double buffer;
108   /** The maximum squared displacement allowed before list rebuild. */
109   private final double motion2;
110   /** The sum of the cutoff + buffer. */
111   private final double cutoffPlusBuffer;
112   /** Total^2 for distance comparisons without taking a sqrt. */
113   private final double cutoffPlusBuffer2;
114   /** The ParallelTeam coordinates use of threads and their schedules. */
115   private final ParallelTeam parallelTeam;
116   /** Number of threads used by the parallelTeam. */
117   private final int threadCount;
118   /**
119    * If this flag is set, then the Verlet lists will be rebuilt even in the absence of motion.
120    */
121   private boolean forceRebuild = false;
122   /**
123    * If this flag is set then the Verlet lists will be printed to the logger.
124    */
125   private boolean print = false;
126   /**
127    * Shared Boolean used to detect list rebuild. If any thread detects motion in the MotionLoop, the
128    * value is set to true.
129    */
130   private final SharedBoolean sharedMotion;
131   /**
132    * Detect motion in the asymmetric unit.
133    */
134   private final MotionLoop[] motionLoops;
135   /**
136    * Perform some initialization tasks prior to rebuilding the list.
137    */
138   private final ListInitBarrierAction listInitBarrierAction;
139   /**
140    * Assign atoms to cells in parallel. This loop is executed once per list rebuild.
141    */
142   private final AssignAtomsToCellsLoop[] assignAtomsToCellsLoops;
143   /** A Verlet list loop for each thread. */
144   private final NeighborListLoop[] verletListLoop;
145   private final MxNListLoop[] mxNListLoop;
146   /**
147    * Time to check for motion.
148    */
149   private long motionTime;
150   /**
151    * Time for some initializations.
152    */
153   private long initTime;
154   /**
155    * Time to assign atoms to cells.
156    */
157   private long assignAtomsToCellsTime;
158   /**
159    * Time to build the Verlet lists.
160    */
161   private long verletListTime;
162   /** Time to build groups and associated lists */
163   private long groupingTime;
164   /** Total number of interactions. */
165   private final SharedInteger sharedCount;
166   /** Optimal pairwise ranges. */
167   private final Range[] ranges;
168   /**
169    * The crystal object defines the unit cell dimensions and space group. If replicates of the unit
170    * cell are being used, this should be the overall replicates crystal and not the small unit cell.
171    */
172   private Crystal crystal;
173   /** The number of asymmetric units in the unit cell. */
174   private int nSymm;
175   /** The number of atoms in the asymmetric unit. */
176   private int nAtoms;
177   /** Reduced coordinates for each symmetry copy. [nSymm][3*nAtoms] */
178   private double[][] coordinates;
179   /** The Cartesian coordinates of the asymmetric unit when the list was last rebuilt. */
180   private double[] previous;
181   /** The Verlet lists. [nSymm][nAtoms][nNeighbors] */
182   private int[][][] lists;
183   /** Lists of groups. Can't know # groups beforehand. [nSymm][group][atom] */
184   private int[][][] groups;
185   private int nGroups;
186   /** Lists of interactions between groups of atoms. nGroups is not known until after groups are built. */
187   private int[][][] groupLists;
188   /** Size of each group of atoms */
189   private int M = -1;
190   /** Number of group interactions */
191   private int N = -1;
192   /** Number of interactions per atom. */
193   private int[] listCount;
194   /** Pairwise ranges for load balancing. */
195   private PairwiseSchedule pairwiseSchedule;
196   /** Number of interactions between atoms in the asymmetric unit. */
197   private int asymmetricUnitCount;
198   /** Number of interactions between atoms in the asymmetric unit and atoms in symmetry mates. */
199   private int symmetryMateCount;
200   /** Number of atoms in the asymmetric unit with interactions lists greater than 0. */
201   private int atomsWithIteractions;
202   /** Atoms being used list. */
203   private boolean[] use;
204   /** List of atoms. */
205   private Atom[] atoms;
206   /**
207    * Time building the neighbor list.
208    */
209   private long time;
210   /** Include intermolecular interactions. */
211   private boolean intermolecular = true;
212   /**
213    * If true, interactions between two inactive atoms are included in the Neighborlist. Set to true
214    * to match OpenMM behavior (i.e. interactions between two atoms with zero mass are included). Set
215    * to false for more efficient "pure Java" optimizations.
216    */
217   private final boolean includeInactivePairs = true;
218   /** Disable updates to the NeighborList; use with caution. */
219   private boolean disableUpdates = false;
220 
221   /**
222    * Constructor for the NeighborList class.
223    *
224    * @param maskingRules This parameter may be null.
225    * @param crystal Definition of the unit cell and space group.
226    * @param atoms The atoms to generate Verlet lists for.
227    * @param cutoff The cutoff distance.
228    * @param buffer The buffer distance.
229    * @param parallelTeam Specifies the parallel environment.
230    * @since 1.0
231    */
232   public NeighborList(MaskingInterface maskingRules, Crystal crystal, Atom[] atoms, double cutoff,
233       double buffer, ParallelTeam parallelTeam) {
234     this.maskingRules = maskingRules;
235     this.crystal = crystal;
236     this.cutoff = cutoff;
237     this.buffer = buffer;
238     this.parallelTeam = new ParallelTeam(parallelTeam.getThreadCount());
239     this.atoms = atoms;
240     nAtoms = atoms.length;
241 
242     // Configure the neighbor cutoff and list rebuilding criteria.
243     cutoffPlusBuffer = cutoff + buffer;
244     cutoffPlusBuffer2 = cutoffPlusBuffer * cutoffPlusBuffer;
245     motion2 = (buffer / 2.0) * (buffer / 2.0);
246 
247     // Initialize parallel constructs.
248     threadCount = parallelTeam.getThreadCount();
249     sharedCount = new SharedInteger();
250     ranges = new Range[threadCount];
251 
252     sharedMotion = new SharedBoolean();
253     motionLoops = new MotionLoop[threadCount];
254     listInitBarrierAction = new ListInitBarrierAction();
255     verletListLoop = new NeighborListLoop[threadCount];
256     assignAtomsToCellsLoops = new AssignAtomsToCellsLoop[threadCount];
257     mxNListLoop = new MxNListLoop[threadCount];
258     for (int i = 0; i < threadCount; i++) {
259       motionLoops[i] = new MotionLoop();
260       verletListLoop[i] = new NeighborListLoop();
261       assignAtomsToCellsLoops[i] = new AssignAtomsToCellsLoop();
262       mxNListLoop[i] = new MxNListLoop();
263     }
264 
265     // Initialize the neighbor list builder subcells.
266     boolean print = logger.isLoggable(Level.FINE);
267     domainDecomposition = new DomainDecomposition(nAtoms, crystal, cutoffPlusBuffer);
268     initNeighborList(print);
269   }
270 
271   /**
272    * This method can be called as necessary to build/rebuild the neighbor lists.
273    *
274    * @param coordinates The coordinates of each atom [nSymm][nAtoms*3].
275    * @param lists The neighbor lists [nSymm][nAtoms][nPairs].
276    * @param forceRebuild If true, the list is rebuilt even if no atom has moved half the buffer
277    *     size.
278    * @param use an array of boolean.
279    * @param print a boolean.
280    * @since 1.0
281    */
282   public void buildList(final double[][] coordinates, final int[][][] lists, boolean[] use,
283       boolean forceRebuild, boolean print) {
284     if (disableUpdates) {
285       return;
286     }
287     this.coordinates = coordinates;
288     this.lists = lists;
289     this.use = use;
290     this.forceRebuild = forceRebuild;
291     this.print = print;
292 
293     try {
294       parallelTeam.execute(this);
295     } catch (Exception e) {
296       String message = "Fatal exception building neighbor list.\n";
297       logger.log(Level.SEVERE, message, e);
298     }
299   }
300 
301   public void buildMxNList(int M, int N, final double[][] coordinates, final int[][][] lists, boolean[] use, boolean forceRebuild, boolean print){
302     if (disableUpdates) {
303       return;
304     }
305     this.coordinates = coordinates;
306     this.lists = lists;
307     this.M = M;
308     this.N = N;
309     this.use = use;
310     this.forceRebuild = forceRebuild;
311     this.print = print;
312 
313     try {
314       parallelTeam.execute(this);
315     } catch (Exception e) {
316       String message = "Fatal exception building neighbor list.\n";
317       logger.log(Level.SEVERE, message, e);
318     }
319   }
320 
321   /**
322    * destroy.
323    *
324    * @throws java.lang.Exception if any.
325    */
326   public void destroy() throws Exception {
327     parallelTeam.shutdown();
328   }
329 
330   /**
331    * {@inheritDoc}
332    *
333    * <p>This is method should not be called; it is invoked by Parallel Java.
334    *
335    * @since 1.0
336    */
337   @Override
338   public void finish() {
339     if (disableUpdates) {
340       return;
341     }
342     if (!forceRebuild && !sharedMotion.get()) {
343       return;
344     }
345 
346     // Collect interactions.
347     atomsWithIteractions = 0;
348     asymmetricUnitCount = 0;
349     symmetryMateCount = 0;
350     int[][] list = lists[0];
351     for (int i = 0; i < nAtoms; i++) {
352       asymmetricUnitCount += list[i].length;
353       if (listCount[i] > 0) {
354         atomsWithIteractions++;
355       }
356     }
357     for (int iSymm = 1; iSymm < nSymm; iSymm++) {
358       list = lists[iSymm];
359       for (int i = 0; i < nAtoms; i++) {
360         symmetryMateCount += list[i].length;
361       }
362     }
363 
364     // Print the interactions counts.
365     if (print) {
366       print();
367     }
368 
369     // Update the pair-wise schedule.
370     long scheduleTime = -System.nanoTime();
371     pairwiseSchedule.updateRanges(sharedCount.get(), atomsWithIteractions, listCount);
372     scheduleTime += System.nanoTime();
373 
374     if (logger.isLoggable(Level.FINE)) {
375       time = System.nanoTime() - time;
376       StringBuilder sb = new StringBuilder();
377       sb.append(format("   Motion Check:           %6.4f sec\n", motionTime * 1e-9));
378       sb.append(format("   List Initialization:    %6.4f sec\n", initTime * 1e-9));
379       sb.append(format("   Assign Atoms to Cells:  %6.4f sec\n", assignAtomsToCellsTime * 1e-9));
380       sb.append(format("   Create Vertlet Lists:   %6.4f sec\n", verletListTime * 1e-9));
381       sb.append(format("   Parallel Schedule:      %6.4f sec\n", scheduleTime * 1e-9));
382       if ( M >= 1 || N >= 1) {
383         sb.append(format("   M x N List Total:       %6.4f sec\n", groupingTime * 1e-9));
384       }
385       sb.append(format("   Neighbor List Total:    %6.4f sec\n", time * 1e-9));
386       logger.fine(sb.toString());
387     }
388   }
389 
390   /**
391    * Returns the cutoff distance used internally by NeighborList.
392    *
393    * @return Cutoff distance in Angstroms.
394    */
395   public double getCutoff() {
396     return cutoff;
397   }
398 
399   /**
400    * If disableUpdates true, disable updating the neighbor list upon motion. Use with caution; best
401    * recommendation is to only use if all atoms have a coordinate restraint.
402    *
403    * @param disableUpdate Disable updating the neighbor list
404    */
405   public void setDisableUpdates(boolean disableUpdate) {
406     this.disableUpdates = disableUpdate;
407   }
408 
409   /**
410    * Return the Verlet list.
411    *
412    * @return The Verlet list of size [nSymm][nAtoms][nNeighbors].
413    */
414   public int[][][] getNeighborList() {
415     return lists;
416   }
417 
418   /**
419    * Getter for the field <code>pairwiseSchedule</code>.
420    *
421    * @return a {@link ffx.potential.nonbonded.PairwiseSchedule} object.
422    */
423   public PairwiseSchedule getPairwiseSchedule() {
424     return pairwiseSchedule;
425   }
426 
427   /**
428    * {@inheritDoc}
429    *
430    * <p>This is method should not be called; it is invoked by Parallel Java.
431    *
432    * @since 1.0
433    */
434   @Override
435   public void run() {
436     if (disableUpdates) {
437       return;
438     }
439 
440     int threadIndex = getThreadIndex();
441     try {
442       if (!forceRebuild) {
443         // Check for motion.
444         if (threadIndex == 0) {
445           motionTime = -System.nanoTime();
446         }
447         execute(0, nAtoms - 1, motionLoops[threadIndex]);
448         if (threadIndex == 0) {
449           motionTime += System.nanoTime();
450         }
451       }
452       if (forceRebuild || sharedMotion.get()) {
453         // Complete some initializations.
454         barrier(listInitBarrierAction);
455         // Assign atoms to cells.
456         if (threadIndex == 0) {
457           assignAtomsToCellsTime = -System.nanoTime();
458         }
459         execute(0, nAtoms - 1, assignAtomsToCellsLoops[threadIndex]);
460         if (threadIndex == 0) {
461           assignAtomsToCellsTime += System.nanoTime();
462         }
463         // Build the Verlet list.
464         if (threadIndex == 0) {
465           verletListTime = -System.nanoTime();
466         }
467         execute(0, nAtoms - 1, verletListLoop[threadIndex]);
468         if (threadIndex == 0) {
469           verletListTime += System.nanoTime();
470         }
471         // Build MxN Loop if desired
472         if ( M >= 1 || N >= 1) {
473           if(threadIndex == 0){
474             groupingTime = -System.nanoTime();
475           }
476           // Fast build of groups
477           if (threadIndex == 0) { groups(); }
478           barrier();
479           execute(0, nGroups - 1, mxNListLoop[threadIndex]);
480           if(threadIndex == 0){
481             groupingTime += System.nanoTime();
482           }
483         }
484       }
485     } catch (Exception e) {
486       String message =
487           "Fatal exception building neighbor list in thread: " + getThreadIndex() + "\n";
488       logger.log(Level.SEVERE, message, e);
489     }
490   }
491 
492   /**
493    * Build groups of size M by walking up Z columns. Must be called
494    * after all the other neighbor-list code.
495    * <p></p>
496    * Builds groups, then builds group neighbor-lists from existing
497    * M=1 neighbor-lists.
498    */
499   private void groups() {
500     // Use cells to build groups
501     int nA = domainDecomposition.nA;
502     int nB = domainDecomposition.nB;
503     int nC = domainDecomposition.nC;
504     // Allocate more than needed i.e. nAtoms always >= nGroups
505     groups = new int[nSymm][nAtoms][M];
506     int[] symmGroups = new int[nSymm];
507     for(int i = 0; i < nA; i++){
508       for(int j = 0; j < nB; j++){
509         int[] group = new int[M];
510         // Slow in-order add, but typically fast due to small # zCol atoms
511         PriorityQueue<AtomIndex>[] atomQueue = new PriorityQueue[nSymm];
512         for(int k = 0; k < nSymm; k++){
513           atomQueue[k] = new PriorityQueue<>();
514         }
515         for(int k = 0; k < nC; k++){
516           // Walk up z-columns adding to groups of size M
517           Cell cell = domainDecomposition.getCell(i, j, k);
518           assert(cell != null); // after domain decomposition
519           for(int l = 0; l < cell.list.size(); l++){
520             int iSymm = cell.list.get(l).iSymm;
521             // Sort by z height
522             atomQueue[iSymm].add(cell.list.get(l));
523             // Flush queue into groups
524             while(atomQueue[iSymm].size() >= M){
525               for(int m = 0; m < M; m++){
526                 AtomIndex atom = atomQueue[iSymm].poll();
527                 assert(atom != null); // with while-loop cond.
528                 group[m] = atom.i;
529               }
530               groups[iSymm][symmGroups[iSymm]++] = group;
531               group = new int[M];
532             }
533           }
534         }
535         for(int k = 0; k < nSymm; k++) {
536           if (atomQueue[k].isEmpty()) {
537             continue;
538           }
539           // Fill in last group with -1 filler
540           for (int l = 0; l < M; l++) {
541             if (!atomQueue[k].isEmpty()) {
542               AtomIndex atom = atomQueue[k].poll();
543               assert (atom != null);
544               group[l] = atom.i;
545             } else {
546               group[l] = -1;
547             }
548           }
549           groups[k][symmGroups[k]++] = group;
550         }
551       }
552     }
553     nGroups = symmGroups[0]; // symmGroups is uniform array
554     groupLists = new int[nSymm][nGroups][]; // Last will be union of N atoms' list * N
555   }
556 
557   /**
558    * The NeighborList will be re-configured, if necessary, for the supplied atom list.
559    *
560    * @param atoms A new list of atoms to operate on.
561    */
562   public void setAtoms(Atom[] atoms) {
563     this.atoms = atoms;
564     this.nAtoms = atoms.length;
565     initNeighborList(false);
566   }
567 
568   /**
569    * The NeighborList will be re-configured, if necessary, for the supplied Crystal. Changes to both
570    * unit cell parameters and number of symmetry operators are also acceptable.
571    *
572    * @param crystal A crystal defining boundary conditions and symmetry.
573    */
574   public void setCrystal(Crystal crystal) {
575     this.crystal = crystal;
576     initNeighborList(false);
577   }
578 
579   /**
580    * Setter for the field <code>intermolecular</code>.
581    *
582    * @param intermolecular If true, the NeighborList will include intermolecular interactions.
583    */
584   public void setIntermolecular(boolean intermolecular) {
585     this.intermolecular = intermolecular;
586   }
587 
588   /**
589    * {@inheritDoc}
590    *
591    * <p>This is method should not be called; it is invoked by Parallel Java.
592    *
593    * @since 1.0
594    */
595   @Override
596   public void start() {
597     if (disableUpdates) {
598       return;
599     }
600     time = System.nanoTime();
601     motionTime = 0;
602     initTime = 0;
603     assignAtomsToCellsTime = 0;
604     verletListTime = 0;
605     sharedMotion.set(false);
606   }
607 
608   private void initNeighborList(boolean print) {
609 
610     // Set the number of symmetry operators.
611     int newNSymm = crystal.spaceGroup.symOps.size();
612     if (nSymm != newNSymm) {
613       nSymm = newNSymm;
614     }
615 
616     // Allocate memory for fractional coordinates and subcell pointers for each atom.
617     if (previous == null || previous.length < 3 * nAtoms) {
618       previous = new double[3 * nAtoms];
619       listCount = new int[nAtoms];
620       pairwiseSchedule = new PairwiseSchedule(threadCount, nAtoms, ranges);
621     } else {
622       pairwiseSchedule.setAtoms(nAtoms);
623     }
624 
625     // Find the largest sphere that is enclosed by the unit cell.
626     final double sphere = min(min(crystal.interfacialRadiusA, crystal.interfacialRadiusB),
627         crystal.interfacialRadiusC);
628 
629     /*
630      Assert that the boundary conditions defined by the crystal allow use
631      of the minimum image condition.
632     */
633     if (!crystal.aperiodic()) {
634       assert (sphere >= cutoffPlusBuffer);
635     }
636 
637     domainDecomposition.initDomainDecomposition(nAtoms, crystal);
638     if (print) {
639       domainDecomposition.log();
640     }
641   }
642 
643   private void print() {
644     StringBuilder sb = new StringBuilder(
645         format("   Buffer:                                %5.2f (A)\n", buffer));
646     sb.append(format("   Cut-off:                               %5.2f (A)\n", cutoff));
647     sb.append(format("   Total:                                 %5.2f (A)\n", cutoffPlusBuffer));
648     sb.append(format("   Neighbors in the asymmetric unit:%11d\n", asymmetricUnitCount));
649     if (nSymm > 1) {
650       int num = (asymmetricUnitCount + symmetryMateCount) * nSymm;
651       sb.append(format("   Neighbors in symmetry mates:    %12d\n", symmetryMateCount));
652       sb.append(format("   Neighbors in the unit cell:     %12d\n", num));
653     }
654     logger.info(sb.toString());
655   }
656 
657   private static final int XX = 0;
658   private static final int YY = 1;
659   private static final int ZZ = 2;
660 
661   /**
662    * Detect if any atom has moved 1/2 the buffer size.
663    *
664    * @since 1.0
665    */
666   private class MotionLoop extends IntegerForLoop {
667 
668     @Override
669     public void run(int lb, int ub) {
670       double[] current = coordinates[0];
671       for (int i = lb; i <= ub; i++) {
672         int i3 = i * 3;
673         int iX = i3 + XX;
674         int iY = i3 + YY;
675         int iZ = i3 + ZZ;
676         double dx = previous[iX] - current[iX];
677         double dy = previous[iY] - current[iY];
678         double dz = previous[iZ] - current[iZ];
679         double dr2 = crystal.image(dx, dy, dz);
680         if (dr2 > motion2) {
681           if (logger.isLoggable(Level.FINE)) {
682             logger.fine(format(" Motion detected for atom %d (%8.6f A).", i, sqrt(dr2)));
683           }
684           sharedMotion.set(true);
685         }
686       }
687     }
688   }
689 
690   /**
691    * Perform some initialization tasks prior to list rebuild.
692    */
693   private class ListInitBarrierAction extends BarrierAction {
694 
695     @Override
696     public void run() {
697       initTime = -System.nanoTime();
698       // Clear the list count.
699       sharedCount.set(0);
700 
701       domainDecomposition.clear();
702 
703       // Allocate memory for neighbor lists.
704       for (int iSymm = 0; iSymm < nSymm; iSymm++) {
705         if (lists[iSymm] == null || lists[iSymm].length < nAtoms) {
706           lists[iSymm] = new int[nAtoms][];
707         }
708       }
709       initTime += System.nanoTime();
710     }
711   }
712 
713   /**
714    * Assign atoms to cells.
715    *
716    * @since 1.0
717    */
718   private class AssignAtomsToCellsLoop extends IntegerForLoop {
719 
720     /**
721      * The cartesian coordinates of the atom in the asymmetric unit.
722      */
723     private final double[] auCart = new double[3];
724     /**
725      * The cartesian coordinates of the atom after symmetry application.
726      */
727     private final double[] cart = new double[3];
728     /**
729      * The fractional coordinates of the atom after symmetry application.
730      */
731     private final double[] frac = new double[3];
732 
733     @Override
734     public void run(int lb, int ub) throws Exception {
735 
736       // Save the current coordinates.
737       double[] current = coordinates[0];
738       for (int i = lb; i <= ub; i++) {
739         int i3 = i * 3;
740         int iX = i3 + XX;
741         int iY = i3 + YY;
742         int iZ = i3 + ZZ;
743         previous[iX] = current[iX];
744         previous[iY] = current[iY];
745         previous[iZ] = current[iZ];
746       }
747 
748       final double[] auXYZ = coordinates[0];
749       final double spCutoff2 = crystal.getSpecialPositionCutoff2();
750       for (int iSymm = 0; iSymm < nSymm; iSymm++) {
751         final double[] xyz = coordinates[iSymm];
752         // Assign each atom to a cell using fractional coordinates.
753         for (int i = lb; i <= ub; i++) {
754           int i3 = i * 3;
755           cart[0] = xyz[i3 + XX];
756           cart[1] = xyz[i3 + YY];
757           cart[2] = xyz[i3 + ZZ];
758           if (iSymm != 0) {
759             auCart[0] = auXYZ[i3 + XX];
760             auCart[1] = auXYZ[i3 + YY];
761             auCart[2] = auXYZ[i3 + ZZ];
762             double dx = auCart[0] - cart[0];
763             double dy = auCart[1] - cart[1];
764             double dz = auCart[2] - cart[2];
765             double dr2 = crystal.image(dx, dy, dz);
766             if (dr2 < spCutoff2) {
767               int molecule = atoms[i].getMoleculeNumber();
768               if (atoms[i].isActive()) {
769                 logger.info(format("   Active Atom %s at Special Position in Molecule %d with SymOp %d (%8.6f A).",
770                     atoms[i], molecule, iSymm, sqrt(dr2)));
771               } else if (logger.isLoggable(Level.FINE)) {
772                 logger.fine(format("   Atom %s at Special Position in Molecule %d with SymOp %d (%8.6f A).",
773                         atoms[i], molecule, iSymm, sqrt(dr2)));
774               }
775               // Exclude molecule-molecule interactions between the asymmetric unit and the iSymm symmetry mate.
776               domainDecomposition.addSpecialPositionExclusion(molecule, iSymm);
777             }
778           }
779           // Convert to fractional coordinates.
780           crystal.toFractionalCoordinates(cart, frac);
781           domainDecomposition.addAtomToCell(i, iSymm, frac);
782         }
783       }
784     }
785   }
786 
787   /**
788    * The VerletListLoop class encapsulates thread local variables and methods for building Verlet
789    * lists based on a spatial decomposition of the unit cell.
790    *
791    * @author Michael J. Schnieders
792    * @since 1.0
793    */
794   private class NeighborListLoop extends IntegerForLoop {
795 
796     private final IntegerSchedule schedule;
797     private int n;
798     private int iSymm;
799     private int atomIndex;
800     private boolean iactive = true;
801     private int count;
802     private double[] xyz;
803     private int[] pairs;
804     private Cell cellForCurrentAtom;
805     private int[] pairCellAtoms;
806     private double[] mask;
807     private boolean[] vdw14;
808 
809     NeighborListLoop() {
810       int len = 1000;
811       pairs = new int[len];
812       schedule = IntegerSchedule.dynamic(10);
813       pairCellAtoms = new int[len];
814     }
815 
816     @Override
817     public void finish() {
818       sharedCount.addAndGet(count);
819     }
820 
821     @Override
822     public void run(final int lb, final int ub) {
823       int nEdge = domainDecomposition.nEdge;
824       int nA = domainDecomposition.nA;
825       int nB = domainDecomposition.nB;
826       int nC = domainDecomposition.nC;
827       for (iSymm = 0; iSymm < nSymm; iSymm++) {
828         int[][] list = lists[iSymm];
829         // Loop over all atoms.
830         for (atomIndex = lb; atomIndex <= ub; atomIndex++) {
831           n = 0;
832 
833           if (iSymm == 0) {
834             listCount[atomIndex] = 0;
835             int molecule = atoms[atomIndex].getMoleculeNumber();
836             List<Integer> symOpList = domainDecomposition.getSpecialPositionSymOps(molecule);
837             atoms[atomIndex].setSpecialPositionSymOps(symOpList);
838           }
839 
840           if (use == null || use[atomIndex]) {
841 
842             iactive = atoms[atomIndex].isActive();
843 
844             cellForCurrentAtom = domainDecomposition.getCellForAtom(atomIndex);
845             int a = cellForCurrentAtom.a;
846             int b = cellForCurrentAtom.b;
847             int c = cellForCurrentAtom.c;
848             int a1 = a + 1;
849             int b1 = b + 1;
850             int c1 = c + 1;
851 
852             /*
853              If the number of divisions is 1 in any direction then
854              set the loop limits to the current cell value.
855              Otherwise search from a - nEdge to a + nEdge.
856             */
857             int aStart = nA == 1 ? a : a - nEdge;
858             int aStop = nA == 1 ? a : a + nEdge;
859             int bStart = nB == 1 ? b : b - nEdge;
860             int bStop = nB == 1 ? b : b + nEdge;
861             int cStart = nC == 1 ? c : c - nEdge;
862             int cStop = nC == 1 ? c : c + nEdge;
863 
864             if (iSymm == 0) {
865               // Interactions within the "self-volume".
866               atomCellPairs(cellForCurrentAtom);
867               // Search half of the neighboring volumes to avoid double counting.
868               // (a, b+1..b+nE, c) -> radial line in y-dir
869               for (int bi = b1; bi <= bStop; bi++) {
870                 atomCellPairs(domainDecomposition.image(a, bi, c));
871               }
872               // (a, b-nE..b+nE, c+1..c+nE) -> cube in in z,y plane
873               for (int bi = bStart; bi <= bStop; bi++) {
874                 for (int ci = c1; ci <= cStop; ci++) {
875                   atomCellPairs(domainDecomposition.image(a, bi, ci));
876                 }
877               }
878               // (a+1..a+nE, b-nE..b+nE, c-nE..c+nE) -> cube pushed in x-dir
879               for (int bi = bStart; bi <= bStop; bi++) {
880                 for (int ci = cStart; ci <= cStop; ci++) {
881                   for (int ai = a1; ai <= aStop; ai++) {
882                     atomCellPairs(domainDecomposition.image(ai, bi, ci));
883                   }
884                 }
885               }
886             } else {
887               // Interactions with all adjacent symmetry mate cells.
888               for (int ai = aStart; ai <= aStop; ai++) {
889                 for (int bi = bStart; bi <= bStop; bi++) {
890                   for (int ci = cStart; ci <= cStop; ci++) {
891                     atomCellPairs(domainDecomposition.image(ai, bi, ci));
892                   }
893                 }
894               }
895             }
896           }
897 
898           list[atomIndex] = new int[n];
899           listCount[atomIndex] += n;
900           count += n;
901           arraycopy(pairs, 0, list[atomIndex], 0, n);
902         }
903       }
904     }
905 
906     @Override
907     public IntegerSchedule schedule() {
908       return schedule;
909     }
910 
911     @Override
912     public void start() {
913       xyz = coordinates[0];
914       count = 0;
915       if (mask == null || mask.length < nAtoms) {
916         mask = new double[nAtoms];
917         fill(mask, 1.0);
918         vdw14 = new boolean[nAtoms];
919         fill(vdw14, false);
920       }
921     }
922 
923     private void atomCellPairs(final Cell cell) {
924       if (pairCellAtoms.length < cell.getCount()) {
925         pairCellAtoms = new int[cell.getCount()];
926       }
927 
928       // Load the cell's atom indices into an array for the specified SymOp.
929       int num = cell.getSymOpAtoms(iSymm, pairCellAtoms);
930       if (num == 0) {
931         return;
932       }
933 
934       // Check if this pair search is over atoms in the asymmetric unit.
935       if (iSymm == 0 && maskingRules != null) {
936         // Interactions between atoms in the asymmetric unit may be masked.
937         maskingRules.applyMask(atomIndex, vdw14, mask);
938       }
939 
940       final int moleculeIndex = atoms[atomIndex].getMoleculeNumber();
941 
942       boolean logClosePairs = logger.isLoggable(Level.FINE);
943 
944       final int i3 = atomIndex * 3;
945       final double xi = xyz[i3 + XX];
946       final double yi = xyz[i3 + YY];
947       final double zi = xyz[i3 + ZZ];
948       final double[] pair = coordinates[iSymm];
949 
950       // Loop over atoms in the "pair" cell.
951       for (int j = 0; j < num; j++) {
952         final int aj = pairCellAtoms[j];
953 
954         // If the self-volume is being searched for pairs, avoid double counting.
955         if (iSymm == 0 && cell == cellForCurrentAtom) {
956           // Only consider atoms with a higher index than the current atom.
957           if (aj <= atomIndex) {
958             continue;
959           }
960         }
961 
962         if (use != null && !use[aj]) {
963           continue;
964         }
965 
966         if (!includeInactivePairs && !iactive) {
967           if (!atoms[aj].isActive()) {
968             continue;
969           }
970         }
971 
972         int moleculeIndexJ = atoms[aj].getMoleculeNumber();
973         // Check if this pair search is only intra-molecular.
974         if (!intermolecular && (moleculeIndex != moleculeIndexJ)) {
975           continue;
976         }
977 
978         // Check for a special position exclusion between two atoms in the same molecule.
979         if (iSymm != 0 && (moleculeIndex == moleculeIndexJ)) {
980           if (domainDecomposition.isSpecialPositionExclusion(moleculeIndex, iSymm)) {
981             if (logger.isLoggable(Level.FINEST)) {
982               logger.finest(
983                   format("   Excluding Interaction for Atoms %d and %d in Molecule %d for SymOp %d.",
984                       atomIndex, aj, moleculeIndex, iSymm));
985             }
986             continue;
987           }
988         }
989 
990         if (mask[aj] > 0 && (iSymm == 0 || aj >= atomIndex)) {
991           int aj3 = aj * 3;
992           final double xj = pair[aj3 + XX];
993           final double yj = pair[aj3 + YY];
994           final double zj = pair[aj3 + ZZ];
995           final double xr = xi - xj;
996           final double yr = yi - yj;
997           final double zr = zi - zj;
998           final double d2 = crystal.image(xr, yr, zr);
999           if (d2 <= cutoffPlusBuffer2) {
1000             if (logClosePairs && d2 < crystal.getSpecialPositionCutoff2()) {
1001               // Warn about close interactions.
1002               logger.fine(
1003                   format(" Close interaction (%6.3f) between atoms (iSymm = %d):\n %s\n %s\n",
1004                       sqrt(d2), iSymm, atoms[atomIndex].toString(), atoms[aj].toString()));
1005             }
1006 
1007             // Add the pair to the list, reallocating the array size if necessary.
1008             try {
1009               pairs[n++] = aj;
1010             } catch (Exception e) {
1011               n = pairs.length;
1012               pairs = copyOf(pairs, n + 100);
1013               pairs[n++] = aj;
1014             }
1015           }
1016         }
1017       }
1018 
1019       // Interactions between atoms in the asymmetric unit may be masked.
1020       if (iSymm == 0 && maskingRules != null) {
1021         maskingRules.removeMask(atomIndex, vdw14, mask);
1022       }
1023     }
1024 
1025   }
1026 
1027   private class MxNListLoop extends IntegerForLoop {
1028 
1029     @Override
1030     public void run(final int lb, final int ub) throws Exception {
1031       // Build lists
1032       for(int i = 0; i < nSymm; i++){
1033         for(int j = lb; j < ub; j++){
1034           // Fill out union
1035           int[] pairs = new int[1000*N];
1036           int n = 0;
1037           boolean[] exists = new boolean[nAtoms];
1038           for(int k = 0; k < M; k++){
1039             int atomID = groups[i][j][k];
1040             if (atomID == -1) {
1041               continue;
1042             }
1043             int[] neighborList = lists[i][atomID];
1044             for(int l = 0; l < neighborList.length; l++){
1045               boolean newInteraction = !exists[neighborList[l]];
1046               if(newInteraction){
1047                 try{
1048                   Arrays.fill(pairs, n, n+N, neighborList[l]);
1049                   n+=N;
1050                 } catch (Exception e){
1051                   pairs = copyOf(pairs, n + 100*N);
1052                   Arrays.fill(pairs, n, n+N, neighborList[l]);
1053                   n+=N;
1054                 }
1055                 exists[neighborList[l]] = true;
1056               }
1057             }
1058           }
1059           groupLists[i][j] = copyOf(pairs, n);
1060         }
1061       }
1062     }
1063   }
1064 
1065   /**
1066    * Break down the simulation domain into a 3D grid of cells.
1067    */
1068   private static class DomainDecomposition {
1069 
1070     /**
1071      * The crystal that defines the simulation domain.
1072      */
1073     private Crystal crystal;
1074     /**
1075      * The targetInterfacialRadius is the smallest interfacial radius for a subcell, given a search
1076      * of nEdge subcells along an axis, that assures all neighbors will be found.
1077      */
1078     private final double targetInterfacialRadius;
1079     /**
1080      * The number of subcells that must be searched along an axis to find all neighbors within
1081      * the cutoff + buffer distance.
1082      *
1083      * <p>If the nEdge == 1 for {X=A,B,C} then all neighbors will be found in 3x3x3 = 27 cells.
1084      * If each nEdge == 2, then all neighbors will be found in 5x5x5 = 125 cells (in this case the
1085      * cells are smaller).
1086      */
1087     private final int nEdge;
1088     /**
1089      * The number of subcells that must be searched along an axis to find all neighbors within the
1090      * cutoff + buffer distance. nSearch = 2 * nEdge + 1
1091      */
1092     private final int nSearch;
1093     /** The number of divisions along the A-axis. */
1094     private int nA;
1095     /** The number of divisions along the B-axis. */
1096     private int nB;
1097     /**
1098      * The number of divisions along the C-Axis.
1099      */
1100     private int nC;
1101     /**
1102      * The number of atoms.
1103      */
1104     private int nAtoms;
1105     /** The cell indices of each atom along the A-axis. */
1106     private int[] cellA;
1107     /** The cell indices of each atom along the B-axis. */
1108     private int[] cellB;
1109     /** The cell indices of each atom along the C-axis. */
1110     private int[] cellC;
1111     /**
1112      * The fractional sub-volumes of the unit cell.
1113      */
1114     private Cell[][][] cells;
1115     /**
1116      * Special position exclusions. Map<Molecule, List<SymOpIndex>>
1117      */
1118     private Map<Integer, List<Integer>> specialPositionExclusionMap;
1119 
1120 
1121     /**
1122      * DomainDecomposition Constructor.
1123      *
1124      * @param nAtoms Number of atoms.
1125      * @param crystal The crystal.
1126      * @param cutoffPlusBuffer The cutoff plus buffer distance.
1127      */
1128     public DomainDecomposition(int nAtoms, Crystal crystal, double cutoffPlusBuffer) {
1129       this.nEdge = 2;
1130       this.nSearch = 2 * nEdge + 1;
1131       targetInterfacialRadius = cutoffPlusBuffer / (double) nEdge;
1132       initDomainDecomposition(nAtoms, crystal);
1133     }
1134 
1135     /**
1136      * Initialize the domain decomposition.
1137      */
1138     public void initDomainDecomposition(int nAtoms, Crystal crystal) {
1139       this.nAtoms = nAtoms;
1140       this.crystal = crystal;
1141 
1142       // Allocate memory for fractional coordinates and subcell pointers for each atom.
1143       if (cellA == null || cellA.length < nAtoms) {
1144         cellA = new int[nAtoms];
1145         cellB = new int[nAtoms];
1146         cellC = new int[nAtoms];
1147       }
1148 
1149       // Determine the number of subcells along each axis.
1150       int maxA = (int) floor(2 * crystal.interfacialRadiusA / targetInterfacialRadius);
1151       int maxB = (int) floor(2 * crystal.interfacialRadiusB / targetInterfacialRadius);
1152       int maxC = (int) floor(2 * crystal.interfacialRadiusC / targetInterfacialRadius);
1153 
1154       /*
1155         To satisfy the cutoff + buffer distance, the number of subcells (maxA, maxB, maxC)
1156         is guaranteed to be at least nSearch - 1 for periodic crystals.
1157         */
1158       if (!crystal.aperiodic()) {
1159         assert (maxA >= nSearch - 1);
1160         assert (maxB >= nSearch - 1);
1161         assert (maxC >= nSearch - 1);
1162       }
1163 
1164       /*
1165         If the number of subcells less than nSearch, then turn off the overhead of the
1166         subcell search by setting the number of cells along that axis to 1.
1167        */
1168       nA = maxA < nSearch ? 1 : maxA;
1169       nB = maxB < nSearch ? 1 : maxB;
1170       nC = maxC < nSearch ? 1 : maxC;
1171 
1172       // Allocate memory for the subcells.
1173       if (cells == null || cells.length != nA || cells[0].length != nB || cells[0][0].length != nC) {
1174         cells = new Cell[nA][nB][nC];
1175         for (int i = 0; i < nA; i++) {
1176           for (int j = 0; j < nB; j++) {
1177             for (int k = 0; k < nC; k++) {
1178               cells[i][j][k] = new Cell(i, j, k);
1179             }
1180           }
1181         }
1182       } else {
1183         clear();
1184       }
1185 
1186     }
1187 
1188     /**
1189      * Clear the sub-cells of the atom list.
1190      */
1191     public void clear() {
1192       // Clear cell contents.
1193       for (int i = 0; i < nA; i++) {
1194         for (int j = 0; j < nB; j++) {
1195           for (int k = 0; k < nC; k++) {
1196             cells[i][j][k].clear();
1197           }
1198         }
1199       }
1200 
1201       // Clear special position exclusions.
1202       // if (specialPositionExclusionMap != null) {
1203       // Clear the lists, but don't remove the keys.
1204       //   for (List<Integer> list : specialPositionExclusionMap.values()) {
1205       //     list.clear();
1206       //   }
1207       //   specialPositionExclusionMap.clear();
1208       // }
1209     }
1210 
1211     /**
1212      * Add a special position exclusion.
1213      *
1214      * @param molecule The molecule.
1215      * @param symOpIndex The symmetry operation index.
1216      */
1217     public void addSpecialPositionExclusion(int molecule, int symOpIndex) {
1218       // Initialize the map.
1219       if (specialPositionExclusionMap == null) {
1220         specialPositionExclusionMap = new HashMap<>();
1221       }
1222       // Initialize the list for the molecule.
1223       List<Integer> list = specialPositionExclusionMap.get(molecule);
1224       if (list == null) {
1225         list = new ArrayList<>();
1226         specialPositionExclusionMap.put(molecule, list);
1227       }
1228       // Add the symOpIndex to the list.
1229       if (!list.contains(symOpIndex)) {
1230         list.add(symOpIndex);
1231       }
1232     }
1233 
1234     /**
1235      * Check if a special position exclusion exists.
1236      *
1237      * @param molecule The molecule.
1238      * @param symOpIndex The symmetry operation index.
1239      * @return True if the special position exclusion exists.
1240      */
1241     public boolean isSpecialPositionExclusion(int molecule, int symOpIndex) {
1242       if (specialPositionExclusionMap == null) {
1243         return false;
1244       }
1245       List<Integer> list = specialPositionExclusionMap.get(molecule);
1246       if (list == null) {
1247         return false;
1248       }
1249       return list.contains(symOpIndex);
1250     }
1251 
1252     /**
1253      * Get the special position SymOps for a molecule.
1254      *
1255      * @param molecule The molecule.
1256      * @return The special position SymOps.
1257      */
1258     public List<Integer> getSpecialPositionSymOps(int molecule) {
1259       if (specialPositionExclusionMap == null) {
1260         return null;
1261       }
1262       if (specialPositionExclusionMap.containsKey(molecule)) {
1263         List<Integer> list = specialPositionExclusionMap.get(molecule);
1264         if (list.isEmpty()) {
1265           return null;
1266         } else {
1267           return list;
1268         }
1269       }
1270       return null;
1271     }
1272 
1273     /**
1274      * Log information about the domain decomposition.
1275      */
1276     public void log() {
1277       int nCells = nA * nB * nC;
1278       int nSymm = crystal.spaceGroup.getNumberOfSymOps();
1279       StringBuilder sb = new StringBuilder("  Neighbor List Builder\n");
1280       sb.append(format("   Total Cells:          %8d\n", nCells));
1281       if (nCells > 1) {
1282         sb.append(format("   Interfacial radius    %8.3f A\n", targetInterfacialRadius));
1283         int searchA = nA == 1 ? 1 : nSearch;
1284         int searchB = nB == 1 ? 1 : nSearch;
1285         int searchC = nC == 1 ? 1 : nSearch;
1286         sb.append(format("   Domain Decomposition: %8d %4d %4d\n", nA, nB, nC));
1287         sb.append(format("   Neighbor Search:      %8d x%3d x%3d\n", searchA, searchB, searchC));
1288         sb.append(format("   Mean Atoms per Cell:  %8d", nAtoms * nSymm / nCells));
1289       }
1290       logger.info(sb.toString());
1291     }
1292 
1293     /**
1294      * If the index is >= to nX, it is mapped back into the periodic unit cell by subtracting nX. If
1295      * the index is less than 0, it is mapped into the periodic unit cell by adding nX. The Neighbor
1296      * list algorithm never requires multiple additions or subtractions of nX.
1297      *
1298      * @param i The index along the a-axis.
1299      * @param j The index along the b-axis.
1300      * @param k The index along the c-axis.
1301      * @return The requested Cell.
1302      */
1303     public Cell image(int i, int j, int k) {
1304       if (i >= nA) {
1305         i -= nA;
1306       } else if (i < 0) {
1307         i += nA;
1308       }
1309       if (j >= nB) {
1310         j -= nB;
1311       } else if (j < 0) {
1312         j += nB;
1313       }
1314       if (k >= nC) {
1315         k -= nC;
1316       } else if (k < 0) {
1317         k += nC;
1318       }
1319       return cells[i][j][k];
1320     }
1321 
1322     /**
1323      * Add an atom to a sub-cell.
1324      *
1325      * @param i The index of the atom.
1326      * @param iSymm The index of the symmetry operator.
1327      * @param frac The fractional coordinates of the atom.
1328      */
1329     public void addAtomToCell(int i, int iSymm, double[] frac) {
1330       double xu = frac[0];
1331       double yu = frac[1];
1332       double zu = frac[2];
1333       // Move the atom into the range 0.0 <= x < 1.0
1334       while (xu < 0.0) {
1335         xu += 1.0;
1336       }
1337       while (xu >= 1.0) {
1338         xu -= 1.0;
1339       }
1340       while (yu < 0.0) {
1341         yu += 1.0;
1342       }
1343       while (yu >= 1.0) {
1344         yu -= 1.0;
1345       }
1346       while (zu < 0.0) {
1347         zu += 1.0;
1348       }
1349       while (zu >= 1.0) {
1350         zu -= 1.0;
1351       }
1352       // The cell indices of this atom.
1353       final int a = (int) floor(xu * nA);
1354       final int b = (int) floor(yu * nB);
1355       final int c = (int) floor(zu * nC);
1356 
1357       if (iSymm == 0) {
1358         // Set the sub-cell indices for an asymmetric unit atom.
1359         cellA[i] = a;
1360         cellB[i] = b;
1361         cellC[i] = c;
1362       }
1363       cells[a][b][c].add(i, iSymm, zu);
1364     }
1365 
1366     /**
1367      * Get the sub-cell for an asymmetric unit atom.
1368      *
1369      * @param i The index of the atom.
1370      * @return The sub-cell for the atom.
1371      */
1372     public Cell getCellForAtom(int i) {
1373       return cells[cellA[i]][cellB[i]][cellC[i]];
1374     }
1375 
1376     public Cell getCell(int a, int b, int c) {
1377       if (a < 0 || a >= nA || b < 0 || b >= nB || c < 0 || c >= nC) {
1378         return null;
1379       }
1380       return cells[a][b][c];
1381     }
1382   }
1383 
1384   /**
1385    * Hold the atom index and its symmetry operator.
1386    */
1387   public static class AtomIndex implements Comparable<AtomIndex> {
1388 
1389     public final int iSymm;
1390     public final int i;
1391     public final double z;
1392 
1393     public AtomIndex(int iSymm, int i, double z) {
1394       this.iSymm = iSymm;
1395       this.i = i;
1396       this.z = z;
1397     }
1398 
1399     @Override
1400     public int compareTo(AtomIndex o) {
1401       return Double.compare(this.z, o.z);
1402     }
1403   }
1404 
1405   /**
1406    * Hold the atoms in each cell.
1407    */
1408   public static class Cell {
1409 
1410     /**
1411      * The cell index along the A-axis.
1412      */
1413     final int a;
1414     /**
1415      * The cell index along the B-axis.
1416      */
1417     final int b;
1418     /**
1419      * The cell index along the C-axis.
1420      */
1421     final int c;
1422 
1423     /**
1424      * The list of atoms in the cell, together with their symmetry operator.
1425      */
1426     final List<AtomIndex> list;
1427     /**
1428      * List of groupIDs that contain at least one atom in this cell.
1429      */
1430     final Set<Integer> groupList;
1431 
1432     public Cell(int a, int b, int c) {
1433       this.a = a;
1434       this.b = b;
1435       this.c = c;
1436       list = Collections.synchronizedList(new ArrayList<>());
1437       groupList = Collections.synchronizedSet(new HashSet<>());
1438     }
1439 
1440     /**
1441      * Add an atom to the cell.
1442      *
1443      * @param atomIndex The atom index.
1444      * @param symOpIndex The symmetry operator index.
1445      */
1446     public void add(int atomIndex, int symOpIndex, double z) {
1447       list.add(new AtomIndex(symOpIndex, atomIndex, z));
1448     }
1449 
1450     public void groupAdd(int groupIndex){
1451       groupList.add(groupIndex);
1452     }
1453 
1454     public AtomIndex get(int index) {
1455       if (index >= list.size()) {
1456         return null;
1457       }
1458       return list.get(index);
1459     }
1460 
1461     public int getCount() {
1462       return list.size();
1463     }
1464 
1465     public int getGroupCount(){
1466       return groupList.size();
1467     }
1468 
1469     /**
1470      * Return the number of atoms in the cell for a given symmetry operator.
1471      *
1472      * @param symOpIndex The symmetry operator index.
1473      * @param index The list of indexes for the given symmetry operator.
1474      * @return The number of atoms in the cell for the symmetry operator.
1475      */
1476     public int getSymOpAtoms(int symOpIndex, int[] index) {
1477       int count = 0;
1478       for (AtomIndex atomIndex : list) {
1479         if (atomIndex.iSymm == symOpIndex) {
1480           if (count >= index.length) {
1481             throw new IndexOutOfBoundsException(
1482                 "Index out of bounds: count " + count + " " + index.length + " " + list.size());
1483           }
1484           index[count++] = atomIndex.i;
1485         }
1486       }
1487       return count;
1488     }
1489 
1490     /**
1491      * Clear the list of atoms in the cell.
1492      */
1493     public void clear() {
1494       list.clear();
1495     }
1496 
1497   }
1498 
1499   /**
1500    * Debugging method.
1501    * @param args
1502    */
1503   public static void main(String[] args){
1504     PotentialsUtils potentialsUtils = new PotentialsUtils();
1505     File xyzFile = new File("./examples/waterbox.xyz");
1506     if(!xyzFile.exists()){
1507       System.out.println("File does not exist");
1508     }
1509     MolecularAssembly molecularAssembly = potentialsUtils.open(xyzFile);
1510     molecularAssembly.getPotentialEnergy().energy(null, true);
1511   }
1512 }