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 static java.lang.String.format;
41  import static org.apache.commons.math3.util.FastMath.PI;
42  import static org.apache.commons.math3.util.FastMath.exp;
43  import static org.apache.commons.math3.util.FastMath.sqrt;
44  import static org.apache.commons.math3.util.FastMath.tanh;
45  
46  import edu.rit.pj.IntegerForLoop;
47  import edu.rit.pj.ParallelRegion;
48  import edu.rit.pj.reduction.SharedDouble;
49  import ffx.potential.bonded.Angle;
50  import ffx.potential.bonded.Atom;
51  import ffx.potential.bonded.Bond;
52  import ffx.potential.bonded.Torsion;
53  import java.util.List;
54  import java.util.logging.Level;
55  import java.util.logging.Logger;
56  
57  /**
58   * Initial implementation of a Hydrophobic PMF.
59   *
60   * @since 1.0
61   */
62  public class HydrophobicPMFRegion extends ParallelRegion {
63  
64    private static final Logger logger = Logger.getLogger(HydrophobicPMFRegion.class.getName());
65    // Radius of a carbon atom.
66    private final double rCarbon = 1.7;
67    // Radius of a water molecule.
68    private final double rWater = 1.4;
69    // Constant for calculation of atomic surface area.
70    private final double safact = 0.3516;
71    // Surface area of a hydrophobic carbon atom.
72    private final double acSurf = 120.7628;
73    // tanh slope (set very steep).
74    private final double tSlope = 100.0;
75    // Shift the tanh plot along the x-axis.
76    private final double tOffset = 6.0;
77    // Cutoff distance for pairwise HPMF interactions.
78    private final double hpmfCut = 11.0;
79    // Cutoff squared
80    private final double hpmfCut2 = hpmfCut * hpmfCut;
81    // Hydrophobic PMF well depth parameter.
82    private final double h1 = -0.7308004860404441194;
83    private final double h2 = 0.2001645051578760659;
84    private final double h3 = -0.0905499953418473502;
85    // Hydrophobic PMF well center point.
86    private final double c1 = 3.8167879266271396155;
87    private final double c2 = 5.4669162286016419472;
88    private final double c3 = 7.1167694861385353278;
89    // Reciprocal of the hydrophobic PMF well width.
90    private final double w1 = 1.6858993102248638341;
91    private final double w2 = 1.3906405621629980285;
92    private final double w3 = 1.5741657341338335385;
93    private final double rSurf = rCarbon + 2.0 * rWater;
94    private final double piSurf = PI * (rCarbon + rWater);
95    // Radius of each atom for use with hydrophobic PMF.
96    private final double[] rPMF;
97    // Number of hydrophobic carbon atoms in the system.
98    private final int nCarbon;
99    // Number of the atom for each HPMF carbon atom site.
100   private final int[] iCarbon;
101   // SASA value for each hydrophobic PMF carbon atom
102   private final double[] carbonSASA;
103   // SASA value
104   private final double[] tanhSA;
105   // Loop to find the SASA value of each hydrophobic carbon.
106   private final CarbonSASALoop[] carbonSASALoop;
107   // Loop to find the hydrophobic energy.
108   private final HydrophobicPMFLoop[] hydrophobicPMFLoop;
109   // Loop to find the SASA chain rule derivatives.
110   private final CarbonSASACRLoop[] carbonSASACRLoop;
111   // Shared energy variable.
112   private final SharedDouble sharedEnergy;
113   private final double[] dtanhSA;
114   private final double[] sasa;
115   private final double[] carbonSASACR;
116   private Atom[] atoms;
117   private int nAtoms;
118   private double[] x, y, z;
119   private boolean[] use;
120   private double[][][] grad;
121   private boolean gradient;
122 
123   public HydrophobicPMFRegion(
124       Atom[] atoms, double[] x, double[] y, double[] z, boolean[] use, double[][][] grad, int nt) {
125     logger.info(format(" Hydrophobic PMF cut-off:              %8.2f (A)", hpmfCut));
126 
127     this.atoms = atoms;
128     nAtoms = atoms.length;
129     this.x = x;
130     this.y = y;
131     this.z = z;
132     this.use = use;
133     this.grad = grad;
134 
135     /** Count hydrophobic carbons. */
136     int count = 0;
137     for (int i = 0; i < nAtoms; i++) {
138       Atom atom = atoms[i];
139       int atomicNumber = atom.getAtomicNumber();
140       if (atomicNumber == 6) {
141         List<Bond> bonds = atom.getBonds();
142         int bondCount = bonds.size();
143         if (bondCount <= 2) {
144           continue;
145         }
146         boolean keep = true;
147         for (int j = 0; j < bondCount; j++) {
148           Atom atom2 = bonds.get(j).get1_2(atom);
149           int atomicNumber2 = atom2.getAtomicNumber();
150           if (bondCount == 3 && atomicNumber2 == 8) {
151             keep = false;
152             break;
153           }
154         }
155         if (keep) {
156           count++;
157         }
158       }
159     }
160     /** Allocate arrays. */
161     rPMF = new double[nAtoms];
162     nCarbon = count;
163     iCarbon = new int[nCarbon];
164     carbonSASA = new double[nCarbon];
165     carbonSASACR = new double[nCarbon];
166     tanhSA = new double[nCarbon];
167     dtanhSA = new double[nCarbon];
168     sasa = new double[nCarbon];
169 
170     carbonSASALoop = new CarbonSASALoop[nt];
171     hydrophobicPMFLoop = new HydrophobicPMFLoop[nt];
172     carbonSASACRLoop = new CarbonSASACRLoop[nt];
173     for (int i = 0; i < nt; i++) {
174       carbonSASALoop[i] = new CarbonSASALoop();
175       hydrophobicPMFLoop[i] = new HydrophobicPMFLoop();
176       carbonSASACRLoop[i] = new CarbonSASACRLoop();
177     }
178     sharedEnergy = new SharedDouble();
179 
180     /** Assign hydrophobic carbon values. */
181     int index = 0;
182     for (int i = 0; i < nAtoms; i++) {
183       Atom atom = atoms[i];
184       int atomicNumber = atom.getAtomicNumber();
185       if (atomicNumber == 6) {
186         int nh = 0;
187         List<Bond> bonds = atom.getBonds();
188         int bondCount = bonds.size();
189         if (bondCount <= 2) {
190           continue;
191         }
192         boolean keep = true;
193         for (int j = 0; j < bondCount; j++) {
194           Atom atom2 = bonds.get(j).get1_2(atom);
195           int atomicNumber2 = atom2.getAtomicNumber();
196           if (atomicNumber2 == 1) {
197             nh++;
198           }
199           if (bondCount == 3 && atomicNumber2 == 8) {
200             keep = false;
201           }
202         }
203         if (keep) {
204           iCarbon[index] = i;
205           carbonSASA[index] = 1.0;
206           if (bondCount == 3 && nh == 0) {
207             carbonSASA[index] = 1.554;
208           } else if (bondCount == 3 && nh == 1) {
209             carbonSASA[index] = 1.073;
210           } else if (bondCount == 4 && nh == 1) {
211             carbonSASA[index] = 1.276;
212           } else if (bondCount == 4 && nh == 2) {
213             carbonSASA[index] = 1.045;
214           } else if (bondCount == 4 && nh == 3) {
215             carbonSASA[index] = 0.880;
216           }
217           carbonSASA[index] = carbonSASA[index] * safact / acSurf;
218           if (logger.isLoggable(Level.FINEST)) {
219             logger.finest(
220                 format(
221                     " %d Base HPMF SASA for atom %d: %10.8f", index + 1, i + 1, carbonSASA[index]));
222           }
223           index++;
224         }
225       }
226     }
227 
228     /** Assign HPMF atomic radii from traditional Bondi values */
229     for (int i = 0; i < nAtoms; i++) {
230       rPMF[i] = 2.0;
231       int atmnum = atoms[i].getAtomicNumber();
232       switch (atmnum) {
233         case 0:
234           rPMF[i] = 0.0;
235           break;
236         case 1:
237           rPMF[i] = 1.20;
238           break;
239         case 2:
240           rPMF[i] = 1.40;
241           break;
242         case 5:
243           rPMF[i] = 1.80;
244           break;
245         case 6:
246           rPMF[i] = 1.70;
247           break;
248         case 7:
249           rPMF[i] = 1.55;
250           break;
251         case 8:
252           rPMF[i] = 1.50;
253           break;
254         case 9:
255           rPMF[i] = 1.47;
256           break;
257         case 10:
258           rPMF[i] = 1.54;
259           break;
260         case 14:
261           rPMF[i] = 2.10;
262           break;
263         case 15:
264           rPMF[i] = 1.80;
265           break;
266         case 16:
267           rPMF[i] = 1.80;
268           break;
269         case 17:
270           rPMF[i] = 1.75;
271           break;
272         case 18:
273           rPMF[i] = 1.88;
274           break;
275         case 34:
276           rPMF[i] = 1.90;
277           break;
278         case 35:
279           rPMF[i] = 1.85;
280           break;
281         case 36:
282           rPMF[i] = 2.02;
283           break;
284         case 53:
285           rPMF[i] = 1.98;
286           break;
287         case 54:
288           rPMF[i] = 2.16;
289           break;
290       }
291     }
292   }
293 
294   public double getEnergy() {
295     return sharedEnergy.get();
296   }
297 
298   @Override
299   public void run() {
300     int ti = getThreadIndex();
301     try {
302       execute(0, nCarbon - 1, carbonSASALoop[ti]);
303       execute(0, nCarbon - 2, hydrophobicPMFLoop[ti]);
304       if (gradient) {
305         execute(0, nCarbon - 1, carbonSASACRLoop[ti]);
306       }
307     } catch (Exception e) {
308       String message = "Fatal exception computing Born radii in thread " + ti + "\n";
309       logger.log(Level.SEVERE, message, e);
310     }
311   }
312 
313   public void setGradient(boolean gradient) {
314     this.gradient = gradient;
315   }
316 
317   @Override
318   public void start() {
319     sharedEnergy.set(0);
320   }
321 
322   /**
323    * Compute Hydrophobic PMF radii.
324    *
325    * @since 1.0
326    */
327   private class CarbonSASALoop extends IntegerForLoop {
328 
329     @Override
330     public void run(int lb, int ub) {
331       /** Get the surface area for each hydrophobic carbon atom. */
332       for (int ii = lb; ii <= ub; ii++) {
333         final int i = iCarbon[ii];
334         if (!use[i]) {
335           continue;
336         }
337         final double carbonSA = carbonSASA[ii];
338         double sa = acSurf;
339         final double xi = x[i];
340         final double yi = y[i];
341         final double zi = z[i];
342         int count = 0;
343         for (int k = 0; k < nAtoms; k++) {
344           if (i != k && use[k]) {
345             final double xr = x[k] - xi;
346             final double yr = y[k] - yi;
347             final double zr = z[k] - zi;
348             final double r2 = xr * xr + yr * yr + zr * zr;
349             double rk = rPMF[k];
350             double rBig = rk + rSurf;
351             if (r2 < rBig * rBig) {
352               final double r = sqrt(r2);
353               final double rSmall = rk - rCarbon;
354               final double part = piSurf * (rBig - r) * (1.0 + rSmall / r);
355               sa *= (1.0 - carbonSA * part);
356               count++;
357             }
358           }
359         }
360         sasa[ii] = sa;
361         // sasa[ii] = carbonSA;
362         double tSA = tanh(tSlope * (sa - tOffset));
363         tanhSA[ii] = 0.5 * (1.0 + tSA);
364         dtanhSA[ii] = 0.5 * tSlope * (1.0 - tSA * tSA);
365       }
366     }
367   }
368 
369   /**
370    * Compute Born radii for a range of atoms via the Grycuk method.
371    *
372    * @since 1.0
373    */
374   private class HydrophobicPMFLoop extends IntegerForLoop {
375 
376     // Omit
377     private final int[] omit;
378     private double energy;
379     private double[] gX;
380     private double[] gY;
381     private double[] gZ;
382 
383     public HydrophobicPMFLoop() {
384       omit = new int[nAtoms];
385     }
386 
387     @Override
388     public void finish() {
389       sharedEnergy.addAndGet(energy);
390     }
391 
392     @Override
393     public void run(int lb, int ub) {
394       /** Hydrophobic PME energy. */
395       for (int ii = lb; ii <= ub; ii++) {
396         final int i = iCarbon[ii];
397         if (!use[i]) {
398           continue;
399         }
400         double tanhSAi = tanhSA[ii];
401         Atom ssAtom = null;
402         Atom atom = atoms[i];
403         List<Bond> bonds = atom.getBonds();
404         for (Bond bond : bonds) {
405           Atom atom2 = bond.get1_2(atom);
406           int k = atom2.getIndex() - 1;
407           if (!use[k]) {
408             continue;
409           }
410           omit[k] = i;
411           if (atom2.getAtomicNumber() == 16) {
412             ssAtom = atom2;
413           }
414         }
415         List<Angle> angles = atom.getAngles();
416         for (Angle angle : angles) {
417           Atom atom2 = angle.get1_3(atom);
418           if (atom2 != null) {
419             int k = atom2.getIndex() - 1;
420             if (!use[k]) {
421               continue;
422             }
423             omit[k] = i;
424           }
425         }
426         List<Torsion> torsions = atom.getTorsions();
427         for (Torsion torsion : torsions) {
428           Atom atom2 = torsion.get1_4(atom);
429           if (atom2 != null) {
430             int k = atom2.getIndex() - 1;
431             if (!use[k]) {
432               continue;
433             }
434             omit[k] = i;
435             if (ssAtom != null) {
436               List<Bond> bonds2 = atom2.getBonds();
437               for (Bond bond : bonds2) {
438                 Atom s = bond.get1_2(atom2);
439                 if (s.getAtomicNumber() == 16) {
440                   List<Bond> sBonds = s.getBonds();
441                   for (Bond sBond : sBonds) {
442                     Atom s2 = sBond.get1_2(s);
443                     if (s2 == ssAtom) {
444                       omit[k] = -1;
445                     }
446                   }
447                 }
448               }
449             }
450           }
451         }
452         final double xi = x[i];
453         final double yi = y[i];
454         final double zi = z[i];
455         double e = 0.0;
456         for (int kk = ii + 1; kk < nCarbon; kk++) {
457           int k = iCarbon[kk];
458           if (!use[k]) {
459             continue;
460           }
461           if (omit[k] != i) {
462             final double xr = xi - x[k];
463             final double yr = yi - y[k];
464             final double zr = zi - z[k];
465             final double r2 = xr * xr + yr * yr + zr * zr;
466             if (r2 < hpmfCut2) {
467               final double r = sqrt(r2);
468               final double a1 = (r - c1) * w1;
469               final double a2 = (r - c2) * w2;
470               final double a3 = (r - c3) * w3;
471               final double e1 = h1 * exp(-a1 * a1);
472               final double e2 = h2 * exp(-a2 * a2);
473               final double e3 = h3 * exp(-a3 * a3);
474               final double t1t2 = tanhSAi * tanhSA[kk];
475               final double sum = (e1 + e2 + e3);
476               e += sum * t1t2;
477               if (gradient) {
478                 /** First part of hydrophobic PMF derivative calculation. */
479                 double de1 = -2.0 * e1 * a1 * w1;
480                 double de2 = -2.0 * e2 * a2 * w2;
481                 double de3 = -2.0 * e3 * a3 * w3;
482                 double dsum = (de1 + de2 + de3) * t1t2 / r;
483                 double dedx = dsum * xr;
484                 double dedy = dsum * yr;
485                 double dedz = dsum * zr;
486                 gX[i] += dedx;
487                 gY[i] += dedy;
488                 gZ[i] += dedz;
489                 gX[k] -= dedx;
490                 gY[k] -= dedy;
491                 gZ[k] -= dedz;
492                 /** Chain Rule Term. */
493                 carbonSASACR[ii] += sum * tanhSA[kk] * dtanhSA[ii];
494                 carbonSASACR[kk] += sum * tanhSA[ii] * dtanhSA[kk];
495               }
496             }
497           }
498         }
499         energy += e;
500       }
501     }
502 
503     @Override
504     public void start() {
505       energy = 0.0;
506       for (int i = 0; i < nAtoms; i++) {
507         omit[i] = -1;
508       }
509       int threadID = getThreadIndex();
510       gX = grad[threadID][0];
511       gY = grad[threadID][1];
512       gZ = grad[threadID][2];
513       if (gradient) {
514         for (int i = 0; i < nCarbon; i++) {
515           carbonSASACR[i] = 0.0;
516         }
517       }
518     }
519   }
520 
521   /**
522    * Compute Hydrophobic PMF chain rule term.
523    *
524    * @since 1.0
525    */
526   private class CarbonSASACRLoop extends IntegerForLoop {
527 
528     private double[] gX;
529     private double[] gY;
530     private double[] gZ;
531 
532     public CarbonSASACRLoop() {}
533 
534     @Override
535     public void run(int lb, int ub) {
536       for (int ii = lb; ii <= ub; ii++) {
537         final int i = iCarbon[ii];
538         if (!use[i]) {
539           continue;
540         }
541         final double carbonSA = carbonSASA[ii];
542         final double xi = x[i];
543         final double yi = y[i];
544         final double zi = z[i];
545         for (int k = 0; k < nAtoms; k++) {
546           if (i != k && use[k]) {
547             final double xr = xi - x[k];
548             final double yr = yi - y[k];
549             final double zr = zi - z[k];
550             final double r2 = xr * xr + yr * yr + zr * zr;
551             double rk = rPMF[k];
552             double rBig = rk + rSurf;
553             if (r2 <= rBig * rBig) {
554               final double r = sqrt(r2);
555               final double rSmall = rk - rCarbon;
556               final double rr = 1.0 / r;
557               final double rr2 = rr * rr;
558               final double part = piSurf * (rBig - r) * (1.0 + rSmall * rr);
559               double t1b = -piSurf * (1.0 + rBig * rSmall * rr2);
560               double t1a = -sasa[ii] / (1.0 / carbonSA - part);
561               double de = t1a * t1b * rr * carbonSASACR[ii];
562               double dedx = de * xr;
563               double dedy = de * yr;
564               double dedz = de * zr;
565               gX[i] += dedx;
566               gY[i] += dedy;
567               gZ[i] += dedz;
568               gX[k] -= dedx;
569               gY[k] -= dedy;
570               gZ[k] -= dedz;
571             }
572           }
573         }
574       }
575     }
576 
577     @Override
578     public void start() {
579       int threadID = getThreadIndex();
580       gX = grad[threadID][0];
581       gY = grad[threadID][1];
582       gZ = grad[threadID][2];
583     }
584   }
585 }