Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@

/**
* A set of atom-atom contacts to hold the results of intra and inter-chain contact calculations
* <p>
* Contacts are keyed by the <i>ordered</i> pair of {@link AtomIdentifier}s of the 2 atoms, i.e. the
* pair (a,b) and the pair (b,a) are 2 different keys. Thus look-ups ({@link #hasContact(Atom, Atom)},
* {@link #getContact(Atom, Atom)}) must give the 2 atoms in the same order in which the contact was
* calculated. The order produced by the calculation ({@link Grid}) is:
* <ul>
* <li>for contacts within a single set of atoms (e.g. the intra-chain contacts of
* <code>StructureTools.getAtomsInContact(Chain, double)</code>), the atom that comes first in the
* atom array is the first member of the pair</li>
* <li>for contacts between 2 sets of atoms (e.g. the inter-chain contacts of
* <code>StructureTools.getAtomsInContact(Chain, Chain, double, boolean)</code>, or a
* {@link StructureInterface}), the atom belonging to the first set is the first member of the pair</li>
* </ul>
* <p>
* Note that the order is not the order of PDB serials or of any other property of the atoms
* themselves: it is only the order in which the atoms were given to the calculation.
*
* @author duarte_j
*
Expand All @@ -37,6 +53,12 @@ public class AtomContactSet implements Serializable, Iterable<AtomContact> {

private static final long serialVersionUID = 1L;

/**
* The default load factor of a {@link HashMap}, needed to size the map from an expected number of
* entries.
*/
private static final float DEFAULT_LOAD_FACTOR = 0.75f;

private HashMap<Pair<AtomIdentifier>, AtomContact> contacts;
Comment thread
aalhossary marked this conversation as resolved.
private double cutoff;

Expand All @@ -45,31 +67,79 @@ public AtomContactSet(double cutoff) {
this.contacts = new HashMap<>();
}

/**
* Creates an AtomContactSet sized to hold the given number of contacts, so that the underlying map
* doesn't have to be repeatedly resized and rehashed as contacts are added. Contact calculations
* produce hundreds of thousands of contacts for a large structure, where the repeated rehashing
* that growing from the default capacity entails is a significant part of the cost.
* @param cutoff the distance cutoff
* @param expectedSize the number of contacts expected to be added. Only affects performance: an
* over-estimate merely leaves the map larger than it needs to be.
*/
public AtomContactSet(double cutoff, int expectedSize) {
this.cutoff = cutoff;
this.contacts = new HashMap<>((int) (expectedSize / DEFAULT_LOAD_FACTOR) + 1);
}

/**
* Adds the given contact to this set, keyed by the ordered pair of its 2 atoms. If a contact
* with the same ordered pair of atoms is already present it is replaced.
* @param contact the contact to add
*/
public void add(AtomContact contact) {
this.contacts.put(getAtomIdPairFromContact(contact), contact);
}

/**
* Adds all given contacts to this set, see {@link #add(AtomContact)}.
* @param list the contacts to add
*/
public void addAll(Collection<AtomContact> list) {
for (AtomContact contact:list) {
this.contacts.put(getAtomIdPairFromContact(contact), contact);
}
}

/**
* Tells whether a contact exists between the 2 given atoms, <i>in the given order</i>.
* <p>
* The 2 atoms have to be passed in the same order in which the contacts of this set were
* calculated, otherwise this returns false even if the 2 atoms are within the distance cutoff.
* See the class documentation for the ordering convention. If the order is not known, both orders
* have to be queried.
* @param atom1 the first atom of the pair
* @param atom2 the second atom of the pair
* @return true if the 2 atoms are in contact in the given order, false otherwise
* @see #getContact(Atom, Atom)
*/
public boolean hasContact(Atom atom1, Atom atom2) {
return hasContact(
new AtomIdentifier(atom1.getPDBserial(),atom1.getGroup().getChainId()),
new AtomIdentifier(atom2.getPDBserial(),atom2.getGroup().getChainId()) );
}

/**
* Tells whether a contact exists between the 2 given atom identifiers, <i>in the given order</i>,
* see {@link #hasContact(Atom, Atom)}.
* @param atomId1 the identifier of the first atom of the pair
* @param atomId2 the identifier of the second atom of the pair
* @return true if the 2 atoms are in contact in the given order, false otherwise
*/
public boolean hasContact(AtomIdentifier atomId1, AtomIdentifier atomId2) {
return contacts.containsKey(new Pair<AtomIdentifier>(atomId1,atomId2));
}

/**
* Returns the corresponding AtomContact or null if no contact exists between the 2 given atoms
* @param atom1
* @param atom2
* @return
* Returns the contact between the 2 given atoms <i>in the given order</i>, or null if there is
* no such contact in this set.
* <p>
* As in {@link #hasContact(Atom, Atom)} the order of the 2 atoms matters: they have to be passed
* in the same order in which the contacts of this set were calculated, otherwise null is returned
* even if the 2 atoms are within the distance cutoff. See the class documentation for the
* ordering convention.
* @param atom1 the first atom of the pair
* @param atom2 the second atom of the pair
* @return the contact between the 2 atoms in the given order, or null if there is none
*/
public AtomContact getContact(Atom atom1, Atom atom2) {
return contacts.get(new Pair<AtomIdentifier>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public class Grid {
private GridCell[][][] cells;

private double cutoff;
private double cutoffSq;
private int cellSize;

private Point3d[] iAtoms;
Expand All @@ -91,6 +92,7 @@ public class Grid {
*/
public Grid(double cutoff) {
this.cutoff = cutoff;
this.cutoffSq = cutoff * cutoff;
this.cellSize = (int) Math.floor(cutoff*SCALE);
this.noOverlap = false;
}
Expand Down Expand Up @@ -380,10 +382,12 @@ private int[] getIntBounds(BoundingBox coordbounds) {
*/
public AtomContactSet getAtomContacts() {

AtomContactSet contacts = new AtomContactSet(cutoff);

List<Contact> list = getIndicesContacts();

// each contact maps to at most one entry in the set, so the number of index contacts sizes it
// without ever under-allocating
AtomContactSet contacts = new AtomContactSet(cutoff, list.size());

if (jAtomObjects == null) {
for (Contact cont : list) {
contacts.add(new AtomContact(new Pair<Atom>(iAtomObjects[cont.getI()],iAtomObjects[cont.getJ()]),cont.getDistance()));
Expand Down Expand Up @@ -496,6 +500,15 @@ public double getCutoff() {
return cutoff;
}

/**
* Returns the square of the cutoff, precomputed at construction. Used by {@link GridCell} to
* compare squared distances, avoiding a square root per candidate pair.
* @return the squared cutoff
*/
protected double getCutoffSq() {
return cutoffSq;
}

/**
* Tells whether (after having added atoms to grid) the i and j grids are not overlapping.
* Overlap is defined as enclosing bounds of the 2 grids being no more than one cell size apart.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package org.biojava.nbio.structure.contact;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.vecmath.Point3d;
Expand All @@ -35,30 +36,61 @@
public class GridCell {


/**
* Shared empty array so that cells that never receive indices (e.g. the j indices when only one
* set of atoms was added to the grid) don't allocate anything at all.
*/
private static final int[] EMPTY = new int[0];

/**
* Capacity of the index arrays on first insertion. Cell occupancy depends on the cutoff (the cell
* side is the cutoff), ranging from a handful of atoms for small cutoffs to a few tens for large
* ones, so we start small and grow geometrically.
*/
private static final int INITIAL_CAPACITY = 8;

private Grid grid;
private ArrayList<Integer> iIndices;
private ArrayList<Integer> jIndices;

/**
* The indices of the i atoms in this cell, held as a primitive array to avoid the boxing (and the
* pointer chasing it entails) of a Collection of Integers: these are read in the innermost loop of
* the contact calculation. Only the first {@link #numIindices} elements are meaningful.
*/
private int[] iIndices;
private int numIindices;

/**
* The indices of the j atoms in this cell. See {@link #iIndices}.
*/
private int[] jIndices;
private int numJindices;

public GridCell(Grid parent){
iIndices = new ArrayList<>();
jIndices = new ArrayList<>();
iIndices = EMPTY;
jIndices = EMPTY;
this.grid = parent;
}

public void addIindex(int serial){
iIndices.add(serial);
if (numIindices == iIndices.length) {
iIndices = Arrays.copyOf(iIndices, numIindices == 0 ? INITIAL_CAPACITY : numIindices * 2);
}
iIndices[numIindices++] = serial;
}

public void addJindex(int serial){
jIndices.add(serial);
if (numJindices == jIndices.length) {
jIndices = Arrays.copyOf(jIndices, numJindices == 0 ? INITIAL_CAPACITY : numJindices * 2);
}
jIndices[numJindices++] = serial;
}

public int getNumIindices() {
return iIndices.size();
return numIindices;
}

public int getNumJindices() {
return jIndices.size();
return numJindices;
}

/**
Expand All @@ -74,23 +106,31 @@ public List<Contact> getContactsWithinCell(){

Point3d[] iAtoms = grid.getIAtoms();
Point3d[] jAtoms = grid.getJAtoms();
double cutoff = grid.getCutoff();
// we compare squared distances to the squared cutoff, so that the expensive square root is
// only computed for the pairs that are actually in contact (the large majority are not)
double cutoffSq = grid.getCutoffSq();

if (jAtoms==null) {
for (int i:iIndices) {
for (int j:iIndices) {
for (int a=0; a<numIindices; a++) {
int i = iIndices[a];
Point3d atomI = iAtoms[i];
for (int b=0; b<numIindices; b++) {
int j = iIndices[b];
if (j>i) {
double distance = iAtoms[i].distance(iAtoms[j]);
if (distance<cutoff) contacts.add(new Contact(i, j, distance));
double distanceSq = atomI.distanceSquared(iAtoms[j]);
if (distanceSq<cutoffSq) contacts.add(new Contact(i, j, Math.sqrt(distanceSq)));
}
}
Comment thread
aalhossary marked this conversation as resolved.
}

} else {
for (int i:iIndices) {
for (int j:jIndices) {
double distance = iAtoms[i].distance(jAtoms[j]);
if (distance<cutoff) contacts.add(new Contact(i, j, distance));
for (int a=0; a<numIindices; a++) {
int i = iIndices[a];
Point3d atomI = iAtoms[i];
for (int b=0; b<numJindices; b++) {
int j = jIndices[b];
double distanceSq = atomI.distanceSquared(jAtoms[j]);
if (distanceSq<cutoffSq) contacts.add(new Contact(i, j, Math.sqrt(distanceSq)));
}
}
}
Expand All @@ -111,26 +151,38 @@ public List<Contact> getContactsToOtherCell(GridCell otherCell){

Point3d[] iAtoms = grid.getIAtoms();
Point3d[] jAtoms = grid.getJAtoms();
double cutoff = grid.getCutoff();
// we compare squared distances to the squared cutoff, so that the expensive square root is
// only computed for the pairs that are actually in contact (the large majority are not)
double cutoffSq = grid.getCutoffSq();


if (jAtoms==null) {

for (int i:iIndices) {
for (int j:otherCell.iIndices) {
int[] otherIndices = otherCell.iIndices;
int otherNum = otherCell.numIindices;
for (int a=0; a<numIindices; a++) {
int i = iIndices[a];
Point3d atomI = iAtoms[i];
for (int b=0; b<otherNum; b++) {
int j = otherIndices[b];
if (j>i) {
double distance = iAtoms[i].distance(iAtoms[j]);
if (distance<cutoff) contacts.add(new Contact(i, j, distance));
double distanceSq = atomI.distanceSquared(iAtoms[j]);
if (distanceSq<cutoffSq) contacts.add(new Contact(i, j, Math.sqrt(distanceSq)));
}
}
}

} else {

for (int i:iIndices) {
for (int j:otherCell.jIndices) {
double distance = iAtoms[i].distance(jAtoms[j]);
if (distance<cutoff) contacts.add(new Contact(i, j, distance));
int[] otherIndices = otherCell.jIndices;
int otherNum = otherCell.numJindices;
for (int a=0; a<numIindices; a++) {
int i = iIndices[a];
Point3d atomI = iAtoms[i];
for (int b=0; b<otherNum; b++) {
int j = otherIndices[b];
double distanceSq = atomI.distanceSquared(jAtoms[j]);
if (distanceSq<cutoffSq) contacts.add(new Contact(i, j, Math.sqrt(distanceSq)));
}
}

Expand All @@ -148,15 +200,17 @@ public List<Contact> getContactsToOtherCell(GridCell otherCell){
* @return
*/
public boolean hasContactToAtom(Point3d[] iAtoms, Point3d[] jAtoms, Point3d query, double cutoff) {
for( int i : iIndices ) {
double distance = iAtoms[i].distance(query);
if( distance<cutoff)
// only the comparison matters here, so we can stay in squared distance space and avoid square roots altogether
double cutoffSq = cutoff * cutoff;
for (int a=0; a<numIindices; a++) {
double distanceSq = iAtoms[iIndices[a]].distanceSquared(query);
if( distanceSq<cutoffSq)
return true;
}
if (jAtoms!=null) {
for( int i : jIndices ) {
double distance = jAtoms[i].distance(query);
if( distance<cutoff)
for (int a=0; a<numJindices; a++) {
double distanceSq = jAtoms[jIndices[a]].distanceSquared(query);
if( distanceSq<cutoffSq)
return true;
}
}
Expand All @@ -168,7 +222,7 @@ public boolean hasContactToAtom(Point3d[] iAtoms, Point3d[] jAtoms, Point3d quer
*/
@Override
public String toString() {
return String.format("GridCell [%d iAtoms,%d jAtoms]",iIndices.size(),jIndices==null?"-":jIndices.size());
return String.format("GridCell [%d iAtoms,%d jAtoms]", numIindices, numJindices);
}


Expand Down
Loading