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 }