diff --git a/modules/mol/alg/tests/test_qsscoring.py b/modules/mol/alg/tests/test_qsscoring.py index c901b9408447b30971c89d97d8b2ec8f30ecfa61..2fd6246b1479a6d1d91188fbd9444c5685e62e49 100644 --- a/modules/mol/alg/tests/test_qsscoring.py +++ b/modules/mol/alg/tests/test_qsscoring.py @@ -1,6 +1,6 @@ import unittest, os import ost -from ost import io +from ost import io, mol from ost.mol.alg.qsscoring import * @@ -131,6 +131,7 @@ class TestQSscore(unittest.TestCase): self.assertAlmostEqual(qs_scorer.global_score, 0.825, 2) # without penalties the interface is the same self.assertAlmostEqual(qs_scorer.best_score, 1.0, 2) + self._CheckScorer(qs_scorer) def test_HeteroCase1b(self): # as above but with assymetric unit of 3fub @@ -150,6 +151,7 @@ class TestQSscore(unittest.TestCase): # check scoring self.assertAlmostEqual(qs_scorer.global_score, 0.356, 2) self.assertAlmostEqual(qs_scorer.best_score, 0.419, 2) + self._CheckScorer(qs_scorer) def test_HeteroCase2(self): # different stoichiometry @@ -157,8 +159,8 @@ class TestQSscore(unittest.TestCase): ent_2 = _LoadFile('4pc6.1.pdb') # A B , no symmetry qs_scorer = QSscorer(ent_1, ent_2) self.assertAlmostEqual(qs_scorer.global_score, 0.3131, 2) - # without penalties the interface is the same self.assertAlmostEqual(qs_scorer.best_score, 0.941, 2) + self._CheckScorer(qs_scorer) def test_HeteroCase3(self): # more chains @@ -167,6 +169,7 @@ class TestQSscore(unittest.TestCase): qs_scorer = QSscorer(ent_1, ent_2) self.assertAlmostEqual(qs_scorer.global_score, 0.359, 2) self.assertAlmostEqual(qs_scorer.best_score, 0.958, 2) + self._CheckScorer(qs_scorer) # user's symmetry groups symm_1 = [('A', 'B'), ('C', 'D'), ('E', 'F'), ('G', 'H'), ('I', 'J'), ('K', 'L')] @@ -176,6 +179,7 @@ class TestQSscore(unittest.TestCase): qs_scorer_symm.SetSymmetries(symm_1, symm_2) self.assertAlmostEqual(qs_scorer_symm.global_score, qs_scorer.global_score) self.assertAlmostEqual(qs_scorer_symm.best_score, qs_scorer.best_score) + self._CheckScorer(qs_scorer_symm) def test_HeteroCase4(self): # inverted chains @@ -184,12 +188,21 @@ class TestQSscore(unittest.TestCase): qs_scorer = QSscorer(ent_1, ent_2) self.assertAlmostEqual(qs_scorer.global_score, 0.980, 2) self.assertAlmostEqual(qs_scorer.best_score, 0.980, 2) + self._CheckScorer(qs_scorer) # check properties self.assertFalse(qs_scorer.calpha_only) # check mappings self.assertEqual(qs_scorer.chem_mapping, {('A',): ('B',), ('B',): ('A',)}) self.assertEqual(qs_scorer.chain_mapping, {'A': 'B', 'B': 'A'}) + # check superposition + sup = qs_scorer.superposition + self.assertAlmostEqual(sup.rmsd, 0.3372, 2) + self.assertEqual(sup.view1.atom_count, 225) + self.assertEqual(sup.view2.atom_count, 225) + rmsd = mol.alg.CalculateRMSD(sup.view1, sup.view2, sup.transformation) + self.assertAlmostEqual(sup.rmsd, rmsd, 2) + # check if CA-only scoring is close to this ent_2_ca = ent_2.Select('aname=CA') # use QSscoreEntity to go faster @@ -197,6 +210,7 @@ class TestQSscore(unittest.TestCase): self.assertTrue(qs_scorer_ca.calpha_only) self.assertAlmostEqual(qs_scorer_ca.global_score, qs_scorer.global_score, 2) self.assertAlmostEqual(qs_scorer_ca.best_score, qs_scorer.best_score, 2) + self._CheckScorer(qs_scorer_ca) # throw exception for messed up chains without CA atoms ent_2_no_ca = ent_2.Select('aname!=CA') with self.assertRaises(QSscoreError): @@ -214,6 +228,7 @@ class TestQSscore(unittest.TestCase): qs_scorer = QSscorer(ent_1, ent_2) self.assertAlmostEqual(qs_scorer.global_score, 0.323, 2) self.assertAlmostEqual(qs_scorer.best_score, 0.921, 2) + self._CheckScorer(qs_scorer) # TESTS HOMO @@ -225,6 +240,7 @@ class TestQSscore(unittest.TestCase): qs_scorer = QSscorer(ent_1, ent_2) self.assertAlmostEqual(qs_scorer.global_score, 0.147, 2) self.assertAlmostEqual(qs_scorer.best_score, 0.866, 2) + self._CheckScorer(qs_scorer) def test_HomoCase2(self): # broken cyclic symmetry @@ -235,6 +251,7 @@ class TestQSscore(unittest.TestCase): self.assertAlmostEqual(qs_scorer.global_score, 1/6., 2) # without penalties the interface is the same self.assertAlmostEqual(qs_scorer.best_score, 1.0, 2) + self._CheckScorer(qs_scorer) # using user symmetry groups symm_1 = [('A', 'B'), ('C', 'D'), ('E', 'F')] symm_2 = [('A', 'B')] @@ -243,6 +260,7 @@ class TestQSscore(unittest.TestCase): qs_scorer_symm.SetSymmetries(symm_1, symm_2) self.assertEqual(qs_scorer_symm.global_score, qs_scorer.global_score) self.assertEqual(qs_scorer_symm.best_score, qs_scorer.best_score) + self._CheckScorer(qs_scorer_symm) # TEST EXTRA SCORES @@ -255,11 +273,13 @@ class TestQSscore(unittest.TestCase): self.assertAlmostEqual(qs_scorer.global_score, 0.171, 2) self.assertAlmostEqual(qs_scorer.best_score, 1.00, 2) self.assertAlmostEqual(qs_scorer.lddt_score, 1.00, 2) + self._CheckScorer(qs_scorer) # flip them (use QSscoreEntity to go faster) qs_scorer2 = QSscorer(qs_scorer.qs_ent_2, qs_scorer.qs_ent_1) self.assertAlmostEqual(qs_scorer2.global_score, 0.171, 2) self.assertAlmostEqual(qs_scorer2.best_score, 1.00, 2) self.assertAlmostEqual(qs_scorer2.lddt_score, 0.483, 2) + self._CheckScorer(qs_scorer) # TEST BIG STUFF and FANCY SYMMETRIES @@ -271,6 +291,7 @@ class TestQSscore(unittest.TestCase): qs_scorer = QSscorer(ent_1, ent_2) self.assertAlmostEqual(qs_scorer.global_score, 1/4., 2) self.assertAlmostEqual(qs_scorer.best_score, 1.0, 2) + self._CheckScorer(qs_scorer) def test_Capsid(self): ent_1 = _LoadFile('4gh4.2.pdb') # A5 B5 C5 D5, symmetry: C5 @@ -278,6 +299,7 @@ class TestQSscore(unittest.TestCase): qs_scorer = QSscorer(ent_1, ent_2) self.assertAlmostEqual(qs_scorer.global_score, 0.921, 2) self.assertAlmostEqual(qs_scorer.best_score, 0.941, 2) + self._CheckScorer(qs_scorer) def test_TetrahedralSymmetry(self): ent_1 = _LoadFile('1mog.1.pdb') # A12, symmetry: T @@ -285,6 +307,7 @@ class TestQSscore(unittest.TestCase): qs_scorer = QSscorer(ent_1, ent_2) self.assertAlmostEqual(qs_scorer.global_score, 0.954, 2) self.assertAlmostEqual(qs_scorer.best_score, 0.994, 2) + self._CheckScorer(qs_scorer) def test_Urease(self): ent_1 = _LoadFile('1e9y.1.pdb') # A12 B12, symmetry: T @@ -292,6 +315,7 @@ class TestQSscore(unittest.TestCase): qs_scorer = QSscorer(ent_1, ent_2) self.assertAlmostEqual(qs_scorer.global_score, 0.958, 2) self.assertAlmostEqual(qs_scorer.best_score, 0.958, 2) + self._CheckScorer(qs_scorer) def test_C6SymmetryHetero(self): ent_1 = _LoadFile('3j3r.1.pdb') # A6 B6, symmetry: C6 @@ -299,6 +323,7 @@ class TestQSscore(unittest.TestCase): qs_scorer = QSscorer(ent_1, ent_2) self.assertAlmostEqual(qs_scorer.global_score, 0.559, 2) self.assertAlmostEqual(qs_scorer.best_score, 0.559, 2) + self._CheckScorer(qs_scorer) def test_OctahedralSymmetry(self): ent_1 = _LoadFile('3vcd.1.pdb') # A24, symmetry: O @@ -306,6 +331,117 @@ class TestQSscore(unittest.TestCase): qs_scorer = QSscorer(ent_1, ent_2) self.assertAlmostEqual(qs_scorer.global_score, 0.975, 2) self.assertAlmostEqual(qs_scorer.best_score, 0.975, 2) + self._CheckScorer(qs_scorer) + + ########################################################################### + # HELPERS + ########################################################################### + + def _CheckEntity(self, qs_ent, check_ca_contacts): + # check internal consistency of QS entity + self.assertTrue(qs_ent.is_valid) + # CA chains + ca_ent = qs_ent.ca_entity + self.assertEqual(ca_ent.residue_count, ca_ent.atom_count) + ca_names = sorted([ch.name for ch in ca_ent.chains]) + self.assertEqual(sorted(qs_ent.ca_chains.keys()), ca_names) + # chem groups + self.assertEqual(sorted([c for cg in qs_ent.chem_groups for c in cg]), + ca_names) + # check contacts (only chain names) + if check_ca_contacts: + contacts = qs_ent.contacts_ca + else: + contacts = qs_ent.contacts + for c1 in contacts: + self.assertTrue(c1 in ca_names) + for c2 in contacts[c1]: + self.assertTrue(c2 in ca_names) + self.assertLess(c1, c2) + + def _CheckScorer(self, qs_scorer): + # check if we live up to our promises (assume: we did global score) + qs_ent_1 = qs_scorer.qs_ent_1 + qs_ent_2 = qs_scorer.qs_ent_2 + # check QS entities + self._CheckEntity(qs_ent_1, qs_scorer.calpha_only) + self._CheckEntity(qs_ent_2, qs_scorer.calpha_only) + self.assertNotEqual(qs_ent_1.GetName(), qs_ent_2.GetName()) + # check scorer attributes + self.assertEqual(qs_scorer.calpha_only, + qs_ent_1.calpha_only or qs_ent_2.calpha_only) + # check chem_mapping + cn1 = set([c for cg in qs_ent_1.chem_groups for c in cg]) + cn2 = set([c for cg in qs_ent_2.chem_groups for c in cg]) + cm_names_1 = list() + cm_names_2 = list() + for cg1, cg2 in qs_scorer.chem_mapping.iteritems(): + ch_ref = qs_scorer.ent_to_cm_1.FindChain(cg1[0]) + self.assertEqual(ch_ref.residue_count, ch_ref.atom_count) + self.assertGreaterEqual(ch_ref.residue_count, 5) + self.assertLessEqual(ch_ref.residue_count, + qs_scorer.max_ca_per_chain_for_cm) + for ch_name in cg1: + self.assertTrue(ch_name in cn1) + ch = qs_scorer.ent_to_cm_1.FindChain(ch_name) + self.assertTrue(ch.IsValid()) + self.assertEqual(ch_ref.residue_count, ch.residue_count) + self.assertEqual(ch_ref.atom_count, ch.atom_count) + cm_names_1.append(ch_name) + for ch_name in cg2: + self.assertTrue(ch_name in cn2) + ch = qs_scorer.ent_to_cm_2.FindChain(ch_name) + self.assertTrue(ch.IsValid()) + self.assertEqual(ch_ref.residue_count, ch.residue_count) + self.assertEqual(ch_ref.atom_count, ch.atom_count) + cm_names_2.append(ch_name) + # check that there's no extra stuff in ent_to_cm_1/2 + cm_names_1.sort() + self.assertEqual(sorted(ch.name for ch in qs_scorer.ent_to_cm_1.chains), + cm_names_1) + cm_names_2.sort() + self.assertEqual(sorted(ch.name for ch in qs_scorer.ent_to_cm_2.chains), + cm_names_2) + # check symm_1 / symm_2 + # (>= 1 symm. group, all groups same length, all chains appear) + self.assertGreaterEqual(qs_scorer.symm_1, 1) + ref_symm_1 = qs_scorer.symm_1[0] + self.assertTrue(all(len(cg) == len(ref_symm_1) for cg in qs_scorer.symm_1)) + self.assertEqual(sorted(c for cg in qs_scorer.symm_1 for c in cg), + cm_names_1) + self.assertGreaterEqual(qs_scorer.symm_2, 1) + ref_symm_2 = qs_scorer.symm_2[0] + self.assertTrue(all(len(cg) == len(ref_symm_2) for cg in qs_scorer.symm_2)) + self.assertEqual(sorted(c for cg in qs_scorer.symm_2 for c in cg), + cm_names_2) + # check chain_mapping + # (all chains of ent with less chains mapped, each only once, chem_map) + chm_names_1 = qs_scorer.chain_mapping.keys() + chm_names_2 = qs_scorer.chain_mapping.values() + self.assertEqual(len(chm_names_1), min(len(cm_names_1), len(cm_names_2))) + self.assertEqual(len(set(chm_names_1)), len(chm_names_1)) + self.assertEqual(len(set(chm_names_2)), len(chm_names_2)) + for cg1, cg2 in qs_scorer.chem_mapping.iteritems(): + for ch_name in cg1: + if ch_name in qs_scorer.chain_mapping: + self.assertTrue(qs_scorer.chain_mapping[ch_name] in cg2) + # check alignments + # (sorted and mapped and with views, all in mapped_residues) + self.assertEqual(len(qs_scorer.alignments), len(chm_names_1)) + self.assertEqual(len(qs_scorer.alignments), len(qs_scorer.mapped_residues)) + for aln, ch_name in zip(qs_scorer.alignments, sorted(chm_names_1)): + self.assertEqual(aln.sequences[0].name, ch_name) + self.assertEqual(aln.sequences[1].name, qs_scorer.chain_mapping[ch_name]) + self.assertTrue(aln.sequences[0].HasAttachedView()) + self.assertTrue(aln.sequences[1].HasAttachedView()) + self.assertEqual(aln.sequences[0].attached_view.handle, qs_ent_1.ent) + self.assertEqual(aln.sequences[1].attached_view.handle, qs_ent_2.ent) + self.assertTrue(ch_name in qs_scorer.mapped_residues) + # best_score / global_score in [0,1] + self.assertGreaterEqual(qs_scorer.best_score, 0.0) + self.assertLessEqual(qs_scorer.best_score, 1.0) + self.assertGreaterEqual(qs_scorer.global_score, 0.0) + self.assertLessEqual(qs_scorer.global_score, 1.0) if __name__ == "__main__":