diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/asa/AsaCalculator.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/asa/AsaCalculator.java
index dc34ff8782..108cea6067 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/asa/AsaCalculator.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/asa/AsaCalculator.java
@@ -21,12 +21,13 @@
package org.biojava.nbio.structure.asa;
import org.biojava.nbio.structure.*;
+import org.biojava.nbio.structure.contact.Contact;
+import org.biojava.nbio.structure.contact.Grid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.vecmath.Point3d;
-import java.util.ArrayList;
-import java.util.TreeMap;
+import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -36,28 +37,34 @@
/**
* Class to calculate Accessible Surface Areas based on
* the rolling ball algorithm by Shrake and Rupley.
- *
+ *
* The code is adapted from a python implementation at http://boscoh.com/protein/asapy
* (now source is available at https://github.com/boscoh/asa).
* Thanks to Bosco K. Ho for a great piece of code and for his fantastic blog.
- *
+ *
* See
* Shrake, A., and J. A. Rupley. "Environment and Exposure to Solvent of Protein Atoms.
* Lysozyme and Insulin." JMB (1973) 79:351-371.
* Lee, B., and Richards, F.M. "The interpretation of Protein Structures: Estimation of
* Static Accessibility" JMB (1971) 55:379-400
- * @author duarte_j
+ *
+ * @author Jose Duarte
*
*/
public class AsaCalculator {
private static final Logger logger = LoggerFactory.getLogger(AsaCalculator.class);
- // Bosco uses as default 960, Shrake and Rupley seem to use in their paper 92 (not sure if this is actually the same parameter)
- public static final int DEFAULT_N_SPHERE_POINTS = 960;
+ /**
+ * The default value for number of sphere points to sample.
+ * See this paper for a nice study on the effect of this parameter: https://f1000research.com/articles/5-189/v1
+ */
+ public static final int DEFAULT_N_SPHERE_POINTS = 1000;
public static final double DEFAULT_PROBE_SIZE = 1.4;
public static final int DEFAULT_NTHREADS = 1;
+ private static final boolean DEFAULT_USE_SPATIAL_HASHING = true;
+
// Chothia's amino acid atoms vdw radii
@@ -101,15 +108,19 @@ public void run() {
private int nThreads;
private Point3d[] spherePoints;
private double cons;
+ private int[][] neighborIndices;
+
+ private boolean useSpatialHashingForNeighbors;
/**
* Constructs a new AsaCalculator. Subsequently call {@link #calculateAsas()}
* or {@link #getGroupAsas()} to calculate the ASAs
* Only non-Hydrogen atoms are considered in the calculation.
- * @param structure
- * @param probe
- * @param nSpherePoints
- * @param nThreads
+ * @param structure the structure, all non-H atoms will be used
+ * @param probe the probe size
+ * @param nSpherePoints the number of points to be used in generating the spherical
+ * dot-density, the more points the more accurate (and slower) calculation
+ * @param nThreads the number of parallel threads to use for the calculation
* @param hetAtoms if true HET residues are considered, if false they aren't, equivalent to
* NACCESS' -h option
* @see StructureTools#getAllNonHAtomArray
@@ -120,16 +131,15 @@ public AsaCalculator(Structure structure, double probe, int nSpherePoints, int n
this.probe = probe;
this.nThreads = nThreads;
+ this.useSpatialHashingForNeighbors = DEFAULT_USE_SPATIAL_HASHING;
+
// initialising the radii by looking them up through AtomRadii
radii = new double[atomCoords.length];
for (int i=0;i asas = new TreeMap();
+ TreeMap asas = new TreeMap<>();
double[] asasPerAtom = calculateAsas();
@@ -238,12 +256,26 @@ public double[] calculateAsas() {
double[] asas = new double[atomCoords.length];
+ long start = System.currentTimeMillis();
+ if (useSpatialHashingForNeighbors) {
+ logger.debug("Will use spatial hashing to find neighbors");
+ neighborIndices = findNeighborIndicesSpatialHashing();
+ } else {
+ logger.debug("Will not use spatial hashing to find neighbors");
+ neighborIndices = findNeighborIndices();
+ }
+ long end = System.currentTimeMillis();
+ logger.debug("Took {} s to find neighbors", (end-start)/1000.0);
+
+ start = System.currentTimeMillis();
if (nThreads<=1) { // (i.e. it will also be 1 thread if 0 or negative number specified)
+ logger.debug("Will use 1 thread for ASA calculation");
for (int i=0;i neighbor_indices = new ArrayList<>(40);
+ int initialCapacity = 60;
- double radius = radii[k] + probe + probe;
+ int[][] nbsIndices = new int[atomCoords.length][];
- for (int i=0;i thisNbIndices = new ArrayList<>(initialCapacity);
+
+ for (int i = 0; i < atomCoords.length; i++) {
+ if (i == k) continue;
+
+ double dist = atomCoords[i].distance(atomCoords[k]);
+
+ if (dist < radius + radii[i]) {
+ thisNbIndices.add(i);
+ }
+ }
+
+ int[] indicesArray = new int[thisNbIndices.size()];
+ for (int i=0;i contactList = calcContacts();
+ Map> indices = new HashMap<>(atomCoords.length);
+ for (Contact contact : contactList) {
+ // note contacts are stored 1-way only, with j>i
+ int i = contact.getI();
+ int j = contact.getJ();
+
+ List iIndices;
+ List jIndices;
+ if (!indices.containsKey(i)) {
+ iIndices = new ArrayList<>(initialCapacity);
+ indices.put(i, iIndices);
+ } else {
+ iIndices = indices.get(i);
+ }
+ if (!indices.containsKey(j)) {
+ jIndices = new ArrayList<>(initialCapacity);
+ indices.put(j, jIndices);
+ } else {
+ jIndices = indices.get(j);
+ }
- if (dist < radius + radii[i]) {
- neighbor_indices.add(i);
+ double radius = radii[i] + probe + probe;
+ double dist = contact.getDistance();
+ if (dist < radius + radii[j]) {
+ iIndices.add(j);
+ jIndices.add(i);
}
+ }
+ // convert map to array for fast access
+ int[][] nbsIndices = new int[atomCoords.length][];
+ for (Map.Entry> entry : indices.entrySet()) {
+ List list = entry.getValue();
+ int[] indicesArray = new int[list.size()];
+ for (int i=0;i calcContacts() {
+ double maxRadius = maxValue(radii);
+ double cutoff = maxRadius + maxRadius + probe + probe;
+ logger.debug("Max radius is {}, cutoff is {}", maxRadius, cutoff);
+ Grid grid = new Grid(cutoff);
+ grid.addCoords(atomCoords);
+ return grid.getIndicesContacts();
+ }
+
+ private static double maxValue(double[] array) {
+ double max = array[0];
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] > max) {
+ max = array[i];
+ }
+ }
+ return max;
}
private double calcSingleAsa(int i) {
Point3d atom_i = atomCoords[i];
- Integer[] neighbor_indices = findNeighborIndices(i);
- int n_neighbor = neighbor_indices.length;
+
+ int n_neighbor = neighborIndices[i].length;
+ int[] neighbor_indices = neighborIndices[i];
int j_closest_neighbor = 0;
double radius = probe + radii[i];
@@ -497,7 +623,7 @@ private static double getRadiusForNucl(NucleotideImpl nuc, Atom atom) {
*
* If atom is neither part of a nucleotide nor of a standard aminoacid,
* the default vdw radius for the element is returned. If atom is of
- * unknown type (element) the vdw radius of {@link #Element().N} is returned
+ * unknown type (element) the vdw radius of {@link Element().N} is returned
*
* @param atom
* @return
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/StructureInterface.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/StructureInterface.java
index ac319fd95f..9e6dcaf1b3 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/StructureInterface.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/StructureInterface.java
@@ -209,8 +209,8 @@ protected void setAsas(double[] asas1, double[] asas2, int nSpherePoints, int nT
throw new IllegalArgumentException("The size of ASAs of complex doesn't match that of ASAs 1 + ASAs 2");
- groupAsas1 = new TreeMap();
- groupAsas2 = new TreeMap();
+ groupAsas1 = new TreeMap<>();
+ groupAsas2 = new TreeMap<>();
this.totalArea = 0;
diff --git a/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/StructureInterfaceList.java b/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/StructureInterfaceList.java
index 9b4144d96a..b5785b514b 100644
--- a/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/StructureInterfaceList.java
+++ b/biojava-structure/src/main/java/org/biojava/nbio/structure/contact/StructureInterfaceList.java
@@ -35,7 +35,7 @@
/**
* A list of interfaces between 2 molecules (2 sets of atoms)
*
- * @author duarte_j
+ * @author Jose Duarte
*
*/
public class StructureInterfaceList implements Serializable, Iterable {
@@ -126,8 +126,8 @@ public void calcAsas(int nSpherePoints, int nThreads, int cofactorSizeToUse) {
// we get discrepancies (not very big but annoying) which lead to things like negative (small) bsa values
- Map uniqAsaChains = new TreeMap();
- Map chainAsas = new TreeMap();
+ Map uniqAsaChains = new TreeMap<>();
+ Map chainAsas = new TreeMap<>();
// first we gather rotation-unique chains (in terms of AU id and transform id)
for (StructureInterface interf:list) {
@@ -138,11 +138,15 @@ public void calcAsas(int nSpherePoints, int nThreads, int cofactorSizeToUse) {
uniqAsaChains.put(molecId2, interf.getSecondAtomsForAsa(cofactorSizeToUse));
}
+ logger.debug("Will calculate uncomplexed ASA for {} orientation-unique chains.", uniqAsaChains.size());
+
long start = System.currentTimeMillis();
// we only need to calculate ASA for that subset (any translation of those will have same values)
for (String molecId:uniqAsaChains.keySet()) {
+ logger.debug("Calculating uncomplexed ASA for molecId {}, with {} atoms", molecId, uniqAsaChains.get(molecId).length);
+
AsaCalculator asaCalc = new AsaCalculator(uniqAsaChains.get(molecId),
AsaCalculator.DEFAULT_PROBE_SIZE, nSpherePoints, nThreads);
@@ -153,8 +157,9 @@ public void calcAsas(int nSpherePoints, int nThreads, int cofactorSizeToUse) {
}
long end = System.currentTimeMillis();
- logger.debug("Calculated uncomplexed ASA for "+uniqAsaChains.size()+" orientation-unique chains. "
- + "Time: "+((end-start)/1000.0)+" s");
+ logger.debug("Calculated uncomplexed ASA for {} orientation-unique chains. Time: {} s", uniqAsaChains.size(), ((end-start)/1000.0));
+
+ logger.debug ("Will calculate complexed ASA for {} pairwise complexes.", list.size());
start = System.currentTimeMillis();
@@ -164,13 +169,14 @@ public void calcAsas(int nSpherePoints, int nThreads, int cofactorSizeToUse) {
String molecId1 = interf.getMoleculeIds().getFirst()+interf.getTransforms().getFirst().getTransformId();
String molecId2 = interf.getMoleculeIds().getSecond()+interf.getTransforms().getSecond().getTransformId();
+ logger.debug("Calculating complexed ASAs for interface {} between molecules {} and {}", interf.getId(), molecId1, molecId2);
+
interf.setAsas(chainAsas.get(molecId1), chainAsas.get(molecId2), nSpherePoints, nThreads, cofactorSizeToUse);
}
end = System.currentTimeMillis();
- logger.debug("Calculated complexes ASA for "+list.size()+" pairwise complexes. "
- + "Time: "+((end-start)/1000.0)+" s");
+ logger.debug("Calculated complexes ASA for {} pairwise complexes. Time: {} s", list.size(), ((end-start)/1000.0));
// finally we sort based on the ChainInterface.comparable() (based in interfaceArea)
diff --git a/biojava-structure/src/test/java/org/biojava/nbio/structure/asa/TestAsaCalc.java b/biojava-structure/src/test/java/org/biojava/nbio/structure/asa/TestAsaCalc.java
index e7ea1d2481..981820dc08 100644
--- a/biojava-structure/src/test/java/org/biojava/nbio/structure/asa/TestAsaCalc.java
+++ b/biojava-structure/src/test/java/org/biojava/nbio/structure/asa/TestAsaCalc.java
@@ -20,22 +20,22 @@
*/
package org.biojava.nbio.structure.asa;
-import junit.framework.TestCase;
-import org.biojava.nbio.structure.Structure;
-import org.biojava.nbio.structure.StructureException;
-import org.biojava.nbio.structure.StructureIO;
+import org.biojava.nbio.structure.*;
import org.biojava.nbio.structure.io.mmcif.ChemCompGroupFactory;
import org.biojava.nbio.structure.io.mmcif.DownloadChemCompProvider;
-import org.junit.Assert;
+import static org.junit.Assert.*;
+
import org.junit.Test;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
/**
* Testing of Accessible Surface Area calculations
*
*
- * @author duarte_j
+ * @author Jose Duarte
*
*/
public class TestAsaCalc {
@@ -70,12 +70,114 @@ public void testAsa3PIU() throws StructureException, IOException {
//System.out.println(groupAsa.getGroup().getPDBName() + " " + groupAsa.getGroup().getResidueNumber() + " " + groupAsa.getAsaU());
totResidues+=groupAsa.getAsaU();
- Assert.assertTrue(groupAsa.getRelativeAsaU() <= 1.0);
+ assertTrue(groupAsa.getRelativeAsaU() <= 1.0);
+ }
+
+ assertEquals(totAtoms, totResidues, 0.000001);
+
+ assertEquals(17462.0, totAtoms, 1.0);
+
+ }
+
+ @Test
+ public void testNeighborIndicesFinding() throws StructureException, IOException {
+ // important: without this the tests can fail when running in maven (but not in IDE)
+ // that's because it depends on the order on how tests were run - JD 2018-03-10
+ ChemCompGroupFactory.setChemCompProvider(new DownloadChemCompProvider());
+
+ Structure structure = StructureIO.getStructure("3PIU");
+
+ AsaCalculator asaCalc = new AsaCalculator(structure,
+ AsaCalculator.DEFAULT_PROBE_SIZE,
+ 1000, 1, false);
+
+ int[][] allNbsSh = asaCalc.findNeighborIndicesSpatialHashing();
+
+ int[][] allNbs = asaCalc.findNeighborIndices();
+
+ for (int indexToTest =0; indexToTest < asaCalc.getAtomCoords().length; indexToTest++) {
+ //int indexToTest = 198;
+ int[] nbsSh = allNbsSh[indexToTest];
+ int[] nbs = allNbs[indexToTest];
+
+ List listOfMatchingIndices = new ArrayList<>();
+ for (int i = 0; i < nbsSh.length; i++) {
+ for (int j = 0; j < nbs.length; j++) {
+ if (nbs[j] == nbsSh[i]) {
+ listOfMatchingIndices.add(j);
+ break;
+ }
+ }
+ }
+
+// for (int i = 0; i