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.numerics.atomic;
39  
40  import static java.lang.String.format;
41  
42  import edu.rit.pj.IntegerForLoop;
43  import edu.rit.pj.ParallelRegion;
44  import edu.rit.pj.ParallelTeam;
45  import ffx.numerics.atomic.AtomicDoubleArray.AtomicDoubleArrayImpl;
46  import ffx.numerics.math.Double3;
47  import ffx.numerics.math.DoubleMath;
48  import java.util.Objects;
49  
50  /**
51   * Implementation of maintaining a 3D double array that is operated on by multiple threads.
52   *
53   * @author Michael J. Schnieders
54   * @since 1.0
55   */
56  public class AtomicDoubleArray3D {
57  
58    /**
59     * Each dimension is stored in its own AtomicDoubleArray.
60     */
61    private final AtomicDoubleArray[] atomicDoubleArray;
62  
63    /**
64     * The implementation.
65     */
66    private final AtomicDoubleArrayImpl atomicDoubleArrayImpl;
67  
68    /**
69     * Private ParallelRegion3D for parallel reduction and resets.
70     */
71    private final ParallelRegion3D parallelRegion3D = new ParallelRegion3D();
72  
73    /**
74     * Construct an atomic 3D double array of the specified size using the specified implementation.
75     *
76     * @param atomicDoubleArrayImpl Implementation.
77     * @param size Size of each dimension.
78     */
79    public AtomicDoubleArray3D(AtomicDoubleArrayImpl atomicDoubleArrayImpl, int size) {
80      this(atomicDoubleArrayImpl, size, ParallelTeam.getDefaultThreadCount());
81    }
82  
83    /**
84     * Construct an atomic 3D double array of the specified size, using the specified implementation,
85     * and the requested number of threads.
86     *
87     * @param atomicDoubleArrayImpl Implementation.
88     * @param size Size of each dimension.
89     * @param nThreads Requested number of threads (only used by the MULTI implementation).
90     */
91    public AtomicDoubleArray3D(AtomicDoubleArrayImpl atomicDoubleArrayImpl, int size, int nThreads) {
92      atomicDoubleArray = new AtomicDoubleArray[3];
93      atomicDoubleArray[0] = atomicDoubleArrayImpl.createInstance(nThreads, size);
94      atomicDoubleArray[1] = atomicDoubleArrayImpl.createInstance(nThreads, size);
95      atomicDoubleArray[2] = atomicDoubleArrayImpl.createInstance(nThreads, size);
96      this.atomicDoubleArrayImpl = atomicDoubleArrayImpl;
97    }
98  
99    /**
100    * Construct an atomic 3D double array using the specified AtomicDoubleArray instances.
101    *
102    * @param x AtomicDoubleArray for the X dimension.
103    * @param y AtomicDoubleArray for the Y dimension.
104    * @param z AtomicDoubleArray for the Z dimension.
105    */
106   public AtomicDoubleArray3D(AtomicDoubleArray x, AtomicDoubleArray y, AtomicDoubleArray z) {
107     atomicDoubleArray = new AtomicDoubleArray[3];
108     atomicDoubleArray[0] = x;
109     atomicDoubleArray[1] = y;
110     atomicDoubleArray[2] = z;
111     if (x instanceof MultiDoubleArray) {
112       this.atomicDoubleArrayImpl = AtomicDoubleArrayImpl.MULTI;
113     } else if (x instanceof AdderDoubleArray) {
114       this.atomicDoubleArrayImpl = AtomicDoubleArrayImpl.ADDER;
115     } else {
116       this.atomicDoubleArrayImpl = AtomicDoubleArrayImpl.PJ;
117     }
118   }
119 
120   /**
121    * Add to the double arrays at the specified index the given values.
122    *
123    * @param threadID the thread ID.
124    * @param index the index.
125    * @param x the x value.
126    * @param y the y value.
127    * @param z the z value.
128    */
129   public void add(int threadID, int index, double x, double y, double z) {
130     atomicDoubleArray[0].add(threadID, index, x);
131     atomicDoubleArray[1].add(threadID, index, y);
132     atomicDoubleArray[2].add(threadID, index, z);
133   }
134 
135   /**
136    * Add to the double arrays at the specified index the given Double3.
137    *
138    * @param threadID the thread ID.
139    * @param index the index.
140    * @param d3 the Double3 containing the values to add.
141    */
142   public void add(int threadID, int index, Double3 d3) {
143     atomicDoubleArray[0].add(threadID, index, d3.x());
144     atomicDoubleArray[1].add(threadID, index, d3.y());
145     atomicDoubleArray[2].add(threadID, index, d3.z());
146   }
147 
148   /**
149    * Ensure the AtomicDoubleArray3D instance is greater than or equal to size.
150    *
151    * @param size the size of the array.
152    */
153   public void alloc(int size) {
154     atomicDoubleArray[0].alloc(size);
155     atomicDoubleArray[1].alloc(size);
156     atomicDoubleArray[2].alloc(size);
157   }
158 
159   /**
160    * Get the value of the array at the specified index after calling the <code>reduce</code> method.
161    *
162    * @param dim Dimension [0, 1, 2]
163    * @param index the index.
164    * @return the value.
165    */
166   public double get(int dim, int index) {
167     return atomicDoubleArray[dim].get(index);
168   }
169 
170   /**
171    * Get the Double3 at the specified index. This is usually after calling the <code>reduce
172    * </code> method.
173    *
174    * @param index the index.
175    * @return a new Double3 with the values at the specified index.
176    */
177   public Double3 get(int index) {
178     return new Double3(
179         atomicDoubleArray[0].get(index),
180         atomicDoubleArray[1].get(index),
181         atomicDoubleArray[2].get(index));
182   }
183 
184   /**
185    * Get the X-value of the array at the specified index after calling the <code>reduce</code>
186    * method.
187    *
188    * @param index the index.
189    * @return the X value.
190    */
191   public double getX(int index) {
192     return atomicDoubleArray[0].get(index);
193   }
194 
195   /**
196    * Get the Y-value of the array at the specified index after calling the <code>reduce</code>
197    * method.
198    *
199    * @param index the index.
200    * @return the Y value.
201    */
202   public double getY(int index) {
203     return atomicDoubleArray[1].get(index);
204   }
205 
206   /**
207    * Get the Z-value of the array at the specified index after calling the <code>reduce</code>
208    * method.
209    *
210    * @param index the index.
211    * @return the Z value.
212    */
213   public double getZ(int index) {
214     return atomicDoubleArray[2].get(index);
215   }
216 
217   /**
218    * Perform reduction between the given lower bound (lb) and upper bound (up) if necessary.
219    *
220    * @param lb the lower bound.
221    * @param ub the upper bound.
222    */
223   public void reduce(int lb, int ub) {
224     // Nothing to do for PJ and Adder.
225     if (Objects.requireNonNull(atomicDoubleArrayImpl) == AtomicDoubleArrayImpl.MULTI) {
226       atomicDoubleArray[0].reduce(lb, ub);
227       atomicDoubleArray[1].reduce(lb, ub);
228       atomicDoubleArray[2].reduce(lb, ub);
229     }
230   }
231 
232   /**
233    * Perform a reduction on the entire array.
234    *
235    * @param parallelTeam ParallelTeam to use.
236    */
237   public void reduce(ParallelTeam parallelTeam) {
238     if (Objects.requireNonNull(atomicDoubleArrayImpl) == AtomicDoubleArrayImpl.MULTI) {
239       parallelRegion3D.setOperation(Operation.REDUCE);
240       try {
241         parallelTeam.execute(parallelRegion3D);
242       } catch (Exception e) {
243         throw new RuntimeException(e);
244       }
245     }
246   }
247 
248   /**
249    * Reset the double array to Zero.
250    *
251    * @param threadID the thread ID.
252    * @param lb the lower bound.
253    * @param ub the upper bound.
254    */
255   public void reset(int threadID, int lb, int ub) {
256     atomicDoubleArray[0].reset(threadID, lb, ub);
257     atomicDoubleArray[1].reset(threadID, lb, ub);
258     atomicDoubleArray[2].reset(threadID, lb, ub);
259   }
260 
261   /**
262    * Reset the double array to Zero.
263    *
264    * @param parallelTeam a ParallelTeam.
265    */
266   public void reset(ParallelTeam parallelTeam) {
267     parallelRegion3D.setOperation(Operation.RESET);
268     try {
269       parallelTeam.execute(parallelRegion3D);
270     } catch (Exception e) {
271       throw new RuntimeException(e);
272     }
273   }
274 
275   /**
276    * Scale the double arrays at the specified index to the given values.
277    *
278    * @param threadID the thread ID.
279    * @param index the index.
280    * @param scale The value to scale by.
281    */
282   public void scale(int threadID, int index, double scale) {
283     atomicDoubleArray[0].scale(threadID, index, scale);
284     atomicDoubleArray[1].scale(threadID, index, scale);
285     atomicDoubleArray[2].scale(threadID, index, scale);
286   }
287 
288   /**
289    * Set the double arrays at the specified index to the given values.
290    *
291    * @param threadID the thread ID.
292    * @param index the index.
293    * @param x the X value.
294    * @param y the Y value.
295    * @param z the Z value.
296    */
297   public void set(int threadID, int index, double x, double y, double z) {
298     atomicDoubleArray[0].set(threadID, index, x);
299     atomicDoubleArray[1].set(threadID, index, y);
300     atomicDoubleArray[2].set(threadID, index, z);
301   }
302 
303   /**
304    * Set the double arrays at the specified index to the given Double3.
305    *
306    * @param threadID the thread ID.
307    * @param index the index.
308    * @param d3 the Double3 containing the values.
309    */
310   public void set(int threadID, int index, Double3 d3) {
311     atomicDoubleArray[0].set(threadID, index, d3.x());
312     atomicDoubleArray[1].set(threadID, index, d3.y());
313     atomicDoubleArray[2].set(threadID, index, d3.z());
314   }
315 
316   /**
317    * Subtracts from the double arrays at the specified index the given values.
318    *
319    * @param threadID the thread ID.
320    * @param index the index.
321    * @param x the X value.
322    * @param y the Y value.
323    * @param z the Z value.
324    */
325   public void sub(int threadID, int index, double x, double y, double z) {
326     atomicDoubleArray[0].sub(threadID, index, x);
327     atomicDoubleArray[1].sub(threadID, index, y);
328     atomicDoubleArray[2].sub(threadID, index, z);
329   }
330 
331   /**
332    * Subtracts from the double arrays at the specified index the given Double3.
333    *
334    * @param threadID the thread ID.
335    * @param index the index.
336    * @param d3 the Double3 containing the values.
337    */
338   public void sub(int threadID, int index, Double3 d3) {
339     atomicDoubleArray[0].sub(threadID, index, d3.x());
340     atomicDoubleArray[1].sub(threadID, index, d3.y());
341     atomicDoubleArray[2].sub(threadID, index, d3.z());
342   }
343 
344   /**
345    * Return a string for given index.
346    *
347    * @param index Index.
348    * @return Returns a String for 3D vector at the given index.
349    */
350   public String toString(int index) {
351     String defaultLabel = " " + index + ": ";
352     return toString(index, defaultLabel);
353   }
354 
355   /**
356    * Return a string for given index.
357    *
358    * @param index Index.
359    * @param label Label to use for the String.
360    * @return Returns a String for 3D vector at the given index.
361    */
362   public String toString(int index, String label) {
363     var d = new double[] {getX(index), getY(index), getZ(index)};
364     return DoubleMath.toString(d, label);
365   }
366 
367   /**
368    * Return a String for entire Array, with one 3D vector per line.
369    *
370    * @return Returns a String for the 3D array.
371    */
372   public String toString() {
373     StringBuilder sb = new StringBuilder();
374     for (int i = 0; i < atomicDoubleArray.length; i++) {
375       sb.append(toString(i)).append("\n");
376     }
377     return sb.toString();
378   }
379 
380   /**
381    * Return a String for entire Array, with one 3D vector per line.
382    *
383    * @param label Label may include one "%d" format conversion to include for the index of each
384    *     entry.
385    * @return Returns a String for the 3D array.
386    */
387   public String toString(String label) {
388     StringBuilder sb = new StringBuilder();
389     if (label.contains("%d")) {
390       for (int i = 0; i < atomicDoubleArray[0].size(); i++) {
391         sb.append(toString(i, format(label, i))).append("\n");
392       }
393     } else {
394       for (int i = 0; i < atomicDoubleArray[0].size(); i++) {
395         sb.append(toString(i, label)).append("\n");
396       }
397     }
398     return sb.toString();
399   }
400 
401   /**
402    * Available parallel operations.
403    */
404   private enum Operation {
405     RESET,
406     REDUCE
407   }
408 
409   /**
410    * Private ParallelRegion for parallel reset and reduction operations.
411    */
412   private class ParallelRegion3D extends ParallelRegion {
413 
414     private Operation operation;
415     private IntegerForLoop3D[] integerForLoop3D;
416 
417     /**
418      * Instantiates a new parallel region.
419      */
420     public ParallelRegion3D() {
421       operation = Operation.RESET;
422     }
423 
424     /**
425      * Sets the operation.
426      *
427      * @param operation the new operation
428      */
429     public void setOperation(Operation operation) {
430       this.operation = operation;
431     }
432 
433     /** {@inheritDoc} */
434     @Override
435     public void start() {
436       int nThreads = getThreadCount();
437       if (integerForLoop3D == null) {
438         integerForLoop3D = new IntegerForLoop3D[nThreads];
439       }
440     }
441 
442     /** {@inheritDoc} */
443     @Override
444     public void run() throws Exception {
445       int threadID = getThreadIndex();
446       if (integerForLoop3D[threadID] == null) {
447         integerForLoop3D[threadID] = new IntegerForLoop3D();
448       }
449       int size = atomicDoubleArray[0].size();
450       execute(0, size - 1, integerForLoop3D[threadID]);
451     }
452 
453     /**
454      * Private class for parallel reset and reduction operations.
455      */
456     private class IntegerForLoop3D extends IntegerForLoop {
457 
458       /** {@inheritDoc} */
459       @Override
460       public void run(int lb, int ub) {
461         int threadID = getThreadIndex();
462         switch (operation) {
463           case RESET -> reset(threadID, lb, ub);
464           case REDUCE -> reduce(lb, ub);
465         }
466       }
467     }
468   }
469 
470 
471 }