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 edu.rit.pj.IntegerForLoop;
41 import edu.rit.pj.ParallelRegion;
42 import edu.rit.pj.reduction.DoubleOp;
43 import edu.rit.pj.reduction.SharedDoubleArray;
44 import ffx.crystal.Crystal;
45 import ffx.potential.bonded.Atom;
46 import ffx.potential.parameters.ForceField;
47 import org.apache.commons.configuration2.CompositeConfiguration;
48
49 import java.util.logging.Level;
50 import java.util.logging.Logger;
51
52 import static ffx.potential.nonbonded.implicit.BornTanhRescaling.MAX_BORN_RADIUS;
53 import static ffx.potential.nonbonded.implicit.BornTanhRescaling.tanhRescaling;
54 import static ffx.potential.nonbonded.implicit.NeckIntegral.getNeckConstants;
55 import static java.lang.Double.isInfinite;
56 import static java.lang.Double.isNaN;
57 import static java.lang.String.format;
58 import static java.lang.System.arraycopy;
59 import static java.util.Arrays.fill;
60 import static org.apache.commons.math3.util.FastMath.PI;
61 import static org.apache.commons.math3.util.FastMath.max;
62 import static org.apache.commons.math3.util.FastMath.pow;
63 import static org.apache.commons.math3.util.FastMath.sqrt;
64
65
66
67
68
69
70
71 public class BornRadiiRegion extends ParallelRegion {
72
73 private static final Logger logger = Logger.getLogger(BornRadiiRegion.class.getName());
74 private static final double oneThird = 1.0 / 3.0;
75 private static final double PI4_3 = 4.0 / 3.0 * PI;
76 private static final double INVERSE_PI4_3 = 1.0 / PI4_3;
77 private static final double PI_12 = PI / 12.0;
78 private final BornRadiiLoop[] bornRadiiLoop;
79
80 protected Atom[] atoms;
81
82 private Crystal crystal;
83
84 private double[][][] sXYZ;
85
86 private int[][][] neighborLists;
87
88 private double[] baseRadius;
89
90 private double[] descreenRadius;
91
92
93
94
95
96
97
98
99 private double[] overlapScale;
100
101
102
103
104 private double[] neckScale;
105
106
107
108 private final double[] perfectRadii;
109
110
111
112 private final boolean usePerfectRadii;
113
114 private double[] born;
115
116
117
118 private boolean[] use;
119
120 private double cut2;
121
122
123
124 private boolean nativeEnvironmentApproximation;
125
126 private final boolean perfectHCTScale;
127 private double descreenOffset = 0.0;
128
129
130
131 private SharedDoubleArray sharedBorn;
132
133
134
135
136 private boolean verboseRadii;
137
138
139
140 private final boolean neckCorrection;
141
142
143
144 private final boolean tanhCorrection;
145
146
147
148
149 private double[] unscaledBornIntegral;
150
151
152
153
154
155
156
157
158
159
160
161 public BornRadiiRegion(int nt, int nAtoms, ForceField forceField, boolean neckCorrection,
162 boolean tanhCorrection, boolean perfectHCTScale) {
163 bornRadiiLoop = new BornRadiiLoop[nt];
164 for (int i = 0; i < nt; i++) {
165 bornRadiiLoop[i] = new BornRadiiLoop();
166 }
167 verboseRadii = forceField.getBoolean("VERBOSE_BORN_RADII", false);
168 this.perfectHCTScale = perfectHCTScale;
169 this.neckCorrection = neckCorrection;
170 this.tanhCorrection = tanhCorrection;
171 if (verboseRadii && logger.isLoggable(Level.FINER)) {
172 logger.finer(" Verbose Born radii.");
173 }
174
175 if (tanhCorrection) {
176 unscaledBornIntegral = new double[nAtoms];
177 }
178
179 CompositeConfiguration compositeConfiguration = forceField.getProperties();
180 usePerfectRadii = forceField.getBoolean("PERFECT_RADII", false);
181 String[] radii = compositeConfiguration.getStringArray("perfect-radius");
182 if (usePerfectRadii) {
183 perfectRadii = new double[nAtoms];
184 if (radii != null && radii.length > 0) {
185 if (logger.isLoggable(Level.FINER)) {
186 logger.finer(format(" Reading %d perfect-radius records .", radii.length));
187 }
188 for (String radius : radii) {
189 String[] tokens = radius.trim().split(" +");
190 if (tokens.length == 2) {
191
192 int index = Integer.parseInt(tokens[0]) - 1;
193 double value = Double.parseDouble(tokens[1]);
194 if (logger.isLoggable(Level.FINER)) {
195 logger.finer(format(" perfect-radius %d %16.8f", index, value));
196 }
197 if (index >= 0 && index < nAtoms && value > 0.0) {
198 perfectRadii[index] = value;
199 }
200 } else {
201 logger.warning(format(" Could not parse perfect-radius line %s", radius));
202 }
203 }
204 }
205 } else {
206 perfectRadii = null;
207 }
208 }
209
210
211
212
213
214
215 public double[] getPerfectRadii() {
216 int nAtoms = atoms.length;
217
218
219 double[] radii = new double[nAtoms];
220 arraycopy(baseRadius, 0, radii, 0, nAtoms);
221
222
223 if (usePerfectRadii) {
224 for (int i = 0; i < nAtoms; i++) {
225 double perfectRadius = perfectRadii[i];
226 if (perfectRadius > 0.0) {
227 radii[i] = perfectRadius;
228 }
229 }
230 }
231
232 return radii;
233 }
234
235 @Override
236 public void finish() {
237 int nAtoms = atoms.length;
238
239
240 if (usePerfectRadii) {
241 for (int i = 0; i < nAtoms; i++) {
242 double perfectRadius = perfectRadii[i];
243 if (!use[i] || perfectRadius <= 0.0) {
244 born[i] = baseRadius[i];
245 } else {
246 born[i] = perfectRadius;
247 }
248 }
249 return;
250 }
251
252 for (int i = 0; i < nAtoms; i++) {
253 final double baseRi = baseRadius[i];
254 if (!use[i]) {
255 born[i] = baseRi;
256 } else {
257
258 double soluteIntegral = -sharedBorn.get(i);
259 if (tanhCorrection) {
260
261 unscaledBornIntegral[i] = soluteIntegral;
262 soluteIntegral = tanhRescaling(soluteIntegral, baseRi);
263 }
264
265 double sum = PI4_3 / (baseRi * baseRi * baseRi) - soluteIntegral;
266
267 if (sum <= 0.0) {
268 born[i] = MAX_BORN_RADIUS;
269 if (verboseRadii) {
270 logger.info(format(" Born Integral < 0 for atom %d; set Born radius to %12.6f (Base Radius: %12.6f)",
271 i + 1, born[i], baseRadius[i]));
272 }
273 } else {
274 born[i] = pow(INVERSE_PI4_3 * sum, -oneThird);
275 if (born[i] < baseRi) {
276 born[i] = baseRi;
277 if (verboseRadii) {
278 logger.info(format(" Born radius < Base Radius for atom %d: set Born radius to %12.6f", i + 1, baseRi));
279 }
280 } else if (born[i] > MAX_BORN_RADIUS) {
281 born[i] = MAX_BORN_RADIUS;
282 if (verboseRadii) {
283 logger.info(format(" Born radius > %12.6f Angstroms for atom %d: set Born radius to %12.6f",
284 MAX_BORN_RADIUS, i + 1, baseRi));
285 }
286 } else if (isInfinite(born[i]) || isNaN(born[i])) {
287 born[i] = baseRi;
288 if (verboseRadii) {
289 logger.info(format(" Born radius NaN / Infinite for atom %d; set Born radius to %12.6f", i + 1, baseRi));
290 }
291 } else {
292 if (verboseRadii) {
293 logger.info(format(" Born radius for atom %d: %12.6f (Base Radius: %2.6f)", i + 1, born[i], baseRi));
294 }
295 }
296 }
297 }
298 }
299
300 if (verboseRadii) {
301
302 logger.info(" Disabling verbose radii printing.");
303 verboseRadii = false;
304 }
305
306 }
307
308 public void init(
309 Atom[] atoms,
310 Crystal crystal,
311 double[][][] sXYZ,
312 int[][][] neighborLists,
313 double[] baseRadius,
314 double[] descreenRadius,
315 double[] overlapScale,
316 double[] neckScale,
317 double descreenOffset,
318 boolean[] use,
319 double cut2,
320 boolean nativeEnvironmentApproximation,
321 double[] born) {
322 this.atoms = atoms;
323 this.crystal = crystal;
324 this.sXYZ = sXYZ;
325 this.neighborLists = neighborLists;
326 this.baseRadius = baseRadius;
327 this.descreenRadius = descreenRadius;
328 this.overlapScale = overlapScale;
329 this.neckScale = neckScale;
330 this.descreenOffset = descreenOffset;
331 this.use = use;
332 this.cut2 = cut2;
333 this.nativeEnvironmentApproximation = nativeEnvironmentApproximation;
334 this.born = born;
335 }
336
337 @Override
338 public void run() {
339 if (!usePerfectRadii) {
340 try {
341 int nAtoms = atoms.length;
342 execute(0, nAtoms - 1, bornRadiiLoop[getThreadIndex()]);
343 } catch (Exception e) {
344 String message = "Fatal exception computing Born radii in thread " + getThreadIndex() + "\n";
345 logger.log(Level.SEVERE, message, e);
346 }
347 }
348 }
349
350 @Override
351 public void start() {
352 int nAtoms = atoms.length;
353 if (sharedBorn == null || sharedBorn.length() < nAtoms) {
354 sharedBorn = new SharedDoubleArray(nAtoms);
355 }
356 for (int i = 0; i < nAtoms; i++) {
357 sharedBorn.set(i, 0.0);
358 }
359 }
360
361 public double[] getBorn() {
362 return born;
363 }
364
365 public double[] getUnscaledBornIntegral() {
366 return unscaledBornIntegral;
367 }
368
369
370
371
372
373
374 private class BornRadiiLoop extends IntegerForLoop {
375
376 private double[] localBorn;
377
378 @Override
379 public void finish() {
380 sharedBorn.reduce(localBorn, DoubleOp.SUM);
381 }
382
383 @Override
384 public void run(int lb, int ub) {
385 int nSymm = crystal.spaceGroup.symOps.size();
386 if (nSymm == 0) {
387 nSymm = 1;
388 }
389 double[] x = sXYZ[0][0];
390 double[] y = sXYZ[0][1];
391 double[] z = sXYZ[0][2];
392 for (int iSymOp = 0; iSymOp < nSymm; iSymOp++) {
393 double[][] xyz = sXYZ[iSymOp];
394 for (int i = lb; i <= ub; i++) {
395 if (!nativeEnvironmentApproximation && !use[i]) {
396 continue;
397 }
398 final double integralStartI = max(baseRadius[i], descreenRadius[i]) + descreenOffset;
399 final double descreenRi = descreenRadius[i];
400 final double xi = x[i];
401 final double yi = y[i];
402 final double zi = z[i];
403 int[] list = neighborLists[iSymOp][i];
404 for (int k : list) {
405 final double integralStartK = max(baseRadius[k], descreenRadius[k]) + descreenOffset;
406 final double descreenRk = descreenRadius[k];
407 if (!nativeEnvironmentApproximation && !use[k]) {
408 continue;
409 }
410
411
412 double mixedNeckScale = 0.5 * (neckScale[i] + neckScale[k]);
413
414 if (i != k) {
415 final double xr = xyz[0][k] - xi;
416 final double yr = xyz[1][k] - yi;
417 final double zr = xyz[2][k] - zi;
418 final double r2 = crystal.image(xr, yr, zr);
419 if (r2 > cut2) {
420 continue;
421 }
422 final double r = sqrt(r2);
423
424 double sk = overlapScale[k];
425
426 if (sk > 0.0) {
427 double descreenIK = descreen(r, r2, integralStartI, descreenRk, sk);
428 localBorn[i] += descreenIK;
429 if (neckCorrection) {
430 localBorn[i] += neckDescreen(r, integralStartI, descreenRk, mixedNeckScale);
431 }
432 }
433
434
435 double si = overlapScale[i];
436 if (si > 0.0) {
437 double descreenKI = descreen(r, r2, integralStartK, descreenRi, si);
438 localBorn[k] += descreenKI;
439 if (neckCorrection) {
440 localBorn[k] += neckDescreen(r, integralStartK, descreenRi, mixedNeckScale);
441 }
442 }
443
444 } else if (iSymOp > 0) {
445 final double xr = xyz[0][k] - xi;
446 final double yr = xyz[1][k] - yi;
447 final double zr = xyz[2][k] - zi;
448 final double r2 = crystal.image(xr, yr, zr);
449 if (r2 > cut2) {
450 continue;
451 }
452 final double r = sqrt(r2);
453
454 double sk = overlapScale[k];
455 if (sk > 0.0) {
456 localBorn[i] += descreen(r, r2, integralStartI, descreenRk, sk);
457 if (neckCorrection) {
458 localBorn[i] += neckDescreen(r, integralStartI, descreenRk, mixedNeckScale);
459 }
460 }
461
462 }
463 }
464 }
465 }
466 }
467
468 @Override
469 public void start() {
470 int nAtoms = atoms.length;
471 if (localBorn == null || localBorn.length < nAtoms) {
472 localBorn = new double[nAtoms];
473 }
474 fill(localBorn, 0.0);
475 }
476
477
478
479
480
481
482
483
484
485
486 private double neckDescreen(double r, double radius, double radiusK, double sneck) {
487 double radiusWater = 1.4;
488
489
490 if (r > radius + radiusK + 2.0 * radiusWater) {
491 return 0.0;
492 }
493
494
495 double[] constants = getNeckConstants(radius, radiusK);
496
497 double Aij = constants[0];
498 double Bij = constants[1];
499
500
501 double rMinusBij = r - Bij;
502 double radiiMinusr = radius + radiusK + 2.0 * radiusWater - r;
503 double power1 = rMinusBij * rMinusBij * rMinusBij * rMinusBij;
504 double power2 = radiiMinusr * radiiMinusr * radiiMinusr * radiiMinusr;
505
506
507
508 double neckIntegral = PI4_3 * sneck * Aij * power1 * power2;
509
510 return -neckIntegral;
511 }
512
513 private double descreen(double r, double r2, double radius, double radiusK, double hctScale) {
514 if (perfectHCTScale) {
515 return perfectHCTIntegral(r, r2, radius, radiusK, hctScale);
516 } else {
517 return integral(r, r2, radius, radiusK * hctScale);
518 }
519 }
520
521
522
523
524
525
526
527
528
529
530 private double integral(double r, double r2, double radius, double scaledRadius) {
531 double integral = 0.0;
532
533
534 if (scaledRadius > 0.0 && (radius < r + scaledRadius)) {
535
536 if (radius + r < scaledRadius) {
537 final double upper = scaledRadius - r;
538 integral = (PI4_3 * (1.0 / (upper * upper * upper) - 1.0 / (radius * radius * radius)));
539 }
540
541
542 double upper = r + scaledRadius;
543
544
545 double lower;
546 if (radius + r < scaledRadius) {
547
548 lower = scaledRadius - r;
549 } else if (r < radius + scaledRadius) {
550
551 lower = radius;
552 } else {
553
554 lower = r - scaledRadius;
555 }
556
557 double l2 = lower * lower;
558 double l4 = l2 * l2;
559 double lr = lower * r;
560 double l4r = l4 * r;
561 double u2 = upper * upper;
562 double u4 = u2 * u2;
563 double ur = upper * r;
564 double u4r = u4 * r;
565 double scaledRk2 = scaledRadius * scaledRadius;
566 double term =
567 (3.0 * (r2 - scaledRk2) + 6.0 * u2 - 8.0 * ur) / u4r
568 - (3.0 * (r2 - scaledRk2) + 6.0 * l2 - 8.0 * lr) / l4r;
569 integral -= PI_12 * term;
570 }
571 return integral;
572 }
573
574 private double perfectHCTIntegral(double r, double r2,
575 double radius, double radiusK, double perfectHCT) {
576 double integral = 0.0;
577
578
579 if (radiusK > 0.0 && (radius < r + radiusK)) {
580
581
582 if (radius + r < radiusK) {
583 final double upper = radiusK - r;
584 integral = (PI4_3 * (1.0 / (upper * upper * upper) - 1.0 / (radius * radius * radius)));
585 }
586
587
588 double upper = r + radiusK;
589
590
591 double lower;
592 if (radius + r < radiusK) {
593
594 lower = radiusK - r;
595 } else if (r < radius + radiusK) {
596
597
598 lower = radius;
599 } else {
600
601 lower = r - radiusK;
602 }
603
604 double l2 = lower * lower;
605 double l4 = l2 * l2;
606 double lr = lower * r;
607 double l4r = l4 * r;
608 double u2 = upper * upper;
609 double u4 = u2 * u2;
610 double ur = upper * r;
611 double u4r = u4 * r;
612 double scaledK2 = radiusK * radiusK;
613 double term =
614 (3.0 * (r2 - scaledK2) + 6.0 * u2 - 8.0 * ur) / u4r
615 - (3.0 * (r2 - scaledK2) + 6.0 * l2 - 8.0 * lr) / l4r;
616 integral -= PI_12 * term;
617 }
618
619 return perfectHCT * integral;
620 }
621 }
622 }