1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
# Copyright 2020 Joao Rodrigues. All rights reserved.
#
# This file is part of the Biopython distribution and governed by your
# choice of the "Biopython License Agreement" or the "BSD 3-Clause License".
# Please see the LICENSE file that should have been included as part of this
# package.
"""Unit tests for the Bio.PDB.SASA module: Surface Accessibility Calculations."""
import copy
import pathlib
import unittest
import warnings
from Bio.PDB import PDBParser
from Bio.PDB.SASA import ShrakeRupley
DATADIR = pathlib.Path(__file__).parent / "PDB"
class TestShrakeRupley(unittest.TestCase):
"""Tests for SR algorithm."""
# Expected values obtained with freesasa 2.0.3 and custom config file.
# e.g. cmd: --shrake-rupley --resolution 100 -n-threads 1 --probe-radius 1.4
@classmethod
def setUpClass(cls):
"""One-time setup for all tests."""
cls.parser = p = PDBParser(QUIET=1)
with warnings.catch_warnings():
structure = p.get_structure("X", DATADIR / "1LCD.pdb")
model = structure[0]
# Remove HETATM and Hs for simplicity/speed
for r in list(model.get_residues()):
if r.id[0] == " ":
for a in list(r):
if a.element == "H":
r.detach_child(a.name)
else:
c = r.parent
c.detach_child(r.id)
cls.model = model
# General Parameters
def test_default_algorithm(self):
"""Run Shrake-Rupley with default parameters."""
m = copy.deepcopy(self.model) # modifies atom.sasa
sasa = ShrakeRupley()
sasa.compute(m)
result = [a.sasa for a in m.get_atoms()][:5]
expected = [50.36, 31.40, 10.87, 12.86, 2.42]
for a, b in zip(result, expected):
self.assertAlmostEqual(a, b, places=2)
def test_higher_resolution(self):
"""Run Shrake-Rupley with 960 points per sphere."""
m = copy.deepcopy(self.model) # modifies atom.sasa
sasa = ShrakeRupley(n_points=960)
sasa.compute(m)
result = [a.sasa for a in m.get_atoms()][:5]
expected = [51.90, 31.45, 12.45, 12.72, 3.02]
for a, b in zip(result, expected):
self.assertAlmostEqual(a, b, places=2)
def test_custom_radii(self):
"""Run Shrake-Rupley with custom radii."""
m = copy.deepcopy(self.model) # modifies atom.sasa
sasa = ShrakeRupley(radii_dict={"C": 5.00})
sasa.compute(m)
result = [a.sasa for a in m.get_atoms()][:5]
expected = [0.0, 190.45, 41.18, 0.0, 36.03]
for a, b in zip(result, expected):
self.assertAlmostEqual(a, b, places=2)
# Compute parameters
def test_level_R(self):
"""Run Shrake-Rupley with level R."""
m = copy.deepcopy(self.model) # modifies atom.sasa
sasa = ShrakeRupley()
sasa.compute(m, level="R")
for r in m.get_residues():
atom_sum = sum(a.sasa for a in r)
self.assertAlmostEqual(atom_sum, r.sasa, places=2)
def test_level_C(self):
"""Run Shrake-Rupley with level C."""
m = copy.deepcopy(self.model) # modifies atom.sasa
sasa = ShrakeRupley()
sasa.compute(m, level="C")
for c in m.get_chains():
atom_sum = sum(a.sasa for a in c.get_atoms())
self.assertAlmostEqual(atom_sum, c.sasa, places=2)
# Exceptions
def test_fail_probe_radius(self):
"""Raise exception on bad probe_radius parameter."""
with self.assertRaisesRegex(ValueError, "must be a positive number"):
sasa = ShrakeRupley(probe_radius=-1.40)
def test_fail_n_points(self):
"""Raise exception on bad n_points parameter."""
with self.assertRaisesRegex(ValueError, "must be larger than 1"):
sasa = ShrakeRupley(n_points=0)
def test_fail_compute_entity_type(self):
"""Raise exception on unsupported entity type."""
with self.assertRaisesRegex(ValueError, "Invalid entity type"):
sasa = ShrakeRupley()
sasa.compute([1, 2, 3, 4, 5])
def test_fail_compute_entity_level(self):
"""Raise exception on input Atom entity."""
atom = list(self.model.get_atoms())[0]
with self.assertRaisesRegex(ValueError, "Invalid entity type"):
sasa = ShrakeRupley()
sasa.compute(atom)
def test_fail_compute_level_1(self):
"""Raise exception on invalid level parameter: X."""
with self.assertRaisesRegex(ValueError, "Invalid level"):
sasa = ShrakeRupley()
sasa.compute(self.model, level="X")
def test_fail_compute_level_2(self):
"""Raise exception on invalid level parameter: S > C."""
chain = self.model["A"]
with self.assertRaisesRegex(ValueError, "be equal or smaller than"):
sasa = ShrakeRupley()
sasa.compute(chain, level="S") # Chain is a child of Structure.
def test_fail_empty_entity(self):
"""Raise exception on invalid level parameter: S > C."""
sasa = ShrakeRupley()
r = copy.deepcopy(self.model["A"].child_list[0])
for a in list(r):
r.detach_child(a.name) # empty residue
self.assertEqual(len(r.child_list), 0)
with self.assertRaisesRegex(ValueError, "Entity has no child atoms"):
sasa.compute(r)
if __name__ == "__main__":
runner = unittest.TextTestRunner(verbosity=2)
unittest.main(testRunner=runner)
|