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-2024.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.potential.nonbonded.implicit;
39  
40  import edu.rit.pj.IntegerForLoop;
41  import edu.rit.pj.ParallelRegion;
42  import ffx.crystal.Crystal;
43  import ffx.crystal.SymOp;
44  import ffx.numerics.atomic.AtomicDoubleArray3D;
45  import ffx.potential.bonded.Atom;
46  
47  import java.util.List;
48  import java.util.logging.Level;
49  import java.util.logging.Logger;
50  
51  import static ffx.numerics.multipole.GKSource.cn;
52  import static org.apache.commons.math3.util.FastMath.exp;
53  import static org.apache.commons.math3.util.FastMath.sqrt;
54  
55  /**
56   * Parallel calculation of the Generalized Kirkwood induced reaction field.
57   *
58   * @author Michael J. Schnieders
59   * @since 1.0
60   */
61  public class InducedGKFieldRegion extends ParallelRegion {
62  
63    private static final Logger logger = Logger.getLogger(InducedGKFieldRegion.class.getName());
64    /** Empirical constant that controls the GK cross-term. */
65    private final double gkc;
66    /** Kirkwood dipole reaction field constant. */
67    private final double fd;
68    /** Kirkwood quadrupole reaction field constant. */
69    private final double fq;
70  
71    private final InducedGKFieldLoop[] inducedGKFieldLoop;
72    /** An ordered array of atoms in the system. */
73    protected Atom[] atoms;
74    /** Periodic boundary conditions and symmetry. */
75    private Crystal crystal;
76    /** Induced dipoles for each symmetry operator. */
77    private double[][][] inducedDipole;
78    /** Induced dipole chain rule terms for each symmetry operator. */
79    private double[][][] inducedDipoleCR;
80    /** Atomic coordinates for each symmetry operator. */
81    private double[][][] sXYZ;
82    /** Neighbor lists for each atom and symmetry operator. */
83    private int[][][] neighborLists;
84    /** Flag to indicate if an atom should be included. */
85    private boolean[] use = null;
86    /** GK cut-off distance squared. */
87    private double cut2;
88    /** Born radius of each atom. */
89    private double[] born;
90    /** Atomic GK field array. */
91    private AtomicDoubleArray3D sharedGKField;
92    /** Atomic GK field chain-rule array. */
93    private AtomicDoubleArray3D sharedGKFieldCR;
94  
95    /**
96     * Compute the GK field due to induced dipoles.
97     *
98     * @param nt  Number of threads.
99     * @param soluteDieletric The solute dielectric.
100    * @param solventDieletric The solvent dielectric.
101    * @param gkc The generalizing function parameter.
102    */
103   public InducedGKFieldRegion(int nt, double soluteDieletric, double solventDieletric, double gkc) {
104     // Set the Kirkwood multipolar reaction field constants.
105     fd = cn(1, soluteDieletric, solventDieletric);
106     fq = cn(2, soluteDieletric, solventDieletric);
107     this.gkc = gkc;
108     inducedGKFieldLoop = new InducedGKFieldLoop[nt];
109     for (int i = 0; i < nt; i++) {
110       inducedGKFieldLoop[i] = new InducedGKFieldLoop();
111     }
112   }
113 
114   public void init(
115       Atom[] atoms,
116       double[][][] inducedDipole,
117       double[][][] inducedDipoleCR,
118       Crystal crystal,
119       double[][][] sXYZ,
120       int[][][] neighborLists,
121       boolean[] use,
122       double cut2,
123       double[] born,
124       AtomicDoubleArray3D sharedGKField,
125       AtomicDoubleArray3D sharedGKFieldCR) {
126     this.atoms = atoms;
127     this.inducedDipole = inducedDipole;
128     this.inducedDipoleCR = inducedDipoleCR;
129     this.crystal = crystal;
130     this.sXYZ = sXYZ;
131     this.neighborLists = neighborLists;
132     this.use = use;
133     this.cut2 = cut2;
134     this.born = born;
135     this.sharedGKField = sharedGKField;
136     this.sharedGKFieldCR = sharedGKFieldCR;
137   }
138 
139   @Override
140   public void run() {
141     try {
142       int nAtoms = atoms.length;
143       int threadIndex = getThreadIndex();
144       execute(0, nAtoms - 1, inducedGKFieldLoop[threadIndex]);
145     } catch (Exception e) {
146       String message = "Fatal exception computing GK field in thread " + getThreadIndex() + "\n";
147       logger.log(Level.SEVERE, message, e);
148     }
149   }
150 
151   /**
152    * Compute the Generalized Kirkwood induced reaction field.
153    *
154    * @since 1.0
155    */
156   private class InducedGKFieldLoop extends IntegerForLoop {
157 
158     private final double[][] a;
159     private final double[] gux;
160     private final double[] guy;
161     private final double[] guz;
162     private final double[] dx_local;
163     private int threadID;
164     private double xi, yi, zi;
165     private double uix, uiy, uiz;
166     private double uixCR, uiyCR, uizCR;
167     private double rbi;
168     private int iSymm;
169     private double[][] transOp;
170 
171     InducedGKFieldLoop() {
172       a = new double[3][2];
173       gux = new double[5];
174       guy = new double[5];
175       guz = new double[5];
176       dx_local = new double[3];
177       transOp = new double[3][3];
178     }
179 
180     @Override
181     public void run(int lb, int ub) {
182 
183       threadID = getThreadIndex();
184 
185       double[] x = sXYZ[0][0];
186       double[] y = sXYZ[0][1];
187       double[] z = sXYZ[0][2];
188 
189       int nSymm = crystal.spaceGroup.symOps.size();
190       List<SymOp> symOps = crystal.spaceGroup.symOps;
191       for (iSymm = 0; iSymm < nSymm; iSymm++) {
192         SymOp symOp = symOps.get(iSymm);
193         crystal.getTransformationOperator(symOp, transOp);
194         for (int i = lb; i <= ub; i++) {
195           if (!use[i]) {
196             continue;
197           }
198           xi = x[i];
199           yi = y[i];
200           zi = z[i];
201           uix = inducedDipole[0][i][0];
202           uiy = inducedDipole[0][i][1];
203           uiz = inducedDipole[0][i][2];
204           uixCR = inducedDipoleCR[0][i][0];
205           uiyCR = inducedDipoleCR[0][i][1];
206           uizCR = inducedDipoleCR[0][i][2];
207           rbi = born[i];
208           int[] list = neighborLists[iSymm][i];
209           for (int k : list) {
210             if (!use[k]) {
211               continue;
212             }
213             inducedGKField(i, k);
214           }
215 
216           // Include the self induced reaction field, which is not in the neighbor list.
217           if (iSymm == 0) {
218             inducedGKField(i, i);
219           }
220         }
221       }
222     }
223 
224     private void inducedGKField(int i, int k) {
225       dx_local[0] = sXYZ[iSymm][0][k] - xi;
226       dx_local[1] = sXYZ[iSymm][1][k] - yi;
227       dx_local[2] = sXYZ[iSymm][2][k] - zi;
228       final double r2 = crystal.image(dx_local);
229       if (r2 > cut2) {
230         return;
231       }
232       double xr = dx_local[0];
233       double yr = dx_local[1];
234       double zr = dx_local[2];
235       double xr2 = xr * xr;
236       double yr2 = yr * yr;
237       double zr2 = zr * zr;
238       final double ukx = inducedDipole[iSymm][k][0];
239       final double uky = inducedDipole[iSymm][k][1];
240       final double ukz = inducedDipole[iSymm][k][2];
241       final double ukxCR = inducedDipoleCR[iSymm][k][0];
242       final double ukyCR = inducedDipoleCR[iSymm][k][1];
243       final double ukzCR = inducedDipoleCR[iSymm][k][2];
244       final double rbk = born[k];
245       final double rb2 = rbi * rbk;
246       final double expterm = exp(-r2 / (gkc * rb2));
247       final double expc = expterm / gkc;
248       final double expc1 = 1.0 - expc;
249       final double gf2 = 1.0 / (r2 + rb2 * expterm);
250       final double gf = sqrt(gf2);
251       final double gf3 = gf2 * gf;
252       final double gf5 = gf3 * gf2;
253 
254       // Reaction potential auxiliary terms.
255       a[1][0] = -gf3;
256       a[2][0] = 3.0 * gf5;
257 
258       // Reaction potential gradient auxiliary term.
259       a[1][1] = expc1 * a[2][0];
260 
261       // Multiply the potential auxiliary terms by their dielectric functions.
262       a[1][0] = fd * a[1][0];
263       a[1][1] = fd * a[1][1];
264       a[2][0] = fq * a[2][0];
265 
266       // Unweighted reaction potential gradient tensor.
267       gux[2] = a[1][0] + xr2 * a[1][1];
268       gux[3] = xr * yr * a[1][1];
269       gux[4] = xr * zr * a[1][1];
270       guy[2] = gux[3];
271       guy[3] = a[1][0] + yr2 * a[1][1];
272       guy[4] = yr * zr * a[1][1];
273       guz[2] = gux[4];
274       guz[3] = guy[4];
275       guz[4] = a[1][0] + zr2 * a[1][1];
276 
277       // Compute the reaction field due to induced dipoles.
278       double fix = ukx * gux[2] + uky * guy[2] + ukz * guz[2];
279       double fiy = ukx * gux[3] + uky * guy[3] + ukz * guz[3];
280       double fiz = ukx * gux[4] + uky * guy[4] + ukz * guz[4];
281       double fkx = uix * gux[2] + uiy * guy[2] + uiz * guz[2];
282       double fky = uix * gux[3] + uiy * guy[3] + uiz * guz[3];
283       double fkz = uix * gux[4] + uiy * guy[4] + uiz * guz[4];
284       double fixCR = ukxCR * gux[2] + ukyCR * guy[2] + ukzCR * guz[2];
285       double fiyCR = ukxCR * gux[3] + ukyCR * guy[3] + ukzCR * guz[3];
286       double fizCR = ukxCR * gux[4] + ukyCR * guy[4] + ukzCR * guz[4];
287       double fkxCR = uixCR * gux[2] + uiyCR * guy[2] + uizCR * guz[2];
288       double fkyCR = uixCR * gux[3] + uiyCR * guy[3] + uizCR * guz[3];
289       double fkzCR = uixCR * gux[4] + uiyCR * guy[4] + uizCR * guz[4];
290 
291       // Scale the self-field by half, such that it sums to one below.
292       if (i == k) {
293         fix *= 0.5;
294         fiy *= 0.5;
295         fiz *= 0.5;
296         fkx *= 0.5;
297         fky *= 0.5;
298         fkz *= 0.5;
299         fixCR *= 0.5;
300         fiyCR *= 0.5;
301         fizCR *= 0.5;
302         fkxCR *= 0.5;
303         fkyCR *= 0.5;
304         fkzCR *= 0.5;
305       }
306 
307       double xc = fkx;
308       double yc = fky;
309       double zc = fkz;
310       fkx = xc * transOp[0][0] + yc * transOp[1][0] + zc * transOp[2][0];
311       fky = xc * transOp[0][1] + yc * transOp[1][1] + zc * transOp[2][1];
312       fkz = xc * transOp[0][2] + yc * transOp[1][2] + zc * transOp[2][2];
313       sharedGKField.add(threadID, i, fix, fiy, fiz);
314       sharedGKField.add(threadID, k, fkx, fky, fkz);
315 
316       xc = fkxCR;
317       yc = fkyCR;
318       zc = fkzCR;
319       fkxCR = xc * transOp[0][0] + yc * transOp[1][0] + zc * transOp[2][0];
320       fkyCR = xc * transOp[0][1] + yc * transOp[1][1] + zc * transOp[2][1];
321       fkzCR = xc * transOp[0][2] + yc * transOp[1][2] + zc * transOp[2][2];
322       sharedGKFieldCR.add(threadID, i, fixCR, fiyCR, fizCR);
323       sharedGKFieldCR.add(threadID, k, fkxCR, fkyCR, fkzCR);
324     }
325   }
326 }