mirror of
https://github.com/biopython/biopython.git
synced 2025-10-20 21:53:47 +08:00
Addresses a bug in the code that caused the Newton-Raphson optimization to quit earlier than it should, resulting in poorer alignments.
186 lines
5.9 KiB
Python
186 lines
5.9 KiB
Python
# Copyright 2017 by Maximilian Greil. All rights reserved.
|
|
# This code is part of the Biopython distribution and governed by its
|
|
# license. Please see the LICENSE file that should have been included
|
|
# as part of this package.
|
|
|
|
"""Tests for QCPSuperimposer module."""
|
|
|
|
import unittest
|
|
|
|
try:
|
|
import numpy as np
|
|
except ImportError:
|
|
from Bio import MissingPythonDependencyError
|
|
|
|
raise MissingPythonDependencyError(
|
|
"Install NumPy if you want to use Bio.QCPSuperimposer."
|
|
) from None
|
|
|
|
|
|
from Bio.PDB import PDBParser
|
|
from Bio.PDB import Selection
|
|
from Bio.PDB.qcprot import QCPSuperimposer
|
|
from Bio.SVDSuperimposer import SVDSuperimposer
|
|
|
|
|
|
class QCPSuperimposerTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.x = np.array(
|
|
[
|
|
[51.65, -1.90, 50.07],
|
|
[50.40, -1.23, 50.65],
|
|
[50.68, -0.04, 51.54],
|
|
[50.22, -0.02, 52.85],
|
|
]
|
|
)
|
|
|
|
self.y = np.array(
|
|
[
|
|
[51.30, -2.99, 46.54],
|
|
[51.09, -1.88, 47.58],
|
|
[52.36, -1.20, 48.03],
|
|
[52.71, -1.18, 49.38],
|
|
]
|
|
)
|
|
|
|
# Public methods
|
|
def test_set(self):
|
|
"""Test setting of initial parameters."""
|
|
sup = QCPSuperimposer()
|
|
sup.set(self.x, self.y)
|
|
|
|
self.assertTrue(np.allclose(sup.reference_coords, self.x, atol=1e-6))
|
|
self.assertTrue(np.allclose(sup.coords, self.y, atol=1e-6))
|
|
self.assertIsNone(sup.transformed_coords)
|
|
self.assertIsNone(sup.rot)
|
|
self.assertIsNone(sup.tran)
|
|
self.assertIsNone(sup.rms)
|
|
self.assertIsNone(sup.init_rms)
|
|
|
|
def test_run(self):
|
|
"""Test QCP on dummy data."""
|
|
sup = QCPSuperimposer()
|
|
sup.set(self.x, self.y)
|
|
|
|
sup.run()
|
|
self.assertTrue(np.allclose(sup.reference_coords, self.x, atol=1e-6))
|
|
self.assertTrue(np.allclose(sup.coords, self.y, atol=1e-6))
|
|
self.assertIsNone(sup.transformed_coords)
|
|
|
|
calc_rot = [
|
|
[0.683, 0.537, 0.495],
|
|
[-0.523, 0.833, -0.181],
|
|
[-0.510, -0.135, 0.849],
|
|
]
|
|
calc_tran = [38.786, -20.655, -15.422]
|
|
|
|
self.assertTrue(np.allclose(np.array(calc_rot), sup.rot, atol=1e-3))
|
|
self.assertTrue(np.allclose(np.array(calc_tran), sup.tran, atol=1e-3))
|
|
|
|
# We can reduce precision here since we do a similar calculation
|
|
# for a full structure down below.
|
|
self.assertAlmostEqual(sup.rms, 0.003, places=3)
|
|
self.assertIsNone(sup.init_rms)
|
|
|
|
def test_compare_to_svd(self):
|
|
"""Compare results of QCP to SVD."""
|
|
sup = QCPSuperimposer()
|
|
sup.set(self.x, self.y)
|
|
sup.run()
|
|
|
|
svd_sup = SVDSuperimposer()
|
|
svd_sup.set(self.x, self.y)
|
|
svd_sup.run()
|
|
|
|
self.assertAlmostEqual(svd_sup.get_rms(), sup.rms, places=3)
|
|
self.assertTrue(np.allclose(svd_sup.rot, sup.rot, atol=1e-3))
|
|
self.assertTrue(np.allclose(svd_sup.tran, sup.tran, atol=1e-3))
|
|
self.assertTrue(
|
|
np.allclose(svd_sup.get_transformed(), sup.get_transformed(), atol=1e-3)
|
|
)
|
|
|
|
def test_compare_to_svd_lines(self):
|
|
"""Compare results of QCP to SVD using simple lines."""
|
|
ref = np.linspace([0, 0, 0], [10, 10, 10], 5)
|
|
mob = np.linspace([20, 10, 30], [20, 20, 30], 5)
|
|
|
|
sup = QCPSuperimposer()
|
|
sup.set(ref, mob)
|
|
sup.run()
|
|
|
|
svd_sup = SVDSuperimposer()
|
|
svd_sup.set(ref, mob)
|
|
svd_sup.run()
|
|
|
|
self.assertAlmostEqual(svd_sup.get_rms(), sup.rms, places=3)
|
|
self.assertTrue(
|
|
np.allclose(svd_sup.get_transformed(), sup.get_transformed(), atol=1e-3)
|
|
)
|
|
|
|
def test_get_transformed(self):
|
|
"""Test transformation of coordinates after QCP."""
|
|
sup = QCPSuperimposer()
|
|
sup.set(self.x, self.y)
|
|
|
|
sup.run()
|
|
transformed_coords = [
|
|
[51.652, -1.900, 50.071],
|
|
[50.398, -1.229, 50.649],
|
|
[50.680, -0.042, 51.537],
|
|
[50.220, -0.019, 52.853],
|
|
]
|
|
|
|
self.assertTrue(
|
|
np.allclose(sup.get_transformed(), np.array(transformed_coords), atol=1e-3)
|
|
)
|
|
|
|
def test_get_init_rms(self):
|
|
"""Test initial RMS calculation."""
|
|
x = np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])
|
|
y = np.array([[2.0, 0.0, 0.0], [1.0, 1.0, 0.0], [1.0, 0.0, 1.0]])
|
|
|
|
sup = QCPSuperimposer()
|
|
sup.set(x, y)
|
|
self.assertIsNone(sup.init_rms)
|
|
|
|
expected_init_rms = 1.0
|
|
self.assertAlmostEqual(sup.get_init_rms(), expected_init_rms, places=6)
|
|
|
|
def test_on_pdb(self):
|
|
"""Align a PDB to itself."""
|
|
pdb1 = "PDB/1A8O.pdb"
|
|
p = PDBParser()
|
|
s1 = p.get_structure("FIXED", pdb1)
|
|
fixed = Selection.unfold_entities(s1, "A")
|
|
s2 = p.get_structure("MOVING", pdb1)
|
|
moving = Selection.unfold_entities(s2, "A")
|
|
|
|
rot = np.eye(3, dtype=np.float64)
|
|
tran = np.array([1.0, 2.0, 3.0], dtype=np.float64)
|
|
for atom in moving:
|
|
atom.transform(rot, tran)
|
|
|
|
sup = QCPSuperimposer()
|
|
sup.set_atoms(fixed, moving)
|
|
self.assertTrue(np.allclose(sup.rotran[0], rot, atol=1e-3))
|
|
self.assertTrue(np.allclose(sup.rotran[1], -tran, atol=1e-3))
|
|
self.assertAlmostEqual(sup.rms, 0.0, places=6)
|
|
|
|
def test_compare_rmsd_to_transformed(self):
|
|
"""Compare RMSD from QCP algorithm to that from transformed"""
|
|
ref = np.linspace([0, 0, 0], [10, 10, 10], 5)
|
|
mob = np.linspace([20, 10, 30], [20, 20, 30], 5)
|
|
|
|
sup = QCPSuperimposer()
|
|
sup.set(ref, mob)
|
|
sup.run()
|
|
rms = sup.get_rms()
|
|
mob_fitted = sup.get_transformed()
|
|
rms_fitted = np.sqrt(((ref - mob_fitted) ** 2).sum() / ref.shape[0])
|
|
self.assertAlmostEqual(rms, rms_fitted, places=6)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
runner = unittest.TextTestRunner(verbosity=2)
|
|
unittest.main(testRunner=runner)
|