diff --git a/modules/mol/alg/tests/test_qsscoring.py b/modules/mol/alg/tests/test_qsscoring.py index ddc3a465a231c32219bf33e1b4eea375cdc0b4ca..c901b9408447b30971c89d97d8b2ec8f30ecfa61 100644 --- a/modules/mol/alg/tests/test_qsscoring.py +++ b/modules/mol/alg/tests/test_qsscoring.py @@ -1,4 +1,5 @@ import unittest, os +import ost from ost import io from ost.mol.alg.qsscoring import * @@ -10,13 +11,115 @@ def _LoadFile(file_name): class TestQSscore(unittest.TestCase): + # TESTS base + + def test_QSscoreEntity(self): + # use smallest test structure... + ent = _LoadFile('3ia3.1.pdb') + ent.SetName("my_ent") + # create clean one + qs_ent = QSscoreEntity(ent) + self.assertTrue(qs_ent.is_valid) + # check naming + qs_ent.SetName("my_qs_ent") + self.assertEqual(qs_ent.original_name, "my_ent") + self.assertEqual(qs_ent.GetName(), "my_qs_ent") + # check cleanup + self.assertEqual(sorted(ch.name for ch in qs_ent.ent.chains), ['A', 'B']) + self.assertEqual(qs_ent.removed_chains, ['_']) + self.assertFalse(qs_ent.calpha_only) + # check CA entity + ca_ent = qs_ent.ca_entity + self.assertEqual(sorted(ch.name for ch in ca_ent.chains), ['A', 'B']) + self.assertEqual(ca_ent.residue_count, ca_ent.atom_count) + self.assertEqual(ca_ent.Select('aname=CA').atom_count, ca_ent.atom_count) + self.assertEqual(sorted(qs_ent.ca_chains.keys()), ['A', 'B']) + for ch in ca_ent.chains: + self.assertEqual(''.join([r.one_letter_code for r in ch.residues]), + str(qs_ent.ca_chains[ch.name])) + # check chem groups + self.assertEqual(sorted(qs_ent.chem_groups), [['A'], ['B']]) + # check contacts + self.assertEqual(len(qs_ent.contacts['A']['B']), 45) + self.assertAlmostEqual(qs_ent.contacts['A']['B'][23][127], 10.069, 2) + self.assertEqual(len(qs_ent.contacts_ca['A']['B']), 42) + self.assertAlmostEqual(qs_ent.contacts_ca['A']['B'][23][127], 10.471, 2) + # check contact filtering + old_contacts = qs_ent.contacts.copy() + old_contacts_ca = qs_ent.contacts_ca.copy() + qs_ent.contacts = old_contacts + self.assertEqual(qs_ent.contacts, old_contacts) + qs_ent.contacts_ca = old_contacts_ca + self.assertEqual(qs_ent.contacts_ca, old_contacts_ca) + dummy_contacts = {'A': {'B': {1: {2: 3.0, 4: 5.0}}, + 'C': {10: {20: 30.0, 40: 50.0}}}, + 'B': {'C': {100: {200: 300.0, 400: 500.0}}}} + qs_ent.contacts = dummy_contacts + self.assertEqual(qs_ent.contacts, {'A': {'B': {1: {2: 3.0, 4: 5.0}}}}) + self.assertEqual(qs_ent.contacts_ca, old_contacts_ca) + qs_ent.contacts = old_contacts + qs_ent.contacts_ca = dummy_contacts + self.assertEqual(qs_ent.contacts, old_contacts) + self.assertEqual(qs_ent.contacts_ca, {'A': {'B': {1: {2: 3.0, 4: 5.0}}}}) + + # check chain removal for non-amino acid chains + ent_extra = ent.Copy() + edi = ent_extra.EditXCS() + # classic ligand chain + ch = edi.InsertChain('C') + for _ in range(30): + r = edi.AppendResidue(ch, 'HOH') + edi.InsertAtom(r, 'O', ost.geom.Vec3()) + # DNA chain + ch = edi.InsertChain('D') + for _ in range(30): + r = edi.AppendResidue(ch, 'A') + edi.InsertAtom(r, 'P', ost.geom.Vec3()) + edi.UpdateICS() + # ensure both removed + qs_ent_test = QSscoreEntity(ent_extra) + self.assertEqual(sorted(qs_ent_test.removed_chains), ['C', 'D', '_']) + + # invalid structures: monomers (or less) before or after cleaning + ost.PushVerbosityLevel(-1) + # empty view + ent_empty = ent.CreateEmptyView() + qs_ent_invalid = QSscoreEntity(ent_empty) + self.assertFalse(qs_ent_invalid.is_valid) + # monomer + ent_mono = ent.Select('cname=A') + qs_ent_invalid = QSscoreEntity(ent_mono) + self.assertFalse(qs_ent_invalid.is_valid) + # short chain removed + ent_short = ent.Select('cname=A or rnum<20') + qs_ent_invalid = QSscoreEntity(ent_short) + self.assertFalse(qs_ent_invalid.is_valid) + self.assertEqual(sorted(qs_ent_invalid.removed_chains), ['B', '_']) + # non-AA chain removal + ent_non_AA = ent_extra.Select('cname=A,C,D') + qs_ent_invalid = QSscoreEntity(ent_non_AA) + self.assertFalse(qs_ent_invalid.is_valid) + self.assertEqual(sorted(qs_ent_invalid.removed_chains), ['C', 'D']) + ost.PopVerbosityLevel() + + # exception when scoring with invalid QSscoreEntity + with self.assertRaises(QSscoreError): + qs_scorer_tst = QSscorer(qs_ent_invalid, qs_ent) + qs_scorer_tst.global_score + with self.assertRaises(QSscoreError): + qs_scorer_tst = QSscorer(qs_ent, qs_ent_invalid) + qs_scorer_tst.global_score + + + # TESTS HETERO + def test_HeteroCase1(self): # additional chains ent_1 = _LoadFile('4ux8.1.pdb') # A2 B2 C2, symmetry: C2 ent_2 = _LoadFile('3fub.2.pdb') # A2 B2 , symmetry: C2 qs_scorer = QSscorer(ent_1, ent_2) # check properties - self.assertEqual(qs_scorer.calpha_only, False) + self.assertFalse(qs_scorer.calpha_only) # check mappings self.assertEqual(qs_scorer.chem_mapping, {('D', 'F'): ('B', 'D'), ('C', 'E'): ('A', 'C')}) @@ -36,7 +139,7 @@ class TestQSscore(unittest.TestCase): ent_2 = _LoadFile('3fub.au.pdb') qs_scorer = QSscorer(ent_1, ent_2) # check properties - self.assertEqual(qs_scorer.calpha_only, False) + self.assertFalse(qs_scorer.calpha_only) # check mappings self.assertEqual(qs_scorer.chem_mapping, {('C', 'E'): ('A', 'C'), ('D', 'F'): ('D', 'B')}) @@ -81,11 +184,17 @@ 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) + # 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 if CA-only scoring is close to this ent_2_ca = ent_2.Select('aname=CA') # use QSscoreEntity to go faster qs_scorer_ca = QSscorer(qs_scorer.qs_ent_1, ent_2_ca) + 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) # throw exception for messed up chains without CA atoms