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.xray;
39  
40  import edu.rit.pj.IntegerForLoop;
41  import edu.rit.pj.IntegerSchedule;
42  import edu.rit.pj.ParallelRegion;
43  import edu.rit.pj.ParallelTeam;
44  import edu.rit.pj.reduction.SharedIntegerArray;
45  import ffx.crystal.Crystal;
46  import ffx.crystal.HKL;
47  import ffx.crystal.ReflectionList;
48  import ffx.crystal.Resolution;
49  import ffx.crystal.SymOp;
50  import ffx.numerics.fft.Complex;
51  import ffx.numerics.fft.Complex3DParallel;
52  import ffx.numerics.math.ComplexNumber;
53  import ffx.potential.bonded.Atom;
54  import ffx.potential.nonbonded.RowLoop;
55  import ffx.potential.nonbonded.RowRegion;
56  import ffx.potential.nonbonded.SliceLoop;
57  import ffx.potential.nonbonded.SliceRegion;
58  import ffx.potential.nonbonded.SpatialDensityLoop;
59  import ffx.potential.nonbonded.SpatialDensityRegion;
60  import ffx.xray.parallel.GradientSchedule;
61  import ffx.xray.parallel.GridMethod;
62  import ffx.xray.parallel.RowSchedule;
63  import ffx.xray.parallel.SliceSchedule;
64  import ffx.xray.refine.RefinementMode;
65  import ffx.xray.scatter.FormFactor;
66  import ffx.xray.scatter.NeutronFormFactor;
67  import ffx.xray.scatter.SolventBinaryFormFactor;
68  import ffx.xray.scatter.SolventGaussFormFactor;
69  import ffx.xray.scatter.SolventPolyFormFactor;
70  import ffx.xray.scatter.XRayFormFactor;
71  import ffx.xray.solvent.BulkSolventDensityRegion;
72  import ffx.xray.solvent.BulkSolventRowRegion;
73  import ffx.xray.solvent.BulkSolventSliceRegion;
74  import ffx.xray.solvent.SolventModel;
75  
76  import java.util.ArrayList;
77  import java.util.List;
78  import java.util.logging.Level;
79  import java.util.logging.Logger;
80  
81  import static ffx.crystal.SymOp.applyTransSymRot;
82  import static ffx.numerics.fft.Complex3D.interleavedIndex;
83  import static ffx.numerics.math.ScalarMath.mod;
84  import static java.lang.String.format;
85  import static java.lang.System.arraycopy;
86  import static java.util.Arrays.fill;
87  import static org.apache.commons.math3.util.FastMath.abs;
88  import static org.apache.commons.math3.util.FastMath.exp;
89  import static org.apache.commons.math3.util.FastMath.floor;
90  import static org.apache.commons.math3.util.FastMath.max;
91  import static org.apache.commons.math3.util.FastMath.min;
92  import static org.apache.commons.math3.util.FastMath.pow;
93  import static org.apache.commons.math3.util.FastMath.sqrt;
94  
95  /**
96   * Structure factor calculation (including bulk solvent structure factors)
97   *
98   * @author Timothy D. Fenn
99   * @see <a href="http://dx.doi.org/10.1107/S0567739473000458" target="_blank"> L. F. Ten Eyck, Acta
100  * Cryst. (1973). A29, 183-191.</a>
101  * @see <a href="http://dx.doi.org/10.1107/S0567739477001211" target="_blank"> L. F. Ten Eyck, Acta
102  * Cryst. (1977). A33, 486-492.</a>
103  * @see <a href="http://dx.doi.org/10.1107/S0365110X55001862" target="_blank"> J. Waser, Acta Cryst.
104  * (1955). 8, 595.</a>
105  * @see <a href="http://dx.doi.org/10.1107/S0108767388009183" target="_blank"> A. T. Brunger, Acta
106  * Cryst. (1989). A45, 42-50.</a>
107  * @see <a href="http://dx.doi.org/10.1107/97809553602060000551" target="_blank"> G. Bricogne, Int.
108  * Tables Cryst. (2006). Vol. B, ch. 1.3, pp. 25-98.</a>
109  * @see <a href="http://dx.doi.org/10.1002/jcc.1032" target="_blank"> J. A. Grant, B. T. Pickup, A.
110  * Nicholls, J. Comp. Chem. (2001). 22, 608-640</a>
111  * @see <a href="http://dx.doi.org/10.1006/jmbi.1994.1633" target="_blank"> J. S. Jiang, A. T.
112  * Brunger, JMB (1994) 243, 100-115.</a>
113  * @see <a href="http://dx.doi.org/10.1107/S0907444910031045" target="_blank"> T.D. Fenn, M. J.
114  * Schnieders, A. T. Brunger, Acta Cryst. (2010). D66, 1024-1031.</a>
115  * @since 1.0
116  */
117 public class CrystalReciprocalSpace {
118 
119   private static final Logger logger = Logger.getLogger(CrystalReciprocalSpace.class.getName());
120   private static final double toSeconds = 1.0e-9;
121   private final boolean neutron;
122   private final double fftScale;
123   private final double[] densityGrid;
124   private final double[] solventGrid;
125   private final int nSymm;
126   private final int bulkNSymm;
127   private final int nScatteringAtoms;
128   private final int fftX, fftY, fftZ;
129   private final double ifftX, ifftY, ifftZ;
130   private final int halfFFTX;
131   private final int complexFFT3DSpace;
132   private final int threadCount;
133   private final int bufferSize = 3;
134   // Slice Scheduling
135   private final SharedIntegerArray optSliceWeightAtomic;
136   private final SharedIntegerArray optSliceWeightSolvent;
137   private final SharedIntegerArray optSliceWeightBulkSolvent;
138   private final int[] previousOptSliceWeightAtomic;
139   private final int[] previousOptSliceWeightSolvent;
140   private final int[] previousOptSliceWeightBulkSolvent;
141   private final SliceSchedule atomicSliceSchedule;
142   private final SliceSchedule solventSliceSchedule;
143   private final SliceSchedule bulkSolventSliceSchedule;
144   // Row Scheduling
145   private final SharedIntegerArray optRowWeightAtomic;
146   private final SharedIntegerArray optRowWeightSolvent;
147   private final SharedIntegerArray optRowWeightBulkSolvent;
148   private final int[] previousOptRowWeightAtomic;
149   private final int[] previousOptRowWeightSolvent;
150   private final int[] previousOptRowWeightBulkSolvent;
151   private final RowSchedule atomicRowSchedule;
152   private final RowSchedule solventRowSchedule;
153   private final RowSchedule bulkSolventRowSchedule;
154 
155   private final ParallelTeam parallelTeam;
156   private final Atom[] scatteringAtoms;
157   private final Crystal crystal;
158   private final ReflectionList reflectionList;
159   private final FormFactor[][] atomFormFactors;
160   private final FormFactor[][] solventFormFactors;
161   private final GridMethod gridMethod;
162   private final SharedIntegerArray optAtomicGradientWeight;
163   private final int[] previousOptAtomicGradientWeight;
164   private final GradientSchedule atomicGradientSchedule;
165   /**
166    * Parallelization of putting atomic form factors onto the 3D grid using a 3D spatial
167    * decomposition.
168    */
169   private final SpatialDensityRegion atomicDensityRegion;
170   private final SpatialDensityRegion solventDensityRegion;
171 
172   /**
173    * Parallelization of putting atomic form factors onto the 3D grid using a slice-based
174    * decomposition.
175    */
176   private final SliceRegion atomicSliceRegion;
177   private final SliceRegion solventSliceRegion;
178   private final AtomicSliceLoop[] atomicSliceLoops;
179   private final SolventSliceLoop[] solventSliceLoops;
180   private final BulkSolventSliceLoop[] bulkSolventSliceLoops;
181   /**
182    * Parallelization of putting atomic form factors onto the 3D grid using a row-based
183    * decomposition.
184    */
185   private final RowRegion atomicRowRegion;
186   private final RowRegion solventRowRegion;
187   private final AtomicRowLoop[] atomicRowLoops;
188   private final SolventRowLoop[] solventRowLoops;
189   private final BulkSolventRowLoop[] bulkSolventRowLoops;
190   /**
191    * Parallelization over atomic structure factor loops.
192    */
193   private final InitRegion initRegion;
194   private final ExtractRegion extractRegion;
195   private final AtomicScaleRegion atomicScaleRegion;
196   private final AtomicGradientRegion atomicGradientRegion;
197   /**
198    * Parallelization over solvent structure factor loops.
199    */
200   private final BabinetRegion babinetRegion;
201 
202   private final SolventGridRegion solventGridRegion;
203   private final SolventScaleRegion solventScaleRegion;
204   private final SolventGradientRegion solventGradientRegion;
205   private final Complex3DParallel complexFFT3D;
206   /**
207    * Isotropic b-factor offset added to all b-factors.
208    */
209   private double bAdd;
210   boolean lambdaTerm = false;
211   private final SolventModel solventModel;
212   private final boolean solvent;
213   private double solventA;
214   private double solventB;
215 
216   private boolean useThreeGaussians = true;
217   // Not final for purposes of finite differences
218   private final double[][][] coordinates;
219   private double weight = 1.0;
220   private final int aRadGrid;
221   /**
222    * If the "Native Environment Approximation" is true, the "use" flag is ignored.
223    */
224   private boolean nativeEnvironmentApproximation = false;
225 
226   /**
227    * Crystal Reciprocal Space constructor, assumes this is not a bulk solvent mask and is not a
228    * neutron data set
229    *
230    * @param reflectionList  the {@link ffx.crystal.ReflectionList} to fill with structure factors
231    * @param scatteringAtoms array of {@link ffx.potential.bonded.Atom atoms} for structure factor
232    *                        computation
233    * @param fftTeam         {@link edu.rit.pj.ParallelTeam} for parallelization
234    * @param parallelTeam    {@link edu.rit.pj.ParallelTeam} for parallelization
235    */
236   public CrystalReciprocalSpace(
237       ReflectionList reflectionList,
238       Atom[] scatteringAtoms,
239       ParallelTeam fftTeam,
240       ParallelTeam parallelTeam) {
241     this(
242         reflectionList,
243         scatteringAtoms,
244         fftTeam,
245         parallelTeam,
246         false,
247         false,
248         SolventModel.POLYNOMIAL,
249         GridMethod.SLICE);
250   }
251 
252   /**
253    * Crystal Reciprocal Space constructor, assumes this is not a neutron data set and implements a
254    * polynomial bulk solvent mask if needed
255    *
256    * @param reflectionList  the {@link ffx.crystal.ReflectionList} to fill with structure factors
257    * @param scatteringAtoms array of {@link ffx.potential.bonded.Atom atoms} for structure factor
258    *                        computation
259    * @param fftTeam         {@link edu.rit.pj.ParallelTeam} for parallelization
260    * @param parallelTeam    {@link edu.rit.pj.ParallelTeam} for parallelization
261    * @param solventMask     true if this is a bulk solvent mask
262    */
263   public CrystalReciprocalSpace(
264       ReflectionList reflectionList,
265       Atom[] scatteringAtoms,
266       ParallelTeam fftTeam,
267       ParallelTeam parallelTeam,
268       boolean solventMask) {
269     this(
270         reflectionList,
271         scatteringAtoms,
272         fftTeam,
273         parallelTeam,
274         solventMask,
275         false,
276         SolventModel.POLYNOMIAL,
277         GridMethod.SLICE);
278   }
279 
280   /**
281    * Crystal Reciprocal Space constructor, all parameters provided
282    *
283    * @param reflectionlist  the {@link ffx.crystal.ReflectionList} to fill with structure factors
284    * @param scatteringAtoms array of {@link ffx.potential.bonded.Atom atoms} for structure factor
285    *                        computation
286    * @param fftTeam         {@link edu.rit.pj.ParallelTeam} for parallelization
287    * @param parallelTeam    {@link edu.rit.pj.ParallelTeam} for parallelization
288    * @param solventMask     true if this is a bulk solvent mask
289    * @param neutron         true if this is a neutron structure
290    * @param solventModel    bulk solvent model type
291    * @param gridMethod      parallel method for putting density onto the FFT grid.
292    * @see SolventModel
293    */
294   public CrystalReciprocalSpace(
295       ReflectionList reflectionlist,
296       Atom[] scatteringAtoms,
297       ParallelTeam fftTeam,
298       ParallelTeam parallelTeam,
299       boolean solventMask,
300       boolean neutron,
301       SolventModel solventModel,
302       GridMethod gridMethod) {
303     this.reflectionList = reflectionlist;
304     this.scatteringAtoms = scatteringAtoms;
305     this.parallelTeam = parallelTeam;
306     this.solvent = solventMask;
307     this.neutron = neutron;
308     this.solventModel = solventModel;
309     this.gridMethod = gridMethod;
310 
311     nScatteringAtoms = scatteringAtoms.length;
312     crystal = reflectionlist.crystal;
313     nSymm = 1;
314     threadCount = parallelTeam.getThreadCount();
315 
316     // Necessary for the bulk-solvent expansion!
317     bulkNSymm = crystal.spaceGroup.symOps.size();
318     Resolution resolution = reflectionlist.resolution;
319     double gridFactor = resolution.samplingLimit() / 2.0;
320     double gridStep = resolution.resolutionLimit() * gridFactor;
321     // Set default FFT grid size from unit cell dimensions.
322     int nX = (int) Math.floor(crystal.a / gridStep) + 1;
323     int nY = (int) Math.floor(crystal.b / gridStep) + 1;
324     int nZ = (int) Math.floor(crystal.c / gridStep) + 1;
325     if (nX % 2 != 0) {
326       nX += 1;
327     }
328     // Use preferred dimensions.
329     while (!Complex.preferredDimension(nX)) {
330       nX += 2;
331     }
332     while (!Complex.preferredDimension(nY)) {
333       nY += 1;
334     }
335     while (!Complex.preferredDimension(nZ)) {
336       nZ += 1;
337     }
338 
339     fftX = nX;
340     fftY = nY;
341     fftZ = nZ;
342     halfFFTX = nX / 2;
343     ifftX = 1.0 / (double) fftX;
344     ifftY = 1.0 / (double) fftY;
345     ifftZ = 1.0 / (double) fftZ;
346     fftScale = crystal.volume;
347     complexFFT3DSpace = fftX * fftY * fftZ * 2;
348     densityGrid = new double[complexFFT3DSpace];
349 
350     // Slice Method Variables
351     optSliceWeightAtomic = new SharedIntegerArray(fftZ);
352     optSliceWeightSolvent = new SharedIntegerArray(fftZ);
353     optSliceWeightBulkSolvent = new SharedIntegerArray(fftZ);
354     previousOptSliceWeightAtomic = new int[fftZ];
355     previousOptSliceWeightSolvent = new int[fftZ];
356     previousOptSliceWeightBulkSolvent = new int[fftZ];
357 
358     atomicSliceSchedule = new SliceSchedule(threadCount, fftZ);
359     solventSliceSchedule = new SliceSchedule(threadCount, fftZ);
360     bulkSolventSliceSchedule = new SliceSchedule(threadCount, fftZ);
361 
362     // Row Method Variables
363     optRowWeightAtomic = new SharedIntegerArray(fftZ * fftY);
364     optRowWeightSolvent = new SharedIntegerArray(fftZ * fftY);
365     optRowWeightBulkSolvent = new SharedIntegerArray(fftZ * fftY);
366     previousOptRowWeightAtomic = new int[fftZ * fftY];
367     previousOptRowWeightSolvent = new int[fftZ * fftY];
368     previousOptRowWeightBulkSolvent = new int[fftZ * fftY];
369     atomicRowSchedule = new RowSchedule(threadCount, fftZ, fftY);
370     solventRowSchedule = new RowSchedule(threadCount, fftZ, fftY);
371     bulkSolventRowSchedule = new RowSchedule(threadCount, fftZ, fftY);
372 
373     optAtomicGradientWeight = new SharedIntegerArray(nScatteringAtoms);
374     previousOptAtomicGradientWeight = new int[nScatteringAtoms];
375     atomicGradientSchedule = new GradientSchedule(threadCount, nScatteringAtoms);
376 
377     if (solvent) {
378       bAdd = 0.0;
379       atomFormFactors = null;
380       double vdwr;
381       switch (solventModel) {
382         case BINARY:
383           // The default probe radius and shrink radius values are both 1.0 Angstrom.
384           solventA = 1.0;
385           solventB = 1.0;
386           solventGrid = new double[complexFFT3DSpace];
387           solventFormFactors = new SolventBinaryFormFactor[bulkNSymm][nScatteringAtoms];
388           for (int iSymm = 0; iSymm < bulkNSymm; iSymm++) {
389             for (int i = 0; i < nScatteringAtoms; i++) {
390               vdwr = scatteringAtoms[i].getVDWType().radius * 0.5;
391               solventFormFactors[iSymm][i] = new SolventBinaryFormFactor(scatteringAtoms[i], vdwr + solventA);
392             }
393           }
394           break;
395         case GAUSSIAN:
396           /*
397            * The parameter "solventA" below is the constant exponent "A" in Equation 6 from:
398            * Fenn TD, Schnieders MJ, Brunger AT. A smooth and differentiable bulk-solvent model for macromolecular diffraction.
399            * Acta Crystallogr D. 2010;66(9):1024–31.
400            *
401            * THe default value of 11.5 is from the work of Grant et al.:
402            * Grant JA, Pickup BT, Nicholls A. A smooth permittivity function for Poisson-Boltzmann solvation methods. J Comput Chem. 2001;22(6):608–40.
403            *
404            * The parameter "solventB" scales the atomic radius of each atom. The default value of 0.55 is from Fenn et al.
405            */
406           solventA = 11.5;
407           solventB = 0.55;
408           solventGrid = new double[complexFFT3DSpace];
409           solventFormFactors = new SolventGaussFormFactor[bulkNSymm][nScatteringAtoms];
410           for (int iSymm = 0; iSymm < bulkNSymm; iSymm++) {
411             for (int i = 0; i < nScatteringAtoms; i++) {
412               vdwr = scatteringAtoms[i].getVDWType().radius * 0.5;
413               solventFormFactors[iSymm][i] = new SolventGaussFormFactor(scatteringAtoms[i], vdwr * solventB);
414             }
415           }
416           break;
417         case POLYNOMIAL:
418           /*
419            * The parameter "solventA" below is added to the radius of each atom (e.g., a "probe radius").
420            * The parameter "solventB" is the polynomial window width.
421            *
422            * The default probe radius of 0.0 A and default value of 0.8 A for the window width are from Fenn et al.
423            *
424            * See Equation 12 from:
425            * Fenn TD, Schnieders MJ, Brunger AT. A smooth and differentiable bulk-solvent model for macromolecular diffraction.
426            * Acta Crystallogr D. 2010;66(9):1024–31.
427            */
428           solventA = 0.0;
429           solventB = 0.8;
430           solventGrid = new double[complexFFT3DSpace];
431           solventFormFactors = new SolventPolyFormFactor[bulkNSymm][nScatteringAtoms];
432           for (int iSymm = 0; iSymm < bulkNSymm; iSymm++) {
433             for (int i = 0; i < nScatteringAtoms; i++) {
434               vdwr = scatteringAtoms[i].getVDWType().radius * 0.5;
435               solventFormFactors[iSymm][i] =
436                   new SolventPolyFormFactor(scatteringAtoms[i], vdwr + solventA, solventB);
437             }
438           }
439           break;
440         case NONE:
441         default:
442           solventGrid = null;
443           solventFormFactors = null;
444           break;
445       }
446     } else {
447       bAdd = 2.0;
448       if (neutron) {
449         atomFormFactors = new NeutronFormFactor[bulkNSymm][nScatteringAtoms];
450         for (int iSymm = 0; iSymm < bulkNSymm; iSymm++) {
451           for (int i = 0; i < nScatteringAtoms; i++) {
452             atomFormFactors[iSymm][i] = new NeutronFormFactor(scatteringAtoms[i], bAdd);
453             // logger.info(" SymOp " + iSymm + " " + atomFormFactors[iSymm][i].toString());
454           }
455         }
456       } else {
457         atomFormFactors = new XRayFormFactor[bulkNSymm][nScatteringAtoms];
458         for (int iSymm = 0; iSymm < bulkNSymm; iSymm++) {
459           for (int i = 0; i < nScatteringAtoms; i++) {
460             atomFormFactors[iSymm][i] = new XRayFormFactor(scatteringAtoms[i], useThreeGaussians, bAdd);
461           }
462         }
463       }
464       solventGrid = null;
465       solventFormFactors = null;
466     }
467 
468     // Determine number of grid points to sample density on
469     double aRad = getaRad(scatteringAtoms, solventModel);
470     aRadGrid = (int) Math.floor(aRad * nX / crystal.a) + 1;
471 
472     // local copy of coordinates - note use of bulknsym
473     coordinates = new double[bulkNSymm][3][nScatteringAtoms];
474     List<SymOp> symops = crystal.spaceGroup.symOps;
475     double[] xyz = new double[3];
476     for (int i = 0; i < bulkNSymm; i++) {
477       double[] x = coordinates[i][0];
478       double[] y = coordinates[i][1];
479       double[] z = coordinates[i][2];
480       for (int j = 0; j < nScatteringAtoms; j++) {
481         Atom aj = scatteringAtoms[j];
482         crystal.applySymOp(aj.getXYZ(null), xyz, symops.get(i));
483         x[j] = xyz[0];
484         y[j] = xyz[1];
485         z[j] = xyz[2];
486       }
487     }
488 
489     if (logger.isLoggable(Level.INFO)) {
490       StringBuilder sb = new StringBuilder();
491       if (solvent) {
492         sb.append("  Bulk Solvent Grid\n");
493         sb.append(format("  Bulk solvent model type:           %8s\n", solventModel));
494       } else {
495         sb.append("  Atomic Scattering Grid\n");
496       }
497       sb.append(format("  Form factor grid points:             %8d\n", aRadGrid * 2));
498       sb.append(format("  Form factor grid diameter:           %8.3f\n", aRad * 2));
499       sb.append(format("  Grid density:                        %8.3f\n", 1.0 / gridStep));
500       sb.append(format("  Grid dimensions:                (%3d,%3d,%3d)\n", fftX, fftY, fftZ));
501       sb.append(format("  Grid method:                         %8s\n", gridMethod));
502       logger.info(sb.toString());
503     }
504 
505     if (solvent) {
506       int minWork = nSymm;
507       if (solventModel != SolventModel.NONE) {
508         solventGradientRegion =
509             new SolventGradientRegion(RefinementMode.COORDINATES_AND_BFACTORS_AND_OCCUPANCIES);
510         // Null out scattering instances used without bulk solvent.
511         atomicGradientRegion = null;
512         atomicSliceLoops = null;
513         atomicRowLoops = null;
514         switch (gridMethod) {
515           case SPATIAL:
516             atomicDensityRegion = new SpatialDensityRegion(fftX, fftY, fftZ, densityGrid,
517                 (aRadGrid + 2) * 2, nSymm, minWork, threadCount, crystal,
518                 scatteringAtoms, coordinates);
519             solventDensityRegion = new BulkSolventDensityRegion(fftX, fftY, fftZ, solventGrid,
520                 (aRadGrid + 2) * 2, bulkNSymm, minWork, threadCount, crystal,
521                 scatteringAtoms, coordinates, 4.0, parallelTeam);
522             if (solventModel == SolventModel.GAUSSIAN) {
523               atomicDensityRegion.setInitValue(0.0);
524             } else {
525               atomicDensityRegion.setInitValue(1.0);
526             }
527             SolventDensityLoop[] solventDensityLoops = new SolventDensityLoop[threadCount];
528             SolventDensityLoop[] bulkSolventDensityLoops = new SolventDensityLoop[threadCount];
529             for (int i = 0; i < threadCount; i++) {
530               solventDensityLoops[i] = new SolventDensityLoop(atomicDensityRegion);
531               bulkSolventDensityLoops[i] = new SolventDensityLoop(solventDensityRegion);
532             }
533             atomicDensityRegion.setDensityLoop(solventDensityLoops);
534             solventDensityRegion.setDensityLoop(bulkSolventDensityLoops);
535 
536             atomicSliceRegion = null;
537             solventSliceRegion = null;
538             bulkSolventSliceLoops = null;
539             solventSliceLoops = null;
540 
541             atomicRowRegion = null;
542             solventRowRegion = null;
543             bulkSolventRowLoops = null;
544             solventRowLoops = null;
545             break;
546           case ROW:
547             atomicRowRegion = new RowRegion(fftX, fftY, fftZ, densityGrid, nSymm, threadCount,
548                 scatteringAtoms, coordinates);
549             solventRowRegion = new BulkSolventRowRegion(fftX, fftY, fftZ, solventGrid, bulkNSymm,
550                 threadCount, crystal, scatteringAtoms, coordinates, 4.0, parallelTeam);
551             if (solventModel == SolventModel.GAUSSIAN) {
552               atomicRowRegion.setInitValue(0.0);
553             } else {
554               atomicRowRegion.setInitValue(1.0);
555             }
556             solventRowLoops = new SolventRowLoop[threadCount];
557             bulkSolventRowLoops = new BulkSolventRowLoop[threadCount];
558             for (int i = 0; i < threadCount; i++) {
559               solventRowLoops[i] = new SolventRowLoop(atomicRowRegion);
560               bulkSolventRowLoops[i] = new BulkSolventRowLoop(solventRowRegion);
561             }
562             atomicRowRegion.setDensityLoop(solventRowLoops);
563             solventRowRegion.setDensityLoop(bulkSolventRowLoops);
564             atomicDensityRegion = null;
565             solventDensityRegion = null;
566             atomicSliceRegion = null;
567             solventSliceRegion = null;
568             bulkSolventSliceLoops = null;
569             solventSliceLoops = null;
570             break;
571           case SLICE:
572           default:
573             atomicSliceRegion = new SliceRegion(fftX, fftY, fftZ, densityGrid, nSymm,
574                 threadCount, scatteringAtoms, coordinates);
575             solventSliceRegion = new BulkSolventSliceRegion(fftX, fftY, fftZ, solventGrid,
576                 bulkNSymm, threadCount, crystal, scatteringAtoms, coordinates, 4.0,
577                 parallelTeam);
578             if (solventModel == SolventModel.GAUSSIAN) {
579               atomicSliceRegion.setInitValue(0.0);
580             } else {
581               atomicSliceRegion.setInitValue(1.0);
582             }
583             solventSliceLoops = new SolventSliceLoop[threadCount];
584             bulkSolventSliceLoops = new BulkSolventSliceLoop[threadCount];
585             for (int i = 0; i < threadCount; i++) {
586               solventSliceLoops[i] = new SolventSliceLoop(atomicSliceRegion);
587               bulkSolventSliceLoops[i] = new BulkSolventSliceLoop(solventSliceRegion);
588             }
589             atomicSliceRegion.setDensityLoop(solventSliceLoops);
590             solventSliceRegion.setDensityLoop(bulkSolventSliceLoops);
591 
592             atomicDensityRegion = null;
593             solventDensityRegion = null;
594             atomicRowRegion = null;
595             solventRowRegion = null;
596             bulkSolventRowLoops = null;
597             solventRowLoops = null;
598         }
599       } else {
600         atomicDensityRegion = null;
601         solventDensityRegion = null;
602         atomicGradientRegion = null;
603         solventGradientRegion = null;
604         atomicSliceRegion = null;
605         atomicSliceLoops = null;
606         solventSliceRegion = null;
607         bulkSolventSliceLoops = null;
608         solventSliceLoops = null;
609         atomicRowRegion = null;
610         atomicRowLoops = null;
611         solventRowRegion = null;
612         bulkSolventRowLoops = null;
613         solventRowLoops = null;
614       }
615     } else {
616       atomicGradientRegion =
617           new AtomicGradientRegion(RefinementMode.COORDINATES_AND_BFACTORS_AND_OCCUPANCIES);
618 
619       // Null out bulk solvent instances.
620       solventDensityRegion = null;
621       solventSliceRegion = null;
622       solventSliceLoops = null;
623       bulkSolventSliceLoops = null;
624       solventRowRegion = null;
625       solventRowLoops = null;
626       bulkSolventRowLoops = null;
627       solventGradientRegion = null;
628 
629       /*
630        * Create nSymm pieces of work per thread; the empty pieces will be
631        * removed leaving 1 piece of work per thread.
632        */
633       switch (gridMethod) {
634         case SPATIAL:
635           int minWork = nSymm;
636           atomicDensityRegion =
637               new SpatialDensityRegion(
638                   fftX,
639                   fftY,
640                   fftZ,
641                   densityGrid,
642                   (aRadGrid + 2) * 2,
643                   nSymm,
644                   minWork,
645                   threadCount,
646                   crystal,
647                   scatteringAtoms,
648                   coordinates);
649           atomicDensityRegion.setInitValue(0.0);
650           AtomicDensityLoop[] atomicDensityLoops = new AtomicDensityLoop[threadCount];
651           for (int i = 0; i < threadCount; i++) {
652             atomicDensityLoops[i] = new AtomicDensityLoop(atomicDensityRegion);
653           }
654           atomicDensityRegion.setDensityLoop(atomicDensityLoops);
655           atomicSliceRegion = null;
656           atomicSliceLoops = null;
657           atomicRowRegion = null;
658           atomicRowLoops = null;
659           break;
660 
661         case ROW:
662           atomicRowRegion =
663               new RowRegion(fftX, fftY, fftZ, densityGrid, nSymm, threadCount, scatteringAtoms, coordinates);
664           atomicRowRegion.setInitValue(0.0);
665           atomicRowLoops = new AtomicRowLoop[threadCount];
666           for (int i = 0; i < threadCount; i++) {
667             atomicRowLoops[i] = new AtomicRowLoop(atomicRowRegion);
668           }
669           atomicRowRegion.setDensityLoop(atomicRowLoops);
670           atomicDensityRegion = null;
671           atomicSliceRegion = null;
672           atomicSliceLoops = null;
673           break;
674 
675         case SLICE:
676         default:
677           atomicSliceRegion =
678               new SliceRegion(
679                   fftX, fftY, fftZ, densityGrid, nSymm, threadCount, scatteringAtoms, coordinates);
680           atomicSliceRegion.setInitValue(0.0);
681           atomicSliceLoops = new AtomicSliceLoop[threadCount];
682           for (int i = 0; i < threadCount; i++) {
683             atomicSliceLoops[i] = new AtomicSliceLoop(atomicSliceRegion);
684           }
685           atomicSliceRegion.setDensityLoop(atomicSliceLoops);
686           atomicDensityRegion = null;
687           atomicRowRegion = null;
688           atomicRowLoops = null;
689       }
690     }
691 
692     extractRegion = new ExtractRegion(threadCount);
693     babinetRegion = new BabinetRegion(threadCount);
694     solventGridRegion = new SolventGridRegion(threadCount);
695     initRegion = new InitRegion(threadCount);
696     solventScaleRegion = new SolventScaleRegion(threadCount);
697     atomicScaleRegion = new AtomicScaleRegion(threadCount);
698     complexFFT3D = new Complex3DParallel(fftX, fftY, fftZ, fftTeam);
699   }
700 
701   private double getaRad(Atom[] atoms, SolventModel solventModel) {
702     double aRad = -1.0;
703     for (Atom a : atoms) {
704       double vdwr = a.getVDWType().radius * 0.5;
705       if (!solvent) {
706         aRad = Math.max(aRad, a.getFormFactorWidth());
707       } else {
708         switch (solventModel) {
709           case BINARY:
710             aRad = Math.max(aRad, vdwr + solventA + 0.2);
711             break;
712           case GAUSSIAN:
713             aRad = Math.max(aRad, vdwr * solventB + 2.0);
714             break;
715           case POLYNOMIAL:
716             aRad = Math.max(aRad, vdwr + solventB + 0.2);
717             break;
718           case NONE:
719           default:
720             aRad = 0.0;
721         }
722       }
723     }
724     return aRad;
725   }
726 
727   /**
728    * compute inverse FFT to determine atomic gradients
729    *
730    * @param hklData        structure factors to apply inverse FFT
731    * @param freer          array of free r flags corresponding to hkldata
732    * @param flag           Rfree flag value
733    * @param refinementMode {@link RefinementMode refinement mode}
734    * @see RefinementMode
735    * @see DiffractionRefinementData
736    */
737   public void computeAtomicGradients(
738       double[][] hklData, int[] freer, int flag, RefinementMode refinementMode) {
739     computeAtomicGradients(hklData, freer, flag, refinementMode, false);
740   }
741 
742   /**
743    * parallelized computation of bulk solvent structure factors
744    *
745    * @param hklData structure factor list to fill in
746    * @see DiffractionRefinementData
747    */
748   public void computeSolventDensity(double[][] hklData) {
749     computeSolventDensity(hklData, false);
750   }
751 
752   /**
753    * densityNorm
754    *
755    * @param data   an array of double.
756    * @param meansd an array of double.
757    * @param norm   a boolean.
758    */
759   public void densityNorm(double[] data, double[] meansd, boolean norm) {
760     double mean = 0.0;
761     double sd = 0.0;
762     int n = 0;
763     for (int k = 0; k < fftZ; k++) {
764       for (int j = 0; j < fftY; j++) {
765         for (int i = 0; i < fftX; i++) {
766           int index = interleavedIndex(i, j, k, fftX, fftY);
767           n++;
768           mean += (data[index] - mean) / n;
769         }
770       }
771     }
772 
773     n = 0;
774     for (int k = 0; k < fftZ; k++) {
775       for (int j = 0; j < fftY; j++) {
776         for (int i = 0; i < fftX; i++) {
777           int index = interleavedIndex(i, j, k, fftX, fftY);
778           sd += pow(data[index] - mean, 2);
779           n++;
780         }
781       }
782     }
783     sd = sqrt(sd / n);
784 
785     if (meansd != null) {
786       meansd[0] = mean;
787       meansd[1] = sd;
788     }
789 
790     if (norm) {
791       double isd = 1.0 / sd;
792       for (int k = 0; k < fftZ; k++) {
793         for (int j = 0; j < fftY; j++) {
794           for (int i = 0; i < fftX; i++) {
795             int index = interleavedIndex(i, j, k, fftX, fftY);
796             data[index] = (data[index] - mean) * isd;
797           }
798         }
799       }
800     }
801   }
802 
803   /**
804    * Getter for the field <code>densityGrid</code>.
805    *
806    * @return the densityGrid
807    */
808   public double[] getDensityGrid() {
809     return densityGrid;
810   }
811 
812   /**
813    * return dataset weight
814    *
815    * @return the weight wA
816    */
817   public double getWeight() {
818     return weight;
819   }
820 
821   /**
822    * set the dataset weight
823    *
824    * @param weight desired weight wA
825    */
826   public void setWeight(double weight) {
827     this.weight = weight;
828   }
829 
830   /**
831    * getXDim
832    *
833    * @return a double.
834    */
835   public double getXDim() {
836     return fftX;
837   }
838 
839   /**
840    * getYDim
841    *
842    * @return a double.
843    */
844   public double getYDim() {
845     return fftY;
846   }
847 
848   /**
849    * getZDim
850    *
851    * @return a double.
852    */
853   public double getZDim() {
854     return fftZ;
855   }
856 
857   /**
858    * Update atomic coordinates from references to Atom instances.
859    */
860   public void updateCoordinates() {
861     List<SymOp> symops = crystal.spaceGroup.symOps;
862     double[] xyz = new double[3];
863     double[] symXYZ = new double[3];
864     for (int i = 0; i < nScatteringAtoms; i++) {
865       Atom atom = scatteringAtoms[i];
866       if (atom.isActive()) {
867         atom.getXYZ(xyz);
868         for (int j = 0; j < nSymm; j++) {
869           crystal.applySymOp(xyz, symXYZ, symops.get(j));
870           coordinates[j][0][i] = symXYZ[0];
871           coordinates[j][1][i] = symXYZ[1];
872           coordinates[j][2][i] = symXYZ[2];
873         }
874       }
875     }
876   }
877 
878   /**
879    * Setter for the field <code>nativeEnvironmentApproximation</code>.
880    *
881    * @param nativeEnvironmentApproximation a boolean.
882    */
883   void setNativeEnvironmentApproximation(boolean nativeEnvironmentApproximation) {
884     this.nativeEnvironmentApproximation = nativeEnvironmentApproximation;
885   }
886 
887   /**
888    * Getter for the field <code>solventGrid</code>.
889    *
890    * @return the solventGrid
891    */
892   double[] getSolventGrid() {
893     return solventGrid;
894   }
895 
896   /**
897    * Setter for the field <code>lambdaTerm</code>.
898    *
899    * @param lambdaTerm a boolean.
900    */
901   void setLambdaTerm(boolean lambdaTerm) {
902     this.lambdaTerm = lambdaTerm;
903   }
904 
905   /**
906    * Retrieves the current solvent model being used.
907    *
908    * @return the solvent model instance.
909    */
910   SolventModel getSolventModel() {
911     return solventModel;
912   }
913 
914   /**
915    * Set the default solvent parameters.
916    */
917   void setDefaultSolventAB() {
918     switch (solventModel) {
919       case BINARY ->
920         // The default probe radius and shrink radius values are both 1.0 Angstrom.
921           setSolventAB(1.0, 1.0);
922       case GAUSSIAN ->
923         /*
924          * The parameter "solventA" below is the constant exponent "A" in Equation 6 from:
925          * Fenn TD, Schnieders MJ, Brunger AT. A smooth and differentiable bulk-solvent model for macromolecular diffraction.
926          * Acta Crystallogr D. 2010;66(9):1024–31.
927          *
928          * THe default value of 11.5 is from the work of Grant et al.:
929          * Grant JA, Pickup BT, Nicholls A. A smooth permittivity function for Poisson-Boltzmann solvation methods. J Comput Chem. 2001;22(6):608–40.
930          *
931          * The parameter "solventB" scales the atomic radius of each atom. The default value of 0.55 is from Fenn et al.
932          */
933           setSolventAB(11.5, 0.55);
934       case POLYNOMIAL ->
935         /*
936          * The parameter "solventA" below is added to the radius of each atom (e.g., a "probe radius").
937          * The parameter "solventB" is the polynomial window width.
938          *
939          * The default probe radius of 0.0 A and default value of 0.8 A for the window width are from Fenn et al.
940          *
941          * See Equation 12 from:
942          * Fenn TD, Schnieders MJ, Brunger AT. A smooth and differentiable bulk-solvent model for macromolecular diffraction.
943          * Acta Crystallogr D. 2010;66(9):1024–31.
944          */
945           setSolventAB(0.0, 0.8);
946       default -> {
947         // Do nothing.
948       }
949     }
950   }
951 
952   /**
953    * set the bulk solvent parameters
954    *
955    * @param a added atom width (binary, polynomial only)
956    * @param b falloff of the switch (Gaussian, polynomial only)
957    */
958   void setSolventAB(double a, double b) {
959     double vdwr;
960     this.solventA = a;
961     this.solventB = b;
962     if (!solvent) {
963       return;
964     }
965     switch (solventModel) {
966       case BINARY:
967         for (int iSymm = 0; iSymm < bulkNSymm; iSymm++) {
968           for (int i = 0; i < nScatteringAtoms; i++) {
969             vdwr = scatteringAtoms[i].getVDWType().radius * 0.5;
970             solventFormFactors[iSymm][i] = new SolventBinaryFormFactor(scatteringAtoms[i], vdwr + solventA);
971           }
972         }
973         break;
974       case GAUSSIAN:
975         for (int iSymm = 0; iSymm < bulkNSymm; iSymm++) {
976           for (int i = 0; i < nScatteringAtoms; i++) {
977             vdwr = scatteringAtoms[i].getVDWType().radius * 0.5;
978             solventFormFactors[iSymm][i] = new SolventGaussFormFactor(scatteringAtoms[i], vdwr * solventB);
979           }
980         }
981         break;
982       case POLYNOMIAL:
983         for (int iSymm = 0; iSymm < bulkNSymm; iSymm++) {
984           for (int i = 0; i < nScatteringAtoms; i++) {
985             vdwr = scatteringAtoms[i].getVDWType().radius * 0.5;
986             solventFormFactors[iSymm][i] =
987                 new SolventPolyFormFactor(scatteringAtoms[i], vdwr + solventA, solventB);
988           }
989         }
990         break;
991     }
992   }
993 
994   /**
995    * Retrieves the value of solventA.
996    *
997    * @return the current value of the solventA parameter as a double
998    */
999   double getSolventA() {
1000     return solventA;
1001   }
1002 
1003   /**
1004    * Retrieves the value of the solventB property.
1005    *
1006    * @return the value of solventB as a double
1007    */
1008   double getSolventB() {
1009     return solventB;
1010   }
1011 
1012   /**
1013    * should the structure factor computation use 3 Gaussians or 6 for atoms?
1014    *
1015    * @param useThreeGaussians if true, use 3 Gaussian
1016    */
1017   void setUse3G(boolean useThreeGaussians) {
1018     this.useThreeGaussians = useThreeGaussians;
1019     if (solvent || neutron) {
1020       return;
1021     }
1022     for (int iSymm = 0; iSymm < bulkNSymm; iSymm++) {
1023       for (int i = 0; i < nScatteringAtoms; i++) {
1024         atomFormFactors[iSymm][i] = new XRayFormFactor(scatteringAtoms[i], useThreeGaussians, bAdd);
1025       }
1026     }
1027   }
1028 
1029   /**
1030    * offset X coordinates (mostly for finite difference checks)
1031    *
1032    * @param n     {@link ffx.potential.bonded.Atom} to apply delta to
1033    * @param delta amount to shift atom by
1034    */
1035   void deltaX(int n, double delta) {
1036     List<SymOp> symops = crystal.spaceGroup.symOps;
1037     double[] xyz = new double[3];
1038     double[] symxyz = new double[3];
1039     scatteringAtoms[n].getXYZ(xyz);
1040     xyz[0] += delta;
1041     for (int i = 0; i < nSymm; i++) {
1042       crystal.applySymOp(xyz, symxyz, symops.get(i));
1043       coordinates[i][0][n] = symxyz[0];
1044     }
1045   }
1046 
1047   /**
1048    * offset Y coordinates (mostly for finite difference checks)
1049    *
1050    * @param n     {@link ffx.potential.bonded.Atom} to apply delta to
1051    * @param delta amount to shift atom by
1052    */
1053   void deltaY(int n, double delta) {
1054     List<SymOp> symops = crystal.spaceGroup.symOps;
1055     double[] xyz = new double[3];
1056     double[] symxyz = new double[3];
1057     scatteringAtoms[n].getXYZ(xyz);
1058     xyz[1] += delta;
1059     for (int i = 0; i < nSymm; i++) {
1060       crystal.applySymOp(xyz, symxyz, symops.get(i));
1061       coordinates[i][1][n] = symxyz[1];
1062     }
1063   }
1064 
1065   /**
1066    * offset Z coordinates (mostly for finite difference checks)
1067    *
1068    * @param n     {@link ffx.potential.bonded.Atom} to apply delta to
1069    * @param delta amount to shift atom by
1070    */
1071   void deltaZ(int n, double delta) {
1072     List<SymOp> symops = crystal.spaceGroup.symOps;
1073     double[] xyz = new double[3];
1074     double[] symxyz = new double[3];
1075     scatteringAtoms[n].getXYZ(xyz);
1076     xyz[2] += delta;
1077     for (int i = 0; i < nSymm; i++) {
1078       crystal.applySymOp(xyz, symxyz, symops.get(i));
1079       coordinates[i][2][n] = symxyz[2];
1080     }
1081   }
1082 
1083   /**
1084    * parallelized computation of structure factors
1085    *
1086    * @param hklData structure factor list to fill in
1087    * @see DiffractionRefinementData
1088    */
1089   void computeDensity(double[][] hklData) {
1090     computeDensity(hklData, false);
1091   }
1092 
1093   /**
1094    * parallelized computation of structure factors
1095    *
1096    * @param hklData structure factor list to fill in
1097    * @param print   if true, print information on timings during the calculation
1098    * @see DiffractionRefinementData
1099    */
1100   void computeDensity(double[][] hklData, boolean print) {
1101     if (solvent) {
1102       if (solventModel != SolventModel.NONE) {
1103         computeSolventDensity(hklData, print);
1104       }
1105     } else {
1106       computeAtomicDensity(hklData, print);
1107     }
1108   }
1109 
1110   /**
1111    * compute inverse FFT to determine atomic gradients
1112    *
1113    * @param hklData        structure factors to apply inverse FFT
1114    * @param freer          array of free r flags corresponding to hkldata
1115    * @param flag           Rfree flag value
1116    * @param refinementMode {@link RefinementMode refinement mode}
1117    * @param print          if true, print information on timings during the calculation
1118    * @see RefinementMode
1119    * @see DiffractionRefinementData
1120    */
1121   void computeAtomicGradients(double[][] hklData, int[] freer, int flag,
1122                               RefinementMode refinementMode, boolean print) {
1123 
1124     if (solvent && solventModel == SolventModel.NONE) {
1125       return;
1126     }
1127 
1128     // zero out the density
1129     for (int i = 0; i < complexFFT3DSpace; i++) {
1130       densityGrid[i] = 0.0;
1131     }
1132 
1133     int nfree = 0;
1134     StringBuilder sb = new StringBuilder();
1135     long symtime = -System.nanoTime();
1136     int nsym = crystal.spaceGroup.symOps.size();
1137     // int nsym = 1;
1138     List<SymOp> symops = crystal.spaceGroup.symOps;
1139     ComplexNumber c = new ComplexNumber();
1140     ComplexNumber cj = new ComplexNumber();
1141     HKL ij = new HKL();
1142     for (HKL ih : reflectionList.hklList) {
1143       double[] fc = hklData[ih.getIndex()];
1144       if (Double.isNaN(fc[0])) {
1145         continue;
1146       }
1147 
1148       // cross validation check!!!
1149       if (freer != null) {
1150         if (freer[ih.getIndex()] == flag) {
1151           nfree++;
1152           continue;
1153         }
1154       }
1155 
1156       c.re(fc[0]);
1157       c.im(fc[1]);
1158       // scale
1159       c.timesIP(2.0 / fftScale);
1160 
1161       // apply symmetry
1162       for (int j = 0; j < nsym; j++) {
1163         cj.copy(c);
1164         SymOp symOp = symops.get(j);
1165         applyTransSymRot(ih, ij, symOp);
1166         double shift = symOp.symPhaseShift(ih);
1167 
1168         int h = mod(ij.getH(), fftX);
1169         int k = mod(ij.getK(), fftY);
1170         int l = mod(ij.getL(), fftZ);
1171 
1172         if (h < halfFFTX + 1) {
1173           final int ii = interleavedIndex(h, k, l, fftX, fftY);
1174           cj.phaseShiftIP(shift);
1175           densityGrid[ii] += cj.re();
1176           // Added parentheses below on June 6, 2022.
1177           // TODO: Should there be a "minus"?
1178           densityGrid[ii + 1] += (-cj.im());
1179         } else {
1180           h = (fftX - h) % fftX;
1181           k = (fftY - k) % fftY;
1182           l = (fftZ - l) % fftZ;
1183           final int ii = interleavedIndex(h, k, l, fftX, fftY);
1184           cj.phaseShiftIP(shift);
1185           densityGrid[ii] += cj.re();
1186           densityGrid[ii + 1] += cj.im();
1187         }
1188       }
1189     }
1190     symtime += System.nanoTime();
1191 
1192     long startTime = System.nanoTime();
1193     complexFFT3D.ifft(densityGrid);
1194     long fftTime = System.nanoTime() - startTime;
1195 
1196     /*
1197     CCP4MapWriter mapout = new CCP4MapWriter(fftX, fftY, fftZ, crystal, "/tmp/foo.map");
1198     mapout.write(densityGrid);
1199     */
1200     startTime = System.nanoTime();
1201     long permanentDensityTime = 0;
1202     try {
1203       if (solvent) {
1204         solventGradientRegion.setRefinementMode(refinementMode);
1205         parallelTeam.execute(solventGradientRegion);
1206       } else {
1207         for (int i = 0; i < nScatteringAtoms; i++) {
1208           optAtomicGradientWeight.set(i, 0);
1209         }
1210         atomicGradientSchedule.updateWeights(previousOptAtomicGradientWeight);
1211         atomicGradientRegion.setRefinementMode(refinementMode);
1212         parallelTeam.execute(atomicGradientRegion);
1213         for (int i = 0; i < nScatteringAtoms; i++) {
1214           previousOptAtomicGradientWeight[i] = optAtomicGradientWeight.get(i);
1215         }
1216       }
1217       permanentDensityTime = System.nanoTime() - startTime;
1218     } catch (Exception e) {
1219       String message = "Exception computing atomic gradients.";
1220       logger.log(Level.SEVERE, message, e);
1221     }
1222 
1223     if (solvent) {
1224       sb.append(format(" Solvent Symmetry:            %8.4f\n", symtime * toSeconds));
1225       sb.append(format(" Solvent Inverse FFT:         %8.4f\n", fftTime * toSeconds));
1226       sb.append(format(" Solvent Gradient from Grid:  %8.4f", permanentDensityTime * toSeconds));
1227     } else {
1228       sb.append(format(" Atomic Symmetry:             %8.4f\n", symtime * toSeconds));
1229       sb.append(format(" Atomic Inverse FFT:          %8.4f\n", fftTime * toSeconds));
1230       sb.append(format(" Atomic Gradient from Grid:   %8.4f", permanentDensityTime * toSeconds));
1231     }
1232 
1233     if (logger.isLoggable(Level.INFO) && print) {
1234       logger.info(sb.toString());
1235     }
1236   }
1237 
1238   /**
1239    * parallelized computation of structure factors (identical to compuateDensity)
1240    *
1241    * @param hklData structure factor list to fill in
1242    * @see DiffractionRefinementData
1243    */
1244   void computeAtomicDensity(double[][] hklData) {
1245     computeAtomicDensity(hklData, false);
1246   }
1247 
1248   /**
1249    * parallelized computation of structure factors (identical to computeDensity)
1250    *
1251    * @param hklData structure factor list to fill in
1252    * @param print   if true, print information on timings during the calculation
1253    * @see DiffractionRefinementData
1254    */
1255   private void computeAtomicDensity(double[][] hklData, boolean print) {
1256     // Zero out reflection data.
1257     long initTime = -System.nanoTime();
1258     try {
1259       initRegion.setHKL(hklData);
1260       parallelTeam.execute(initRegion);
1261     } catch (Exception e) {
1262       String message = "Fatal exception zeroing out reflection data.";
1263       logger.log(Level.SEVERE, message, e);
1264     }
1265     initTime += System.nanoTime();
1266 
1267     // Assign atomic electron density to FFT grid.
1268     long atomicGridTime = -System.nanoTime();
1269     try {
1270       switch (gridMethod) {
1271         case SPATIAL:
1272           atomicDensityRegion.assignAtomsToCells();
1273           parallelTeam.execute(atomicDensityRegion);
1274           break;
1275         case ROW:
1276           for (int i = 0; i < fftZ * fftY; i++) {
1277             optRowWeightAtomic.set(i, 0);
1278           }
1279           atomicRowSchedule.updateWeights(previousOptRowWeightAtomic);
1280           parallelTeam.execute(atomicRowRegion);
1281           for (int i = 0; i < fftZ * fftY; i++) {
1282             previousOptRowWeightAtomic[i] = optRowWeightAtomic.get(i);
1283           }
1284           break;
1285         case SLICE:
1286         default:
1287           for (int i = 0; i < fftZ; i++) {
1288             optSliceWeightAtomic.set(i, 0);
1289           }
1290           atomicSliceSchedule.updateWeights(previousOptSliceWeightAtomic);
1291           parallelTeam.execute(atomicSliceRegion);
1292           for (int i = 0; i < fftZ; i++) {
1293             previousOptSliceWeightAtomic[i] = optSliceWeightAtomic.get(i);
1294           }
1295           break;
1296       }
1297     } catch (Exception e) {
1298       String message = "Fatal exception evaluating atomic electron density.";
1299       logger.log(Level.SEVERE, message, e);
1300     }
1301     atomicGridTime += System.nanoTime();
1302 
1303     // Compute model structure factors via an FFT of the electron density.
1304     long startTime = System.nanoTime();
1305     complexFFT3D.fft(densityGrid);
1306     long fftTime = System.nanoTime() - startTime;
1307 
1308     // Extract and scale structure factors.
1309     long symTime = -System.nanoTime();
1310     try {
1311       extractRegion.setHKL(hklData);
1312       parallelTeam.execute(extractRegion);
1313       double scale = (fftScale) / (fftX * fftY * fftZ);
1314       atomicScaleRegion.setHKL(hklData);
1315       atomicScaleRegion.setScale(scale);
1316       parallelTeam.execute(atomicScaleRegion);
1317     } catch (Exception e) {
1318       String message = "Fatal exception extracting structure factors.";
1319       logger.log(Level.SEVERE, message, e);
1320     }
1321     symTime += System.nanoTime();
1322 
1323     if (logger.isLoggable(Level.INFO) && print) {
1324       StringBuilder sb = new StringBuilder();
1325       sb.append(format("\n Atomic Fc Initialization:    %8.4f\n", initTime * toSeconds));
1326       sb.append(format(" Atomic Grid Density:         %8.4f\n", atomicGridTime * toSeconds));
1327       sb.append(format(" Atomic FFT:                  %8.4f\n", fftTime * toSeconds));
1328       sb.append(format(" Atomic Symmetry & Scaling:   %8.4f", symTime * toSeconds));
1329       logger.info(sb.toString());
1330 
1331       StringBuilder ASB = new StringBuilder();
1332       switch (gridMethod) {
1333         case ROW:
1334           double atomicRowTotal = 0;
1335           double atomicRowMin = atomicRowLoops[0].getThreadTime();
1336           double atomicRowMax = 0;
1337           double atomicRowWeightTotal = 0;
1338 
1339           for (int i = 0; i < threadCount; i++) {
1340             atomicRowTotal += atomicRowLoops[i].getThreadTime();
1341             atomicRowMax = max(atomicRowLoops[i].getThreadTime(), atomicRowMax);
1342             atomicRowMin = min(atomicRowLoops[i].getThreadTime(), atomicRowMin);
1343             atomicRowWeightTotal += atomicRowLoops[i].getThreadWeight();
1344           }
1345 
1346           // Atomic timing and balance analysis
1347           ASB.append(format("\n RowLoop (Atomic): %7.5f (sec)                 | Total Weight: %7.0f\n",
1348               atomicGridTime * toSeconds, atomicRowWeightTotal));
1349           ASB.append(" Thread     LoopTime    Balance(%)  Normalized   |      Rows       Weight    Balance(%)  Normalized\n");
1350 
1351           // check for no weights issued, then set to 1 so a 0 is printed instead of NaN
1352           if (atomicRowWeightTotal == 0) {
1353             atomicRowWeightTotal = 1;
1354           }
1355 
1356           for (int i = 0; i < threadCount; i++) {
1357             ASB.append(
1358                 format(
1359                     "     %3d     %7.5f     %7.1f     %7.1f     |   %7d     %7d     %7.1f     %7.1f\n",
1360                     i, atomicRowLoops[i].getThreadTime() * toSeconds,
1361                     ((atomicRowLoops[i].getThreadTime()) / (atomicRowTotal)) * 100.00,
1362                     ((atomicRowLoops[i].getThreadTime()) / (atomicRowTotal)) * (100.00
1363                         * threadCount),
1364                     atomicRowLoops[i].getNumberofSlices(),
1365                     atomicRowLoops[i].getThreadWeight(),
1366                     100.00 * (atomicRowLoops[i].getThreadWeight() / atomicRowWeightTotal),
1367                     (100.00 * threadCount) * (atomicRowLoops[i].getThreadWeight()
1368                         / atomicRowWeightTotal)));
1369           }
1370           ASB.append(format("    Min      %7.5f\n", atomicRowMin * toSeconds));
1371           ASB.append(format("    Max      %7.5f\n", atomicRowMax * toSeconds));
1372           ASB.append(format("    Delta    %7.5f\n", (atomicRowMax - atomicRowMin) * toSeconds));
1373           logger.info(ASB.toString());
1374 
1375           break;
1376         case SPATIAL:
1377           break;
1378         case SLICE:
1379           double atomicSliceTotal = 0;
1380           double atomicSliceMin = atomicSliceLoops[0].getThreadTime();
1381           double atomicSliceMax = 0;
1382           double atomicSliceWeightTotal = 0;
1383 
1384           for (int i = 0; i < threadCount; i++) {
1385             atomicSliceTotal += atomicSliceLoops[i].getThreadTime();
1386             atomicSliceMax = max(atomicSliceLoops[i].getThreadTime(), atomicSliceMax);
1387             atomicSliceMin = min(atomicSliceLoops[i].getThreadTime(), atomicSliceMin);
1388             atomicSliceWeightTotal += atomicSliceLoops[i].getThreadWeight();
1389           }
1390 
1391           // Atomic timing and balance analysis
1392           ASB.append(format("\n SliceLoop (Atomic): %7.5f (sec)               | Total Weight: %7.0f\n",
1393               atomicGridTime * toSeconds, atomicSliceWeightTotal));
1394           ASB.append(" Thread     LoopTime    Balance(%)  Normalized   |      Slices    Weight    Balance(%)  Normalized\n");
1395 
1396           // check for no weights issued, then set to 1 so a 0 is printed instead of NaN
1397           if (atomicSliceWeightTotal == 0) {
1398             atomicSliceWeightTotal = 1;
1399           }
1400 
1401           for (int i = 0; i < threadCount; i++) {
1402             ASB.append(
1403                 format(
1404                     "     %3d     %7.5f     %7.1f     %7.1f     |   %7d     %7d     %7.1f     %7.1f\n",
1405                     i,
1406                     atomicSliceLoops[i].getThreadTime() * toSeconds,
1407                     ((atomicSliceLoops[i].getThreadTime()) / (atomicSliceTotal)) * 100.00,
1408                     ((atomicSliceLoops[i].getThreadTime()) / (atomicSliceTotal)) * (100.00
1409                         * threadCount),
1410                     atomicSliceLoops[i].getNumberofSlices(),
1411                     atomicSliceLoops[i].getThreadWeight(),
1412                     100.00 * (atomicSliceLoops[i].getThreadWeight() / atomicSliceWeightTotal),
1413                     (100.00 * threadCount) * (atomicSliceLoops[i].getThreadWeight()
1414                         / atomicSliceWeightTotal)));
1415           }
1416           ASB.append(format("    Min      %7.5f\n", atomicSliceMin * toSeconds));
1417           ASB.append(format("    Max      %7.5f\n", atomicSliceMax * toSeconds));
1418           ASB.append(format("    Delta    %7.5f\n", (atomicSliceMax - atomicSliceMin) * toSeconds));
1419           logger.info(ASB.toString());
1420           break;
1421         default:
1422       }
1423     }
1424   }
1425 
1426   /**
1427    * parallelized computation of bulk solvent structure factors
1428    *
1429    * @param hklData structure factor list to fill in
1430    * @param print   if true, print information on timings during the calculation
1431    * @see DiffractionRefinementData
1432    */
1433   private void computeSolventDensity(double[][] hklData, boolean print) {
1434     // Zero out the reflection data.
1435     long initTime = -System.nanoTime();
1436     try {
1437       initRegion.setHKL(hklData);
1438       parallelTeam.execute(initRegion);
1439     } catch (Exception e) {
1440       String message = "Fatal exception initializing structure factors.";
1441       logger.log(Level.SEVERE, message, e);
1442     }
1443     initTime += System.nanoTime();
1444 
1445     long solventGridTime = -System.nanoTime();
1446     try {
1447       switch (gridMethod) {
1448         case SPATIAL:
1449           atomicDensityRegion.assignAtomsToCells();
1450           parallelTeam.execute(atomicDensityRegion);
1451           break;
1452         case ROW:
1453           for (int i = 0; i < fftZ * fftY; i++) {
1454             optRowWeightSolvent.set(i, 0);
1455           }
1456           solventRowSchedule.updateWeights(previousOptRowWeightSolvent);
1457           parallelTeam.execute(atomicRowRegion);
1458           for (int i = 0; i < fftZ * fftY; i++) {
1459             previousOptRowWeightSolvent[i] = optRowWeightSolvent.get(i);
1460           }
1461           break;
1462         case SLICE:
1463         default:
1464           for (int i = 0; i < fftZ; i++) {
1465             optSliceWeightSolvent.set(i, 0);
1466           }
1467           solventSliceSchedule.updateWeights(previousOptSliceWeightSolvent);
1468           parallelTeam.execute(atomicSliceRegion);
1469           for (int i = 0; i < fftZ; i++) {
1470             previousOptSliceWeightSolvent[i] = optSliceWeightSolvent.get(i);
1471           }
1472           break;
1473       }
1474     } catch (Exception e) {
1475       String message = "Fatal exception evaluating solvent electron density.";
1476       logger.log(Level.SEVERE, message, e);
1477     }
1478     solventGridTime += System.nanoTime();
1479 
1480     long expTime = -System.nanoTime();
1481     if (solventModel == SolventModel.BINARY) {
1482       /*
1483        * Need to shrink mask. First, copy densityGrid to solventGrid
1484        * temporarily to store original map.
1485        */
1486       int nmap = densityGrid.length;
1487       arraycopy(densityGrid, 0, solventGrid, 0, nmap);
1488       // Logic to loop within the cutoff box.
1489       double[] xyz = {solventB, solventB, solventB};
1490       double[] uvw = new double[3];
1491       crystal.toFractionalCoordinates(xyz, uvw);
1492       final double frx = fftX * uvw[0];
1493       final int ifrx = (int) frx;
1494       final double fry = fftY * uvw[1];
1495       final int ifry = (int) fry;
1496       final double frz = fftZ * uvw[2];
1497       final int ifrz = (int) frz;
1498       for (int k = 0; k < fftZ; k++) {
1499         for (int j = 0; j < fftY; j++) {
1500           for (int i = 0; i < fftX; i++) {
1501             final int ii = interleavedIndex(i, j, k, fftX, fftY);
1502             if (densityGrid[ii] == 1.0) {
1503               continue;
1504             }
1505             outerLoop:
1506             for (int iz = k - ifrz; iz <= k + ifrz; iz++) {
1507               int giz = mod(iz, fftZ);
1508               for (int iy = j - ifry; iy <= j + ifry; iy++) {
1509                 int giy = mod(iy, fftY);
1510                 for (int ix = i - ifrx; ix <= i + ifrx; ix++) {
1511                   int gix = mod(ix, fftX);
1512                   final int jj = interleavedIndex(gix, giy, giz, fftX, fftY);
1513                   if (densityGrid[jj] == 1.0) {
1514                     solventGrid[ii] = 1.0;
1515                     break outerLoop;
1516                   }
1517                 }
1518               }
1519             }
1520           }
1521         }
1522       }
1523 
1524       // Copy the completed solvent grid back.
1525       arraycopy(solventGrid, 0, densityGrid, 0, nmap);
1526     }
1527 
1528     // Copy the grid over for derivatives.
1529     ArrayCopyRegion arrayCopyRegion = new ArrayCopyRegion();
1530     try {
1531       parallelTeam.execute(arrayCopyRegion);
1532     } catch (Exception e) {
1533       logger.info(e.toString());
1534     }
1535 
1536     // Babinet Principle.
1537     try {
1538       parallelTeam.execute(babinetRegion);
1539     } catch (Exception e) {
1540       String message = "Fatal exception Babinet Principle.";
1541       logger.log(Level.SEVERE, message, e);
1542     }
1543     expTime += System.nanoTime();
1544 
1545     long fftTime = -System.nanoTime();
1546     complexFFT3D.fft(densityGrid);
1547     fftTime += System.nanoTime();
1548 
1549     // Extract and scale structure factors.
1550     long symTime = -System.nanoTime();
1551     try {
1552       extractRegion.setHKL(hklData);
1553       parallelTeam.execute(extractRegion);
1554       final double scale = (fftScale) / (fftX * fftY * fftZ);
1555       solventScaleRegion.setHKL(hklData);
1556       solventScaleRegion.setScale(scale);
1557       parallelTeam.execute(solventScaleRegion);
1558     } catch (Exception e) {
1559       String message = "Fatal exception extracting structure factors.";
1560       logger.log(Level.SEVERE, message, e);
1561     }
1562     symTime += System.nanoTime();
1563 
1564     // Expand derivative mask so it includes nearby local density.
1565     long solventDensityTime = -System.nanoTime();
1566     try {
1567       switch (gridMethod) {
1568         case SPATIAL:
1569           solventDensityRegion.assignAtomsToCells();
1570           parallelTeam.execute(solventDensityRegion);
1571           break;
1572 
1573         case ROW:
1574           for (int i = 0; i < (fftZ * fftY); i++) {
1575             optRowWeightBulkSolvent.set(i, 0);
1576           }
1577           bulkSolventRowSchedule.updateWeights(previousOptRowWeightBulkSolvent);
1578           parallelTeam.execute(solventRowRegion);
1579           for (int i = 0; i < fftZ * fftY; i++) {
1580             previousOptRowWeightBulkSolvent[i] = optRowWeightBulkSolvent.get(i);
1581           }
1582           break;
1583         case SLICE:
1584         default:
1585           for (int i = 0; i < (fftZ); i++) {
1586             optSliceWeightBulkSolvent.set(i, 0);
1587           }
1588 
1589           bulkSolventSliceSchedule.updateWeights(previousOptSliceWeightBulkSolvent);
1590           parallelTeam.execute(solventSliceRegion);
1591           for (int i = 0; i < fftZ; i++) {
1592             previousOptSliceWeightBulkSolvent[i] = optSliceWeightBulkSolvent.get(i);
1593           }
1594           break;
1595       }
1596     } catch (Exception e) {
1597       String message = "Fatal exception evaluating solvent electron density.";
1598       logger.log(Level.SEVERE, message, e);
1599     }
1600     solventDensityTime += System.nanoTime();
1601 
1602     long solventExpTime = 0;
1603     if (solventModel == SolventModel.GAUSSIAN) {
1604       solventExpTime = -System.nanoTime();
1605       try {
1606         parallelTeam.execute(solventGridRegion);
1607       } catch (Exception e) {
1608         String message = "Fatal exception solvent grid region.";
1609         logger.log(Level.SEVERE, message, e);
1610       }
1611       solventExpTime += System.nanoTime();
1612     }
1613 
1614     if (logger.isLoggable(Level.INFO) && print) {
1615       StringBuilder sb = new StringBuilder();
1616       sb.append(format(" Solvent Fc Initialization:   %8.4f\n", initTime * toSeconds));
1617       sb.append(format(" Solvent Grid Density:        %8.4f\n", solventGridTime * toSeconds));
1618       sb.append(format(" Bulk Solvent Exponentiation: %8.4f\n", expTime * toSeconds));
1619       sb.append(format(" Solvent FFT:                 %8.4f\n", fftTime * toSeconds));
1620       sb.append(format(" Solvent Symmetry & Scaling:  %8.4f\n", symTime * toSeconds));
1621       sb.append(format(" Solvent Grid Expansion:      %8.4f", solventDensityTime * toSeconds));
1622 
1623       if (solventModel == SolventModel.GAUSSIAN) {
1624         sb.append(format("\n Gaussian grid exponentiation:%8.4f", solventExpTime * toSeconds));
1625       }
1626       logger.info(sb.toString());
1627       StringBuilder solventSB = new StringBuilder();
1628       StringBuilder bulkSB = new StringBuilder();
1629       switch (gridMethod) {
1630         case ROW:
1631           double solventRowTotal = 0;
1632           double solventRowMin = solventRowLoops[0].getThreadTime();
1633           double solventRowMax = 0;
1634           double solventRowWeightTotal = 0;
1635 
1636           double bulkSolventRowTotal = 0;
1637           double bulkSolventRowMin = bulkSolventRowLoops[0].getTimePerThread();
1638           double bulkSolventRowMax = 0;
1639           double bulkSolventRowWeightTotal = 0;
1640 
1641           for (int i = 0; i < threadCount; i++) {
1642             solventRowTotal += solventRowLoops[i].getThreadTime();
1643             solventRowMax = max(solventRowLoops[i].getThreadTime(), solventRowMax);
1644             solventRowMin = min(solventRowLoops[i].getThreadTime(), solventRowMin);
1645             solventRowWeightTotal += solventRowLoops[i].getThreadWeight();
1646 
1647             bulkSolventRowTotal += bulkSolventRowLoops[i].getTimePerThread();
1648             bulkSolventRowMax = max(bulkSolventRowLoops[i].getTimePerThread(), bulkSolventRowMax);
1649             bulkSolventRowMin = min(bulkSolventRowLoops[i].getTimePerThread(), bulkSolventRowMin);
1650             bulkSolventRowWeightTotal += bulkSolventRowLoops[i].getWeightPerThread()[i];
1651           }
1652 
1653           // Solvent timing and balance analysis
1654           solventSB.append(format("\n RowLoop (Solvent): %7.5f (sec)                | Total Weight: %7.0f\n",
1655               solventGridTime * toSeconds, solventRowWeightTotal));
1656           solventSB.append(" Thread     LoopTime    Balance(%)  Normalized   |      Rows       Weight     Balance(%)  Normalized\n");
1657 
1658           // Check for no weights issued, then set to 1 so a 0 is printed instead of NaN
1659           if (solventRowWeightTotal == 0) {
1660             solventRowWeightTotal = 1;
1661           }
1662 
1663           for (int i = 0; i < threadCount; i++) {
1664             solventSB.append(
1665                 format(
1666                     "     %3d     %7.5f     %7.1f     %7.1f     |   %7d     %7d     %7.1f     %7.1f\n",
1667                     i, solventRowLoops[i].getThreadTime() * toSeconds,
1668                     ((solventRowLoops[i].getThreadTime()) / (solventRowTotal)) * 100.00,
1669                     ((solventRowLoops[i].getThreadTime()) / (solventRowTotal))
1670                         * (100.00 * threadCount),
1671                     solventRowLoops[i].getNumberofSlices(),
1672                     solventRowLoops[i].getThreadWeight(),
1673                     100.00 * (solventRowLoops[i].getThreadWeight() / solventRowWeightTotal),
1674                     (100.00 * threadCount)
1675                         * (solventRowLoops[i].getThreadWeight() / solventRowWeightTotal)));
1676           }
1677           solventSB.append(format("    Min      %7.5f\n", solventRowMin * toSeconds));
1678           solventSB.append(format("    Max      %7.5f\n", solventRowMax * toSeconds));
1679           solventSB.append(format("    Delta    %7.5f\n", (solventRowMax - solventRowMin) * toSeconds));
1680           logger.info(solventSB.toString());
1681 
1682           // Bulk solvent timing and balance analysis
1683           bulkSB.append(format("\n RowLoop (Bulk Solvent): %7.5f (sec)           | Total Weight: %7.0f\n",
1684               bulkSolventRowTotal * toSeconds, bulkSolventRowWeightTotal));
1685           bulkSB.append(" Thread     LoopTime    Balance(%)  Normalized   |      Rows       Weight     Balance(%)  Normalized\n");
1686 
1687           // check for no weights issued, then set to 1 so a 0 is printed instead of NaN
1688           if (bulkSolventRowWeightTotal == 0) {
1689             bulkSolventRowWeightTotal = 1;
1690           }
1691 
1692           for (int i = 0; i < threadCount; i++) {
1693             bulkSB.append(
1694                 format(
1695                     "     %3d     %7.5f     %7.1f     %7.1f     |   %7d     %7d     %7.1f     %7.1f\n",
1696                     i, bulkSolventRowLoops[i].getTimePerThread() * toSeconds,
1697                     ((bulkSolventRowLoops[i].getTimePerThread()) / (bulkSolventRowTotal)) * 100.00,
1698                     ((bulkSolventRowLoops[i].getTimePerThread()) / (bulkSolventRowTotal))
1699                         * (100.00 * threadCount),
1700                     bulkSolventRowLoops[i].getBoundsPerThread()[i],
1701                     bulkSolventRowLoops[i].getWeightPerThread()[i],
1702                     100.00
1703                         * (bulkSolventRowLoops[i].getWeightPerThread()[i]
1704                         / bulkSolventRowWeightTotal),
1705                     (100.00 * threadCount)
1706                         * (bulkSolventRowLoops[i].getWeightPerThread()[i]
1707                         / bulkSolventRowWeightTotal)));
1708           }
1709           bulkSB.append(format("    Min      %7.5f\n", bulkSolventRowMin * toSeconds));
1710           bulkSB.append(format("    Max      %7.5f\n", bulkSolventRowMax * toSeconds));
1711           bulkSB.append(format("    Delta    %7.5f\n", (bulkSolventRowMax - bulkSolventRowMin) * toSeconds));
1712           logger.info(bulkSB.toString());
1713           break;
1714         case SPATIAL:
1715           break;
1716         case SLICE:
1717           double solventSliceTotal = 0;
1718           double solventSliceMin = solventSliceLoops[0].getThreadTime();
1719           double solventSliceMax = 0;
1720           double solventSliceWeightTotal = 0;
1721 
1722           double bulkSolventSliceTotal = 0;
1723           double bulkSolventSliceMin = bulkSolventSliceLoops[0].getTimePerThread();
1724           double bulkSolventSliceMax = 0;
1725           double bulkSolventSliceWeightTotal = 0;
1726 
1727           for (int i = 0; i < threadCount; i++) {
1728             solventSliceTotal += solventSliceLoops[i].getThreadTime();
1729             solventSliceMax = max(solventSliceLoops[i].getThreadTime(), solventSliceMax);
1730             solventSliceMin = min(solventSliceLoops[i].getThreadTime(), solventSliceMin);
1731             solventSliceWeightTotal += solventSliceLoops[i].getThreadWeight();
1732 
1733             bulkSolventSliceTotal += bulkSolventSliceLoops[i].getTimePerThread();
1734             bulkSolventSliceMax =
1735                 max(bulkSolventSliceLoops[i].getTimePerThread(), bulkSolventSliceMax);
1736             bulkSolventSliceMin =
1737                 min(bulkSolventSliceLoops[i].getTimePerThread(), bulkSolventSliceMin);
1738             bulkSolventSliceWeightTotal += bulkSolventSliceLoops[i].getWeightPerThread()[i];
1739           }
1740 
1741           // Solvent timing and balance analysis
1742           solventSB.append(format("\n SliceLoop (Solvent): %7.5f (sec)              | Total Weight: %7.0f\n",
1743               solventGridTime * toSeconds, solventSliceWeightTotal));
1744           solventSB.append(" Thread     LoopTime    Balance(%)  Normalized   |      Slices    Weight     Balance(%)  Normalized\n");
1745 
1746           // Check for no weights issued, then set to 1 so a 0 is printed instead of NaN
1747           if (solventSliceWeightTotal == 0) {
1748             solventSliceWeightTotal = 1;
1749           }
1750 
1751           for (int i = 0; i < threadCount; i++) {
1752             solventSB.append(
1753                 format(
1754                     "     %3d     %7.5f     %7.1f     %7.1f     |   %7d     %7d     %7.1f     %7.1f\n",
1755                     i, solventSliceLoops[i].getThreadTime() * toSeconds,
1756                     ((solventSliceLoops[i].getThreadTime()) / (solventSliceTotal)) * 100.00,
1757                     ((solventSliceLoops[i].getThreadTime()) / (solventSliceTotal))
1758                         * (100.00 * threadCount),
1759                     solventSliceLoops[i].getNumberofSlices(),
1760                     solventSliceLoops[i].getThreadWeight(),
1761                     100.00 * (solventSliceLoops[i].getThreadWeight() / solventSliceWeightTotal),
1762                     (100.00 * threadCount)
1763                         * (solventSliceLoops[i].getThreadWeight() / solventSliceWeightTotal)));
1764           }
1765           solventSB.append(format("    Min      %7.5f\n", solventSliceMin * toSeconds));
1766           solventSB.append(format("    Max      %7.5f\n", solventSliceMax * toSeconds));
1767           solventSB.append(format("    Delta    %7.5f\n", (solventSliceMax - solventSliceMin) * toSeconds));
1768           logger.info(solventSB.toString());
1769 
1770           // Bulk solvent timing and balance analysis
1771           bulkSB.append(format(" SliceLoop (Bulk Solvent): %7.5f (sec)         | Total Weight: %7.0f\n",
1772               bulkSolventSliceTotal * toSeconds, bulkSolventSliceWeightTotal));
1773           bulkSB.append(" Thread     LoopTime    Balance(%)  Normalized   |      Slices     Weight     Balance(%)  Normalized\n");
1774 
1775           // Check for no weights issued, then set to 1 so a 0 is printed instead of NaN
1776           if (bulkSolventSliceWeightTotal == 0) {
1777             bulkSolventSliceWeightTotal = 1;
1778           }
1779 
1780           for (int i = 0; i < threadCount; i++) {
1781             bulkSB.append(
1782                 format(
1783                     "     %3d     %7.5f     %7.1f     %7.1f     |   %7d     %7d     %7.1f     %7.1f\n",
1784                     i, bulkSolventSliceLoops[i].getTimePerThread() * toSeconds,
1785                     ((bulkSolventSliceLoops[i].getTimePerThread()) / (bulkSolventSliceTotal))
1786                         * 100.00,
1787                     ((bulkSolventSliceLoops[i].getTimePerThread()) / (bulkSolventSliceTotal))
1788                         * (100.00 * threadCount),
1789                     bulkSolventSliceLoops[i].getBoundsPerThread()[i],
1790                     bulkSolventSliceLoops[i].getWeightPerThread()[i],
1791                     100.00
1792                         * (bulkSolventSliceLoops[i].getWeightPerThread()[i]
1793                         / bulkSolventSliceWeightTotal),
1794                     (100.00 * threadCount)
1795                         * (bulkSolventSliceLoops[i].getWeightPerThread()[i]
1796                         / bulkSolventSliceWeightTotal)));
1797           }
1798           bulkSB.append(format("    Min      %7.5f\n", bulkSolventSliceMin * toSeconds));
1799           bulkSB.append(format("    Max      %7.5f\n", bulkSolventSliceMax * toSeconds));
1800           bulkSB.append(format("    Delta    %7.5f\n", (bulkSolventSliceMax - bulkSolventSliceMin) * toSeconds));
1801           logger.info(bulkSB.toString());
1802           break;
1803         default:
1804       }
1805     }
1806   }
1807 
1808   private class AtomicDensityLoop extends SpatialDensityLoop {
1809 
1810     final double[] xyz = new double[3];
1811     final double[] uvw = new double[3];
1812     final double[] xc = new double[3];
1813     final double[] xf = new double[3];
1814     final double[] grid;
1815 
1816     AtomicDensityLoop(SpatialDensityRegion region) {
1817       super(region, region.getNsymm(), region.actualCount);
1818       grid = region.getGrid();
1819     }
1820 
1821     @Override
1822     public void gridDensity(int iSymm, int n) {
1823       if (!scatteringAtoms[n].getUse() && !nativeEnvironmentApproximation) {
1824         return;
1825       }
1826 
1827       if (lambdaTerm && scatteringAtoms[n].applyLambda()) {
1828         return;
1829       }
1830 
1831       xyz[0] = coordinates[iSymm][0][n];
1832       xyz[1] = coordinates[iSymm][1][n];
1833       xyz[2] = coordinates[iSymm][2][n];
1834       FormFactor atomff = atomFormFactors[iSymm][n];
1835       crystal.toFractionalCoordinates(xyz, uvw);
1836       final int frad = Math.min(
1837           aRadGrid, (int) Math.floor(scatteringAtoms[n].getFormFactorWidth() * fftX / crystal.a) + 1);
1838 
1839       // Logic to loop within the cutoff box.
1840       final double frx = fftX * uvw[0];
1841       final int ifrx = (int) frx;
1842       final int ifrxu = ifrx + frad;
1843 
1844       final double fry = fftY * uvw[1];
1845       final int ifry = (int) fry;
1846       final int ifryu = ifry + frad;
1847 
1848       final double frz = fftZ * uvw[2];
1849       final int ifrz = (int) frz;
1850       final int ifrzu = ifrz + frad;
1851 
1852       for (int iz = ifrz - frad; iz <= ifrzu; iz++) {
1853         int giz = mod(iz, fftZ);
1854         xf[2] = iz * ifftZ;
1855         for (int iy = ifry - frad; iy <= ifryu; iy++) {
1856           int giy = mod(iy, fftY);
1857           xf[1] = iy * ifftY;
1858           for (int ix = ifrx - frad; ix <= ifrxu; ix++) {
1859             int gix = mod(ix, fftX);
1860             xf[0] = ix * ifftX;
1861             crystal.toCartesianCoordinates(xf, xc);
1862             final int ii = interleavedIndex(gix, giy, giz, fftX, fftY);
1863             grid[ii] = atomff.rho(grid[ii], 1.0, xc);
1864           }
1865         }
1866       }
1867     }
1868   }
1869 
1870   private class SolventDensityLoop extends SpatialDensityLoop {
1871 
1872     final double[] xyz = new double[3];
1873     final double[] uvw = new double[3];
1874     final double[] xc = new double[3];
1875     final double[] xf = new double[3];
1876     final double[] grid;
1877 
1878     SolventDensityLoop(SpatialDensityRegion region) {
1879       super(region, region.getNsymm(), region.actualCount);
1880       grid = region.getGrid();
1881     }
1882 
1883     @Override
1884     public void gridDensity(int iSymm, int n) {
1885       if (!scatteringAtoms[n].getUse() && !nativeEnvironmentApproximation) {
1886         return;
1887       }
1888 
1889       if (lambdaTerm && scatteringAtoms[n].applyLambda()) {
1890         return;
1891       }
1892 
1893       xyz[0] = coordinates[iSymm][0][n];
1894       xyz[1] = coordinates[iSymm][1][n];
1895       xyz[2] = coordinates[iSymm][2][n];
1896       FormFactor solventff = solventFormFactors[iSymm][n];
1897       crystal.toFractionalCoordinates(xyz, uvw);
1898       double vdwr = scatteringAtoms[n].getVDWType().radius * 0.5;
1899       int frad = aRadGrid;
1900       switch (solventModel) {
1901         case BINARY:
1902           frad =
1903               Math.min(aRadGrid, (int) Math.floor((vdwr + solventA + 0.2) * fftX / crystal.a) + 1);
1904           break;
1905         case GAUSSIAN:
1906           frad =
1907               Math.min(aRadGrid, (int) Math.floor((vdwr * solventB + 2.0) * fftX / crystal.a) + 1);
1908           break;
1909         case POLYNOMIAL:
1910           frad =
1911               Math.min(aRadGrid, (int) Math.floor((vdwr + solventB + 0.2) * fftX / crystal.a) + 1);
1912           break;
1913         case NONE:
1914         default:
1915           return;
1916       }
1917 
1918       // Logic to loop within the cutoff box.
1919       final double frx = fftX * uvw[0];
1920       final int ifrx = (int) frx;
1921       final int ifrxu = ifrx + frad;
1922 
1923       final double fry = fftY * uvw[1];
1924       final int ifry = (int) fry;
1925       final int ifryu = ifry + frad;
1926 
1927       final double frz = fftZ * uvw[2];
1928       final int ifrz = (int) frz;
1929       final int ifrzu = ifrz + frad;
1930 
1931       for (int iz = ifrz - frad; iz <= ifrzu; iz++) {
1932         int giz = mod(iz, fftZ);
1933         xf[2] = iz * ifftZ;
1934         for (int iy = ifry - frad; iy <= ifryu; iy++) {
1935           int giy = mod(iy, fftY);
1936           xf[1] = iy * ifftY;
1937           for (int ix = ifrx - frad; ix <= ifrxu; ix++) {
1938             int gix = mod(ix, fftX);
1939             xf[0] = ix * ifftX;
1940             crystal.toCartesianCoordinates(xf, xc);
1941             final int ii = interleavedIndex(gix, giy, giz, fftX, fftY);
1942             grid[ii] = solventff.rho(grid[ii], 1.0, xc);
1943           }
1944         }
1945       }
1946     }
1947   }
1948 
1949   private class AtomicRowLoop extends RowLoop {
1950 
1951     final double[] xyz = new double[3];
1952     final double[] uvw = new double[3];
1953     final double[] xc = new double[3];
1954     final double[] xf = new double[3];
1955     final double[] grid;
1956     final int[] optLocal;
1957     long timer;
1958     int previousUB, previousLB;
1959     int actualWeight;
1960 
1961     AtomicRowLoop(RowRegion region) {
1962       super(region.getNatoms(), region.getNsymm(), region);
1963       grid = region.getGrid();
1964       optLocal = new int[fftZ * fftY];
1965     }
1966 
1967     public void buildList(int iSymm, int iAtom, int lb, int ub) {
1968       if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
1969         return;
1970       }
1971       xyz[0] = coordinates[iSymm][0][iAtom];
1972       xyz[1] = coordinates[iSymm][1][iAtom];
1973       xyz[2] = coordinates[iSymm][2][iAtom];
1974       crystal.toFractionalCoordinates(xyz, uvw);
1975       final int frad =
1976           min(aRadGrid, (int) floor(scatteringAtoms[iAtom].getFormFactorWidth() * fftX / crystal.a) + 1);
1977 
1978       final double frz = fftZ * uvw[2];
1979       final int ifrz = (int) frz;
1980       final int ifrzu = ifrz + frad;
1981       final int ifrzl = ifrz - frad;
1982 
1983       final double fry = fftY * uvw[1];
1984       final int ifry = (int) fry;
1985       final int ifryu = ifry + frad;
1986       final int ifryl = ifry - frad;
1987 
1988       // Loop over allowed z coordinates for this Loop
1989       // Check if the current atom is close enough
1990       // If so, add to list.
1991       int buff = bufferSize;
1992 
1993       int lbZ = rowRegion.zFromRowIndex(lb);
1994       int ubZ = rowRegion.zFromRowIndex(ub);
1995 
1996       for (int iz = ifrzl - buff; iz <= ifrzu + buff; iz++) {
1997         int giz = mod(iz, fftZ);
1998         if (lbZ > giz || giz > ubZ) {
1999           continue;
2000         }
2001         int rowLB = rowRegion.rowIndexForYZ(mod(ifryl - buff, fftY), giz);
2002         int rowUB = rowRegion.rowIndexForYZ(mod(ifryu + buff, fftY), giz);
2003         if (lb >= rowLB || rowUB <= ub) {
2004           buildListA.add(iAtom);
2005           buildListS.add(iSymm);
2006           break;
2007         }
2008       }
2009     }
2010 
2011     @Override
2012     public boolean checkList(int[][][] zyAtListBuild, int buff) {
2013       for (int iSymm = 0; iSymm < nSymm; iSymm++) {
2014         for (int iAtom = 0; iAtom < nScatteringAtoms; iAtom++) {
2015           if (rowRegion.select[iSymm][iAtom]) {
2016             if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2017               continue;
2018             }
2019             xyz[0] = coordinates[iSymm][0][iAtom];
2020             xyz[1] = coordinates[iSymm][1][iAtom];
2021             xyz[2] = coordinates[iSymm][2][iAtom];
2022             crystal.toFractionalCoordinates(xyz, uvw);
2023             final double frz = fftZ * uvw[2];
2024             final int ifrz = (int) frz;
2025             final int previousZ = zyAtListBuild[iSymm][iAtom][0];
2026 
2027             final double fry = fftY * uvw[1];
2028             final int ifry = (int) fry;
2029             final int previousY = zyAtListBuild[iSymm][iAtom][1];
2030             if (abs(ifrz - previousZ) >= buff || abs(ifry - previousY) >= buff) {
2031               return true;
2032             }
2033           }
2034         }
2035       }
2036       return false;
2037     }
2038 
2039     @Override
2040     public void finish() {
2041       for (int i = 0; i < fftZ * fftY; i++) {
2042         optRowWeightAtomic.addAndGet(i, optLocal[i]);
2043       }
2044       timer += System.nanoTime();
2045     }
2046 
2047     @Override
2048     public void gridDensity(int iSymm, int iAtom, int lb, int ub) {
2049       if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2050         return;
2051       }
2052 
2053       if (lambdaTerm && scatteringAtoms[iAtom].applyLambda()) {
2054         return;
2055       }
2056 
2057       int lbZ = rowRegion.zFromRowIndex(lb);
2058       int ubZ = rowRegion.zFromRowIndex(ub);
2059 
2060       xyz[0] = coordinates[iSymm][0][iAtom];
2061       xyz[1] = coordinates[iSymm][1][iAtom];
2062       xyz[2] = coordinates[iSymm][2][iAtom];
2063       FormFactor atomff = atomFormFactors[iSymm][iAtom];
2064       crystal.toFractionalCoordinates(xyz, uvw);
2065       final int frad =
2066           min(aRadGrid, (int) floor(scatteringAtoms[iAtom].getFormFactorWidth() * fftX / crystal.a) + 1);
2067 
2068       // Logic to loop within the cutoff box.
2069       final double frx = fftX * uvw[0];
2070       final int ifrx = (int) frx;
2071       final int ifrxu = ifrx + frad;
2072 
2073       final double fry = fftY * uvw[1];
2074       final int ifry = (int) fry;
2075       final int ifryu = ifry + frad;
2076 
2077       final double frz = fftZ * uvw[2];
2078       final int ifrz = (int) frz;
2079       final int ifrzu = ifrz + frad;
2080 
2081       for (int iz = ifrz - frad; iz <= ifrzu; iz++) {
2082         int giz = mod(iz, fftZ);
2083         if (lbZ > giz || giz > ubZ) {
2084           continue;
2085         }
2086         xf[2] = iz * ifftZ;
2087         for (int iy = ifry - frad; iy <= ifryu; iy++) {
2088           int giy = mod(iy, fftY);
2089           int rowIndex = rowRegion.rowIndexForYZ(giy, giz);
2090           if (lb > rowIndex || rowIndex > ub) {
2091             continue;
2092           }
2093           xf[1] = iy * ifftY;
2094           for (int ix = ifrx - frad; ix <= ifrxu; ix++) {
2095             int gix = mod(ix, fftX);
2096             xf[0] = ix * ifftX;
2097             crystal.toCartesianCoordinates(xf, xc);
2098             optLocal[rowIndex]++;
2099             actualWeight++;
2100             final int ii = interleavedIndex(gix, giy, giz, fftX, fftY);
2101             grid[ii] = atomff.rho(grid[ii], 1.0, xc);
2102           }
2103         }
2104       }
2105     }
2106 
2107     @Override
2108     public void run(int lb, int ub) throws Exception {
2109       boolean boundsChange = false;
2110       if (previousLB != lb || previousUB != ub) {
2111         boundsChange = true;
2112       }
2113       previousLB = lb;
2114       previousUB = ub;
2115       if (rebuildList || boundsChange) {
2116         buildListA = new ArrayList<>();
2117         buildListS = new ArrayList<>();
2118         for (int iSymm = 0; iSymm < nSymm; iSymm++) {
2119           for (int iAtom = 0; iAtom < nScatteringAtoms; iAtom++) {
2120             if (rowRegion.select[iSymm][iAtom]) {
2121               buildList(iSymm, iAtom, lb, ub);
2122             }
2123           }
2124         }
2125       }
2126       for (int i = 0; i < buildListA.size(); i++) {
2127         if (rowRegion.select[buildListS.get(i)][buildListA.get(i)]) {
2128           gridDensity(buildListS.get(i), buildListA.get(i), lb, ub);
2129         }
2130       }
2131     }
2132 
2133     @Override
2134     public void saveZYValues(int[][][] zyAtListBuild) {
2135       for (int iSymm = 0; iSymm < nSymm; iSymm++) {
2136         for (int iAtom = 0; iAtom < nScatteringAtoms; iAtom++) {
2137           zyAtListBuild[iSymm][iAtom][0] = 0;
2138           zyAtListBuild[iSymm][iAtom][1] = 0;
2139           if (rowRegion.select[iSymm][iAtom]) {
2140             if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2141               continue;
2142             }
2143             xyz[0] = coordinates[iSymm][0][iAtom];
2144             xyz[1] = coordinates[iSymm][1][iAtom];
2145             xyz[2] = coordinates[iSymm][2][iAtom];
2146             crystal.toFractionalCoordinates(xyz, uvw);
2147             final double frz = fftZ * uvw[2];
2148             final int ifrz = (int) frz;
2149 
2150             final double fry = fftY * uvw[1];
2151             final int ifry = (int) fry;
2152             zyAtListBuild[iSymm][iAtom][0] = ifrz;
2153             zyAtListBuild[iSymm][iAtom][1] = ifry;
2154           }
2155         }
2156       }
2157     }
2158 
2159     @Override
2160     public IntegerSchedule schedule() {
2161       return atomicRowSchedule;
2162     }
2163 
2164     @Override
2165     public void start() {
2166       fill(optLocal, 0);
2167       actualWeight = 0;
2168       timer = -System.nanoTime();
2169     }
2170 
2171     double getThreadTime() {
2172       return timer;
2173     }
2174 
2175     int getNumberofSlices() {
2176       return (previousUB - previousLB + 1);
2177     }
2178 
2179     int getThreadWeight() {
2180       return actualWeight;
2181     }
2182   }
2183 
2184   private class SolventRowLoop extends RowLoop {
2185 
2186     final double[] xyz = new double[3];
2187     final double[] uvw = new double[3];
2188     final double[] xc = new double[3];
2189     final double[] xf = new double[3];
2190     final double[] grid;
2191     final int[] optLocal;
2192     long timer;
2193     int previousUB, previousLB;
2194     int actualWeight;
2195 
2196     public SolventRowLoop(RowRegion region) {
2197       super(region.getNatoms(), region.getNsymm(), region);
2198       grid = region.getGrid();
2199       optLocal = new int[fftZ * fftY];
2200     }
2201 
2202     public void buildList(int iSymm, int iAtom, int lb, int ub) {
2203       if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2204         return;
2205       }
2206       xyz[0] = coordinates[iSymm][0][iAtom];
2207       xyz[1] = coordinates[iSymm][1][iAtom];
2208       xyz[2] = coordinates[iSymm][2][iAtom];
2209       crystal.toFractionalCoordinates(xyz, uvw);
2210       final int frad =
2211           min(aRadGrid, (int) floor(scatteringAtoms[iAtom].getFormFactorWidth() * fftX / crystal.a) + 1);
2212 
2213       final double frz = fftZ * uvw[2];
2214       final int ifrz = (int) frz;
2215       final int ifrzu = ifrz + frad;
2216       final int ifrzl = ifrz - frad;
2217 
2218       final double fry = fftY * uvw[1];
2219       final int ifry = (int) fry;
2220       final int ifryu = ifry + frad;
2221       final int ifryl = ifry - frad;
2222 
2223       // Loop over allowed z coordinates for this Loop
2224       // Check if the current atom is close enough
2225       // If so, add to list.
2226       int buff = bufferSize;
2227 
2228       int lbZ = rowRegion.zFromRowIndex(lb);
2229       int ubZ = rowRegion.zFromRowIndex(ub);
2230 
2231       for (int iz = ifrzl - buff; iz <= ifrzu + buff; iz++) {
2232         int giz = mod(iz, fftZ);
2233         if (lbZ > giz || giz > ubZ) {
2234           continue;
2235         }
2236         int rowLB = rowRegion.rowIndexForYZ(mod(ifryl - buff, fftY), giz);
2237         int rowUB = rowRegion.rowIndexForYZ(mod(ifryu + buff, fftY), giz);
2238         if (lb >= rowLB || rowUB <= ub) {
2239           buildListA.add(iAtom);
2240           buildListS.add(iSymm);
2241           break;
2242         }
2243       }
2244     }
2245 
2246     @Override
2247     public boolean checkList(int[][][] zyAtListBuild, int buff) {
2248       for (int iSymm = 0; iSymm < nSymm; iSymm++) {
2249         for (int iAtom = 0; iAtom < nScatteringAtoms; iAtom++) {
2250           if (rowRegion.select[iSymm][iAtom]) {
2251             if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2252               continue;
2253             }
2254             xyz[0] = coordinates[iSymm][0][iAtom];
2255             xyz[1] = coordinates[iSymm][1][iAtom];
2256             xyz[2] = coordinates[iSymm][2][iAtom];
2257             crystal.toFractionalCoordinates(xyz, uvw);
2258             final double frz = fftZ * uvw[2];
2259             final int ifrz = (int) frz;
2260             final int previousZ = zyAtListBuild[iSymm][iAtom][0];
2261 
2262             final double fry = fftY * uvw[1];
2263             final int ifry = (int) fry;
2264             final int previousY = zyAtListBuild[iSymm][iAtom][1];
2265             if (abs(ifrz - previousZ) >= buff || abs(ifry - previousY) >= buff) {
2266               return true;
2267             }
2268           }
2269         }
2270       }
2271       return false;
2272     }
2273 
2274     @Override
2275     public void finish() {
2276       timer += System.nanoTime();
2277       for (int i = 0; i < fftZ * fftY; i++) {
2278         optRowWeightSolvent.addAndGet(i, optLocal[i]);
2279       }
2280     }
2281 
2282     public int getNumberofSlices() {
2283       return (previousUB - previousLB + 1);
2284     }
2285 
2286     public double getThreadTime() {
2287       return timer;
2288     }
2289 
2290     public int getThreadWeight() {
2291       return actualWeight;
2292     }
2293 
2294     @Override
2295     public void gridDensity(int iSymm, int iAtom, int lb, int ub) {
2296 
2297       if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2298         return;
2299       }
2300 
2301       if (lambdaTerm && scatteringAtoms[iAtom].applyLambda()) {
2302         return;
2303       }
2304 
2305       xyz[0] = coordinates[iSymm][0][iAtom];
2306       xyz[1] = coordinates[iSymm][1][iAtom];
2307       xyz[2] = coordinates[iSymm][2][iAtom];
2308       FormFactor formFactor = solventFormFactors[iSymm][iAtom];
2309       crystal.toFractionalCoordinates(xyz, uvw);
2310       double vdwr = scatteringAtoms[iAtom].getVDWType().radius * 0.5;
2311       int frad = aRadGrid;
2312       switch (solventModel) {
2313         case BINARY:
2314           frad = min(aRadGrid, (int) floor((vdwr + solventA + 0.2) * fftX / crystal.a) + 1);
2315           break;
2316         case GAUSSIAN:
2317           frad = min(aRadGrid, (int) floor((vdwr * solventB + 2.0) * fftX / crystal.a) + 1);
2318           break;
2319         case POLYNOMIAL:
2320           frad = min(aRadGrid, (int) floor((vdwr + solventB + 0.2) * fftX / crystal.a) + 1);
2321           break;
2322         case NONE:
2323         default:
2324           return;
2325       }
2326 
2327       int ubZ = rowRegion.zFromRowIndex(ub);
2328       int lbZ = rowRegion.zFromRowIndex(lb);
2329 
2330       // Logic to loop within the cutoff box.
2331       final double frx = fftX * uvw[0];
2332       final int ifrx = (int) frx;
2333       final int ifrxu = ifrx + frad;
2334 
2335       final double fry = fftY * uvw[1];
2336       final int ifry = (int) fry;
2337       final int ifryu = ifry + frad;
2338 
2339       final double frz = fftZ * uvw[2];
2340       final int ifrz = (int) frz;
2341       final int ifrzu = ifrz + frad;
2342 
2343       for (int iz = ifrz - frad; iz <= ifrzu; iz++) {
2344         int giz = mod(iz, fftZ);
2345         if (lbZ > giz || giz > ubZ) {
2346           continue;
2347         }
2348         xf[2] = iz * ifftZ;
2349         for (int iy = ifry - frad; iy <= ifryu; iy++) {
2350           int giy = mod(iy, fftY);
2351           int rowIndex = rowRegion.rowIndexForYZ(giy, giz);
2352           if (lb > rowIndex || rowIndex > ub) {
2353             continue;
2354           }
2355           xf[1] = iy * ifftY;
2356           for (int ix = ifrx - frad; ix <= ifrxu; ix++) {
2357             int gix = mod(ix, fftX);
2358             xf[0] = ix * ifftX;
2359             crystal.toCartesianCoordinates(xf, xc);
2360             optLocal[rowIndex]++;
2361             actualWeight++;
2362             final int ii = interleavedIndex(gix, giy, giz, fftX, fftY);
2363             grid[ii] = formFactor.rho(grid[ii], 1.0, xc);
2364           }
2365         }
2366       }
2367     }
2368 
2369     @Override
2370     public void run(int lb, int ub) throws Exception {
2371       boolean boundsChange = false;
2372       if (previousLB != lb || previousUB != ub) {
2373         boundsChange = true;
2374       }
2375       previousLB = lb;
2376       previousUB = ub;
2377       if (rebuildList || boundsChange) {
2378         buildListA = new ArrayList<>();
2379         buildListS = new ArrayList<>();
2380         for (int iSymm = 0; iSymm < nSymm; iSymm++) {
2381           for (int iAtom = 0; iAtom < nScatteringAtoms; iAtom++) {
2382             if (rowRegion.select[iSymm][iAtom]) {
2383               buildList(iSymm, iAtom, lb, ub);
2384             }
2385           }
2386         }
2387       }
2388       for (int i = 0; i < buildListA.size(); i++) {
2389         if (rowRegion.select[buildListS.get(i)][buildListA.get(i)]) {
2390           gridDensity(buildListS.get(i), buildListA.get(i), lb, ub);
2391         }
2392       }
2393     }
2394 
2395     @Override
2396     public void saveZYValues(int[][][] zyAtListBuild) {
2397       for (int iSymm = 0; iSymm < nSymm; iSymm++) {
2398         for (int iAtom = 0; iAtom < nScatteringAtoms; iAtom++) {
2399           zyAtListBuild[iSymm][iAtom][0] = 0;
2400           zyAtListBuild[iSymm][iAtom][1] = 0;
2401           if (rowRegion.select[iSymm][iAtom]) {
2402             if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2403               continue;
2404             }
2405             xyz[0] = coordinates[iSymm][0][iAtom];
2406             xyz[1] = coordinates[iSymm][1][iAtom];
2407             xyz[2] = coordinates[iSymm][2][iAtom];
2408             crystal.toFractionalCoordinates(xyz, uvw);
2409             final double frz = fftZ * uvw[2];
2410             final int ifrz = (int) frz;
2411 
2412             final double fry = fftY * uvw[1];
2413             final int ifry = (int) fry;
2414             zyAtListBuild[iSymm][iAtom][0] = ifrz;
2415             zyAtListBuild[iSymm][iAtom][1] = ifry;
2416           }
2417         }
2418       }
2419     }
2420 
2421     @Override
2422     public IntegerSchedule schedule() {
2423       return solventRowSchedule;
2424     }
2425 
2426     @Override
2427     public void start() {
2428       fill(optLocal, 0);
2429       timer = -System.nanoTime();
2430     }
2431   }
2432 
2433   private class BulkSolventRowLoop extends RowLoop {
2434 
2435     final double[] xyz = new double[3];
2436     final double[] uvw = new double[3];
2437     final double[] xc = new double[3];
2438     final double[] xf = new double[3];
2439     final double[] grid;
2440     final int[] optLocal;
2441     long timer;
2442     int[] threadWeights;
2443     int[] threadBounds;
2444 
2445     public BulkSolventRowLoop(RowRegion region) {
2446       super(region.getNatoms(), region.getNsymm(), region);
2447       grid = region.getGrid();
2448       optLocal = new int[fftZ * fftY];
2449     }
2450 
2451     @Override
2452     public void finish() {
2453       for (int i = 0; i < fftZ * fftY; i++) {
2454         optRowWeightBulkSolvent.addAndGet(i, optLocal[i]);
2455       }
2456       timer += System.nanoTime();
2457       threadBounds = bulkSolventRowSchedule.getLowerBounds().clone();
2458       threadWeights = bulkSolventRowSchedule.getThreadWeights().clone();
2459       for (int i = threadCount - 1; i > 0; i--) {
2460         threadWeights[i] -= threadWeights[i - 1];
2461       }
2462     }
2463 
2464     public int[] getBoundsPerThread() {
2465       return threadBounds;
2466     }
2467 
2468     public double getTimePerThread() {
2469       return timer;
2470     }
2471 
2472     public int[] getWeightPerThread() {
2473       return optLocal;
2474     }
2475 
2476     @Override
2477     public void gridDensity(int iSymm, int iAtom, int lb, int ub) {
2478       if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2479         return;
2480       }
2481 
2482       if (lambdaTerm && scatteringAtoms[iAtom].applyLambda()) {
2483         return;
2484       }
2485 
2486       xyz[0] = coordinates[iSymm][0][iAtom];
2487       xyz[1] = coordinates[iSymm][1][iAtom];
2488       xyz[2] = coordinates[iSymm][2][iAtom];
2489       FormFactor formFactor = solventFormFactors[iSymm][iAtom];
2490       crystal.toFractionalCoordinates(xyz, uvw);
2491       double vdwr = scatteringAtoms[iAtom].getVDWType().radius * 0.5;
2492       int frad = aRadGrid;
2493       switch (solventModel) {
2494         case BINARY:
2495           frad = min(aRadGrid, (int) floor((vdwr + solventA + 0.2) * fftX / crystal.a) + 1);
2496           break;
2497         case GAUSSIAN:
2498           frad = min(aRadGrid, (int) floor((vdwr * solventB + 2.0) * fftX / crystal.a) + 1);
2499           break;
2500         case POLYNOMIAL:
2501           frad = min(aRadGrid, (int) floor((vdwr + solventB + 0.2) * fftX / crystal.a) + 1);
2502           break;
2503         case NONE:
2504         default:
2505           return;
2506       }
2507 
2508       int ubZ = rowRegion.zFromRowIndex(ub);
2509       int lbZ = rowRegion.zFromRowIndex(lb);
2510 
2511       // Logic to loop within the cutoff box.
2512       final double frx = fftX * uvw[0];
2513       final int ifrx = (int) frx;
2514       final int ifrxu = ifrx + frad;
2515 
2516       final double fry = fftY * uvw[1];
2517       final int ifry = (int) fry;
2518       final int ifryu = ifry + frad;
2519 
2520       final double frz = fftZ * uvw[2];
2521       final int ifrz = (int) frz;
2522       final int ifrzu = ifrz + frad;
2523 
2524       for (int iz = ifrz - frad; iz <= ifrzu; iz++) {
2525         int giz = mod(iz, fftZ);
2526         if (lbZ > giz || giz > ubZ) {
2527           continue;
2528         }
2529         xf[2] = iz * ifftZ;
2530         for (int iy = ifry - frad; iy <= ifryu; iy++) {
2531           int giy = mod(iy, fftY);
2532           int rowIndex = rowRegion.rowIndexForYZ(giy, giz);
2533           if (lb > rowIndex || rowIndex > ub) {
2534             continue;
2535           }
2536           xf[1] = iy * ifftY;
2537           for (int ix = ifrx - frad; ix <= ifrxu; ix++) {
2538             int gix = mod(ix, fftX);
2539             xf[0] = ix * ifftX;
2540             crystal.toCartesianCoordinates(xf, xc);
2541             optLocal[rowIndex]++;
2542             final int ii = interleavedIndex(gix, giy, giz, fftX, fftY);
2543             grid[ii] = formFactor.rho(grid[ii], 1.0, xc);
2544           }
2545         }
2546       }
2547     }
2548 
2549     @Override
2550     public IntegerSchedule schedule() {
2551       return bulkSolventRowSchedule;
2552     }
2553 
2554     @Override
2555     public void start() {
2556       timer = -System.nanoTime();
2557       fill(optLocal, 0);
2558     }
2559   }
2560 
2561   private class AtomicSliceLoop extends SliceLoop {
2562 
2563     final double[] xyz = new double[3];
2564     final double[] uvw = new double[3];
2565     final double[] xc = new double[3];
2566     final double[] xf = new double[3];
2567     final double[] grid;
2568     final int[] optLocal;
2569     long timer;
2570     int previousUB, previousLB;
2571     int actualWeight;
2572 
2573     public AtomicSliceLoop(SliceRegion region) {
2574       super(region.getNatoms(), region.getNsymm(), region);
2575       grid = region.getGrid();
2576       optLocal = new int[fftZ];
2577     }
2578 
2579     public void buildList(int iSymm, int iAtom, int lb, int ub) {
2580       if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2581         return;
2582       }
2583       xyz[0] = coordinates[iSymm][0][iAtom];
2584       xyz[1] = coordinates[iSymm][1][iAtom];
2585       xyz[2] = coordinates[iSymm][2][iAtom];
2586       crystal.toFractionalCoordinates(xyz, uvw);
2587       final int frad =
2588           min(aRadGrid, (int) floor(scatteringAtoms[iAtom].getFormFactorWidth() * fftX / crystal.a) + 1);
2589       final double frz = fftZ * uvw[2];
2590       final int ifrz = (int) frz;
2591       final int ifrzu = ifrz + frad;
2592       final int ifrzl = ifrz - frad;
2593       // Loop over allowed z coordinates for this Loop
2594       // Check if the current atom is close enough
2595       // If so, add to list.
2596       int buff = bufferSize;
2597       for (int iz = ifrzl - buff; iz <= ifrzu + buff; iz++) {
2598         int giz = mod(iz, fftZ);
2599         if (giz >= lb && giz <= ub) {
2600           buildListA.add(iAtom);
2601           buildListS.add(iSymm);
2602           break;
2603         }
2604       }
2605     }
2606 
2607     @Override
2608     public boolean checkList(int[][] zAtListBuild, int buff) {
2609       for (int iSymm = 0; iSymm < nSymm; iSymm++) {
2610         for (int iAtom = 0; iAtom < nScatteringAtoms; iAtom++) {
2611           if (sliceRegion.select[iSymm][iAtom]) {
2612             if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2613               continue;
2614             }
2615             xyz[0] = coordinates[iSymm][0][iAtom];
2616             xyz[1] = coordinates[iSymm][1][iAtom];
2617             xyz[2] = coordinates[iSymm][2][iAtom];
2618             crystal.toFractionalCoordinates(xyz, uvw);
2619             final double frz = fftZ * uvw[2];
2620             final int ifrz = (int) frz;
2621             final int previousZ = zAtListBuild[iSymm][iAtom];
2622             if (abs(ifrz - previousZ) >= buff) {
2623               return true;
2624             }
2625           }
2626         }
2627       }
2628       return false;
2629     }
2630 
2631     @Override
2632     public void finish() {
2633       for (int i = 0; i < fftZ; i++) {
2634         optSliceWeightAtomic.addAndGet(i, optLocal[i]);
2635       }
2636       timer += System.nanoTime();
2637     }
2638 
2639     public int getNumberofSlices() {
2640       return (previousUB - previousLB + 1);
2641     }
2642 
2643     public double getThreadTime() {
2644       return timer;
2645     }
2646 
2647     public int getThreadWeight() {
2648       return actualWeight;
2649     }
2650 
2651     @Override
2652     public void gridDensity(int iSymm, int iAtom, int lb, int ub) {
2653       if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2654         return;
2655       }
2656 
2657       if (lambdaTerm && scatteringAtoms[iAtom].applyLambda()) {
2658         return;
2659       }
2660 
2661       xyz[0] = coordinates[iSymm][0][iAtom];
2662       xyz[1] = coordinates[iSymm][1][iAtom];
2663       xyz[2] = coordinates[iSymm][2][iAtom];
2664       FormFactor atomff = atomFormFactors[iSymm][iAtom];
2665       crystal.toFractionalCoordinates(xyz, uvw);
2666       final int frad =
2667           min(aRadGrid, (int) floor(scatteringAtoms[iAtom].getFormFactorWidth() * fftX / crystal.a) + 1);
2668 
2669       // Logic to loop within the cutoff box.
2670       final double frx = fftX * uvw[0];
2671       final int ifrx = (int) frx;
2672       final int ifrxu = ifrx + frad;
2673 
2674       final double fry = fftY * uvw[1];
2675       final int ifry = (int) fry;
2676       final int ifryu = ifry + frad;
2677 
2678       final double frz = fftZ * uvw[2];
2679       final int ifrz = (int) frz;
2680       final int ifrzu = ifrz + frad;
2681 
2682       for (int iz = ifrz - frad; iz <= ifrzu; iz++) {
2683         int giz = mod(iz, fftZ);
2684         if (lb > giz || giz > ub) {
2685           continue;
2686         }
2687         xf[2] = iz * ifftZ;
2688         for (int iy = ifry - frad; iy <= ifryu; iy++) {
2689           int giy = mod(iy, fftY);
2690           xf[1] = iy * ifftY;
2691           for (int ix = ifrx - frad; ix <= ifrxu; ix++) {
2692             int gix = mod(ix, fftX);
2693             xf[0] = ix * ifftX;
2694             crystal.toCartesianCoordinates(xf, xc);
2695             optLocal[giz]++;
2696             actualWeight++;
2697             final int ii = interleavedIndex(gix, giy, giz, fftX, fftY);
2698             grid[ii] = atomff.rho(grid[ii], 1.0, xc);
2699           }
2700         }
2701       }
2702     }
2703 
2704     @Override
2705     public void run(int lb, int ub) throws Exception {
2706       boolean boundsChange = false;
2707       if (previousLB != lb || previousUB != ub) {
2708         boundsChange = true;
2709       }
2710       previousLB = lb;
2711       previousUB = ub;
2712       if (rebuildList || boundsChange) {
2713         buildListA = new ArrayList<>();
2714         buildListS = new ArrayList<>();
2715         for (int iSymm = 0; iSymm < nSymm; iSymm++) {
2716           for (int iAtom = 0; iAtom < nScatteringAtoms; iAtom++) {
2717             if (sliceRegion.select[iSymm][iAtom]) {
2718               buildList(iSymm, iAtom, lb, ub);
2719             }
2720           }
2721         }
2722       }
2723       for (int i = 0; i < buildListA.size(); i++) {
2724         if (sliceRegion.select[buildListS.get(i)][buildListA.get(i)]) {
2725           gridDensity(buildListS.get(i), buildListA.get(i), lb, ub);
2726         }
2727       }
2728     }
2729 
2730     @Override
2731     public void saveZValues(int[][] zAtListBuild) {
2732       for (int iSymm = 0; iSymm < nSymm; iSymm++) {
2733         for (int iAtom = 0; iAtom < nScatteringAtoms; iAtom++) {
2734           zAtListBuild[iSymm][iAtom] = 0;
2735           if (sliceRegion.select[iSymm][iAtom]) {
2736             if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2737               continue;
2738             }
2739             xyz[0] = coordinates[iSymm][0][iAtom];
2740             xyz[1] = coordinates[iSymm][1][iAtom];
2741             xyz[2] = coordinates[iSymm][2][iAtom];
2742             crystal.toFractionalCoordinates(xyz, uvw);
2743             final double frz = fftZ * uvw[2];
2744             final int ifrz = (int) frz;
2745             zAtListBuild[iSymm][iAtom] = ifrz;
2746           }
2747         }
2748       }
2749     }
2750 
2751     @Override
2752     public IntegerSchedule schedule() {
2753       return atomicSliceSchedule;
2754     }
2755 
2756     @Override
2757     public void start() {
2758       fill(optLocal, 0);
2759       actualWeight = 0;
2760       timer = -System.nanoTime();
2761     }
2762   }
2763 
2764   private class SolventSliceLoop extends SliceLoop {
2765 
2766     final double[] xyz = new double[3];
2767     final double[] uvw = new double[3];
2768     final double[] xc = new double[3];
2769     final double[] xf = new double[3];
2770     final double[] grid;
2771     final int[] optLocal;
2772     long timer;
2773     int previousUB, previousLB;
2774     int actualWeight;
2775 
2776     public SolventSliceLoop(SliceRegion region) {
2777       super(region.getNatoms(), region.getNsymm(), region);
2778       grid = region.getGrid();
2779       optLocal = new int[fftZ];
2780     }
2781 
2782     public void buildList(int iSymm, int iAtom, int lb, int ub) {
2783       if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2784         return;
2785       }
2786       xyz[0] = coordinates[iSymm][0][iAtom];
2787       xyz[1] = coordinates[iSymm][1][iAtom];
2788       xyz[2] = coordinates[iSymm][2][iAtom];
2789       crystal.toFractionalCoordinates(xyz, uvw);
2790       final int frad =
2791           min(aRadGrid, (int) floor(scatteringAtoms[iAtom].getFormFactorWidth() * fftX / crystal.a) + 1);
2792       final double frz = fftZ * uvw[2];
2793       final int ifrz = (int) frz;
2794       final int ifrzu = ifrz + frad;
2795       final int ifrzl = ifrz - frad;
2796       // Loop over allowed z coordinates for this Loop
2797       // Check if the current atom is close enough
2798       // If so, add to list.
2799       int buff = bufferSize;
2800       for (int iz = ifrzl - buff; iz <= ifrzu + buff; iz++) {
2801         int giz = mod(iz, fftZ);
2802         if (giz >= lb && giz <= ub) {
2803           buildListA.add(iAtom);
2804           buildListS.add(iSymm);
2805           break;
2806         }
2807       }
2808     }
2809 
2810     @Override
2811     public boolean checkList(int[][] zAtListBuild, int buff) {
2812       for (int iSymm = 0; iSymm < nSymm; iSymm++) {
2813         for (int iAtom = 0; iAtom < nScatteringAtoms; iAtom++) {
2814           if (sliceRegion.select[iSymm][iAtom]) {
2815             if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2816               continue;
2817             }
2818             xyz[0] = coordinates[iSymm][0][iAtom];
2819             xyz[1] = coordinates[iSymm][1][iAtom];
2820             xyz[2] = coordinates[iSymm][2][iAtom];
2821             crystal.toFractionalCoordinates(xyz, uvw);
2822             final double frz = fftZ * uvw[2];
2823             final int ifrz = (int) frz;
2824             final int previousZ = zAtListBuild[iSymm][iAtom];
2825             if (abs(ifrz - previousZ) >= buff) {
2826               return true;
2827             }
2828           }
2829         }
2830       }
2831       return false;
2832     }
2833 
2834     @Override
2835     public void finish() {
2836       timer += System.nanoTime();
2837       for (int i = 0; i < fftZ; i++) {
2838         optSliceWeightSolvent.addAndGet(i, optLocal[i]);
2839       }
2840     }
2841 
2842     public int getNumberofSlices() {
2843       return (previousUB - previousLB + 1);
2844     }
2845 
2846     public double getThreadTime() {
2847       return timer;
2848     }
2849 
2850     public int getThreadWeight() {
2851       return actualWeight;
2852     }
2853 
2854     @Override
2855     public void gridDensity(int iSymm, int iAtom, int lb, int ub) {
2856       if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2857         return;
2858       }
2859 
2860       if (lambdaTerm && scatteringAtoms[iAtom].applyLambda()) {
2861         return;
2862       }
2863 
2864       xyz[0] = coordinates[iSymm][0][iAtom];
2865       xyz[1] = coordinates[iSymm][1][iAtom];
2866       xyz[2] = coordinates[iSymm][2][iAtom];
2867       FormFactor formFactor = solventFormFactors[iSymm][iAtom];
2868       crystal.toFractionalCoordinates(xyz, uvw);
2869       double vdwr = scatteringAtoms[iAtom].getVDWType().radius * 0.5;
2870       int frad = aRadGrid;
2871       switch (solventModel) {
2872         case BINARY:
2873           frad = min(aRadGrid, (int) floor((vdwr + solventA + 0.2) * fftX / crystal.a) + 1);
2874           break;
2875         case GAUSSIAN:
2876           frad = min(aRadGrid, (int) floor((vdwr * solventB + 2.0) * fftX / crystal.a) + 1);
2877           break;
2878         case POLYNOMIAL:
2879           frad = min(aRadGrid, (int) floor((vdwr + solventB + 0.2) * fftX / crystal.a) + 1);
2880           break;
2881         case NONE:
2882         default:
2883           return;
2884       }
2885 
2886       // Logic to loop within the cutoff box.
2887       final double frx = fftX * uvw[0];
2888       final int ifrx = (int) frx;
2889       final int ifrxu = ifrx + frad;
2890 
2891       final double fry = fftY * uvw[1];
2892       final int ifry = (int) fry;
2893       final int ifryu = ifry + frad;
2894 
2895       final double frz = fftZ * uvw[2];
2896       final int ifrz = (int) frz;
2897       final int ifrzu = ifrz + frad;
2898 
2899       for (int iz = ifrz - frad; iz <= ifrzu; iz++) {
2900         int giz = mod(iz, fftZ);
2901         if (lb > giz || giz > ub) {
2902           continue;
2903         }
2904         xf[2] = iz * ifftZ;
2905         for (int iy = ifry - frad; iy <= ifryu; iy++) {
2906           int giy = mod(iy, fftY);
2907           xf[1] = iy * ifftY;
2908           for (int ix = ifrx - frad; ix <= ifrxu; ix++) {
2909             int gix = mod(ix, fftX);
2910             xf[0] = ix * ifftX;
2911             crystal.toCartesianCoordinates(xf, xc);
2912             optLocal[giz]++;
2913             actualWeight++;
2914             final int ii = interleavedIndex(gix, giy, giz, fftX, fftY);
2915             grid[ii] = formFactor.rho(grid[ii], 1.0, xc);
2916           }
2917         }
2918       }
2919     }
2920 
2921     @Override
2922     public void run(int lb, int ub) throws Exception {
2923       boolean boundsChange = false;
2924       if (previousLB != lb || previousUB != ub) {
2925         boundsChange = true;
2926       }
2927       previousLB = lb;
2928       previousUB = ub;
2929       if (rebuildList || boundsChange) {
2930         buildListA = new ArrayList<>();
2931         buildListS = new ArrayList<>();
2932         for (int iSymm = 0; iSymm < nSymm; iSymm++) {
2933           for (int iAtom = 0; iAtom < nScatteringAtoms; iAtom++) {
2934             if (sliceRegion.select[iSymm][iAtom]) {
2935               buildList(iSymm, iAtom, lb, ub);
2936             }
2937           }
2938         }
2939       }
2940       for (int i = 0; i < buildListA.size(); i++) {
2941         if (sliceRegion.select[buildListS.get(i)][buildListA.get(i)]) {
2942           gridDensity(buildListS.get(i), buildListA.get(i), lb, ub);
2943         }
2944       }
2945     }
2946 
2947     @Override
2948     public void saveZValues(int[][] zAtListBuild) {
2949       for (int iSymm = 0; iSymm < nSymm; iSymm++) {
2950         for (int iAtom = 0; iAtom < nScatteringAtoms; iAtom++) {
2951           zAtListBuild[iSymm][iAtom] = 0;
2952           if (sliceRegion.select[iSymm][iAtom]) {
2953             if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
2954               continue;
2955             }
2956             xyz[0] = coordinates[iSymm][0][iAtom];
2957             xyz[1] = coordinates[iSymm][1][iAtom];
2958             xyz[2] = coordinates[iSymm][2][iAtom];
2959             crystal.toFractionalCoordinates(xyz, uvw);
2960             final double frz = fftZ * uvw[2];
2961             final int ifrz = (int) frz;
2962             zAtListBuild[iSymm][iAtom] = ifrz;
2963           }
2964         }
2965       }
2966     }
2967 
2968     @Override
2969     public IntegerSchedule schedule() {
2970       return solventSliceSchedule;
2971     }
2972 
2973     @Override
2974     public void start() {
2975       fill(optLocal, 0);
2976       actualWeight = 0;
2977       timer = -System.nanoTime();
2978     }
2979   }
2980 
2981   private class BulkSolventSliceLoop extends SliceLoop {
2982 
2983     final double[] xyz = new double[3];
2984     final double[] uvw = new double[3];
2985     final double[] xc = new double[3];
2986     final double[] xf = new double[3];
2987     final double[] grid;
2988     final int[] optLocal;
2989     long timer;
2990     int[] threadBounds;
2991     int[] threadWeights;
2992 
2993     public BulkSolventSliceLoop(SliceRegion region) {
2994       super(region.getNatoms(), region.getNsymm(), region);
2995       grid = region.getGrid();
2996       optLocal = new int[fftZ];
2997     }
2998 
2999     @Override
3000     public void finish() {
3001       timer += System.nanoTime();
3002       for (int i = 0; i < fftZ; i++) {
3003         optSliceWeightBulkSolvent.addAndGet(i, optLocal[i]);
3004       }
3005       threadBounds = bulkSolventSliceSchedule.getLowerBounds().clone();
3006       threadWeights = bulkSolventSliceSchedule.getThreadWeights().clone();
3007       for (int i = threadBounds.length - 1; i > 0; i--) {
3008         threadBounds[i] -= threadBounds[i - 1];
3009       }
3010     }
3011 
3012     public int[] getBoundsPerThread() {
3013       return threadBounds;
3014     }
3015 
3016     public double getTimePerThread() {
3017       return timer;
3018     }
3019 
3020     public int[] getWeightPerThread() {
3021       return threadWeights;
3022     }
3023 
3024     @Override
3025     public void gridDensity(int iSymm, int iAtom, int lb, int ub) {
3026       if (!scatteringAtoms[iAtom].getUse() && !nativeEnvironmentApproximation) {
3027         return;
3028       }
3029 
3030       if (lambdaTerm && scatteringAtoms[iAtom].applyLambda()) {
3031         return;
3032       }
3033 
3034       xyz[0] = coordinates[iSymm][0][iAtom];
3035       xyz[1] = coordinates[iSymm][1][iAtom];
3036       xyz[2] = coordinates[iSymm][2][iAtom];
3037       FormFactor formFactor = solventFormFactors[iSymm][iAtom];
3038       crystal.toFractionalCoordinates(xyz, uvw);
3039       double vdwr = scatteringAtoms[iAtom].getVDWType().radius * 0.5;
3040       int frad = aRadGrid;
3041       switch (solventModel) {
3042         case BINARY:
3043           frad = min(aRadGrid, (int) floor((vdwr + solventA + 0.2) * fftX / crystal.a) + 1);
3044           break;
3045         case GAUSSIAN:
3046           frad = min(aRadGrid, (int) floor((vdwr * solventB + 2.0) * fftX / crystal.a) + 1);
3047           break;
3048         case POLYNOMIAL:
3049           frad = min(aRadGrid, (int) floor((vdwr + solventB + 0.2) * fftX / crystal.a) + 1);
3050           break;
3051         case NONE:
3052         default:
3053           return;
3054       }
3055 
3056       // Logic to loop within the cutoff box.
3057       final double frx = fftX * uvw[0];
3058       final int ifrx = (int) frx;
3059       final int ifrxu = ifrx + frad;
3060 
3061       final double fry = fftY * uvw[1];
3062       final int ifry = (int) fry;
3063       final int ifryu = ifry + frad;
3064 
3065       final double frz = fftZ * uvw[2];
3066       final int ifrz = (int) frz;
3067       final int ifrzu = ifrz + frad;
3068 
3069       for (int iz = ifrz - frad; iz <= ifrzu; iz++) {
3070         int giz = mod(iz, fftZ);
3071         if (lb > giz || giz > ub) {
3072           continue;
3073         }
3074         xf[2] = iz * ifftZ;
3075         for (int iy = ifry - frad; iy <= ifryu; iy++) {
3076           int giy = mod(iy, fftY);
3077           xf[1] = iy * ifftY;
3078           for (int ix = ifrx - frad; ix <= ifrxu; ix++) {
3079             int gix = mod(ix, fftX);
3080             xf[0] = ix * ifftX;
3081             crystal.toCartesianCoordinates(xf, xc);
3082             optLocal[giz]++;
3083             final int ii = interleavedIndex(gix, giy, giz, fftX, fftY);
3084             grid[ii] = formFactor.rho(grid[ii], 1.0, xc);
3085           }
3086         }
3087       }
3088     }
3089 
3090     @Override
3091     public IntegerSchedule schedule() {
3092       return bulkSolventSliceSchedule;
3093     }
3094 
3095     @Override
3096     public void start() {
3097       fill(optLocal, 0);
3098       timer = -System.nanoTime();
3099     }
3100   }
3101 
3102   private class AtomicGradientRegion extends ParallelRegion {
3103 
3104     private final AtomicGradientLoop[] atomicGradientLoop;
3105     private RefinementMode refinementmode;
3106 
3107     public AtomicGradientRegion(RefinementMode refinementmode) {
3108       this.refinementmode = refinementmode;
3109       atomicGradientLoop = new AtomicGradientLoop[threadCount];
3110       for (int i = 0; i < threadCount; i++) {
3111         atomicGradientLoop[i] = new AtomicGradientLoop();
3112       }
3113     }
3114 
3115     public RefinementMode getRefinementMode() {
3116       return refinementmode;
3117     }
3118 
3119     public void setRefinementMode(RefinementMode refinementmode) {
3120       this.refinementmode = refinementmode;
3121     }
3122 
3123     @Override
3124     public void run() {
3125       try {
3126         execute(0, nScatteringAtoms - 1, atomicGradientLoop[getThreadIndex()]);
3127       } catch (Exception e) {
3128         logger.severe(e.toString());
3129       }
3130     }
3131 
3132     private class AtomicGradientLoop extends IntegerForLoop {
3133 
3134       final double[] xyz = new double[3];
3135       final double[] uvw = new double[3];
3136       final double[] xc = new double[3];
3137       final double[] xf = new double[3];
3138       final int[] optLocal = new int[nScatteringAtoms];
3139       long timer;
3140 
3141       @Override
3142       public void finish() {
3143         timer += System.nanoTime();
3144         for (int i = 0; i < nScatteringAtoms; i++) {
3145           optAtomicGradientWeight.addAndGet(i, optLocal[i]);
3146         }
3147       }
3148 
3149       @Override
3150       public void run(final int lb, final int ub) {
3151         for (int n = lb; n <= ub; n++) {
3152           if (!scatteringAtoms[n].getUse() && !nativeEnvironmentApproximation) {
3153             continue;
3154           }
3155           if (lambdaTerm && scatteringAtoms[n].applyLambda()) {
3156             continue;
3157           }
3158 
3159           xyz[0] = coordinates[0][0][n];
3160           xyz[1] = coordinates[0][1][n];
3161           xyz[2] = coordinates[0][2][n];
3162           FormFactor atomff = atomFormFactors[0][n];
3163           atomff.update(xyz, 0.0);
3164           crystal.toFractionalCoordinates(xyz, uvw);
3165           final int dfrad = Math.min(aRadGrid, (int) Math.floor(scatteringAtoms[n].getFormFactorWidth() * fftX / crystal.a) + 1);
3166 
3167           // Logic to loop within the cutoff box.
3168           final double frx = fftX * uvw[0];
3169           final int ifrx = (int) frx;
3170 
3171           final double fry = fftY * uvw[1];
3172           final int ifry = (int) fry;
3173 
3174           final double frz = fftZ * uvw[2];
3175           final int ifrz = (int) frz;
3176 
3177           for (int ix = ifrx - dfrad; ix <= ifrx + dfrad; ix++) {
3178             int gix = mod(ix, fftX);
3179             xf[0] = ix * ifftX;
3180             for (int iy = ifry - dfrad; iy <= ifry + dfrad; iy++) {
3181               int giy = mod(iy, fftY);
3182               xf[1] = iy * ifftY;
3183               for (int iz = ifrz - dfrad; iz <= ifrz + dfrad; iz++) {
3184                 int giz = mod(iz, fftZ);
3185                 xf[2] = iz * ifftZ;
3186                 crystal.toCartesianCoordinates(xf, xc);
3187                 optLocal[n]++;
3188                 final int ii = interleavedIndex(gix, giy, giz, fftX, fftY);
3189                 atomff.rhoGrad(xc, weight * getDensityGrid()[ii], refinementmode);
3190               }
3191             }
3192           }
3193         }
3194       }
3195 
3196       @Override
3197       public IntegerSchedule schedule() {
3198         return atomicGradientSchedule;
3199       }
3200 
3201       @Override
3202       public void start() {
3203         fill(optLocal, 0);
3204         timer = -System.nanoTime();
3205       }
3206     }
3207   }
3208 
3209   private class SolventGradientRegion extends ParallelRegion {
3210 
3211     private final SolventGradientLoop[] solventGradientLoop;
3212     private RefinementMode refinementmode;
3213 
3214     public SolventGradientRegion(RefinementMode refinementmode) {
3215       this.refinementmode = refinementmode;
3216       solventGradientLoop = new SolventGradientLoop[threadCount];
3217       for (int i = 0; i < threadCount; i++) {
3218         solventGradientLoop[i] = new SolventGradientLoop();
3219       }
3220     }
3221 
3222     public RefinementMode getRefinementMode() {
3223       return refinementmode;
3224     }
3225 
3226     public void setRefinementMode(RefinementMode refinementmode) {
3227       this.refinementmode = refinementmode;
3228     }
3229 
3230     @Override
3231     public void run() {
3232       try {
3233         execute(0, nScatteringAtoms - 1, solventGradientLoop[getThreadIndex()]);
3234       } catch (Exception e) {
3235         logger.severe(e.toString());
3236       }
3237     }
3238 
3239     private class SolventGradientLoop extends IntegerForLoop {
3240 
3241       double[] xyz = new double[3];
3242       double[] uvw = new double[3];
3243       double[] xc = new double[3];
3244       double[] xf = new double[3];
3245 
3246       @Override
3247       public void run(final int lb, final int ub) {
3248         for (int n = lb; n <= ub; n++) {
3249           if (!scatteringAtoms[n].getUse() && !nativeEnvironmentApproximation) {
3250             continue;
3251           }
3252           if (lambdaTerm && scatteringAtoms[n].applyLambda()) {
3253             continue;
3254           }
3255           xyz[0] = coordinates[0][0][n];
3256           xyz[1] = coordinates[0][1][n];
3257           xyz[2] = coordinates[0][2][n];
3258           FormFactor solventff = solventFormFactors[0][n];
3259           solventff.update(xyz);
3260           crystal.toFractionalCoordinates(xyz, uvw);
3261           double vdwr = scatteringAtoms[n].getVDWType().radius * 0.5;
3262           double dfcmult = 1.0;
3263           int dfrad = aRadGrid;
3264           switch (solventModel) {
3265             case BINARY:
3266               dfrad =
3267                   Math.min(
3268                       aRadGrid, (int) Math.floor((vdwr + solventA + 0.2) * fftX / crystal.a) + 1);
3269               break;
3270             case GAUSSIAN:
3271               dfrad =
3272                   Math.min(
3273                       aRadGrid, (int) Math.floor((vdwr * solventB + 2.0) * fftX / crystal.a) + 1);
3274               dfcmult = solventA;
3275               break;
3276             case POLYNOMIAL:
3277               dfrad =
3278                   Math.min(
3279                       aRadGrid, (int) Math.floor((vdwr + solventB + 0.2) * fftX / crystal.a) + 1);
3280               break;
3281             case NONE:
3282               return;
3283           }
3284 
3285           // Logic to loop within the cutoff box.
3286           final double frx = fftX * uvw[0];
3287           final int ifrx = (int) frx;
3288           final int ifrxu = ifrx + dfrad;
3289 
3290           final double fry = fftY * uvw[1];
3291           final int ifry = (int) fry;
3292           final int ifryu = ifry + dfrad;
3293 
3294           final double frz = fftZ * uvw[2];
3295           final int ifrz = (int) frz;
3296           final int ifrzu = ifrz + dfrad;
3297 
3298           for (int ix = ifrx - dfrad; ix <= ifrxu; ix++) {
3299             int gix = mod(ix, fftX);
3300             xf[0] = ix * ifftX;
3301             for (int iy = ifry - dfrad; iy <= ifryu; iy++) {
3302               int giy = mod(iy, fftY);
3303               xf[1] = iy * ifftY;
3304               for (int iz = ifrz - dfrad; iz <= ifrzu; iz++) {
3305                 int giz = mod(iz, fftZ);
3306                 xf[2] = iz * ifftZ;
3307                 crystal.toCartesianCoordinates(xf, xc);
3308                 final int ii = interleavedIndex(gix, giy, giz, fftX, fftY);
3309                 solventff.rhoGrad(
3310                     xc,
3311                     weight * getDensityGrid()[ii] * dfcmult * getSolventGrid()[ii],
3312                     refinementmode);
3313               }
3314             }
3315           }
3316         }
3317       }
3318     }
3319   }
3320 
3321   private class BabinetRegion extends ParallelRegion {
3322 
3323     BabinetLoop[] babinetLoops;
3324 
3325     public BabinetRegion(int nThreads) {
3326       babinetLoops = new BabinetLoop[nThreads];
3327     }
3328 
3329     @Override
3330     public void run() {
3331       int ti = getThreadIndex();
3332 
3333       if (babinetLoops[ti] == null) {
3334         babinetLoops[ti] = new BabinetLoop();
3335       }
3336 
3337       try {
3338         execute(0, fftZ - 1, babinetLoops[ti]);
3339       } catch (Exception e) {
3340         logger.info(e.toString());
3341       }
3342     }
3343 
3344     private class BabinetLoop extends IntegerForLoop {
3345 
3346       @Override
3347       public void run(int lb, int ub) {
3348         switch (solventModel) {
3349           case BINARY:
3350           case POLYNOMIAL:
3351             for (int k = lb; k <= ub; k++) {
3352               for (int j = 0; j < fftY; j++) {
3353                 for (int i = 0; i < fftX; i++) {
3354                   final int ii = interleavedIndex(i, j, k, fftX, fftY);
3355                   densityGrid[ii] = 1.0 - getDensityGrid()[ii];
3356                 }
3357               }
3358             }
3359             break;
3360           case GAUSSIAN:
3361             for (int k = lb; k <= ub; k++) {
3362               for (int j = 0; j < fftY; j++) {
3363                 for (int i = 0; i < fftX; i++) {
3364                   final int ii = interleavedIndex(i, j, k, fftX, fftY);
3365                   densityGrid[ii] = 1.0 - exp(-solventA * getDensityGrid()[ii]);
3366                 }
3367               }
3368             }
3369             break;
3370           default:
3371         }
3372       }
3373     }
3374   }
3375 
3376   private class SolventGridRegion extends ParallelRegion {
3377 
3378     SolventGridLoop[] solventGridLoops;
3379 
3380     public SolventGridRegion(int nThreads) {
3381       solventGridLoops = new SolventGridLoop[nThreads];
3382     }
3383 
3384     @Override
3385     public void run() {
3386       int ti = getThreadIndex();
3387 
3388       if (solventGridLoops[ti] == null) {
3389         solventGridLoops[ti] = new SolventGridLoop();
3390       }
3391 
3392       try {
3393         execute(0, fftZ - 1, solventGridLoops[ti]);
3394       } catch (Exception e) {
3395         logger.info(e.toString());
3396       }
3397     }
3398 
3399     private class SolventGridLoop extends IntegerForLoop {
3400 
3401       @Override
3402       public void run(int lb, int ub) {
3403         // case GAUSSIAN:
3404         for (int k = lb; k <= ub; k++) {
3405           for (int j = 0; j < fftY; j++) {
3406             for (int i = 0; i < fftX; i++) {
3407               final int ii = interleavedIndex(i, j, k, fftX, fftY);
3408               solventGrid[ii] = exp(-solventA * getSolventGrid()[ii]);
3409             }
3410           }
3411         }
3412       }
3413     }
3414   }
3415 
3416   private class InitRegion extends ParallelRegion {
3417 
3418     InitLoop[] initLoops;
3419     FormFactorUpdateLoop[] formFactorUpdateLoops;
3420     int nHKL = reflectionList.hklList.size();
3421     double[][] hkldata = null;
3422 
3423     public InitRegion(int nThreads) {
3424       initLoops = new InitLoop[nThreads];
3425       formFactorUpdateLoops = new FormFactorUpdateLoop[nThreads];
3426     }
3427 
3428     @Override
3429     public void run() {
3430       int ti = getThreadIndex();
3431 
3432       if (initLoops[ti] == null) {
3433         initLoops[ti] = new InitLoop();
3434         formFactorUpdateLoops[ti] = new FormFactorUpdateLoop();
3435       }
3436 
3437       try {
3438         execute(0, nHKL - 1, initLoops[ti]);
3439         execute(0, nScatteringAtoms - 1, formFactorUpdateLoops[ti]);
3440       } catch (Exception e) {
3441         logger.info(e.toString());
3442       }
3443     }
3444 
3445     public void setHKL(double[][] hkldata) {
3446       this.hkldata = hkldata;
3447     }
3448 
3449     private class InitLoop extends IntegerForLoop {
3450 
3451       @Override
3452       public void run(int lb, int ub) {
3453         for (int i = lb; i <= ub; i++) {
3454           hkldata[i][0] = 0.0;
3455           hkldata[i][1] = 0.0;
3456         }
3457       }
3458     }
3459 
3460     private class FormFactorUpdateLoop extends IntegerForLoop {
3461 
3462       private final double[] xyz = new double[3];
3463 
3464       @Override
3465       public void run(int lb, int ub) {
3466         for (int iSymm = 0; iSymm < bulkNSymm; iSymm++) {
3467           for (int i = lb; i <= ub; i++) {
3468             xyz[0] = coordinates[iSymm][0][i];
3469             xyz[1] = coordinates[iSymm][1][i];
3470             xyz[2] = coordinates[iSymm][2][i];
3471             if (solventFormFactors != null) {
3472               solventFormFactors[iSymm][i].update(xyz);
3473             }
3474             if (atomFormFactors != null) {
3475               atomFormFactors[iSymm][i].update(xyz, bAdd);
3476             }
3477           }
3478         }
3479       }
3480     }
3481   }
3482 
3483   private class AtomicScaleRegion extends ParallelRegion {
3484 
3485     AtomicScaleLoop[] atomicScaleLoops;
3486     int nHKL = reflectionList.hklList.size();
3487     double[][] hklData = null;
3488     double scale;
3489 
3490     public AtomicScaleRegion(int nThreads) {
3491       atomicScaleLoops = new AtomicScaleLoop[nThreads];
3492     }
3493 
3494     @Override
3495     public void run() throws Exception {
3496       int ti = getThreadIndex();
3497 
3498       if (atomicScaleLoops[ti] == null) {
3499         atomicScaleLoops[ti] = new AtomicScaleLoop();
3500       }
3501 
3502       try {
3503         execute(0, nHKL - 1, atomicScaleLoops[ti]);
3504       } catch (Exception e) {
3505         logger.info(e.toString());
3506       }
3507     }
3508 
3509     public void setHKL(double[][] hkldata) {
3510       this.hklData = hkldata;
3511     }
3512 
3513     public void setScale(double scale) {
3514       this.scale = scale;
3515     }
3516 
3517     private class AtomicScaleLoop extends IntegerForLoop {
3518 
3519       ComplexNumber c;
3520 
3521       public AtomicScaleLoop() {
3522         c = new ComplexNumber();
3523       }
3524 
3525       @Override
3526       public void run(int lb, int ub) {
3527         for (int i = lb; i <= ub; i++) {
3528           HKL ih = reflectionList.hklList.get(i);
3529           double[] fc = hklData[ih.getIndex()];
3530           c.re(fc[0]);
3531           c.im(fc[1]);
3532           // Remove Badd
3533           double s = crystal.invressq(ih);
3534           c.timesIP(scale * exp(0.25 * bAdd * s));
3535           c.conjugateIP();
3536           fc[0] = c.re();
3537           fc[1] = c.im();
3538         }
3539       }
3540     }
3541   }
3542 
3543   private class SolventScaleRegion extends ParallelRegion {
3544 
3545     SolventScaleLoop[] solventScaleLoops;
3546     int nHKL = reflectionList.hklList.size();
3547     double[][] hkldata = null;
3548     double scale;
3549 
3550     public SolventScaleRegion(int nThreads) {
3551       solventScaleLoops = new SolventScaleLoop[nThreads];
3552     }
3553 
3554     @Override
3555     public void run() throws Exception {
3556       int ti = getThreadIndex();
3557 
3558       if (solventScaleLoops[ti] == null) {
3559         solventScaleLoops[ti] = new SolventScaleLoop();
3560       }
3561 
3562       try {
3563         execute(0, nHKL - 1, solventScaleLoops[ti]);
3564       } catch (Exception e) {
3565         logger.info(e.toString());
3566       }
3567     }
3568 
3569     public void setHKL(double[][] hkldata) {
3570       this.hkldata = hkldata;
3571     }
3572 
3573     public void setScale(double scale) {
3574       this.scale = scale;
3575     }
3576 
3577     private class SolventScaleLoop extends IntegerForLoop {
3578 
3579       ComplexNumber c;
3580 
3581       public SolventScaleLoop() {
3582         c = new ComplexNumber();
3583       }
3584 
3585       @Override
3586       public void run(int lb, int ub) throws Exception {
3587         for (int i = lb; i <= ub; i++) {
3588           HKL ih = reflectionList.hklList.get(i);
3589           double[] fc = hkldata[ih.getIndex()];
3590           c.re(fc[0]);
3591           c.im(fc[1]);
3592           c.timesIP(scale);
3593           c.conjugateIP();
3594           // negative: babinet
3595           fc[0] = -c.re();
3596           fc[1] = -c.im();
3597         }
3598       }
3599     }
3600   }
3601 
3602   private class ExtractRegion extends ParallelRegion {
3603 
3604     ExtractLoop[] extractLoops;
3605     int nHKL = reflectionList.hklList.size();
3606     double[][] hkldata = null;
3607 
3608     public ExtractRegion(int nThreads) {
3609       extractLoops = new ExtractLoop[nThreads];
3610     }
3611 
3612     @Override
3613     public void run() throws Exception {
3614       int ti = getThreadIndex();
3615 
3616       if (extractLoops[ti] == null) {
3617         extractLoops[ti] = new ExtractLoop();
3618       }
3619 
3620       try {
3621         execute(0, nHKL - 1, extractLoops[ti]);
3622       } catch (Exception e) {
3623         logger.info(e.toString());
3624       }
3625     }
3626 
3627     public void setHKL(double[][] hkldata) {
3628       this.hkldata = hkldata;
3629     }
3630 
3631     private class ExtractLoop extends IntegerForLoop {
3632 
3633       ComplexNumber c;
3634       int nsym;
3635       List<SymOp> symops;
3636       HKL ij;
3637 
3638       public ExtractLoop() {
3639         c = new ComplexNumber();
3640         nsym = crystal.spaceGroup.symOps.size();
3641         symops = crystal.spaceGroup.symOps;
3642         ij = new HKL();
3643       }
3644 
3645       @Override
3646       public void run(int lb, int ub) throws Exception {
3647         for (int i = lb; i <= ub; i++) {
3648           HKL ih = reflectionList.hklList.get(i);
3649           double[] fc = hkldata[ih.getIndex()];
3650           // Apply symmetry
3651           for (int j = 0; j < nsym; j++) {
3652             SymOp symOp = symops.get(j);
3653             applyTransSymRot(ih, ij, symOp);
3654             double shift = symOp.symPhaseShift(ih);
3655             int h = mod(ij.getH(), fftX);
3656             int k = mod(ij.getK(), fftY);
3657             int l = mod(ij.getL(), fftZ);
3658             if (h < halfFFTX + 1) {
3659               final int ii = interleavedIndex(h, k, l, fftX, fftY);
3660               c.re(getDensityGrid()[ii]);
3661               c.im(getDensityGrid()[ii + 1]);
3662             } else {
3663               h = (fftX - h) % fftX;
3664               k = (fftY - k) % fftY;
3665               l = (fftZ - l) % fftZ;
3666               final int ii = interleavedIndex(h, k, l, fftX, fftY);
3667               c.re(getDensityGrid()[ii]);
3668               c.im(-getDensityGrid()[ii + 1]);
3669             }
3670             c.phaseShiftIP(shift);
3671             fc[0] += c.re();
3672             fc[1] += c.im();
3673           }
3674         }
3675       }
3676     }
3677   }
3678 
3679   private class ArrayCopyRegion extends ParallelRegion {
3680 
3681     ArrayCopyLoop[] arrayCopyLoops;
3682 
3683     public ArrayCopyRegion() {
3684       arrayCopyLoops = new ArrayCopyLoop[threadCount];
3685     }
3686 
3687     @Override
3688     public void run() {
3689       int ti = getThreadIndex();
3690 
3691       if (arrayCopyLoops[ti] == null) {
3692         arrayCopyLoops[ti] = new ArrayCopyLoop();
3693       }
3694 
3695       try {
3696         execute(0, complexFFT3DSpace - 1, arrayCopyLoops[ti]);
3697       } catch (Exception e) {
3698         logger.info(e.toString());
3699       }
3700     }
3701 
3702     private class ArrayCopyLoop extends IntegerForLoop {
3703 
3704       @Override
3705       public void run(int lb, int ub) {
3706         int length = ub - lb + 1;
3707         arraycopy(getDensityGrid(), lb, getSolventGrid(), lb, length);
3708       }
3709     }
3710   }
3711 }