Skip to content

Commit ae8b992

Browse files
committed
Another optimisation from Eisenhaber 1994
1 parent 8e84d62 commit ae8b992

File tree

2 files changed

+43
-41
lines changed

2 files changed

+43
-41
lines changed

biojava-structure/src/main/java/org/biojava/nbio/structure/asa/AsaCalculator.java

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.slf4j.LoggerFactory;
2828

2929
import javax.vecmath.Point3d;
30+
import javax.vecmath.Vector3d;
3031
import java.util.*;
3132
import java.util.concurrent.ExecutorService;
3233
import 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
/**

biojava-structure/src/test/java/org/biojava/nbio/structure/asa/TestAsaCalc.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,19 +98,19 @@ public void testNeighborIndicesFinding() throws StructureException, IOException
9898
AsaCalculator.DEFAULT_PROBE_SIZE,
9999
1000, 1, false);
100100

101-
int[][] allNbsSh = asaCalc.findNeighborIndicesSpatialHashing();
101+
AsaCalculator.IndexAndDistance[][] allNbsSh = asaCalc.findNeighborIndicesSpatialHashing();
102102

103-
int[][] allNbs = asaCalc.findNeighborIndices();
103+
AsaCalculator.IndexAndDistance[][] allNbs = asaCalc.findNeighborIndices();
104104

105105
for (int indexToTest =0; indexToTest < asaCalc.getAtomCoords().length; indexToTest++) {
106106
//int indexToTest = 198;
107-
int[] nbsSh = allNbsSh[indexToTest];
108-
int[] nbs = allNbs[indexToTest];
107+
AsaCalculator.IndexAndDistance[] nbsSh = allNbsSh[indexToTest];
108+
AsaCalculator.IndexAndDistance[] nbs = allNbs[indexToTest];
109109

110110
List<Integer> listOfMatchingIndices = new ArrayList<>();
111111
for (int i = 0; i < nbsSh.length; i++) {
112112
for (int j = 0; j < nbs.length; j++) {
113-
if (nbs[j] == nbsSh[i]) {
113+
if (nbs[j].index == nbsSh[i].index) {
114114
listOfMatchingIndices.add(j);
115115
break;
116116
}
@@ -201,21 +201,21 @@ public void testNoNeighborsIssue() {
201201
AsaCalculator.DEFAULT_PROBE_SIZE,
202202
1000, 1);
203203

204-
int[][] allNbsSh = asaCalc.findNeighborIndicesSpatialHashing();
204+
AsaCalculator.IndexAndDistance[][] allNbsSh = asaCalc.findNeighborIndicesSpatialHashing();
205205

206-
int[][] allNbs = asaCalc.findNeighborIndices();
206+
AsaCalculator.IndexAndDistance[][] allNbs = asaCalc.findNeighborIndices();
207207

208208
assertEquals(3, allNbs.length);
209209
assertEquals(3, allNbsSh.length);
210210

211211
for (int indexToTest =0; indexToTest < asaCalc.getAtomCoords().length; indexToTest++) {
212-
int[] nbsSh = allNbsSh[indexToTest];
213-
int[] nbs = allNbs[indexToTest];
212+
AsaCalculator.IndexAndDistance[] nbsSh = allNbsSh[indexToTest];
213+
AsaCalculator.IndexAndDistance[] nbs = allNbs[indexToTest];
214214

215215
List<Integer> listOfMatchingIndices = new ArrayList<>();
216216
for (int i = 0; i < nbsSh.length; i++) {
217217
for (int j = 0; j < nbs.length; j++) {
218-
if (nbs[j] == nbsSh[i]) {
218+
if (nbs[j].index == nbsSh[i].index) {
219219
listOfMatchingIndices.add(j);
220220
break;
221221
}

0 commit comments

Comments
 (0)