/* * BioJava development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public Licence. This should * be distributed with the code. If you do not have a copy, * see: * * http://www.gnu.org/copyleft/lesser.html * * Copyright for this code is held jointly by the individual * authors. These should be listed in @author doc comments. * * For more information on the BioJava project and its aims, * or to join the biojava-l mailing list, visit the home page * at: * * http://www.biojava.org/ * */ package org.biojava.nbio.structure.contact; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import javax.vecmath.Point3d; import org.biojava.nbio.structure.Atom; import org.biojava.nbio.structure.Calc; /** * A grid to be used for calculating atom contacts through a spatial hashing algorithm. * <p> * The grid is composed of cells of size of the cutoff so that the distances that need to be calculated * are reduced to those within each cell and to the neighbouring cells. * <p> * Usage, for generic 3D points: * <pre> * Point3d[] points = ...; * Grid grid = new Grid(8.0); * grid.addCoords(points); * List<Contact> contacts = getIndicesContacts(); * </pre> * Usage, for atoms: * <pre> * Atom[] atoms = ...; * Grid grid = new Grid(8.0); * grid.addCoords(atoms); * AtomContactSet contacts = getAtomContacts(); * </pre> * * @author Jose Duarte * */ public class Grid { /** * The scale: we use units of hundredths of Angstroms (thus cutoffs can be specified with a maximum precision of 0.01A) */ private static final int SCALE=100; private GridCell[][][] cells; private double cutoff; private int cellSize; private Point3d[] iAtoms; private Point3d[] jAtoms; private Atom[] iAtomObjects; private Atom[] jAtomObjects; // the bounds in int grid coordinates private int[] bounds; // the i and j bounding boxes in original double coordinates private BoundingBox ibounds; private BoundingBox jbounds; private boolean noOverlap; // if the 2 sets of atoms are found not to overlap then this is set to true /** * Creates a <code>Grid</code>, the cutoff is in the same units as the coordinates * (Angstroms if they are atom coordinates) and can * be specified to a precision of 0.01. * @param cutoff */ public Grid(double cutoff) { this.cutoff = cutoff; this.cellSize = (int) Math.floor(cutoff*SCALE); this.noOverlap = false; } private int getFloor(double number) { return (cellSize*((int)Math.floor(number*SCALE/cellSize))); } private int xintgrid2xgridindex(int xgridDim) { return (xgridDim-bounds[0])/cellSize; } private int yintgrid2ygridindex(int ygridDim) { return (ygridDim-bounds[1])/cellSize; } private int zintgrid2zgridindex(int zgridDim) { return (zgridDim-bounds[2])/cellSize; } /** * Adds the i and j atoms and fills the grid. Their bounds will be computed. * Subsequent call to {@link #getIndicesContacts()} or {@link #getAtomContacts()} will produce the interatomic contacts. * @param iAtoms * @param jAtoms */ public void addAtoms(Atom[] iAtoms, Atom[] jAtoms) { addAtoms(iAtoms, null, jAtoms, null); } /** * Adds the i and j atoms and fills the grid, passing their bounds (array of size 6 with x,y,z minima and x,y,z maxima) * This way the bounds don't need to be recomputed. * Subsequent call to {@link #getIndicesContacts()} or {@link #getAtomContacts()} will produce the interatomic contacts. * @param iAtoms * @param icoordbounds * @param jAtoms * @param jcoordbounds */ public void addAtoms(Atom[] iAtoms, BoundingBox icoordbounds, Atom[] jAtoms, BoundingBox jcoordbounds) { this.iAtoms = Calc.atomsToPoints(iAtoms); this.iAtomObjects = iAtoms; if (icoordbounds!=null) { this.ibounds = icoordbounds; } else { this.ibounds = new BoundingBox(this.iAtoms); } this.jAtoms = Calc.atomsToPoints(jAtoms); this.jAtomObjects = jAtoms; if (jAtoms==iAtoms) { this.jbounds=ibounds; } else { if (jcoordbounds!=null) { this.jbounds = jcoordbounds; } else { this.jbounds = new BoundingBox(this.jAtoms); } } fillGrid(); } /** * Adds a set of atoms, subsequent call to {@link #getIndicesContacts()} or {@link #getAtomContacts()} will produce the interatomic contacts. * The bounding box of the atoms will be computed based on input array * @param atoms */ public void addAtoms(Atom[] atoms) { addAtoms(atoms, (BoundingBox) null); } /** * Adds a set of atoms, subsequent call to {@link #getIndicesContacts()} or {@link #getAtomContacts()} will produce the interatomic contacts. * The bounds calculated elsewhere can be passed, or if null they are computed. * @param atoms * @param bounds */ public void addAtoms(Atom[] atoms, BoundingBox bounds) { this.iAtoms = Calc.atomsToPoints(atoms); this.iAtomObjects = atoms; if (bounds!=null) { this.ibounds = bounds; } else { this.ibounds = new BoundingBox(iAtoms); } this.jAtoms = null; this.jAtomObjects = null; this.jbounds = null; fillGrid(); } /** * Adds the i and j coordinates and fills the grid. Their bounds will be computed. * Subsequent call to {@link #getIndicesContacts()} will produce the * contacts, i.e. the set of points within distance cutoff. * * Subsequent calls to method {@link #getAtomContacts()} will produce a NullPointerException * since this only adds coordinates and no atom information. * @param iAtoms * @param jAtoms */ public void addCoords(Point3d[] iAtoms, Point3d[] jAtoms) { addCoords(iAtoms, null, jAtoms, null); } /** * Adds the i and j coordinates and fills the grid, passing their bounds (array of size 6 with x,y,z minima and x,y,z maxima) * This way the bounds don't need to be recomputed. * Subsequent call to {@link #getIndicesContacts()} will produce the * contacts, i.e. the set of points within distance cutoff. * * Subsequent calls to method {@link #getAtomContacts()} will produce a NullPointerException * since this only adds coordinates and no atom information. * @param iAtoms * @param icoordbounds * @param jAtoms * @param jcoordbounds */ public void addCoords(Point3d[] iAtoms, BoundingBox icoordbounds, Point3d[] jAtoms, BoundingBox jcoordbounds) { this.iAtoms = iAtoms; this.iAtomObjects = null; if (icoordbounds!=null) { this.ibounds = icoordbounds; } else { this.ibounds = new BoundingBox(this.iAtoms); } this.jAtoms = jAtoms; this.jAtomObjects = null; if (jAtoms==iAtoms) { this.jbounds=ibounds; } else { if (jcoordbounds!=null) { this.jbounds = jcoordbounds; } else { this.jbounds = new BoundingBox(this.jAtoms); } } fillGrid(); } /** * Adds a set of coordinates, subsequent call to {@link #getIndicesContacts()} will produce the * contacts, i.e. the set of points within distance cutoff. * The bounding box of the atoms will be computed based on input array. * Subsequent calls to method {@link #getAtomContacts()} will produce a NullPointerException * since this only adds coordinates and no atom information. * @param atoms */ public void addCoords(Point3d[] atoms) { addCoords(atoms, (BoundingBox) null); } /** * Adds a set of coordinates, subsequent call to {@link #getIndicesContacts()} will produce the * contacts, i.e. the set of points within distance cutoff. * The bounds calculated elsewhere can be passed, or if null they are computed. * Subsequent calls to method {@link #getAtomContacts()} will produce a NullPointerException * since this only adds coordinates and no atom information. * @param atoms * @param bounds */ public void addCoords(Point3d[] atoms, BoundingBox bounds) { this.iAtoms = atoms; this.iAtomObjects = null; if (bounds!=null) { this.ibounds = bounds; } else { this.ibounds = new BoundingBox(iAtoms); } this.jAtoms = null; this.jAtomObjects = null; this.jbounds = null; fillGrid(); } /** * Creates the grid based on the boundaries defined by all atoms given (iAtoms and jAtoms) * and places the atoms in their corresponding grid cells. * Checks also if the i and j grid overlap, i.e. the enclosing bounds of * the 2 grids (i and j) are no more than one cell size apart. If they don't * overlap then they are too far apart so there's nothing to calculate, we set * the noOverlap flag and then {@link #getIndicesContacts()} will do no calculation at all. */ private void fillGrid() { if (jbounds!=null && !ibounds.overlaps(jbounds, cutoff)) { //System.out.print("-"); noOverlap = true; return; } findFullGridIntBounds(); cells = new GridCell[1+(bounds[3]-bounds[0])/cellSize] [1+(bounds[4]-bounds[1])/cellSize] [1+(bounds[5]-bounds[2])/cellSize]; int i = 0; for (Point3d atom:iAtoms) { int xind = xintgrid2xgridindex(getFloor(atom.x)); int yind = yintgrid2ygridindex(getFloor(atom.y)); int zind = zintgrid2zgridindex(getFloor(atom.z)); if (cells[xind][yind][zind]==null) { cells[xind][yind][zind] = new GridCell(this); } cells[xind][yind][zind].addIindex(i); i++; } if (jAtoms==null) return; int j = 0; for (Point3d atom:jAtoms) { int xind = xintgrid2xgridindex(getFloor(atom.x)); int yind = yintgrid2ygridindex(getFloor(atom.y)); int zind = zintgrid2zgridindex(getFloor(atom.z)); if (cells[xind][yind][zind]==null) { cells[xind][yind][zind] = new GridCell(this); } cells[xind][yind][zind].addJindex(j); j++; } } /** * Calculates an int array of size 6 into member variable bounds: * - elements 0,1,2: minimum x,y,z of the iAtoms and jAtoms * - elements 3,4,5: maximum x,y,z of the iAtoms and jAtoms */ private void findFullGridIntBounds() { int[] iIntBounds = getIntBounds(ibounds); bounds = new int[6]; if (jbounds==null) { bounds = iIntBounds; } else { int[] jIntBounds = getIntBounds(jbounds); bounds[0] = Math.min(iIntBounds[0],jIntBounds[0]); bounds[1] = Math.min(iIntBounds[1],jIntBounds[1]); bounds[2] = Math.min(iIntBounds[2],jIntBounds[2]); bounds[3] = Math.max(iIntBounds[3],jIntBounds[3]); bounds[4] = Math.max(iIntBounds[4],jIntBounds[4]); bounds[5] = Math.max(iIntBounds[5],jIntBounds[5]); } } /** * Returns an int array of size 6 : * - elements 0,1,2: minimum x,y,z (in grid int coordinates) of the given atoms * - elements 3,4,5: maximum x,y,z (in grid int coordinates) of the given atoms * @return */ private int[] getIntBounds(BoundingBox coordbounds) { int[] bs = new int[6]; bs[0] = getFloor(coordbounds.xmin); bs[1] = getFloor(coordbounds.ymin); bs[2] = getFloor(coordbounds.zmin); bs[3] = getFloor(coordbounds.xmax); bs[4] = getFloor(coordbounds.ymax); bs[5] = getFloor(coordbounds.zmax); return bs; } /** * Returns all contacts, i.e. all atoms that are within the cutoff distance. * If both iAtoms and jAtoms are defined then contacts are between iAtoms and jAtoms, * if jAtoms is null, then contacts are within the iAtoms. * @return */ public AtomContactSet getAtomContacts() { AtomContactSet contacts = new AtomContactSet(cutoff); List<Contact> list = getIndicesContacts(); if (jAtomObjects == null) { for (Contact cont : list) { contacts.add(new AtomContact(new Pair<Atom>(iAtomObjects[cont.getI()],iAtomObjects[cont.getJ()]),cont.getDistance())); } } else { for (Contact cont : list) { contacts.add(new AtomContact(new Pair<Atom>(iAtomObjects[cont.getI()],jAtomObjects[cont.getJ()]),cont.getDistance())); } } return contacts; } /** * Returns all contacts, i.e. all atoms that are within the cutoff distance. * If both iAtoms and jAtoms are defined then contacts are between iAtoms and jAtoms, * if jAtoms is null, then contacts are within the iAtoms. * @return * @deprecated use {@link #getAtomContacts()} instead */ @Deprecated public AtomContactSet getContacts() { return getAtomContacts(); } /** * Returns all contacts, i.e. all atoms that are within the cutoff distance, as simple Contact objects containing the atom indices pairs and the distance. * If both iAtoms and jAtoms are defined then contacts are between iAtoms and jAtoms, * if jAtoms is null, then contacts are within the iAtoms. * @return */ public List<Contact> getIndicesContacts() { List<Contact> list = new ArrayList<>(); // if the 2 sets of atoms are not overlapping they are too far away and no need to calculate anything // this won't apply if there's only one set of atoms (iAtoms), where we would want all-to-all contacts if (noOverlap) return list; for (int xind=0;xind<cells.length;xind++) { for (int yind=0;yind<cells[xind].length;yind++) { for (int zind=0;zind<cells[xind][yind].length;zind++) { // distances of points within this cell GridCell thisCell = cells[xind][yind][zind]; if (thisCell==null) continue; list.addAll(thisCell.getContactsWithinCell()); // distances of points from this box to all neighbouring boxes: 26 iterations (26 neighbouring boxes) for (int x=xind-1;x<=xind+1;x++) { for (int y=yind-1;y<=yind+1;y++) { for (int z=zind-1;z<=zind+1;z++) { if (x==xind && y==yind && z==zind) continue; if (x>=0 && x<cells.length && y>=0 && y<cells[x].length && z>=0 && z<cells[x][y].length) { if (cells[x][y][z] == null) continue; list.addAll(thisCell.getContactsToOtherCell(cells[x][y][z])); } } } } } } } return list; } /** * Fast determination of whether any atoms from a given set fall within * the cutoff of iAtoms. If {@link #addAtoms(Atom[], Atom[])} was called * with two sets of atoms, contacts to either set are considered. * @param atoms * @return */ public boolean hasAnyContact(Point3d[] atoms) { return hasAnyContact(Arrays.asList(atoms)); } public boolean hasAnyContact(Collection<Point3d> atoms) { for(Point3d atom : atoms) { // Calculate Grid cell for the atom int xind = xintgrid2xgridindex(getFloor(atom.x)); int yind = yintgrid2ygridindex(getFloor(atom.y)); int zind = zintgrid2zgridindex(getFloor(atom.z)); // Consider 3x3x3 grid of cells around point for (int x=xind-1;x<=xind+1;x++) { if( x<0 || cells.length<=x) continue; for (int y=yind-1;y<=yind+1;y++) { if( y<0 || cells[x].length<=y ) continue; for (int z=zind-1;z<=zind+1;z++) { if( z<0 || cells[x][y].length<=z ) continue; GridCell cell = cells[x][y][z]; // Check for contacts in this cell if(cell != null && cell.hasContactToAtom(iAtoms, jAtoms, atom, cutoff)) { return true; } } } } } return false; } public double getCutoff() { return cutoff; } /** * 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. * @return true if the 2 grids don't overlap, false if they do */ public boolean isNoOverlap() { return noOverlap; } protected Point3d[] getIAtoms() { return iAtoms; } protected Point3d[] getJAtoms() { return jAtoms; } }