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