1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package ffx.potential.nonbonded.implicit;
39
40 import ffx.numerics.atomic.AtomicDoubleArray3D;
41 import ffx.numerics.switching.MultiplicativeSwitch;
42 import ffx.potential.bonded.Atom;
43 import ffx.potential.nonbonded.GeneralizedKirkwood;
44 import ffx.potential.parameters.ForceField;
45
46 import java.util.logging.Level;
47 import java.util.logging.Logger;
48
49 import static java.lang.String.format;
50 import static org.apache.commons.math3.util.FastMath.PI;
51 import static org.apache.commons.math3.util.FastMath.cbrt;
52 import static org.apache.commons.math3.util.FastMath.pow;
53 import static org.apache.commons.math3.util.FastMath.sqrt;
54
55
56
57
58
59
60
61
62 public class ChandlerCavitation {
63
64 private static final Logger logger = Logger.getLogger(ChandlerCavitation.class.getName());
65
66 private final double HALF_SWITCH_RANGE = 3.5;
67
68
69
70
71
72 private final double SA_SWITCH_OFFSET = 0.2;
73
74 private final boolean doVolume;
75 private final boolean doSurfaceArea;
76
77 private final GaussVol gaussVol;
78 private final ConnollyRegion connollyRegion;
79
80 private double surfaceArea;
81
82 private double surfaceAreaEnergy;
83
84 private double volume;
85
86 private double volumeEnergy;
87
88 private double cavitationEnergy;
89
90
91
92
93
94
95
96
97
98
99 private double effectiveRadius;
100
101
102
103
104 private double solventPressure = GeneralizedKirkwood.DEFAULT_SOLVENT_PRESSURE;
105
106 private double surfaceTension = GeneralizedKirkwood.DEFAULT_CAVDISP_SURFACE_TENSION;
107
108
109
110 private double crossOver = GeneralizedKirkwood.DEFAULT_CROSSOVER;
111
112 private double beginVolumeOff = crossOver - HALF_SWITCH_RANGE;
113
114 private double endVolumeOff = beginVolumeOff + 2.0 * HALF_SWITCH_RANGE;
115
116 private double beginSurfaceAreaOff = crossOver + SA_SWITCH_OFFSET + HALF_SWITCH_RANGE;
117
118 private double endSurfaceAreaOff = beginSurfaceAreaOff - 2.0 * HALF_SWITCH_RANGE;
119
120 private MultiplicativeSwitch volumeSwitch =
121 new MultiplicativeSwitch(beginVolumeOff, endVolumeOff);
122
123 private MultiplicativeSwitch surfaceAreaSwitch =
124 new MultiplicativeSwitch(beginSurfaceAreaOff, endSurfaceAreaOff);
125
126 private final int nAtoms;
127 private final Atom[] atoms;
128
129 public ChandlerCavitation(Atom[] atoms, GaussVol gaussVol, ForceField forceField) {
130 this.atoms = atoms;
131 this.nAtoms = atoms.length;
132 this.gaussVol = gaussVol;
133 this.connollyRegion = null;
134
135 doVolume = forceField.getBoolean("VOLUMETERM", true);
136 doSurfaceArea = forceField.getBoolean("AREATERM", true);
137 }
138
139 public ChandlerCavitation(Atom[] atoms, ConnollyRegion connollyRegion, ForceField forceField) {
140 this.atoms = atoms;
141 this.nAtoms = atoms.length;
142 this.connollyRegion = connollyRegion;
143 this.gaussVol = null;
144
145 doVolume = forceField.getBoolean("VOLUMETERM", true);
146 doSurfaceArea = forceField.getBoolean("AREATERM", true);
147 }
148
149
150
151
152
153
154
155
156 public double energyAndGradient(double[][] positions, AtomicDoubleArray3D gradient) {
157 if (gaussVol != null) {
158 return energyAndGradientGausVol(positions, gradient);
159 } else {
160 return energyAndGradientConnolly(gradient);
161 }
162 }
163
164
165
166
167
168
169
170 public double energyAndGradientConnolly(AtomicDoubleArray3D gradient) {
171
172
173 cavitationEnergy = 0.0;
174
175 connollyRegion.init(atoms, true);
176 connollyRegion.runVolume();
177
178
179 surfaceArea = connollyRegion.getSurfaceArea();
180 surfaceAreaEnergy = surfaceArea * surfaceTension;
181
182
183 volume = connollyRegion.getVolume();
184 volumeEnergy = volume * solventPressure;
185
186
187
188
189
190
191
192
193
194
195 effectiveRadius = cbrt(3.0 * volume / (4.0 * PI));
196 double reff = effectiveRadius;
197 double reff2 = reff * reff;
198 double reff3 = reff2 * reff;
199 double reff4 = reff3 * reff;
200 double reff5 = reff4 * reff;
201 double vdWVolPI23 = pow(volume / PI, 2.0 / 3.0);
202 double dReffdvdW = 1.0 / (pow(6.0, 2.0 / 3.0) * PI * vdWVolPI23);
203
204
205 if (doVolume) {
206 if (!doSurfaceArea || reff < beginVolumeOff) {
207
208 cavitationEnergy = volumeEnergy;
209 double[][] volumeGradient = connollyRegion.getVolumeGradient();
210 for (int i = 0; i < nAtoms; i++) {
211 double dx = solventPressure * volumeGradient[0][i];
212 double dy = solventPressure * volumeGradient[1][i];
213 double dz = solventPressure * volumeGradient[2][i];
214 gradient.add(0, i, dx, dy, dz);
215 }
216 } else if (reff <= endVolumeOff) {
217
218 double taper = volumeSwitch.taper(reff, reff2, reff3, reff4, reff5);
219 cavitationEnergy = taper * volumeEnergy;
220 double dtaper = volumeSwitch.dtaper(reff, reff2, reff3, reff4) * dReffdvdW;
221 double factor = dtaper * volumeEnergy + taper * solventPressure;
222 double[][] volumeGradient = connollyRegion.getVolumeGradient();
223 for (int i = 0; i < nAtoms; i++) {
224 double dx = factor * volumeGradient[0][i];
225 double dy = factor * volumeGradient[1][i];
226 double dz = factor * volumeGradient[2][i];
227 gradient.add(0, i, dx, dy, dz);
228 }
229 }
230 }
231
232
233 if (doSurfaceArea) {
234 if (!doVolume || reff > beginSurfaceAreaOff) {
235
236 cavitationEnergy = surfaceAreaEnergy;
237
238 } else if (reff >= endSurfaceAreaOff) {
239
240 double taperSA = surfaceAreaSwitch.taper(reff, reff2, reff3, reff4, reff5);
241 cavitationEnergy += taperSA * surfaceAreaEnergy;
242
243
244 }
245 }
246
247 if (logger.isLoggable(Level.FINE)) {
248 logger.fine("\n Connolly");
249 logger.fine(format(" Volume: %8.3f (Ang^3)", volume));
250 logger.fine(format(" Volume Energy: %8.3f (kcal/mol)", volumeEnergy));
251 logger.fine(format(" Surface Area: %8.3f (Ang^2)", surfaceArea));
252 logger.fine(format(" Surface Area Energy: %8.3f (kcal/mol)", surfaceAreaEnergy));
253 logger.fine(format(" Volume + SA Energy: %8.3f (kcal/mol)", cavitationEnergy));
254 logger.fine(format(" Effective Radius: %8.3f (Ang)", reff));
255 }
256
257 return cavitationEnergy;
258 }
259
260
261
262
263
264
265
266
267 public double energyAndGradientGausVol(double[][] positions, AtomicDoubleArray3D gradient) {
268
269
270 cavitationEnergy = 0.0;
271
272 gaussVol.computeVolumeAndSA(positions);
273
274
275 volume = gaussVol.getVolume();
276 double[] volumeGradient = gaussVol.getVolumeGradient();
277
278
279 volumeEnergy = volume * solventPressure;
280
281
282 surfaceArea = gaussVol.getSurfaceArea();
283 double[] surfaceAreaGradient = gaussVol.getSurfaceAreaGradient();
284
285
286 surfaceAreaEnergy = surfaceArea * surfaceTension;
287
288
289 effectiveRadius = 0.5 * sqrt(surfaceArea / PI);
290 double reff = effectiveRadius;
291 double reff2 = reff * reff;
292 double reff3 = reff2 * reff;
293 double reff4 = reff3 * reff;
294 double reff5 = reff4 * reff;
295 double dRdSA = reff / (2.0 * surfaceArea);
296
297
298
299
300
301
302
303
304
305
306
307
308 if (doVolume) {
309 if (!doSurfaceArea || reff < beginVolumeOff) {
310
311 cavitationEnergy = volumeEnergy;
312 addVolumeGradient(0.0, solventPressure, surfaceAreaGradient, volumeGradient, gradient);
313 } else if (reff <= endVolumeOff) {
314
315 double taper = volumeSwitch.taper(reff, reff2, reff3, reff4, reff5);
316 double dtaper = volumeSwitch.dtaper(reff, reff2, reff3, reff4) * dRdSA;
317 cavitationEnergy = taper * volumeEnergy;
318 double factorSA = dtaper * volumeEnergy;
319 double factorVol = taper * solventPressure;
320 addVolumeGradient(factorSA, factorVol, surfaceAreaGradient, volumeGradient, gradient);
321 }
322 }
323
324 if (doSurfaceArea) {
325 if (!doVolume || reff > beginSurfaceAreaOff) {
326
327 cavitationEnergy = surfaceAreaEnergy;
328 addSurfaceAreaGradient(surfaceTension, surfaceAreaGradient, gradient);
329 } else if (reff >= endSurfaceAreaOff) {
330
331 double taperSA = surfaceAreaSwitch.taper(reff, reff2, reff3, reff4, reff5);
332 double dtaperSA = surfaceAreaSwitch.dtaper(reff, reff2, reff3, reff4) * dRdSA;
333 cavitationEnergy += taperSA * surfaceAreaEnergy;
334 double factor = dtaperSA * surfaceAreaEnergy + taperSA * surfaceTension;
335 addSurfaceAreaGradient(factor, surfaceAreaGradient, gradient);
336 }
337 }
338
339 if (logger.isLoggable(Level.FINE)) {
340 logger.fine("\n GaussVol");
341 logger.fine(format(" Volume: %8.3f (Ang^3)", volume));
342 logger.fine(format(" Volume Energy: %8.3f (kcal/mol)", volumeEnergy));
343 logger.fine(format(" Surface Area: %8.3f (Ang^2)", surfaceArea));
344 logger.fine(format(" Surface Area Energy: %8.3f (kcal/mol)", surfaceAreaEnergy));
345 logger.fine(format(" Volume + SA Energy: %8.3f (kcal/mol)", cavitationEnergy));
346 logger.fine(format(" Effective Radius: %8.3f (Ang)", reff));
347 }
348
349 return cavitationEnergy;
350 }
351
352 public ConnollyRegion getConnollyRegion() {
353 return connollyRegion;
354 }
355
356 public double getCrossOver() {
357 return crossOver;
358 }
359
360 public void setCrossOver(double crossOver) {
361 if (crossOver < 3.5) {
362 logger.severe(
363 format(" The cross-over point (%8.6f A) must be greater than 3.5 A", crossOver));
364 return;
365 }
366 this.crossOver = crossOver;
367 beginVolumeOff = crossOver - HALF_SWITCH_RANGE;
368 endVolumeOff = beginVolumeOff + 2.0 * HALF_SWITCH_RANGE;
369 beginSurfaceAreaOff = crossOver + SA_SWITCH_OFFSET + HALF_SWITCH_RANGE;
370 endSurfaceAreaOff = beginSurfaceAreaOff - 2.0 * HALF_SWITCH_RANGE;
371 volumeSwitch = new MultiplicativeSwitch(beginVolumeOff, endVolumeOff);
372 surfaceAreaSwitch = new MultiplicativeSwitch(beginSurfaceAreaOff, endSurfaceAreaOff);
373 }
374
375 public double getEffectiveRadius() {
376 return effectiveRadius;
377 }
378
379 public double getEnergy() {
380 return cavitationEnergy;
381 }
382
383 public GaussVol getGaussVol() {
384 return gaussVol;
385 }
386
387 public double getSolventPressure() {
388 return solventPressure;
389 }
390
391 public void setSolventPressure(double solventPressure) {
392 double newCrossOver = 3.0 * surfaceTension / solventPressure;
393 if (newCrossOver < 3.5) {
394 logger.severe(
395 format(
396 " The solvent pressure (%8.6f kcal/mol/A^3)"
397 + " and surface tension (%8.6f kcal/mol/A^2) combination is not supported.",
398 solventPressure, surfaceTension));
399 return;
400 }
401 this.solventPressure = solventPressure;
402 this.setCrossOver(newCrossOver);
403 }
404
405
406
407
408
409
410 public double getSurfaceArea() {
411 return surfaceArea;
412 }
413
414
415
416
417
418
419 public double getSurfaceAreaEnergy() {
420 return surfaceAreaEnergy;
421 }
422
423 public double getSurfaceTension() {
424 return surfaceTension;
425 }
426
427 public void setSurfaceTension(double surfaceTension) {
428 double newCrossOver = 3.0 * surfaceTension / solventPressure;
429 if (newCrossOver < 3.5) {
430 logger.severe(
431 format(
432 " The solvent pressure (%8.6f kcal/mol/A^3)"
433 + " and surface tension (%8.6f kcal/mol/A^2) combination is not supported.",
434 solventPressure, surfaceTension));
435 return;
436 }
437 this.surfaceTension = surfaceTension;
438 this.setCrossOver(newCrossOver);
439 }
440
441
442
443
444
445
446 public double getVolume() {
447 return volume;
448 }
449
450
451
452
453
454
455 public double getVolumeEnergy() {
456 return volumeEnergy;
457 }
458
459
460
461
462
463
464
465
466
467
468 private void addVolumeGradient(
469 double factorSA,
470 double factorVol,
471 double[] surfaceAreaGradient,
472 double[] volumeGradient,
473 AtomicDoubleArray3D gradient) {
474 for (int i = 0; i < nAtoms; i++) {
475 int index = i * 3;
476 double gx = factorSA * surfaceAreaGradient[index] + factorVol * volumeGradient[index++];
477 double gy = factorSA * surfaceAreaGradient[index] + factorVol * volumeGradient[index++];
478 double gz = factorSA * surfaceAreaGradient[index] + factorVol * volumeGradient[index];
479 gradient.add(0, i, gx, gy, gz);
480 }
481 }
482
483
484
485
486
487
488
489
490 private void addSurfaceAreaGradient(
491 double factor, double[] surfaceAreaGradient, AtomicDoubleArray3D gradient) {
492 for (int i = 0; i < nAtoms; i++) {
493 int index = i * 3;
494 double gx = factor * surfaceAreaGradient[index++];
495 double gy = factor * surfaceAreaGradient[index++];
496 double gz = factor * surfaceAreaGradient[index];
497 gradient.add(0, i, gx, gy, gz);
498 }
499 }
500 }