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.IntegerSchedule;
41 import edu.rit.util.Range;
42
43 /**
44 * A fixed schedule that load balances work chunks across threads.
45 *
46 * @author Michael J. Schnieders
47 * @since 1.0
48 */
49 public class SpatialDensitySchedule extends IntegerSchedule {
50
51 private final int[] atomsPerChunk;
52 private final double loadBalancePercentage;
53 private int nThreads;
54 private Range chunkRange;
55 private boolean[] threadDone;
56 private Range[] ranges;
57
58 /**
59 * Constructor for SpatialDensitySchedule.
60 *
61 * @param nThreads a int.
62 * @param nAtoms a int.
63 * @param atomsPerChunk an array of int.
64 * @param loadBalancePercentage a double.
65 */
66 SpatialDensitySchedule(
67 int nThreads, int nAtoms, int[] atomsPerChunk, double loadBalancePercentage) {
68 this.atomsPerChunk = atomsPerChunk;
69 this.nThreads = nThreads;
70
71 threadDone = new boolean[nThreads];
72 ranges = new Range[nThreads];
73
74 if (loadBalancePercentage > 0.01 && loadBalancePercentage <= 1.0) {
75 this.loadBalancePercentage = loadBalancePercentage;
76 } else {
77 this.loadBalancePercentage = 1.0;
78 }
79 }
80
81 /**
82 * {@inheritDoc}
83 *
84 * <p>This is a fixed schedule.
85 */
86 @Override
87 public boolean isFixedSchedule() {
88 return true;
89 }
90
91 /** {@inheritDoc} */
92 @Override
93 public Range next(int threadID) {
94 if (!threadDone[threadID]) {
95 threadDone[threadID] = true;
96 return ranges[threadID];
97 }
98 return null;
99 }
100
101 /** {@inheritDoc} */
102 @Override
103 public void start(int nThreads, Range chunkRange) {
104 this.nThreads = nThreads;
105 this.chunkRange = chunkRange;
106 if (nThreads != threadDone.length) {
107 threadDone = new boolean[nThreads];
108 }
109 for (int i = 0; i < nThreads; i++) {
110 threadDone[i] = false;
111 }
112 if (nThreads != ranges.length) {
113 ranges = new Range[nThreads];
114 }
115
116 defineRanges();
117 }
118
119 private void defineRanges() {
120 int lb = chunkRange.lb();
121 int ub = chunkRange.ub();
122 int thread = 0;
123 int start = 0;
124 int total = 0;
125
126 // Calculate the total number of atoms that will be place on the grid.
127 for (int value : atomsPerChunk) {
128 total += value;
129 }
130 int goal = (int) ((total * loadBalancePercentage) / nThreads);
131
132 total = 0;
133 for (int i = lb; i <= ub; i++) {
134 int chunksLeft = ub - i + 1;
135 int threadsLeft = nThreads - thread;
136 // Count the number of atoms in each work chunk.
137 total += atomsPerChunk[i];
138 // Check if the load balancing goal has been reached.
139 if (total > goal || chunksLeft <= threadsLeft) {
140 int stop = i;
141 // Define the range for this thread.
142 Range current = ranges[thread];
143 if (current == null || current.lb() != start || current.ub() != stop) {
144 ranges[thread] = new Range(start, stop);
145 }
146
147 // Initialization for the next thread.
148 thread++;
149 start = i + 1;
150 total = 0;
151 // The last thread gets the rest of the work chunks.
152 if (thread == nThreads - 1) {
153 stop = ub;
154 current = ranges[thread];
155 if (current == null || current.lb() != start || current.ub() != stop) {
156 ranges[thread] = new Range(start, stop);
157 }
158 break;
159 }
160 } else if (i == ub) {
161 // The final range may not meet the goal.
162 int stop = i;
163 Range current = ranges[thread];
164 if (current == null || current.lb() != start || current.ub() != stop) {
165 ranges[thread] = new Range(start, stop);
166 }
167 }
168 }
169
170 // No work for remaining threads.
171 for (int i = thread + 1; i < nThreads; i++) {
172 ranges[i] = null;
173 }
174 }
175 }