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 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.coordinates = coordinates;
190     this.nSymm = nSymm;
191     this.nAtoms = atoms.length;
192     this.nThreads = threadCount;
193     this.basisSize = basisSize;
194     this.minWork = minWork;
195     gridInitLoop = new GridInitLoop();
196     setCrystal(crystal.getUnitCell(), gX, gY, gZ);
197   }
198 
199   /**
200    * Assign asymmetric and symmetry mate atoms to cells. This is very fast; there is little to be
201    * gained from parallelization at this point.
202    */
203   public void assignAtomsToCells() {
204     // Call the selectAtoms method of subclasses.
205     selectAtoms();
206 
207     for (int iSymm = 0; iSymm < nSymm; iSymm++) {
208       final int[] cellIndexs = cellIndex[iSymm];
209       final int[] cellCounts = cellCount[iSymm];
210       final int[] cellStarts = cellStart[iSymm];
211       final int[] cellLists = cellList[iSymm];
212       final int[] cellOffsets = cellOffset[iSymm];
213       final boolean[] selected = select[iSymm];
214 
215       // Zero out the cell counts.
216       for (int i = 0; i < nCells; i++) {
217         cellCounts[i] = 0;
218       }
219 
220       // Convert to fractional coordinates.
221       final double[][] xyz = coordinates[iSymm];
222       final double[] x = xyz[0];
223       final double[] y = xyz[1];
224       final double[] z = xyz[2];
225       crystal.toFractionalCoordinates(nAtoms, x, y, z, xf, yf, zf);
226 
227       // Assign each atom to a cell using fractional coordinates.
228       for (int i = 0; i < nAtoms; i++) {
229         if (!selected[i]) {
230           continue;
231         }
232 
233         final int index = getCellIndex(i);
234         cellIndexs[i] = index;
235 
236         // The offset of this atom from the beginning of the cell.
237         cellOffsets[i] = cellCounts[index]++;
238       }
239 
240       // Define the starting indices.
241       cellStarts[0] = 0;
242       for (int i = 1; i < nCells; i++) {
243         final int i1 = i - 1;
244         cellStarts[i] = cellStarts[i1] + cellCounts[i1];
245       }
246 
247       // Move atom locations into a list ordered by cell.
248       for (int i = 0; i < nAtoms; i++) {
249         if (!selected[i]) {
250           continue;
251         }
252         final int index = cellIndexs[i];
253         cellLists[cellStarts[index]++] = i;
254       }
255 
256       // Redefine the starting indices again.
257       cellStarts[0] = 0;
258       for (int i = 1; i < nCells; i++) {
259         final int i1 = i - 1;
260         cellStarts[i] = cellStarts[i1] + cellCounts[i1];
261       }
262     }
263 
264     // Loop over work chunks and get rid of empty chunks.
265     actualWork = 0;
266     for (int icell = 0; icell < nWork; icell++) {
267       int ia = workA[icell];
268       int ib = workB[icell];
269       int ic = workC[icell];
270       int ii = count(ia, ib, ic);
271       // Fractional chunks along the C-axis.
272       if (nC > 1) {
273         ii += count(ia, ib, ic + 1);
274         // Fractional chunks along the B-axis.
275         if (nB > 1) {
276           ii += count(ia, ib + 1, ic);
277           ii += count(ia, ib + 1, ic + 1);
278           // Fractional chunks along the A-axis.
279           if (nA > 1) {
280             ii += count(ia + 1, ib, ic);
281             ii += count(ia + 1, ib, ic + 1);
282             ii += count(ia + 1, ib + 1, ic);
283             ii += count(ia + 1, ib + 1, ic + 1);
284           }
285         }
286       }
287 
288       // If there is work in this chunk, include it.
289       if (ii > 0) {
290         actualA[actualWork] = ia;
291         actualB[actualWork] = ib;
292         actualC[actualWork] = ic;
293         actualCount[actualWork++] = ii;
294       }
295     }
296 
297     if (logger.isLoggable(Level.FINEST)) {
298       logger.finest(format(" Empty chunks: %d out of %d.", nWork - actualWork, nWork));
299     }
300   }
301 
302   private int getCellIndex(int i) {
303     double xu = xf[i];
304     double yu = yf[i];
305     double zu = zf[i];
306 
307     // Move the atom into the range 0.0 <= x < 1.0
308     while (xu >= 1.0) {
309       xu -= 1.0;
310     }
311     while (xu < 0.0) {
312       xu += 1.0;
313     }
314     while (yu >= 1.0) {
315       yu -= 1.0;
316     }
317     while (yu < 0.0) {
318       yu += 1.0;
319     }
320     while (zu >= 1.0) {
321       zu -= 1.0;
322     }
323     while (zu < 0.0) {
324       zu += 1.0;
325     }
326 
327     // The cell indices of this atom.
328     int a = (int) floor(xu * nA);
329     int b = (int) floor(yu * nB);
330     int c = (int) floor(zu * nC);
331 
332     // Check to make sure a, b and c are less than nA, nB and nC, respectively.
333     if (a >= nA) {
334       a = nA - 1;
335     }
336     if (b >= nB) {
337       b = nB - 1;
338     }
339     if (c >= nC) {
340       c = nC - 1;
341     }
342 
343     // The cell index of this atom.
344     final int index = a + b * nA + c * nAB;
345     return index;
346   }
347 
348   /**
349    * Getter for the field <code>grid</code>.
350    *
351    * @return an array of double.
352    */
353   public double[] getGrid() {
354     return grid;
355   }
356 
357   /**
358    * getNsymm
359    *
360    * @return a int.
361    */
362   public int getNsymm() {
363     return nSymm;
364   }
365 
366   /**
367    * index
368    *
369    * @param ia a int.
370    * @param ib a int.
371    * @param ic a int.
372    * @return a int.
373    */
374   public int index(int ia, int ib, int ic) {
375     return ia + ib * nA + ic * nAB;
376   }
377 
378   /** {@inheritDoc} */
379   @Override
380   public void run() {
381     int ti = getThreadIndex();
382     int actualWork1 = actualWork - 1;
383     SpatialDensityLoop loop = spatialDensityLoop[ti];
384 
385     // This lets the same SpatialDensityLoops be used with different SpatialDensityRegions.
386     loop.setNsymm(nSymm);
387 
388     try {
389       execute(0, gridSize - 1, gridInitLoop);
390       execute(0, actualWork1, loop.setOctant(0));
391       // Fractional chunks along the C-axis.
392       if (nC > 1) {
393         execute(0, actualWork1, loop.setOctant(1));
394         // Fractional chunks along the B-axis.
395         if (nB > 1) {
396           execute(0, actualWork1, loop.setOctant(2));
397           execute(0, actualWork1, loop.setOctant(3));
398           // Fractional chunks along the A-axis.
399           if (nA > 1) {
400             execute(0, actualWork1, loop.setOctant(4));
401             execute(0, actualWork1, loop.setOctant(5));
402             execute(0, actualWork1, loop.setOctant(6));
403             execute(0, actualWork1, loop.setOctant(7));
404           }
405         }
406       }
407     } catch (Exception e) {
408       String message = " Exception in SpatialDensityRegion.";
409       logger.log(Level.SEVERE, message, e);
410     }
411   }
412 
413   /**
414    * Select atoms that should be assigned to cells. The default is to include all atoms, which is
415    * set up in the constructor. This function should be over-ridden by subclasses that want finer
416    * control.
417    */
418   public void selectAtoms() {}
419 
420   /**
421    * setAtoms.
422    *
423    * @param atoms an array of {@link ffx.potential.bonded.Atom} objects.
424    */
425   public void setAtoms(Atom[] atoms) {
426     nAtoms = atoms.length;
427     if (select == null || select.length < nSymm || select[0].length < nAtoms) {
428       select = new boolean[nSymm][nAtoms];
429       for (int i = 0; i < nSymm; i++) {
430         fill(select[i], true);
431       }
432       cellList = new int[nSymm][nAtoms];
433       cellIndex = new int[nSymm][nAtoms];
434       cellOffset = new int[nSymm][nAtoms];
435     }
436     if (xf == null || xf.length < nAtoms) {
437       xf = new double[nAtoms];
438       yf = new double[nAtoms];
439       zf = new double[nAtoms];
440     }
441   }
442 
443   /**
444    * Setter for the field <code>crystal</code>.
445    *
446    * @param crystal a {@link ffx.crystal.Crystal} object.
447    * @param gX a int.
448    * @param gY a int.
449    * @param gZ a int.
450    */
451   public final void setCrystal(Crystal crystal, int gX, int gY, int gZ) {
452     // If the crystal is unchanged, return.
453     if (this.crystal != null && this.crystal.equals(crystal.getUnitCell())) {
454       return;
455     }
456 
457     this.crystal = crystal.getUnitCell();
458     if (xf == null || xf.length < nAtoms) {
459       xf = new double[nAtoms];
460       yf = new double[nAtoms];
461       zf = new double[nAtoms];
462     }
463 
464     gridSize = gX * gY * gZ * 2;
465     int nX = gX / basisSize;
466     int nY = gY / basisSize;
467     int nZ = gZ / basisSize;
468     if (nThreads > 1 && nZ > 1) {
469       if (nZ % 2 != 0) {
470         nZ--;
471       }
472       nC = nZ;
473       int div = 2;
474       int currentWork = nC / div / nThreads;
475       // If we have enough work per thread, stop dividing the domain.
476       if (currentWork >= minWork || nY < 2) {
477         nA = 1;
478         nB = 1;
479         // Reduce the number of divisions along the Z-axis if possible
480         while (currentWork >= 2 * minWork) {
481           nC -= 2;
482           currentWork = nC / div / nThreads;
483         }
484 
485       } else {
486         if (nY % 2 != 0) {
487           nY--;
488         }
489         nB = nY;
490         div = 4;
491         currentWork = nB * nC / div / nThreads;
492         // If we have 4 * threadCount * minWork chunks, stop dividing the domain.
493         if (currentWork >= minWork || nX < 2) {
494           nA = 1;
495           while (currentWork >= 2 * minWork) {
496             nB -= 2;
497             currentWork = nB * nC / div / nThreads;
498           }
499         } else {
500           if (nX % 2 != 0) {
501             nX--;
502           }
503           nA = nX;
504           div = 8;
505           currentWork = nA * nB * nC / div / nThreads;
506           while (currentWork >= 2 * minWork) {
507             nA -= 2;
508             currentWork = nA * nB * nC / div / nThreads;
509           }
510         }
511       }
512       nAB = nA * nB;
513       nCells = nAB * nC;
514       nWork = nCells / div;
515     } else {
516       nA = 1;
517       nB = 1;
518       nC = 1;
519       nAB = 1;
520       nCells = 1;
521       nWork = 1;
522     }
523 
524     logger.fine(format("   Spatial cells:                 (%3d,%3d,%3d)", nA, nB, nC));
525     logger.fine(format("   Spatial work:                           %4d", nWork));
526 
527     if (workA == null || workA.length < nWork) {
528       workA = new int[nWork];
529       workB = new int[nWork];
530       workC = new int[nWork];
531       actualA = new int[nWork];
532       actualB = new int[nWork];
533       actualC = new int[nWork];
534       actualCount = new int[nWork];
535       int index = 0;
536       for (int h = 0; h < nA; h += 2) {
537         for (int k = 0; k < nB; k += 2) {
538           for (int l = 0; l < nC; l += 2) {
539             workA[index] = h;
540             workB[index] = k;
541             workC[index++] = l;
542           }
543         }
544       }
545     }
546     if (select == null || select.length < nSymm || select[0].length < nAtoms) {
547       select = new boolean[nSymm][nAtoms];
548       for (int i = 0; i < nSymm; i++) {
549         fill(select[i], true);
550       }
551       cellList = new int[nSymm][nAtoms];
552       cellIndex = new int[nSymm][nAtoms];
553       cellOffset = new int[nSymm][nAtoms];
554     }
555     if (cellStart == null || cellStart.length < nSymm || cellStart[0].length < nCells) {
556       cellStart = new int[nSymm][nCells];
557       cellCount = new int[nSymm][nCells];
558     }
559   }
560 
561   /**
562    * setDensityLoop
563    *
564    * @param loops an array of {@link ffx.potential.nonbonded.SpatialDensityLoop} objects.
565    */
566   public void setDensityLoop(SpatialDensityLoop[] loops) {
567     spatialDensityLoop = loops;
568   }
569 
570   /**
571    * Setter for the field <code>initValue</code>.
572    *
573    * @param initValue a double.
574    */
575   public void setInitValue(double initValue) {
576     this.initValue = initValue;
577   }
578 
579   /**
580    * Setter for the field <code>gridBuffer</code>.
581    *
582    * @param grid a {@link java.nio.DoubleBuffer} object.
583    */
584   void setGridBuffer(DoubleBuffer grid) {
585     gridBuffer = grid;
586   }
587 
588   private int count(int ia, int ib, int ic) {
589     int count = 0;
590     for (int iSymm = 0; iSymm < nSymm; iSymm++) {
591       final int index = index(ia, ib, ic);
592       final int start = cellStart[iSymm][index];
593       final int stop = start + cellCount[iSymm][index];
594       count += (stop - start);
595     }
596     return count;
597   }
598 
599   private class GridInitLoop extends IntegerForLoop {
600 
601     private final IntegerSchedule schedule = IntegerSchedule.fixed();
602 
603     @Override
604     public void run(int lb, int ub) {
605       if (gridBuffer != null) {
606         // if (grid != null) {
607         for (int i = lb; i <= ub; i++) {
608           // grid[i] = initValue;
609           gridBuffer.put(i, initValue);
610         }
611       }
612     }
613 
614     @Override
615     public IntegerSchedule schedule() {
616       return schedule;
617     }
618   }
619 }