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}