001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 */ 021package org.biojava.nbio.structure.asa; 022 023import org.biojava.nbio.structure.*; 024import org.biojava.nbio.structure.contact.Contact; 025import org.biojava.nbio.structure.contact.Grid; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029import javax.vecmath.Point3d; 030import javax.vecmath.Vector3d; 031import java.util.*; 032import java.util.concurrent.ExecutorService; 033import java.util.concurrent.Executors; 034 035 036/** 037 * Class to calculate Accessible Surface Areas based on 038 * the rolling ball algorithm by Shrake and Rupley. 039 * <p> 040 * The code is adapted from a python implementation at http://boscoh.com/protein/asapy 041 * (now source is available at https://github.com/boscoh/asa). 042 * Thanks to Bosco K. Ho for a great piece of code and for his fantastic blog. 043 * <p> 044 * A few optimizations come from Eisenhaber et al, J Comp Chemistry 1994 045 * (https://onlinelibrary.wiley.com/doi/epdf/10.1002/jcc.540160303) 046 * <p> 047 * See 048 * Shrake, A., and J. A. Rupley. "Environment and Exposure to Solvent of Protein Atoms. 049 * Lysozyme and Insulin." JMB (1973) 79:351-371. 050 * Lee, B., and Richards, F.M. "The interpretation of Protein Structures: Estimation of 051 * Static Accessibility" JMB (1971) 55:379-400 052 * 053 * @author Jose Duarte 054 */ 055public class AsaCalculator { 056 057 private static final Logger logger = LoggerFactory.getLogger(AsaCalculator.class); 058 059 /** 060 * The default value for number of sphere points to sample. 061 * See this paper for a nice study on the effect of this parameter: https://f1000research.com/articles/5-189/v1 062 */ 063 public static final int DEFAULT_N_SPHERE_POINTS = 1000; 064 public static final double DEFAULT_PROBE_SIZE = 1.4; 065 public static final int DEFAULT_NTHREADS = 1; 066 067 private static final boolean DEFAULT_USE_SPATIAL_HASHING = true; 068 069 070 071 // Chothia's amino acid atoms vdw radii 072 public static final double TRIGONAL_CARBON_VDW = 1.76; 073 public static final double TETRAHEDRAL_CARBON_VDW = 1.87; 074 public static final double TRIGONAL_NITROGEN_VDW = 1.65; 075 public static final double TETRAHEDRAL_NITROGEN_VDW = 1.50; 076 public static final double SULFUR_VDW = 1.85; 077 public static final double OXIGEN_VDW = 1.40; 078 079 // Chothia's nucleotide atoms vdw radii 080 public static final double NUC_CARBON_VDW = 1.80; 081 public static final double NUC_NITROGEN_VDW = 1.60; 082 public static final double PHOSPHOROUS_VDW = 1.90; 083 084 085 086 087 088 private class AsaCalcWorker implements Runnable { 089 090 private final int i; 091 private final double[] asas; 092 093 private AsaCalcWorker(int i, double[] asas) { 094 this.i = i; 095 this.asas = asas; 096 } 097 098 @Override 099 public void run() { 100 asas[i] = calcSingleAsa(i); 101 } 102 } 103 104 static class IndexAndDistance { 105 final int index; 106 final double dist; 107 IndexAndDistance(int index, double dist) { 108 this.index = index; 109 this.dist = dist; 110 } 111 } 112 113 114 private final Point3d[] atomCoords; 115 private final Atom[] atoms; 116 private final double[] radii; 117 private final double probe; 118 private final int nThreads; 119 private Vector3d[] spherePoints; 120 private double cons; 121 private IndexAndDistance[][] neighborIndices; 122 123 private boolean useSpatialHashingForNeighbors; 124 125 /** 126 * Constructs a new AsaCalculator. Subsequently call {@link #calculateAsas()} 127 * or {@link #getGroupAsas()} to calculate the ASAs 128 * Only non-Hydrogen atoms are considered in the calculation. 129 * @param structure the structure, all non-H atoms will be used 130 * @param probe the probe size 131 * @param nSpherePoints the number of points to be used in generating the spherical 132 * dot-density, the more points the more accurate (and slower) calculation 133 * @param nThreads the number of parallel threads to use for the calculation 134 * @param hetAtoms if true HET residues are considered, if false they aren't, equivalent to 135 * NACCESS' -h option 136 */ 137 public AsaCalculator(Structure structure, double probe, int nSpherePoints, int nThreads, boolean hetAtoms) { 138 this.atoms = StructureTools.getAllNonHAtomArray(structure, hetAtoms); 139 this.atomCoords = Calc.atomsToPoints(atoms); 140 this.probe = probe; 141 this.nThreads = nThreads; 142 143 this.useSpatialHashingForNeighbors = DEFAULT_USE_SPATIAL_HASHING; 144 145 // initialising the radii by looking them up through AtomRadii 146 radii = new double[atomCoords.length]; 147 for (int i=0;i<atomCoords.length;i++) { 148 radii[i] = getRadius(atoms[i]); 149 } 150 151 initSpherePoints(nSpherePoints); 152 } 153 154 /** 155 * Constructs a new AsaCalculator. Subsequently call {@link #calculateAsas()} 156 * or {@link #getGroupAsas()} to calculate the ASAs. 157 * @param atoms an array of atoms not containing Hydrogen atoms 158 * @param probe the probe size 159 * @param nSpherePoints the number of points to be used in generating the spherical 160 * dot-density, the more points the more accurate (and slower) calculation 161 * @param nThreads the number of parallel threads to use for the calculation 162 * @throws IllegalArgumentException if any atom in the array is a Hydrogen atom 163 */ 164 public AsaCalculator(Atom[] atoms, double probe, int nSpherePoints, int nThreads) { 165 this.atoms = atoms; 166 this.atomCoords = Calc.atomsToPoints(atoms); 167 this.probe = probe; 168 this.nThreads = nThreads; 169 170 this.useSpatialHashingForNeighbors = DEFAULT_USE_SPATIAL_HASHING; 171 172 for (Atom atom:atoms) { 173 if (atom.getElement()==Element.H) 174 throw new IllegalArgumentException("Can't calculate ASA for an array that contains Hydrogen atoms "); 175 } 176 177 // initialising the radii by looking them up through AtomRadii 178 radii = new double[atoms.length]; 179 for (int i=0;i<atoms.length;i++) { 180 radii[i] = getRadius(atoms[i]); 181 } 182 183 initSpherePoints(nSpherePoints); 184 } 185 186 /** 187 * Constructs a new AsaCalculator. Subsequently call {@link #calcSingleAsa(int)} 188 * to calculate the atom ASAs. The given radius parameter will be taken as the radius for 189 * all points given. No ASA calculation per group will be possible with this constructor, so 190 * usage of {@link #getGroupAsas()} will result in a NullPointerException. 191 * @param atomCoords 192 * the coordinates representing the center of atoms 193 * @param probe 194 * the probe size 195 * @param nSpherePoints 196 * the number of points to be used in generating the spherical 197 * dot-density, the more points the more accurate (and slower) calculation 198 * @param nThreads 199 * the number of parallel threads to use for the calculation 200 * @param radius 201 * the radius that will be assign to all given coordinates 202 */ 203 public AsaCalculator(Point3d[] atomCoords, double probe, int nSpherePoints, int nThreads, double radius) { 204 this.atoms = null; 205 this.atomCoords = atomCoords; 206 this.probe = probe; 207 this.nThreads = nThreads; 208 209 this.useSpatialHashingForNeighbors = DEFAULT_USE_SPATIAL_HASHING; 210 211 // initialising the radii to the given radius for all atoms 212 radii = new double[atomCoords.length]; 213 for (int i=0;i<atomCoords.length;i++) { 214 radii[i] = radius; 215 } 216 217 initSpherePoints(nSpherePoints); 218 } 219 220 private void initSpherePoints(int nSpherePoints) { 221 222 logger.debug("Will use {} sphere points", nSpherePoints); 223 224 // initialising the sphere points to sample 225 spherePoints = generateSpherePoints(nSpherePoints); 226 227 cons = 4.0 * Math.PI / nSpherePoints; 228 } 229 230 /** 231 * Calculates ASA for all atoms and return them as a GroupAsa 232 * array (one element per residue in structure) containing ASAs per residue 233 * and per atom. 234 * The sorting of Groups in returned array is as specified by {@link org.biojava.nbio.structure.ResidueNumber} 235 * @return 236 */ 237 public GroupAsa[] getGroupAsas() { 238 239 TreeMap<ResidueNumber, GroupAsa> asas = new TreeMap<>(); 240 241 double[] asasPerAtom = calculateAsas(); 242 243 for (int i=0;i<atomCoords.length;i++) { 244 Group g = atoms[i].getGroup(); 245 if (!asas.containsKey(g.getResidueNumber())) { 246 GroupAsa groupAsa = new GroupAsa(g); 247 groupAsa.addAtomAsaU(asasPerAtom[i]); 248 asas.put(g.getResidueNumber(), groupAsa); 249 } else { 250 GroupAsa groupAsa = asas.get(g.getResidueNumber()); 251 groupAsa.addAtomAsaU(asasPerAtom[i]); 252 } 253 } 254 255 return asas.values().toArray(new GroupAsa[0]); 256 } 257 258 /** 259 * Calculates the Accessible Surface Areas for the atoms given in constructor and with parameters given. 260 * Beware that the parallel implementation is quite memory hungry. It scales well as long as there is 261 * enough memory available. 262 * @return an array with asa values corresponding to each atom of the input array 263 */ 264 public double[] calculateAsas() { 265 266 double[] asas = new double[atomCoords.length]; 267 268 long start = System.currentTimeMillis(); 269 if (useSpatialHashingForNeighbors) { 270 logger.debug("Will use spatial hashing to find neighbors"); 271 neighborIndices = findNeighborIndicesSpatialHashing(); 272 } else { 273 logger.debug("Will not use spatial hashing to find neighbors"); 274 neighborIndices = findNeighborIndices(); 275 } 276 long end = System.currentTimeMillis(); 277 logger.debug("Took {} s to find neighbors", (end-start)/1000.0); 278 279 start = System.currentTimeMillis(); 280 if (nThreads<=1) { // (i.e. it will also be 1 thread if 0 or negative number specified) 281 logger.debug("Will use 1 thread for ASA calculation"); 282 for (int i=0;i<atomCoords.length;i++) { 283 asas[i] = calcSingleAsa(i); 284 } 285 286 } else { 287 logger.debug("Will use {} threads for ASA calculation", nThreads); 288 289 ExecutorService threadPool = Executors.newFixedThreadPool(nThreads); 290 291 for (int i=0;i<atomCoords.length;i++) { 292 threadPool.submit(new AsaCalcWorker(i,asas)); 293 } 294 295 threadPool.shutdown(); 296 297 while (!threadPool.isTerminated()); 298 299 } 300 end = System.currentTimeMillis(); 301 logger.debug("Took {} s to calculate all {} atoms ASAs (excluding neighbors calculation)", (end-start)/1000.0, atomCoords.length); 302 303 return asas; 304 } 305 306 /** 307 * Set the useSpatialHashingForNeighbors flag to use spatial hashing to calculate neighbors (true) or all-to-all 308 * distance calculation (false). Default is {@value DEFAULT_USE_SPATIAL_HASHING}. 309 * Use for testing performance only. 310 * @param useSpatialHashingForNeighbors the flag 311 */ 312 void setUseSpatialHashingForNeighbors(boolean useSpatialHashingForNeighbors) { 313 this.useSpatialHashingForNeighbors = useSpatialHashingForNeighbors; 314 } 315 316 /** 317 * Returns list of 3d coordinates of points on a unit sphere using the 318 * Golden Section Spiral algorithm. 319 * @param nSpherePoints the number of points to be used in generating the spherical dot-density 320 * @return the array of points as Vector3d objects 321 */ 322 private Vector3d[] generateSpherePoints(int nSpherePoints) { 323 Vector3d[] points = new Vector3d[nSpherePoints]; 324 double inc = Math.PI * (3.0 - Math.sqrt(5.0)); 325 double offset = 2.0 / nSpherePoints; 326 for (int k=0;k<nSpherePoints;k++) { 327 double y = k * offset - 1.0 + (offset / 2.0); 328 double r = Math.sqrt(1.0 - y*y); 329 double phi = k * inc; 330 points[k] = new Vector3d(Math.cos(phi)*r, y, Math.sin(phi)*r); 331 } 332 return points; 333 } 334 335 /** 336 * Returns the 2-dimensional array with neighbor indices for every atom. 337 * @return 2-dimensional array of size: n_atoms x n_neighbors_per_atom 338 */ 339 IndexAndDistance[][] findNeighborIndices() { 340 341 // looking at a typical protein case, number of neighbours are from ~10 to ~50, with an average of ~30 342 int initialCapacity = 60; 343 344 IndexAndDistance[][] nbsIndices = new IndexAndDistance[atomCoords.length][]; 345 346 for (int k=0; k<atomCoords.length; k++) { 347 double radius = radii[k] + probe + probe; 348 349 List<IndexAndDistance> thisNbIndices = new ArrayList<>(initialCapacity); 350 351 for (int i = 0; i < atomCoords.length; i++) { 352 if (i == k) continue; 353 354 double dist = atomCoords[i].distance(atomCoords[k]); 355 356 if (dist < radius + radii[i]) { 357 thisNbIndices.add(new IndexAndDistance(i, dist)); 358 } 359 } 360 361 IndexAndDistance[] indicesArray = thisNbIndices.toArray(new IndexAndDistance[0]); 362 nbsIndices[k] = indicesArray; 363 } 364 return nbsIndices; 365 } 366 367 /** 368 * Returns the 2-dimensional array with neighbor indices for every atom, 369 * using spatial hashing to avoid all to all distance calculation. 370 * @return 2-dimensional array of size: n_atoms x n_neighbors_per_atom 371 */ 372 IndexAndDistance[][] findNeighborIndicesSpatialHashing() { 373 374 // looking at a typical protein case, number of neighbours are from ~10 to ~50, with an average of ~30 375 int initialCapacity = 60; 376 377 List<Contact> contactList = calcContacts(); 378 Map<Integer, List<IndexAndDistance>> indices = new HashMap<>(atomCoords.length); 379 for (Contact contact : contactList) { 380 // note contacts are stored 1-way only, with j>i 381 int i = contact.getI(); 382 int j = contact.getJ(); 383 384 List<IndexAndDistance> iIndices; 385 List<IndexAndDistance> jIndices; 386 if (!indices.containsKey(i)) { 387 iIndices = new ArrayList<>(initialCapacity); 388 indices.put(i, iIndices); 389 } else { 390 iIndices = indices.get(i); 391 } 392 if (!indices.containsKey(j)) { 393 jIndices = new ArrayList<>(initialCapacity); 394 indices.put(j, jIndices); 395 } else { 396 jIndices = indices.get(j); 397 } 398 399 double radius = radii[i] + probe + probe; 400 double dist = contact.getDistance(); 401 if (dist < radius + radii[j]) { 402 iIndices.add(new IndexAndDistance(j, dist)); 403 jIndices.add(new IndexAndDistance(i, dist)); 404 } 405 } 406 407 // convert map to array for fast access 408 IndexAndDistance[][] nbsIndices = new IndexAndDistance[atomCoords.length][]; 409 for (Map.Entry<Integer, List<IndexAndDistance>> entry : indices.entrySet()) { 410 List<IndexAndDistance> list = entry.getValue(); 411 IndexAndDistance[] indexAndDistances = list.toArray(new IndexAndDistance[0]); 412 nbsIndices[entry.getKey()] = indexAndDistances; 413 } 414 415 // important: some atoms might have no neighbors at all: we need to initialise to empty arrays 416 for (int i=0; i<nbsIndices.length; i++) { 417 if (nbsIndices[i] == null) { 418 nbsIndices[i] = new IndexAndDistance[0]; 419 } 420 } 421 422 return nbsIndices; 423 } 424 425 Point3d[] getAtomCoords() { 426 return atomCoords; 427 } 428 429 private List<Contact> calcContacts() { 430 if (atomCoords.length == 0) 431 return new ArrayList<>(); 432 double maxRadius = 0; 433 OptionalDouble optionalDouble = Arrays.stream(radii).max(); 434 if (optionalDouble.isPresent()) 435 maxRadius = optionalDouble.getAsDouble(); 436 double cutoff = maxRadius + maxRadius + probe + probe; 437 logger.debug("Max radius is {}, cutoff is {}", maxRadius, cutoff); 438 Grid grid = new Grid(cutoff); 439 grid.addCoords(atomCoords); 440 return grid.getIndicesContacts(); 441 } 442 443 private double calcSingleAsa(int i) { 444 Point3d atom_i = atomCoords[i]; 445 446 int n_neighbor = neighborIndices[i].length; 447 IndexAndDistance[] neighbor_indices = neighborIndices[i]; 448 // Sorting by closest to farthest away neighbors achieves faster runtimes when checking for occluded 449 // sphere sample points below. This follows the ideas exposed in 450 // Eisenhaber et al, J Comp Chemistry 1994 (https://onlinelibrary.wiley.com/doi/epdf/10.1002/jcc.540160303) 451 // This is essential for performance. In my tests this brings down the number of occlusion checks in loop below to 452 // an average of n_sphere_points/10 per atom i, producing ~ x4 performance gain overall 453 Arrays.sort(neighbor_indices, Comparator.comparingDouble(o -> o.dist)); 454 455 double radius_i = probe + radii[i]; 456 457 int n_accessible_point = 0; 458 // purely for debugging 459 int[] numDistsCalced = null; 460 if (logger.isDebugEnabled()) numDistsCalced = new int[n_neighbor]; 461 462 // now we precalculate anything depending only on i,j in equation 3 in Eisenhaber 1994 463 double[] sqRadii = new double[n_neighbor]; 464 Vector3d[] aj_minus_ais = new Vector3d[n_neighbor]; 465 for (int nbArrayInd =0; nbArrayInd<n_neighbor; nbArrayInd++) { 466 int j = neighbor_indices[nbArrayInd].index; 467 double dist = neighbor_indices[nbArrayInd].dist; 468 double radius_j = radii[j] + probe; 469 // see equation 3 in Eisenhaber 1994 470 sqRadii[nbArrayInd] = (dist*dist + radius_i*radius_i - radius_j*radius_j)/(2*radius_i); 471 Vector3d aj_minus_ai = new Vector3d(atomCoords[j]); 472 aj_minus_ai.sub(atom_i); 473 aj_minus_ais[nbArrayInd] = aj_minus_ai; 474 } 475 476 for (Vector3d point: spherePoints){ 477 boolean is_accessible = true; 478 479 // note that the neighbors are sorted by distance, achieving optimal performance in this inner loop 480 // See Eisenhaber et al, J Comp Chemistry 1994 481 482 for (int nbArrayInd =0; nbArrayInd<n_neighbor; nbArrayInd++) { 483 484 // see equation 3 in Eisenhaber 1994. This is slightly more efficient than 485 // calculating distances to the actual sphere points on atom_i (which would be obtained with: 486 // Point3d test_point = new Point3d(point.x*radius + atom_i.x,point.y*radius + atom_i.y,point.z*radius + atom_i.z)) 487 double dotProd = aj_minus_ais[nbArrayInd].dot(point); 488 489 if (numDistsCalced!=null) numDistsCalced[nbArrayInd]++; 490 491 if (dotProd > sqRadii[nbArrayInd]) { 492 is_accessible = false; 493 break; 494 } 495 } 496 if (is_accessible) { 497 n_accessible_point++; 498 } 499 } 500 501 // purely for debugging 502 if (numDistsCalced!=null) { 503 int sum = 0; 504 for (int numDistCalcedForJ : numDistsCalced) sum += numDistCalcedForJ; 505 logger.debug("Number of sample points distances calculated for neighbors of i={} : average {}, all {}", i, (double) sum / (double) n_neighbor, numDistsCalced); 506 } 507 508 return cons*n_accessible_point*radius_i*radius_i; 509 } 510 511 /** 512 * Gets the radius for given amino acid and atom 513 * @param amino 514 * @param atom 515 * @return 516 */ 517 private static double getRadiusForAmino(AminoAcid amino, Atom atom) { 518 519 if (atom.getElement().equals(Element.H)) return Element.H.getVDWRadius(); 520 // some unusual entries (e.g. 1tes) contain Deuterium atoms in standard aminoacids 521 if (atom.getElement().equals(Element.D)) return Element.D.getVDWRadius(); 522 523 String atomCode = atom.getName(); 524 char aa = amino.getAminoType(); 525 526 // here we use the values that Chothia gives in his paper (as NACCESS does) 527 if (atom.getElement()==Element.O) { 528 return OXIGEN_VDW; 529 } 530 else if (atom.getElement()==Element.S) { 531 return SULFUR_VDW; 532 } 533 else if (atom.getElement()==Element.N) { 534 if (atomCode.equals("NZ")) return TETRAHEDRAL_NITROGEN_VDW; // tetrahedral Nitrogen 535 return TRIGONAL_NITROGEN_VDW; // trigonal Nitrogen 536 } 537 else if (atom.getElement()==Element.C) { // it must be a carbon 538 if (atomCode.equals("C") || 539 atomCode.equals("CE1") || atomCode.equals("CE2") || atomCode.equals("CE3") || 540 atomCode.equals("CH2") || 541 atomCode.equals("CZ") || atomCode.equals("CZ2") || atomCode.equals("CZ3")) { 542 return TRIGONAL_CARBON_VDW; // trigonal Carbon 543 } 544 else if (atomCode.equals("CA") || atomCode.equals("CB") || 545 atomCode.equals("CE") || 546 atomCode.equals("CG1") || atomCode.equals("CG2")) { 547 return TETRAHEDRAL_CARBON_VDW; // tetrahedral Carbon 548 } 549 // the rest of the cases (CD, CD1, CD2, CG) depend on amino acid 550 else { 551 switch (aa) { 552 case 'F': 553 case 'W': 554 case 'Y': 555 case 'H': 556 case 'D': 557 case 'N': 558 return TRIGONAL_CARBON_VDW; 559 560 case 'P': 561 case 'K': 562 case 'R': 563 case 'M': 564 case 'I': 565 case 'L': 566 return TETRAHEDRAL_CARBON_VDW; 567 568 case 'Q': 569 case 'E': 570 if (atomCode.equals("CD")) return TRIGONAL_CARBON_VDW; 571 else if (atomCode.equals("CG")) return TETRAHEDRAL_CARBON_VDW; 572 573 default: 574 logger.info("Unexpected carbon atom {} for aminoacid {}, assigning its standard vdw radius", atomCode, aa); 575 return Element.C.getVDWRadius(); 576 } 577 } 578 579 // not any of the expected atoms 580 } else { 581 // non standard aas, (e.g. MSE, LLP) will always have this problem, 582 logger.debug("Unexpected atom {} for aminoacid {} ({}), assigning its standard vdw radius", atomCode, aa, amino.getPDBName()); 583 584 return atom.getElement().getVDWRadius(); 585 } 586 } 587 588 589 /** 590 * Gets the radius for given nucleotide atom 591 * @param atom 592 * @return 593 */ 594 private static double getRadiusForNucl(NucleotideImpl nuc, Atom atom) { 595 596 if (atom.getElement().equals(Element.H)) return Element.H.getVDWRadius(); 597 if (atom.getElement().equals(Element.D)) return Element.D.getVDWRadius(); 598 599 if (atom.getElement()==Element.C) return NUC_CARBON_VDW; 600 601 if (atom.getElement()==Element.N) return NUC_NITROGEN_VDW; 602 603 if (atom.getElement()==Element.P) return PHOSPHOROUS_VDW; 604 605 if (atom.getElement()==Element.O) return OXIGEN_VDW; 606 607 logger.info("Unexpected atom "+atom.getName()+" for nucleotide "+nuc.getPDBName()+", assigning its standard vdw radius"); 608 return atom.getElement().getVDWRadius(); 609 } 610 611 612 /** 613 * Gets the van der Waals radius of the given atom following the values defined by 614 * Chothia (1976) J.Mol.Biol.105,1-14 615 * NOTE: the vdw values defined by the paper assume no Hydrogens and thus "inflates" 616 * slightly the heavy atoms to account for Hydrogens. Thus this method cannot be used 617 * in a structure that contains Hydrogens! 618 * 619 * If atom is neither part of a nucleotide nor of a standard aminoacid, 620 * the default vdw radius for the element is returned. If atom is of 621 * unknown type (element) the vdw radius of {@link Element().N} is returned 622 * 623 * @param atom 624 * @return 625 */ 626 public static double getRadius(Atom atom) { 627 628 if (atom.getElement()==null) { 629 logger.warn("Unrecognised atom "+atom.getName()+" with serial "+atom.getPDBserial()+ 630 ", assigning the default vdw radius (Nitrogen vdw radius)."); 631 return Element.N.getVDWRadius(); 632 } 633 634 Group res = atom.getGroup(); 635 636 if (res==null) { 637 logger.warn("Unknown parent residue for atom "+atom.getName()+" with serial "+ 638 atom.getPDBserial()+", assigning its default vdw radius"); 639 return atom.getElement().getVDWRadius(); 640 } 641 642 GroupType type = res.getType(); 643 644 if (type == GroupType.AMINOACID) return getRadiusForAmino(((AminoAcid)res), atom); 645 646 if (type == GroupType.NUCLEOTIDE) return getRadiusForNucl((NucleotideImpl)res,atom); 647 648 649 return atom.getElement().getVDWRadius(); 650 } 651 652}