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.IntegerForLoop;
41  import edu.rit.pj.IntegerSchedule;
42  import edu.rit.pj.ParallelRegion;
43  import ffx.crystal.Crystal;
44  import ffx.potential.bonded.Atom;
45  
46  import java.nio.DoubleBuffer;
47  import java.util.logging.Level;
48  import java.util.logging.Logger;
49  
50  import static java.lang.String.format;
51  import static java.util.Arrays.fill;
52  import static org.apache.commons.math3.util.FastMath.floor;
53  
54  /**
55   * This class implements a spatial decomposition based on partitioning a grid into octants.
56   *
57   * @author Michael J. Schnieders
58   * @since 1.0
59   */
60  public class SpatialDensityRegion extends ParallelRegion {
61  
62    /** Constant <code>logger</code> */
63    protected static final Logger logger = Logger.getLogger(SpatialDensityRegion.class.getName());
64  
65    public final int nThreads;
66    protected final int nSymm;
67    public int[] actualCount;
68    public int nAtoms;
69    /** The number of divisions along the A-axis. */
70    protected int nA;
71    /** The number of divisions along the B-axis. */
72    protected int nB;
73    /** The number of divisions along the C-Axis. */
74    protected int nC;
75    /**
76     * Number of octant work cells with at least one atom (actualWork is less than or equal to nWork).
77     */
78    protected int actualWork;
79  
80    protected double[][][] coordinates;
81    protected boolean[][] select;
82    protected Crystal crystal;
83    protected SpatialDensityLoop[] spatialDensityLoop;
84    /** The A index of each octant (0..nA - 1) that has atoms. */
85    int[] actualA;
86    /** The B index of each octant (0..nB - 1) that has atoms. */
87    int[] actualB;
88    /** The C index of each octant (0..nC - 1) that has atoms. */
89    int[] actualC;
90    /** The list of atoms in each cell. [nsymm][natom] = atom index */
91    int[][] cellList;
92    /** The number of atoms in each cell. [nsymm][ncell] */
93    int[][] cellCount;
94    /** The index of the first atom in each cell. [nsymm][ncell] */
95    int[][] cellStart;
96    /** Basis size. */
97    private int basisSize;
98    /** Minimum amount of work. */
99    private int minWork;
100   /** The number of cells in one plane (nDivisions^2). */
101   private int nAB;
102   /** The number of cells (nDivisions^3). */
103   private int nCells;
104   /** Number of octant work cells. */
105   private int nWork;
106   /** A temporary array that holds the index of the cell each atom is assigned to. */
107   private int[][] cellIndex;
108   /** The A index of each octant (0..nA - 1) that may not have any atoms. */
109   private int[] workA;
110   /** The B index of each octant (0..nB - 1) that may not have any atoms. */
111   private int[] workB;
112   /** The C index of each octant (0..nC - 1) that may not have any atoms. */
113   private int[] workC;
114   /**
115    * The offset of each atom from the start of the cell. The first atom atom in the cell has 0
116    * offset. [nsymm][natom] = offset of the atom
117    */
118   private int[][] cellOffset;
119 
120   private double[] xf;
121   private double[] yf;
122   private double[] zf;
123   private int gridSize;
124   private double[] grid = null;
125   private DoubleBuffer gridBuffer;
126   private double initValue = 0.0;
127   private GridInitLoop gridInitLoop;
128 
129   /**
130    * Constructor for SpatialDensityRegion.
131    *
132    * @param gX a int.
133    * @param gY a int.
134    * @param gZ a int.
135    * @param grid an array of double.
136    * @param basisSize a int.
137    * @param nSymm a int.
138    * @param minWork a int.
139    * @param threadCount a int.
140    * @param crystal a {@link ffx.crystal.Crystal} object.
141    * @param atoms an array of {@link ffx.potential.bonded.Atom} objects.
142    * @param coordinates an array of double.
143    */
144   public SpatialDensityRegion(
145       int gX,
146       int gY,
147       int gZ,
148       double[] grid,
149       int basisSize,
150       int nSymm,
151       int minWork,
152       int threadCount,
153       Crystal crystal,
154       Atom[] atoms,
155       double[][][] coordinates) {
156     this(gX, gY, gZ, basisSize, nSymm, minWork, threadCount, crystal, atoms, coordinates);
157     this.grid = grid;
158     if (grid != null) {
159       gridBuffer = DoubleBuffer.wrap(grid);
160     }
161   }
162 
163   /**
164    * Chop up the 3D unit cell domain into fractional coordinate chunks to allow multiple threads to
165    * put charge density onto the grid without needing the same grid point. First, we partition the
166    * X-axis, then the Y-axis, and finally the Z-axis if necessary.
167    *
168    * @param gX a int.
169    * @param gY a int.
170    * @param gZ a int.
171    * @param basisSize a int.
172    * @param nSymm a int.
173    * @param minWork a int.
174    * @param threadCount a int.
175    * @param crystal a {@link ffx.crystal.Crystal} object.
176    * @param atoms an array of {@link ffx.potential.bonded.Atom} objects.
177    * @param coordinates an array of double.
178    */
179   private SpatialDensityRegion(
180       int gX,
181       int gY,
182       int gZ,
183       int basisSize,
184       int nSymm,
185       int minWork,
186       int threadCount,
187       Crystal crystal,
188       Atom[] atoms,
189       double[][][] coordinates) {
190     this.coordinates = coordinates;
191     this.nSymm = nSymm;
192     this.nAtoms = atoms.length;
193     this.nThreads = threadCount;
194     this.basisSize = basisSize;
195     this.minWork = minWork;
196     gridInitLoop = new GridInitLoop();
197     setCrystal(crystal.getUnitCell(), gX, gY, gZ);
198   }
199 
200   /**
201    * Assign asymmetric and symmetry mate atoms to cells. This is very fast; there is little to be
202    * gained from parallelization at this point.
203    */
204   public void assignAtomsToCells() {
205     // Call the selectAtoms method of subclasses.
206     selectAtoms();
207 
208     for (int iSymm = 0; iSymm < nSymm; iSymm++) {
209       final int[] cellIndexs = cellIndex[iSymm];
210       final int[] cellCounts = cellCount[iSymm];
211       final int[] cellStarts = cellStart[iSymm];
212       final int[] cellLists = cellList[iSymm];
213       final int[] cellOffsets = cellOffset[iSymm];
214       final boolean[] selected = select[iSymm];
215 
216       // Zero out the cell counts.
217       for (int i = 0; i < nCells; i++) {
218         cellCounts[i] = 0;
219       }
220 
221       // Convert to fractional coordinates.
222       final double[][] xyz = coordinates[iSymm];
223       final double[] x = xyz[0];
224       final double[] y = xyz[1];
225       final double[] z = xyz[2];
226       crystal.toFractionalCoordinates(nAtoms, x, y, z, xf, yf, zf);
227 
228       // Assign each atom to a cell using fractional coordinates.
229       for (int i = 0; i < nAtoms; i++) {
230         if (!selected[i]) {
231           continue;
232         }
233 
234         final int index = getCellIndex(i);
235         cellIndexs[i] = index;
236 
237         // The offset of this atom from the beginning of the cell.
238         cellOffsets[i] = cellCounts[index]++;
239       }
240 
241       // Define the starting indices.
242       cellStarts[0] = 0;
243       for (int i = 1; i < nCells; i++) {
244         final int i1 = i - 1;
245         cellStarts[i] = cellStarts[i1] + cellCounts[i1];
246       }
247 
248       // Move atom locations into a list ordered by cell.
249       for (int i = 0; i < nAtoms; i++) {
250         if (!selected[i]) {
251           continue;
252         }
253         final int index = cellIndexs[i];
254         cellLists[cellStarts[index]++] = i;
255       }
256 
257       // Redefine the starting indices again.
258       cellStarts[0] = 0;
259       for (int i = 1; i < nCells; i++) {
260         final int i1 = i - 1;
261         cellStarts[i] = cellStarts[i1] + cellCounts[i1];
262       }
263     }
264 
265     // Loop over work chunks and get rid of empty chunks.
266     actualWork = 0;
267     for (int icell = 0; icell < nWork; icell++) {
268       int ia = workA[icell];
269       int ib = workB[icell];
270       int ic = workC[icell];
271       int ii = count(ia, ib, ic);
272       // Fractional chunks along the C-axis.
273       if (nC > 1) {
274         ii += count(ia, ib, ic + 1);
275         // Fractional chunks along the B-axis.
276         if (nB > 1) {
277           ii += count(ia, ib + 1, ic);
278           ii += count(ia, ib + 1, ic + 1);
279           // Fractional chunks along the A-axis.
280           if (nA > 1) {
281             ii += count(ia + 1, ib, ic);
282             ii += count(ia + 1, ib, ic + 1);
283             ii += count(ia + 1, ib + 1, ic);
284             ii += count(ia + 1, ib + 1, ic + 1);
285           }
286         }
287       }
288 
289       // If there is work in this chunk, include it.
290       if (ii > 0) {
291         actualA[actualWork] = ia;
292         actualB[actualWork] = ib;
293         actualC[actualWork] = ic;
294         actualCount[actualWork++] = ii;
295       }
296     }
297 
298     if (logger.isLoggable(Level.FINEST)) {
299       logger.finest(format(" Empty chunks: %d out of %d.", nWork - actualWork, nWork));
300     }
301   }
302 
303   private int getCellIndex(int i) {
304     double xu = xf[i];
305     double yu = yf[i];
306     double zu = zf[i];
307 
308     // Move the atom into the range 0.0 <= x < 1.0
309     while (xu >= 1.0) {
310       xu -= 1.0;
311     }
312     while (xu < 0.0) {
313       xu += 1.0;
314     }
315     while (yu >= 1.0) {
316       yu -= 1.0;
317     }
318     while (yu < 0.0) {
319       yu += 1.0;
320     }
321     while (zu >= 1.0) {
322       zu -= 1.0;
323     }
324     while (zu < 0.0) {
325       zu += 1.0;
326     }
327 
328     // The cell indices of this atom.
329     int a = (int) floor(xu * nA);
330     int b = (int) floor(yu * nB);
331     int c = (int) floor(zu * nC);
332 
333     // Check to make sure a, b and c are less than nA, nB and nC, respectively.
334     if (a >= nA) {
335       a = nA - 1;
336     }
337     if (b >= nB) {
338       b = nB - 1;
339     }
340     if (c >= nC) {
341       c = nC - 1;
342     }
343 
344     // The cell index of this atom.
345     final int index = a + b * nA + c * nAB;
346     return index;
347   }
348 
349   /**
350    * Getter for the field <code>grid</code>.
351    *
352    * @return an array of double.
353    */
354   public double[] getGrid() {
355     return grid;
356   }
357 
358   /**
359    * getNsymm
360    *
361    * @return a int.
362    */
363   public int getNsymm() {
364     return nSymm;
365   }
366 
367   /**
368    * index
369    *
370    * @param ia a int.
371    * @param ib a int.
372    * @param ic a int.
373    * @return a int.
374    */
375   public int index(int ia, int ib, int ic) {
376     return ia + ib * nA + ic * nAB;
377   }
378 
379   /** {@inheritDoc} */
380   @Override
381   public void run() {
382     int ti = getThreadIndex();
383     int actualWork1 = actualWork - 1;
384     SpatialDensityLoop loop = spatialDensityLoop[ti];
385 
386     // This lets the same SpatialDensityLoops be used with different SpatialDensityRegions.
387     loop.setNsymm(nSymm);
388 
389     try {
390       execute(0, gridSize - 1, gridInitLoop);
391       execute(0, actualWork1, loop.setOctant(0));
392       // Fractional chunks along the C-axis.
393       if (nC > 1) {
394         execute(0, actualWork1, loop.setOctant(1));
395         // Fractional chunks along the B-axis.
396         if (nB > 1) {
397           execute(0, actualWork1, loop.setOctant(2));
398           execute(0, actualWork1, loop.setOctant(3));
399           // Fractional chunks along the A-axis.
400           if (nA > 1) {
401             execute(0, actualWork1, loop.setOctant(4));
402             execute(0, actualWork1, loop.setOctant(5));
403             execute(0, actualWork1, loop.setOctant(6));
404             execute(0, actualWork1, loop.setOctant(7));
405           }
406         }
407       }
408     } catch (Exception e) {
409       String message = " Exception in SpatialDensityRegion.";
410       logger.log(Level.SEVERE, message, e);
411     }
412   }
413 
414   /**
415    * Select atoms that should be assigned to cells. The default is to include all atoms, which is
416    * set up in the constructor. This function should be over-ridden by subclasses that want finer
417    * control.
418    */
419   public void selectAtoms() {}
420 
421   /**
422    * setAtoms.
423    *
424    * @param atoms an array of {@link ffx.potential.bonded.Atom} objects.
425    */
426   public void setAtoms(Atom[] atoms) {
427     nAtoms = atoms.length;
428     if (select == null || select.length < nSymm || select[0].length < nAtoms) {
429       select = new boolean[nSymm][nAtoms];
430       for (int i = 0; i < nSymm; i++) {
431         fill(select[i], true);
432       }
433       cellList = new int[nSymm][nAtoms];
434       cellIndex = new int[nSymm][nAtoms];
435       cellOffset = new int[nSymm][nAtoms];
436     }
437     if (xf == null || xf.length < nAtoms) {
438       xf = new double[nAtoms];
439       yf = new double[nAtoms];
440       zf = new double[nAtoms];
441     }
442   }
443 
444   /**
445    * Setter for the field <code>crystal</code>.
446    *
447    * @param crystal a {@link ffx.crystal.Crystal} object.
448    * @param gX a int.
449    * @param gY a int.
450    * @param gZ a int.
451    */
452   public final void setCrystal(Crystal crystal, int gX, int gY, int gZ) {
453     // If the crystal is unchanged, return.
454     if (this.crystal != null && this.crystal.equals(crystal.getUnitCell())) {
455       return;
456     }
457 
458     this.crystal = crystal.getUnitCell();
459     if (xf == null || xf.length < nAtoms) {
460       xf = new double[nAtoms];
461       yf = new double[nAtoms];
462       zf = new double[nAtoms];
463     }
464 
465     gridSize = gX * gY * gZ * 2;
466     int nX = gX / basisSize;
467     int nY = gY / basisSize;
468     int nZ = gZ / basisSize;
469     if (nThreads > 1 && nZ > 1) {
470       if (nZ % 2 != 0) {
471         nZ--;
472       }
473       nC = nZ;
474       int div = 2;
475       int currentWork = nC / div / nThreads;
476       // If we have enough work per thread, stop dividing the domain.
477       if (currentWork >= minWork || nY < 2) {
478         nA = 1;
479         nB = 1;
480         // Reduce the number of divisions along the Z-axis if possible
481         while (currentWork >= 2 * minWork) {
482           nC -= 2;
483           currentWork = nC / div / nThreads;
484         }
485 
486       } else {
487         if (nY % 2 != 0) {
488           nY--;
489         }
490         nB = nY;
491         div = 4;
492         currentWork = nB * nC / div / nThreads;
493         // If we have 4 * threadCount * minWork chunks, stop dividing the domain.
494         if (currentWork >= minWork || nX < 2) {
495           nA = 1;
496           while (currentWork >= 2 * minWork) {
497             nB -= 2;
498             currentWork = nB * nC / div / nThreads;
499           }
500         } else {
501           if (nX % 2 != 0) {
502             nX--;
503           }
504           nA = nX;
505           div = 8;
506           currentWork = nA * nB * nC / div / nThreads;
507           while (currentWork >= 2 * minWork) {
508             nA -= 2;
509             currentWork = nA * nB * nC / div / nThreads;
510           }
511         }
512       }
513       nAB = nA * nB;
514       nCells = nAB * nC;
515       nWork = nCells / div;
516     } else {
517       nA = 1;
518       nB = 1;
519       nC = 1;
520       nAB = 1;
521       nCells = 1;
522       nWork = 1;
523     }
524 
525     logger.fine(format("   Spatial cells:                 (%3d,%3d,%3d)", nA, nB, nC));
526     logger.fine(format("   Spatial work:                           %4d", nWork));
527 
528     if (workA == null || workA.length < nWork) {
529       workA = new int[nWork];
530       workB = new int[nWork];
531       workC = new int[nWork];
532       actualA = new int[nWork];
533       actualB = new int[nWork];
534       actualC = new int[nWork];
535       actualCount = new int[nWork];
536       int index = 0;
537       for (int h = 0; h < nA; h += 2) {
538         for (int k = 0; k < nB; k += 2) {
539           for (int l = 0; l < nC; l += 2) {
540             workA[index] = h;
541             workB[index] = k;
542             workC[index++] = l;
543           }
544         }
545       }
546     }
547     if (select == null || select.length < nSymm || select[0].length < nAtoms) {
548       select = new boolean[nSymm][nAtoms];
549       for (int i = 0; i < nSymm; i++) {
550         fill(select[i], true);
551       }
552       cellList = new int[nSymm][nAtoms];
553       cellIndex = new int[nSymm][nAtoms];
554       cellOffset = new int[nSymm][nAtoms];
555     }
556     if (cellStart == null || cellStart.length < nSymm || cellStart[0].length < nCells) {
557       cellStart = new int[nSymm][nCells];
558       cellCount = new int[nSymm][nCells];
559     }
560   }
561 
562   /**
563    * setDensityLoop
564    *
565    * @param loops an array of {@link ffx.potential.nonbonded.SpatialDensityLoop} objects.
566    */
567   public void setDensityLoop(SpatialDensityLoop[] loops) {
568     spatialDensityLoop = loops;
569   }
570 
571   /**
572    * Setter for the field <code>initValue</code>.
573    *
574    * @param initValue a double.
575    */
576   public void setInitValue(double initValue) {
577     this.initValue = initValue;
578   }
579 
580   /**
581    * Setter for the field <code>gridBuffer</code>.
582    *
583    * @param grid a {@link java.nio.DoubleBuffer} object.
584    */
585   void setGridBuffer(DoubleBuffer grid) {
586     gridBuffer = grid;
587   }
588 
589   private int count(int ia, int ib, int ic) {
590     int count = 0;
591     for (int iSymm = 0; iSymm < nSymm; iSymm++) {
592       final int index = index(ia, ib, ic);
593       final int start = cellStart[iSymm][index];
594       final int stop = start + cellCount[iSymm][index];
595       count += (stop - start);
596     }
597     return count;
598   }
599 
600   private class GridInitLoop extends IntegerForLoop {
601 
602     private final IntegerSchedule schedule = IntegerSchedule.fixed();
603 
604     @Override
605     public void run(int lb, int ub) {
606       if (gridBuffer != null) {
607         // if (grid != null) {
608         for (int i = lb; i <= ub; i++) {
609           // grid[i] = initValue;
610           gridBuffer.put(i, initValue);
611         }
612       }
613     }
614 
615     @Override
616     public IntegerSchedule schedule() {
617       return schedule;
618     }
619   }
620 }