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-2024.
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 static java.lang.String.format;
41  import static java.util.Arrays.fill;
42  import static org.apache.commons.math3.util.FastMath.floor;
43  
44  import edu.rit.pj.IntegerForLoop;
45  import edu.rit.pj.IntegerSchedule;
46  import edu.rit.pj.ParallelRegion;
47  import ffx.crystal.Crystal;
48  import ffx.potential.bonded.Atom;
49  import java.nio.DoubleBuffer;
50  import java.util.logging.Level;
51  import java.util.logging.Logger;
52  
53  /**
54   * This class implements a spatial decomposition based on partitioning a grid into octants.
55   *
56   * @author Michael J. Schnieders
57   * @since 1.0
58   */
59  public class SpatialDensityRegion extends ParallelRegion {
60  
61    /** Constant <code>logger</code> */
62    protected static final Logger logger = Logger.getLogger(SpatialDensityRegion.class.getName());
63  
64    public final int nThreads;
65    protected final int nSymm;
66    public int[] actualCount;
67    public int nAtoms;
68    /** The number of divisions along the A-axis. */
69    protected int nA;
70    /** The number of divisions along the B-axis. */
71    protected int nB;
72    /** The number of divisions along the C-Axis. */
73    protected int nC;
74    /**
75     * Number of octant work cells with at least one atom (actualWork is less than or equal to nWork).
76     */
77    protected int actualWork;
78  
79    protected double[][][] coordinates;
80    protected boolean[][] select;
81    protected Crystal crystal;
82    protected SpatialDensityLoop[] spatialDensityLoop;
83    /** The A index of each octant (0..nA - 1) that has atoms. */
84    int[] actualA;
85    /** The B index of each octant (0..nB - 1) that has atoms. */
86    int[] actualB;
87    /** The C index of each octant (0..nC - 1) that has atoms. */
88    int[] actualC;
89    /** The list of atoms in each cell. [nsymm][natom] = atom index */
90    int[][] cellList;
91    /** The number of atoms in each cell. [nsymm][ncell] */
92    int[][] cellCount;
93    /** The index of the first atom in each cell. [nsymm][ncell] */
94    int[][] cellStart;
95    /** Basis size. */
96    private int basisSize;
97    /** Minimum amount of work. */
98    private int minWork;
99    /** The number of cells in one plane (nDivisions^2). */
100   private int nAB;
101   /** The number of cells (nDivisions^3). */
102   private int nCells;
103   /** Number of octant work cells. */
104   private int nWork;
105   /** A temporary array that holds the index of the cell each atom is assigned to. */
106   private int[][] cellIndex;
107   /** The A index of each octant (0..nA - 1) that may not have any atoms. */
108   private int[] workA;
109   /** The B index of each octant (0..nB - 1) that may not have any atoms. */
110   private int[] workB;
111   /** The C index of each octant (0..nC - 1) that may not have any atoms. */
112   private int[] workC;
113   /**
114    * The offset of each atom from the start of the cell. The first atom atom in the cell has 0
115    * offset. [nsymm][natom] = offset of the atom
116    */
117   private int[][] cellOffset;
118 
119   private double[] xf;
120   private double[] yf;
121   private double[] zf;
122   private int gridSize;
123   private double[] grid = null;
124   private DoubleBuffer gridBuffer;
125   private double initValue = 0.0;
126   private GridInitLoop gridInitLoop;
127 
128   /**
129    * Constructor for SpatialDensityRegion.
130    *
131    * @param gX a int.
132    * @param gY a int.
133    * @param gZ a int.
134    * @param grid an array of double.
135    * @param basisSize a int.
136    * @param nSymm a int.
137    * @param minWork a int.
138    * @param threadCount a int.
139    * @param crystal a {@link ffx.crystal.Crystal} object.
140    * @param atoms an array of {@link ffx.potential.bonded.Atom} objects.
141    * @param coordinates an array of double.
142    */
143   public SpatialDensityRegion(
144       int gX,
145       int gY,
146       int gZ,
147       double[] grid,
148       int basisSize,
149       int nSymm,
150       int minWork,
151       int threadCount,
152       Crystal crystal,
153       Atom[] atoms,
154       double[][][] coordinates) {
155     this(gX, gY, gZ, basisSize, nSymm, minWork, threadCount, crystal, atoms, coordinates);
156     this.grid = grid;
157     if (grid != null) {
158       gridBuffer = DoubleBuffer.wrap(grid);
159     }
160   }
161 
162   /**
163    * Chop up the 3D unit cell domain into fractional coordinate chunks to allow multiple threads to
164    * put charge density onto the grid without needing the same grid point. First, we partition the
165    * X-axis, then the Y-axis, and finally the Z-axis if necessary.
166    *
167    * @param gX a int.
168    * @param gY a int.
169    * @param gZ a int.
170    * @param basisSize a int.
171    * @param nSymm a int.
172    * @param minWork a int.
173    * @param threadCount a int.
174    * @param crystal a {@link ffx.crystal.Crystal} object.
175    * @param atoms an array of {@link ffx.potential.bonded.Atom} objects.
176    * @param coordinates an array of double.
177    */
178   private SpatialDensityRegion(
179       int gX,
180       int gY,
181       int gZ,
182       int basisSize,
183       int nSymm,
184       int minWork,
185       int threadCount,
186       Crystal crystal,
187       Atom[] atoms,
188       double[][][] coordinates) {
189     this.crystal = crystal.getUnitCell();
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     this.crystal = crystal.getUnitCell();
454 
455     if (xf == null || xf.length < nAtoms) {
456       xf = new double[nAtoms];
457       yf = new double[nAtoms];
458       zf = new double[nAtoms];
459     }
460 
461     gridSize = gX * gY * gZ * 2;
462     int nX = gX / basisSize;
463     int nY = gY / basisSize;
464     int nZ = gZ / basisSize;
465     if (nThreads > 1 && nZ > 1) {
466       if (nZ % 2 != 0) {
467         nZ--;
468       }
469       nC = nZ;
470       int div = 2;
471       int currentWork = nC / div / nThreads;
472       // If we have enough work per thread, stop dividing the domain.
473       if (currentWork >= minWork || nY < 2) {
474         nA = 1;
475         nB = 1;
476         // Reduce the number of divisions along the Z-axis if possible
477         while (currentWork >= 2 * minWork) {
478           nC -= 2;
479           currentWork = nC / div / nThreads;
480         }
481 
482       } else {
483         if (nY % 2 != 0) {
484           nY--;
485         }
486         nB = nY;
487         div = 4;
488         currentWork = nB * nC / div / nThreads;
489         // If we have 4 * threadCount * minWork chunks, stop dividing the domain.
490         if (currentWork >= minWork || nX < 2) {
491           nA = 1;
492           while (currentWork >= 2 * minWork) {
493             nB -= 2;
494             currentWork = nB * nC / div / nThreads;
495           }
496         } else {
497           if (nX % 2 != 0) {
498             nX--;
499           }
500           nA = nX;
501           div = 8;
502           currentWork = nA * nB * nC / div / nThreads;
503           while (currentWork >= 2 * minWork) {
504             nA -= 2;
505             currentWork = nA * nB * nC / div / nThreads;
506           }
507         }
508       }
509       nAB = nA * nB;
510       nCells = nAB * nC;
511       nWork = nCells / div;
512     } else {
513       nA = 1;
514       nB = 1;
515       nC = 1;
516       nAB = 1;
517       nCells = 1;
518       nWork = 1;
519     }
520 
521     logger.fine(format("   Spatial cells:                 (%3d,%3d,%3d)", nA, nB, nC));
522     logger.fine(format("   Spatial work:                           %4d", nWork));
523 
524     if (workA == null || workA.length < nWork) {
525       workA = new int[nWork];
526       workB = new int[nWork];
527       workC = new int[nWork];
528       actualA = new int[nWork];
529       actualB = new int[nWork];
530       actualC = new int[nWork];
531       actualCount = new int[nWork];
532       int index = 0;
533       for (int h = 0; h < nA; h += 2) {
534         for (int k = 0; k < nB; k += 2) {
535           for (int l = 0; l < nC; l += 2) {
536             workA[index] = h;
537             workB[index] = k;
538             workC[index++] = l;
539           }
540         }
541       }
542     }
543     if (select == null || select.length < nSymm || select[0].length < nAtoms) {
544       select = new boolean[nSymm][nAtoms];
545       for (int i = 0; i < nSymm; i++) {
546         fill(select[i], true);
547       }
548       cellList = new int[nSymm][nAtoms];
549       cellIndex = new int[nSymm][nAtoms];
550       cellOffset = new int[nSymm][nAtoms];
551     }
552     if (cellStart == null || cellStart.length < nSymm || cellStart[0].length < nCells) {
553       cellStart = new int[nSymm][nCells];
554       cellCount = new int[nSymm][nCells];
555     }
556   }
557 
558   /**
559    * setDensityLoop
560    *
561    * @param loops an array of {@link ffx.potential.nonbonded.SpatialDensityLoop} objects.
562    */
563   public void setDensityLoop(SpatialDensityLoop[] loops) {
564     spatialDensityLoop = loops;
565   }
566 
567   /**
568    * Setter for the field <code>initValue</code>.
569    *
570    * @param initValue a double.
571    */
572   public void setInitValue(double initValue) {
573     this.initValue = initValue;
574   }
575 
576   /**
577    * Setter for the field <code>gridBuffer</code>.
578    *
579    * @param grid a {@link java.nio.DoubleBuffer} object.
580    */
581   void setGridBuffer(DoubleBuffer grid) {
582     gridBuffer = grid;
583   }
584 
585   private int count(int ia, int ib, int ic) {
586     int count = 0;
587     for (int iSymm = 0; iSymm < nSymm; iSymm++) {
588       final int index = index(ia, ib, ic);
589       final int start = cellStart[iSymm][index];
590       final int stop = start + cellCount[iSymm][index];
591       count += (stop - start);
592     }
593     return count;
594   }
595 
596   private class GridInitLoop extends IntegerForLoop {
597 
598     private final IntegerSchedule schedule = IntegerSchedule.fixed();
599 
600     @Override
601     public void run(int lb, int ub) {
602       if (gridBuffer != null) {
603         // if (grid != null) {
604         for (int i = lb; i <= ub; i++) {
605           // grid[i] = initValue;
606           gridBuffer.put(i, initValue);
607         }
608       }
609     }
610 
611     @Override
612     public IntegerSchedule schedule() {
613       return schedule;
614     }
615   }
616 }