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