2727import org .slf4j .LoggerFactory ;
2828
2929import javax .vecmath .Point3d ;
30+ import javax .vecmath .Vector3d ;
3031import java .util .*;
3132import java .util .concurrent .ExecutorService ;
3233import java .util .concurrent .Executors ;
@@ -99,9 +100,9 @@ public void run() {
99100 }
100101 }
101102
102- private static class IndexAndDistance {
103- private final int index ;
104- private final double dist ;
103+ public static class IndexAndDistance {
104+ public final int index ;
105+ public final double dist ;
105106 public IndexAndDistance (int index , double dist ) {
106107 this .index = index ;
107108 this .dist = dist ;
@@ -116,7 +117,7 @@ public IndexAndDistance(int index, double dist) {
116117 private final int nThreads ;
117118 private Point3d [] spherePoints ;
118119 private double cons ;
119- private int [][] neighborIndices ;
120+ private IndexAndDistance [][] neighborIndices ;
120121
121122 private boolean useSpatialHashingForNeighbors ;
122123
@@ -369,30 +370,29 @@ private Point3d[] generateSpherePoints(int nSpherePoints) {
369370 * Returns the 2-dimensional array with neighbor indices for every atom.
370371 * @return 2-dimensional array of size: n_atoms x n_neighbors_per_atom
371372 */
372- int [][] findNeighborIndices () {
373+ IndexAndDistance [][] findNeighborIndices () {
373374
374375 // looking at a typical protein case, number of neighbours are from ~10 to ~50, with an average of ~30
375376 int initialCapacity = 60 ;
376377
377- int [][] nbsIndices = new int [atomCoords .length ][];
378+ IndexAndDistance [][] nbsIndices = new IndexAndDistance [atomCoords .length ][];
378379
379380 for (int k =0 ; k <atomCoords .length ; k ++) {
380381 double radius = radii [k ] + probe + probe ;
381382
382- List <Integer > thisNbIndices = new ArrayList <>(initialCapacity );
383+ List <IndexAndDistance > thisNbIndices = new ArrayList <>(initialCapacity );
383384
384385 for (int i = 0 ; i < atomCoords .length ; i ++) {
385386 if (i == k ) continue ;
386387
387388 double dist = atomCoords [i ].distance (atomCoords [k ]);
388389
389390 if (dist < radius + radii [i ]) {
390- thisNbIndices .add (i );
391+ thisNbIndices .add (new IndexAndDistance ( i , dist ) );
391392 }
392393 }
393394
394- int [] indicesArray = new int [thisNbIndices .size ()];
395- for (int i =0 ;i <thisNbIndices .size ();i ++) indicesArray [i ] = thisNbIndices .get (i );
395+ IndexAndDistance [] indicesArray = thisNbIndices .toArray (new IndexAndDistance [0 ]);
396396 nbsIndices [k ] = indicesArray ;
397397 }
398398 return nbsIndices ;
@@ -403,7 +403,7 @@ int[][] findNeighborIndices() {
403403 * using spatial hashing to avoid all to all distance calculation.
404404 * @return 2-dimensional array of size: n_atoms x n_neighbors_per_atom
405405 */
406- int [][] findNeighborIndicesSpatialHashing () {
406+ IndexAndDistance [][] findNeighborIndicesSpatialHashing () {
407407
408408 // looking at a typical protein case, number of neighbours are from ~10 to ~50, with an average of ~30
409409 int initialCapacity = 60 ;
@@ -439,22 +439,21 @@ int[][] findNeighborIndicesSpatialHashing() {
439439 }
440440
441441 // convert map to array for fast access
442- int [][] nbsIndices = new int [atomCoords .length ][];
442+ IndexAndDistance [][] nbsIndices = new IndexAndDistance [atomCoords .length ][];
443443 for (Map .Entry <Integer , List <IndexAndDistance >> entry : indices .entrySet ()) {
444444 List <IndexAndDistance > list = entry .getValue ();
445445 // Sorting by closest to farthest away neighbors achieves faster runtimes when checking for occluded sphere sample points in calcSingleAsa.
446446 // This follows the ideas exposed in Eisenhaber et al, J Comp Chemistry 1994 (https://onlinelibrary.wiley.com/doi/epdf/10.1002/jcc.540160303)
447447 list = list .stream ().sorted (Comparator .comparingDouble (o -> o .dist )).collect (Collectors .toList ());
448- int [] indicesArray = new int [list .size ()];
449- for (int i =0 ; i <list .size (); i ++) indicesArray [i ] = list .get (i ).index ;
448+ IndexAndDistance [] indexAndDistances = list .toArray (new IndexAndDistance [0 ]);
450449
451- nbsIndices [entry .getKey ()] = indicesArray ;
450+ nbsIndices [entry .getKey ()] = indexAndDistances ;
452451 }
453452
454453 // important: some atoms might have no neighbors at all: we need to initialise to empty arrays
455454 for (int i =0 ; i <nbsIndices .length ; i ++) {
456455 if (nbsIndices [i ] == null ) {
457- nbsIndices [i ] = new int [0 ];
456+ nbsIndices [i ] = new IndexAndDistance [0 ];
458457 }
459458 }
460459
@@ -483,39 +482,42 @@ private double calcSingleAsa(int i) {
483482 Point3d atom_i = atomCoords [i ];
484483
485484 int n_neighbor = neighborIndices [i ].length ;
486- int [] neighbor_indices = neighborIndices [i ];
487- double radius = probe + radii [i ];
485+ IndexAndDistance [] neighbor_indices = neighborIndices [i ];
486+ double radius_i = probe + radii [i ];
488487
489488 int n_accessible_point = 0 ;
490489
491490 int [] numDistsCalced = null ;
492491 if (logger .isDebugEnabled ()) numDistsCalced = new int [n_neighbor ];
493492
494- // now we precalculate the squared j radii to compare to in inner loop (which does not depend on each sphere point, but only on i, j
493+ // now we precalculate anything depending only on i,j in equation 3 in Eisenhaber 1994
495494 double [] sqRadii = new double [n_neighbor ];
495+ Vector3d [] aj_minus_ais = new Vector3d [n_neighbor ];
496496 for (int nbArrayInd =0 ; nbArrayInd <n_neighbor ; nbArrayInd ++) {
497- int j = neighbor_indices [nbArrayInd ];
498- double r = radii [j ] + probe ;
499- sqRadii [nbArrayInd ] = r * r ;
497+ int j = neighbor_indices [nbArrayInd ].index ;
498+ double dist = neighbor_indices [nbArrayInd ].dist ;
499+ double radius_j = radii [j ] + probe ;
500+ // see equation 3 in Eisenhaber 1994
501+ sqRadii [nbArrayInd ] = (dist *dist + radius_i *radius_i - radius_j *radius_j )/(2 *radius_i );
502+ Vector3d aj_minus_ai = new Vector3d (atomCoords [j ]);
503+ aj_minus_ai .sub (atom_i );
504+ aj_minus_ais [nbArrayInd ] = aj_minus_ai ;
500505 }
501506
502507 for (Point3d point : spherePoints ){
503508 boolean is_accessible = true ;
504- Point3d test_point = new Point3d (point .x *radius + atom_i .x ,
505- point .y *radius + atom_i .y ,
506- point .z *radius + atom_i .z );
507509
508510 // note that the neighbors are sorted by distance, achieving optimal performance in this inner loop
509511 // See Eisenhaber et al, J Comp Chemistry 1994 (https://onlinelibrary.wiley.com/doi/epdf/10.1002/jcc.540160303)
510512
511513 for (int nbArrayInd =0 ; nbArrayInd <n_neighbor ; nbArrayInd ++) {
512- int j = neighbor_indices [ nbArrayInd ];
513- Point3d atom_j = atomCoords [ j ];
514- double diff_sq = test_point . distanceSquared ( atom_j );
514+
515+ // see equation 3 in Eisenhaber 1994
516+ double dotProd = aj_minus_ais [ nbArrayInd ]. dot ( new Vector3d ( point ) );
515517
516518 if (numDistsCalced !=null ) numDistsCalced [nbArrayInd ]++;
517519
518- if (diff_sq < sqRadii [nbArrayInd ]) {
520+ if (dotProd > sqRadii [nbArrayInd ]) {
519521 is_accessible = false ;
520522 break ;
521523 }
@@ -527,11 +529,11 @@ private double calcSingleAsa(int i) {
527529
528530 if (numDistsCalced !=null ) {
529531 int sum = 0 ;
530- for (int j = 0 ; j < n_neighbor ; j ++) sum += numDistsCalced [j ];
532+ for (int nbArrayInd = 0 ; nbArrayInd < n_neighbor ; nbArrayInd ++) sum += numDistsCalced [nbArrayInd ];
531533 logger .debug ("Number of sample points distances calculated for neighbors of i={} : average {}, all {}" , i , (double ) sum / (double ) n_neighbor , numDistsCalced );
532534 }
533535
534- return cons *n_accessible_point *radius * radius ;
536+ return cons *n_accessible_point *radius_i * radius_i ;
535537 }
536538
537539 /**
0 commit comments