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                // important: some atoms might have no neighbors at all: we need to initialise to empty arrays
446                for (int i=0; i<nbsIndices.length; i++) {
447                        if (nbsIndices[i] == null) {
448                                nbsIndices[i] = new int[0];
449                        }
450                }
451
452                return nbsIndices;
453        }
454
455        Point3d[] getAtomCoords() {
456                return atomCoords;
457        }
458
459        private List<Contact> calcContacts() {
460                if (atomCoords.length == 0)
461                        return new ArrayList<>();
462                double maxRadius = 0;
463                OptionalDouble optionalDouble = Arrays.stream(radii).max();
464                if (optionalDouble.isPresent())
465                        maxRadius = optionalDouble.getAsDouble();
466                double cutoff = maxRadius + maxRadius + probe + probe;
467                logger.debug("Max radius is {}, cutoff is {}", maxRadius, cutoff);
468                Grid grid = new Grid(cutoff);
469                grid.addCoords(atomCoords);
470                return grid.getIndicesContacts();
471        }
472
473        private double calcSingleAsa(int i) {
474                Point3d atom_i = atomCoords[i];
475
476                int n_neighbor = neighborIndices[i].length;
477                int[] neighbor_indices = neighborIndices[i];
478                int j_closest_neighbor = 0;
479                double radius = probe + radii[i];
480
481                int n_accessible_point = 0;
482
483                for (Point3d point: spherePoints){
484                        boolean is_accessible = true;
485                        Point3d test_point = new Point3d(point.x*radius + atom_i.x,
486                                        point.y*radius + atom_i.y,
487                                        point.z*radius + atom_i.z);
488
489                        int[] cycled_indices = new int[n_neighbor];
490                        int arind = 0;
491                        for (int ind=j_closest_neighbor;ind<n_neighbor;ind++) {
492                                cycled_indices[arind] = ind;
493                                arind++;
494                        }
495                        for (int ind=0;ind<j_closest_neighbor;ind++){
496                                cycled_indices[arind] = ind;
497                                arind++;
498                        }
499
500                        for (int j: cycled_indices) {
501                                Point3d atom_j = atomCoords[neighbor_indices[j]];
502                                double r = radii[neighbor_indices[j]] + probe;
503                                double diff_sq = test_point.distanceSquared(atom_j);
504                                if (diff_sq < r*r) {
505                                        j_closest_neighbor = j;
506                                        is_accessible = false;
507                                        break;
508                                }
509                        }
510                        if (is_accessible) {
511                                n_accessible_point++;
512                        }
513                }
514                return cons*n_accessible_point*radius*radius;
515        }
516
517        /**
518         * Gets the radius for given amino acid and atom
519         * @param amino
520         * @param atom
521         * @return
522         */
523        private static double getRadiusForAmino(AminoAcid amino, Atom atom) {
524
525                if (atom.getElement().equals(Element.H)) return Element.H.getVDWRadius();
526                // some unusual entries (e.g. 1tes) contain Deuterium atoms in standard aminoacids
527                if (atom.getElement().equals(Element.D)) return Element.D.getVDWRadius();
528
529                String atomCode = atom.getName();
530                char aa = amino.getAminoType();
531
532                // here we use the values that Chothia gives in his paper (as NACCESS does)
533                if (atom.getElement()==Element.O) {
534                        return OXIGEN_VDW;
535                }
536                else if (atom.getElement()==Element.S) {
537                        return SULFUR_VDW;
538                }
539                else if (atom.getElement()==Element.N) {
540                        if (atomCode.equals("NZ")) return TETRAHEDRAL_NITROGEN_VDW; // tetrahedral Nitrogen
541                        return TRIGONAL_NITROGEN_VDW;                                                           // trigonal Nitrogen
542                }
543                else if (atom.getElement()==Element.C) { // it must be a carbon
544                        if (atomCode.equals("C") ||
545                                        atomCode.equals("CE1") || atomCode.equals("CE2") || atomCode.equals("CE3") ||
546                                        atomCode.equals("CH2") ||
547                                        atomCode.equals("CZ") || atomCode.equals("CZ2") || atomCode.equals("CZ3")) {
548                                return TRIGONAL_CARBON_VDW;                                                     // trigonal Carbon
549                        }
550                        else if (atomCode.equals("CA") || atomCode.equals("CB") ||
551                                        atomCode.equals("CE") ||
552                                        atomCode.equals("CG1") || atomCode.equals("CG2")) {
553                                return TETRAHEDRAL_CARBON_VDW;                                                  // tetrahedral Carbon
554                        }
555                        // the rest of the cases (CD, CD1, CD2, CG) depend on amino acid
556                        else {
557                                switch (aa) {
558                                case 'F':
559                                case 'W':
560                                case 'Y':
561                                case 'H':
562                                case 'D':
563                                case 'N':
564                                        return TRIGONAL_CARBON_VDW;
565
566                                case 'P':
567                                case 'K':
568                                case 'R':
569                                case 'M':
570                                case 'I':
571                                case 'L':
572                                        return TETRAHEDRAL_CARBON_VDW;
573
574                                case 'Q':
575                                case 'E':
576                                        if (atomCode.equals("CD")) return TRIGONAL_CARBON_VDW;
577                                        else if (atomCode.equals("CG")) return TETRAHEDRAL_CARBON_VDW;
578
579                                default:
580                                        logger.info("Unexpected carbon atom "+atomCode+" for aminoacid "+aa+", assigning its standard vdw radius");
581                                        return Element.C.getVDWRadius();
582                                }
583                        }
584
585                        // not any of the expected atoms
586                } else {
587                        // non standard aas, (e.g. MSE, LLP) will always have this problem,
588                        logger.info("Unexpected atom "+atomCode+" for aminoacid "+aa+ " ("+amino.getPDBName()+"), assigning its standard vdw radius");
589
590
591                        return atom.getElement().getVDWRadius();
592                }
593        }
594
595
596        /**
597         * Gets the radius for given nucleotide atom
598         * @param atom
599         * @return
600         */
601        private static double getRadiusForNucl(NucleotideImpl nuc, Atom atom) {
602
603                if (atom.getElement().equals(Element.H)) return Element.H.getVDWRadius();
604                if (atom.getElement().equals(Element.D)) return Element.D.getVDWRadius();
605
606                if (atom.getElement()==Element.C) return NUC_CARBON_VDW;
607
608                if (atom.getElement()==Element.N) return NUC_NITROGEN_VDW;
609
610                if (atom.getElement()==Element.P) return PHOSPHOROUS_VDW;
611
612                if (atom.getElement()==Element.O) return OXIGEN_VDW;
613
614                logger.info("Unexpected atom "+atom.getName()+" for nucleotide "+nuc.getPDBName()+", assigning its standard vdw radius");
615                return atom.getElement().getVDWRadius();
616        }
617
618
619        /**
620         * Gets the van der Waals radius of the given atom following the values defined by
621         * Chothia (1976) J.Mol.Biol.105,1-14
622         * NOTE: the vdw values defined by the paper assume no Hydrogens and thus "inflates"
623         * slightly the heavy atoms to account for Hydrogens. Thus this method cannot be used
624         * in a structure that contains Hydrogens!
625         *
626         * If atom is neither part of a nucleotide nor of a standard aminoacid,
627         * the default vdw radius for the element is returned. If atom is of
628         * unknown type (element) the vdw radius of {@link Element().N} is returned
629         *
630         * @param atom
631         * @return
632         */
633        public static double getRadius(Atom atom) {
634
635                if (atom.getElement()==null) {
636                        logger.warn("Unrecognised atom "+atom.getName()+" with serial "+atom.getPDBserial()+
637                                        ", assigning the default vdw radius (Nitrogen vdw radius).");
638                        return Element.N.getVDWRadius();
639                }
640
641                Group res = atom.getGroup();
642
643                if (res==null) {
644                        logger.warn("Unknown parent residue for atom "+atom.getName()+" with serial "+
645                                        atom.getPDBserial()+", assigning its default vdw radius");
646                        return atom.getElement().getVDWRadius();
647                }
648
649                GroupType type = res.getType();
650
651                if (type == GroupType.AMINOACID) return getRadiusForAmino(((AminoAcid)res), atom);
652
653                if (type == GroupType.NUCLEOTIDE) return getRadiusForNucl((NucleotideImpl)res,atom);
654
655
656                return atom.getElement().getVDWRadius();
657        }
658
659}