diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/input/mini.cif | 116 | ||||
| -rw-r--r-- | test/input/no_title.cif | 3 | ||||
| -rw-r--r-- | test/input/not_modeled.cif | 89 | ||||
| -rw-r--r-- | test/input/struct_only.cif | 4 | ||||
| -rw-r--r-- | test/test_alignment.py | 37 | ||||
| -rw-r--r-- | test/test_associated.py | 19 | ||||
| -rw-r--r-- | test/test_descriptor.py | 19 | ||||
| -rw-r--r-- | test/test_dumper.py | 1579 | ||||
| -rw-r--r-- | test/test_edit.py | 150 | ||||
| -rw-r--r-- | test/test_examples.py | 103 | ||||
| -rw-r--r-- | test/test_main.py | 230 | ||||
| -rw-r--r-- | test/test_make_mmcif.py | 97 | ||||
| -rw-r--r-- | test/test_model.py | 33 | ||||
| -rw-r--r-- | test/test_qa_metric.py | 101 | ||||
| -rw-r--r-- | test/test_reader.py | 2003 | ||||
| -rw-r--r-- | test/test_reference.py | 62 | ||||
| -rw-r--r-- | test/utils.py | 51 |
17 files changed, 4696 insertions, 0 deletions
diff --git a/test/input/mini.cif b/test/input/mini.cif new file mode 100644 index 0000000..02940d3 --- /dev/null +++ b/test/input/mini.cif @@ -0,0 +1,116 @@ +data_model +# +_exptl.method 'model, MODELLER Version 9.24 2020/08/21 11:54:31' +# +_modeller.version 9.24 +# +loop_ +_struct_asym.id +_struct_asym.entity_id +_struct_asym.details +A 1 ? +B 2 ? +# +loop_ +_entity_poly_seq.entity_id +_entity_poly_seq.num +_entity_poly_seq.mon_id +1 1 VAL +1 2 GLY +1 3 GLN +1 4 GLN +1 5 TYR +1 6 SER +1 7 SER +2 1 ASP +2 2 GLU +# +loop_ +_atom_site.group_PDB +_atom_site.type_symbol +_atom_site.label_atom_id +_atom_site.label_alt_id +_atom_site.label_comp_id +_atom_site.label_asym_id +_atom_site.auth_asym_id +_atom_site.label_seq_id +_atom_site.auth_seq_id +_atom_site.pdbx_PDB_ins_code +_atom_site.Cartn_x +_atom_site.Cartn_y +_atom_site.Cartn_z +_atom_site.occupancy +_atom_site.B_iso_or_equiv +_atom_site.label_entity_id +_atom_site.id +_atom_site.pdbx_PDB_model_num +ATOM N N . VAL A A 1 2 ? 115.846 27.965 -26.370 1.000 141.830 1 1 1 +ATOM C CA . VAL A A 1 2 ? 114.370 27.980 -26.088 1.000 143.490 1 2 1 +ATOM C C . VAL A A 1 2 ? 113.517 27.504 -27.287 1.000 143.910 1 3 1 +ATOM O O . VAL A A 1 2 ? 113.885 27.746 -28.441 1.000 146.600 1 4 1 +ATOM C CB . VAL A A 1 2 ? 113.901 29.406 -25.683 1.000 143.750 1 5 1 +ATOM C CG1 . VAL A A 1 2 ? 115.030 30.438 -25.931 1.000 144.590 1 6 1 +ATOM C CG2 . VAL A A 1 2 ? 112.669 29.783 -26.486 1.000 144.500 1 7 1 +ATOM N N . GLY A A 2 3 ? 112.371 26.869 -27.012 1.000 142.200 1 8 1 +ATOM C CA . GLY A A 2 3 ? 111.506 26.368 -28.075 1.000 137.530 1 9 1 +ATOM C C . GLY A A 2 3 ? 111.719 24.869 -28.275 1.000 135.820 1 10 1 +ATOM O O . GLY A A 2 3 ? 110.768 24.093 -28.268 1.000 134.380 1 11 1 +ATOM N N . GLN A A 3 4 ? 112.989 24.479 -28.392 1.000 134.310 1 12 1 +ATOM C CA . GLN A A 3 4 ? 113.468 23.113 -28.639 1.000 128.420 1 13 1 +ATOM C C . GLN A A 3 4 ? 113.556 22.956 -30.163 1.000 121.240 1 14 1 +ATOM O O . GLN A A 3 4 ? 113.552 23.977 -30.840 1.000 127.090 1 15 1 +ATOM C CB . GLN A A 3 4 ? 112.614 22.038 -27.919 1.000 132.340 1 16 1 +ATOM C CG . GLN A A 3 4 ? 113.028 21.943 -26.407 1.000 135.370 1 17 1 +ATOM C CD . GLN A A 3 4 ? 112.604 20.667 -25.677 1.000 138.260 1 18 1 +ATOM O OE1 . GLN A A 3 4 ? 112.836 19.543 -26.150 1.000 141.450 1 19 1 +ATOM N NE2 . GLN A A 3 4 ? 112.006 20.839 -24.497 1.000 139.310 1 20 1 +ATOM N N . GLN A A 4 5 ? 113.648 21.739 -30.710 1.000 124.970 1 21 1 +ATOM C CA . GLN A A 4 5 ? 113.808 21.534 -32.168 1.000 117.620 1 22 1 +ATOM C C . GLN A A 4 5 ? 114.778 22.519 -32.833 1.000 112.980 1 23 1 +ATOM O O . GLN A A 4 5 ? 114.677 23.727 -32.677 1.000 116.850 1 24 1 +ATOM C CB . GLN A A 4 5 ? 112.456 21.545 -32.905 1.000 121.870 1 25 1 +ATOM C CG . GLN A A 4 5 ? 111.763 20.153 -32.917 1.000 123.750 1 26 1 +ATOM C CD . GLN A A 4 5 ? 110.863 19.874 -34.145 1.000 123.650 1 27 1 +ATOM O OE1 . GLN A A 4 5 ? 110.040 20.712 -34.537 1.000 122.500 1 28 1 +ATOM N NE2 . GLN A A 4 5 ? 111.008 18.674 -34.737 1.000 122.090 1 29 1 +ATOM N N . TYR A A 5 6 ? 115.713 21.980 -33.598 1.000 109.460 1 30 1 +ATOM C CA . TYR A A 5 6 ? 116.743 22.770 -34.259 1.000 103.700 1 31 1 +ATOM C C . TYR A A 5 6 ? 116.348 23.366 -35.602 1.000 100.320 1 32 1 +ATOM O O . TYR A A 5 6 ? 115.530 22.799 -36.311 1.000 98.760 1 33 1 +ATOM C CB . TYR A A 5 6 ? 117.973 21.876 -34.402 1.000 104.580 1 34 1 +ATOM C CG . TYR A A 5 6 ? 119.003 22.282 -35.425 1.000 105.030 1 35 1 +ATOM C CD1 . TYR A A 5 6 ? 119.591 23.546 -35.395 1.000 106.020 1 36 1 +ATOM C CD2 . TYR A A 5 6 ? 119.450 21.366 -36.380 1.000 105.180 1 37 1 +ATOM C CE1 . TYR A A 5 6 ? 120.606 23.890 -36.289 1.000 106.990 1 38 1 +ATOM C CE2 . TYR A A 5 6 ? 120.461 21.694 -37.276 1.000 106.420 1 39 1 +ATOM C CZ . TYR A A 5 6 ? 121.039 22.958 -37.226 1.000 107.110 1 40 1 +ATOM O OH . TYR A A 5 6 ? 122.057 23.290 -38.095 1.000 107.500 1 41 1 +ATOM N N . SER A A 6 7 ? 116.921 24.519 -35.944 1.000 96.290 1 42 1 +ATOM C CA . SER A A 6 7 ? 116.626 25.161 -37.229 1.000 93.490 1 43 1 +ATOM C C . SER A A 6 7 ? 117.900 25.595 -37.944 1.000 91.900 1 44 1 +ATOM O O . SER A A 6 7 ? 118.767 26.246 -37.352 1.000 91.810 1 45 1 +ATOM C CB . SER A A 6 7 ? 115.732 26.388 -37.048 1.000 93.090 1 46 1 +ATOM O OG . SER A A 6 7 ? 116.503 27.521 -36.705 1.000 92.330 1 47 1 +ATOM N N . SER A A 7 8 ? 117.999 25.245 -39.224 1.000 89.750 1 48 1 +ATOM C CA . SER A A 7 8 ? 119.165 25.590 -40.036 1.000 87.320 1 49 1 +ATOM C C . SER A A 7 8 ? 119.224 27.089 -40.277 1.000 84.820 1 50 1 +ATOM O O . SER A A 7 8 ? 120.074 27.594 -41.008 1.000 84.020 1 51 1 +ATOM C CB . SER A A 7 8 ? 119.112 24.859 -41.383 1.000 88.180 1 52 1 +ATOM O OG . SER A A 7 8 ? 117.956 25.221 -42.117 1.000 88.850 1 53 1 +ATOM N N . ASP B B 1 3 ? 71.339 57.678 52.031 1.000 152.010 2 54 1 +ATOM C CA . ASP B B 1 3 ? 70.427 58.819 51.717 1.000 152.390 2 55 1 +ATOM C C . ASP B B 1 3 ? 70.144 58.821 50.222 1.000 151.960 2 56 1 +ATOM O O . ASP B B 1 3 ? 70.984 59.245 49.435 1.000 151.590 2 57 1 +ATOM C CB . ASP B B 1 3 ? 71.083 60.142 52.119 1.000 153.250 2 58 1 +ATOM C CG . ASP B B 1 3 ? 71.660 60.105 53.526 1.000 154.120 2 59 1 +ATOM O OD1 . ASP B B 1 3 ? 72.652 59.371 53.741 1.000 154.200 2 60 1 +ATOM O OD2 . ASP B B 1 3 ? 71.119 60.804 54.415 1.000 154.250 2 61 1 +ATOM N N . GLU B B 2 4 ? 68.956 58.362 49.837 1.000 151.910 2 62 1 +ATOM C CA . GLU B B 2 4 ? 68.584 58.274 48.425 1.000 152.090 2 63 1 +ATOM C C . GLU B B 2 4 ? 68.584 59.573 47.616 1.000 151.320 2 64 1 +ATOM O O . GLU B B 2 4 ? 67.786 59.730 46.686 1.000 150.840 2 65 1 +ATOM C CB . GLU B B 2 4 ? 67.218 57.585 48.274 1.000 153.600 2 66 1 +ATOM C CG . GLU B B 2 4 ? 66.035 58.328 48.890 1.000 155.740 2 67 1 +ATOM C CD . GLU B B 2 4 ? 64.690 57.699 48.526 1.000 156.760 2 68 1 +ATOM O OE1 . GLU B B 2 4 ? 64.487 56.498 48.819 1.000 156.940 2 69 1 +ATOM O OE2 . GLU B B 2 4 ? 63.835 58.409 47.947 1.000 157.060 2 70 1 diff --git a/test/input/no_title.cif b/test/input/no_title.cif new file mode 100644 index 0000000..56db457 --- /dev/null +++ b/test/input/no_title.cif @@ -0,0 +1,3 @@ +data_PDBDEV_00000025 +_entry.id PDBDEV_00000025 +_struct.entry_id PDBDEV_00000025 diff --git a/test/input/not_modeled.cif b/test/input/not_modeled.cif new file mode 100644 index 0000000..367ae17 --- /dev/null +++ b/test/input/not_modeled.cif @@ -0,0 +1,89 @@ +data_model +# +_exptl.method 'model, MODELLER Version 9.24 2020/08/21 11:54:31' +# +_modeller.version 9.24 +# +loop_ +_struct_asym.id +_struct_asym.entity_id +_struct_asym.details +A 1 ? +B 2 ? +# +loop_ +_entity_poly_seq.entity_id +_entity_poly_seq.num +_entity_poly_seq.mon_id +1 1 VAL +1 2 GLY +1 3 GLN +1 4 GLN +1 5 TYR +1 6 SER +1 7 SER +2 1 ASP +2 2 GLU +# +loop_ +_atom_site.group_PDB +_atom_site.type_symbol +_atom_site.label_atom_id +_atom_site.label_alt_id +_atom_site.label_comp_id +_atom_site.label_asym_id +_atom_site.auth_asym_id +_atom_site.label_seq_id +_atom_site.auth_seq_id +_atom_site.pdbx_PDB_ins_code +_atom_site.Cartn_x +_atom_site.Cartn_y +_atom_site.Cartn_z +_atom_site.occupancy +_atom_site.B_iso_or_equiv +_atom_site.label_entity_id +_atom_site.id +_atom_site.pdbx_PDB_model_num +ATOM N N . VAL A A 1 2 ? 115.846 27.965 -26.370 1.000 141.830 1 1 1 +ATOM C CA . VAL A A 1 2 ? 114.370 27.980 -26.088 1.000 143.490 1 2 1 +ATOM C C . VAL A A 1 2 ? 113.517 27.504 -27.287 1.000 143.910 1 3 1 +ATOM O O . VAL A A 1 2 ? 113.885 27.746 -28.441 1.000 146.600 1 4 1 +ATOM C CB . VAL A A 1 2 ? 113.901 29.406 -25.683 1.000 143.750 1 5 1 +ATOM C CG1 . VAL A A 1 2 ? 115.030 30.438 -25.931 1.000 144.590 1 6 1 +ATOM C CG2 . VAL A A 1 2 ? 112.669 29.783 -26.486 1.000 144.500 1 7 1 +ATOM N N . GLY A A 2 3 ? 112.371 26.869 -27.012 1.000 142.200 1 8 1 +ATOM C CA . GLY A A 2 3 ? 111.506 26.368 -28.075 1.000 137.530 1 9 1 +ATOM C C . GLY A A 2 3 ? 111.719 24.869 -28.275 1.000 135.820 1 10 1 +ATOM O O . GLY A A 2 3 ? 110.768 24.093 -28.268 1.000 134.380 1 11 1 +ATOM N N . GLN A A 3 4 ? 112.989 24.479 -28.392 1.000 134.310 1 12 1 +ATOM C CA . GLN A A 3 4 ? 113.468 23.113 -28.639 1.000 128.420 1 13 1 +ATOM C C . GLN A A 3 4 ? 113.556 22.956 -30.163 1.000 121.240 1 14 1 +ATOM O O . GLN A A 3 4 ? 113.552 23.977 -30.840 1.000 127.090 1 15 1 +ATOM C CB . GLN A A 3 4 ? 112.614 22.038 -27.919 1.000 132.340 1 16 1 +ATOM C CG . GLN A A 3 4 ? 113.028 21.943 -26.407 1.000 135.370 1 17 1 +ATOM C CD . GLN A A 3 4 ? 112.604 20.667 -25.677 1.000 138.260 1 18 1 +ATOM O OE1 . GLN A A 3 4 ? 112.836 19.543 -26.150 1.000 141.450 1 19 1 +ATOM N NE2 . GLN A A 3 4 ? 112.006 20.839 -24.497 1.000 139.310 1 20 1 +ATOM N N . GLN A A 4 5 ? 113.648 21.739 -30.710 1.000 124.970 1 21 1 +ATOM C CA . GLN A A 4 5 ? 113.808 21.534 -32.168 1.000 117.620 1 22 1 +ATOM C C . GLN A A 4 5 ? 114.778 22.519 -32.833 1.000 112.980 1 23 1 +ATOM O O . GLN A A 4 5 ? 114.677 23.727 -32.677 1.000 116.850 1 24 1 +ATOM C CB . GLN A A 4 5 ? 112.456 21.545 -32.905 1.000 121.870 1 25 1 +ATOM C CG . GLN A A 4 5 ? 111.763 20.153 -32.917 1.000 123.750 1 26 1 +ATOM C CD . GLN A A 4 5 ? 110.863 19.874 -34.145 1.000 123.650 1 27 1 +ATOM O OE1 . GLN A A 4 5 ? 110.040 20.712 -34.537 1.000 122.500 1 28 1 +ATOM N NE2 . GLN A A 4 5 ? 111.008 18.674 -34.737 1.000 122.090 1 29 1 +ATOM N N . SER A A 7 8 ? 117.999 25.245 -39.224 1.000 89.750 1 48 1 +ATOM C CA . SER A A 7 8 ? 119.165 25.590 -40.036 1.000 87.320 1 49 1 +ATOM C C . SER A A 7 8 ? 119.224 27.089 -40.277 1.000 84.820 1 50 1 +ATOM O O . SER A A 7 8 ? 120.074 27.594 -41.008 1.000 84.020 1 51 1 +ATOM C CB . SER A A 7 8 ? 119.112 24.859 -41.383 1.000 88.180 1 52 1 +ATOM O OG . SER A A 7 8 ? 117.956 25.221 -42.117 1.000 88.850 1 53 1 +ATOM N N . ASP B B 1 3 ? 71.339 57.678 52.031 1.000 152.010 2 54 1 +ATOM C CA . ASP B B 1 3 ? 70.427 58.819 51.717 1.000 152.390 2 55 1 +ATOM C C . ASP B B 1 3 ? 70.144 58.821 50.222 1.000 151.960 2 56 1 +ATOM O O . ASP B B 1 3 ? 70.984 59.245 49.435 1.000 151.590 2 57 1 +ATOM C CB . ASP B B 1 3 ? 71.083 60.142 52.119 1.000 153.250 2 58 1 +ATOM C CG . ASP B B 1 3 ? 71.660 60.105 53.526 1.000 154.120 2 59 1 +ATOM O OD1 . ASP B B 1 3 ? 72.652 59.371 53.741 1.000 154.200 2 60 1 +ATOM O OD2 . ASP B B 1 3 ? 71.119 60.804 54.415 1.000 154.250 2 61 1 diff --git a/test/input/struct_only.cif b/test/input/struct_only.cif new file mode 100644 index 0000000..fd8617c --- /dev/null +++ b/test/input/struct_only.cif @@ -0,0 +1,4 @@ +data_PDBDEV_00000025 +_entry.id PDBDEV_00000025 +_struct.entry_id PDBDEV_00000025 +_struct.title 'Architecture of Pol II(G) and molecular mechanism of transcription regulation by Gdown1' diff --git a/test/test_alignment.py b/test/test_alignment.py new file mode 100644 index 0000000..5f0a903 --- /dev/null +++ b/test/test_alignment.py @@ -0,0 +1,37 @@ +import utils +import os +import unittest + +TOPDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +utils.set_search_paths(TOPDIR) +import modelcif.alignment + + +class Tests(unittest.TestCase): + def test_identity(self): + """Test sequence identity classes""" + ident = modelcif.alignment.ShorterSequenceIdentity(42.0) + self.assertEqual(ident.denominator, "Length of the shorter sequence") + self.assertIsNone(ident.other_details) + self.assertAlmostEqual(ident.value, 42.0, delta=1e-4) + ident = modelcif.alignment.AlignedPositionsIdentity(42.0) + ident = modelcif.alignment.AlignedResiduePairsIdentity(42.0) + ident = modelcif.alignment.MeanSequenceIdentity(42.0) + + # generic "other" identity + ident = modelcif.alignment.Identity(42.0) + self.assertEqual(ident.denominator, "Other") + self.assertIsNone(ident.other_details) + + # custom "other" identity + class CustomIdentity(modelcif.alignment.Identity): + """foo + bar""" + + ident = CustomIdentity(42.0) + self.assertEqual(ident.denominator, "Other") + self.assertEqual(ident.other_details, "foo") + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_associated.py b/test/test_associated.py new file mode 100644 index 0000000..c1b62ac --- /dev/null +++ b/test/test_associated.py @@ -0,0 +1,19 @@ +import utils +import os +import unittest + +TOPDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +utils.set_search_paths(TOPDIR) +import modelcif.associated + + +class Tests(unittest.TestCase): + def test_local_pairwise_qa_scores_file(self): + """Test LocalPairwiseQAScoresFile class""" + self.assertWarns(UserWarning, + modelcif.associated.LocalPairwiseQAScoresFile, + path='foo') + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_descriptor.py b/test/test_descriptor.py new file mode 100644 index 0000000..5591899 --- /dev/null +++ b/test/test_descriptor.py @@ -0,0 +1,19 @@ +import utils +import os +import unittest + +TOPDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +utils.set_search_paths(TOPDIR) +import modelcif.descriptor + + +class Tests(unittest.TestCase): + def test_descriptor(self): + """Test Descriptor classes""" + base = modelcif.descriptor.Descriptor("1abc") + self.assertEqual(base.value, "1abc") + _ = repr(base) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_dumper.py b/test/test_dumper.py new file mode 100644 index 0000000..ae2333c --- /dev/null +++ b/test/test_dumper.py @@ -0,0 +1,1579 @@ +from datetime import date +import warnings +import utils +import os +import unittest +from io import StringIO +try: + import msgpack +except ImportError: + msgpack = None + +TOPDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +utils.set_search_paths(TOPDIR) +import modelcif.dumper +import modelcif.protocol +import modelcif.model +import modelcif.reference +import modelcif.alignment +import modelcif.associated +import modelcif.descriptor +import ihm.format +import ihm.dumper + + +def _get_dumper_output(dumper, system, check=True): + dumper._check = check + fh = StringIO() + writer = ihm.format.CifWriter(fh) + dumper.dump(system, writer) + return fh.getvalue() + + +class Tests(unittest.TestCase): + def test_write(self): + """Test write() function""" + sys1 = modelcif.System(id='system1') + sys2 = modelcif.System(id='system 2+3') + fh = StringIO() + modelcif.dumper.write(fh, [sys1, sys2]) + lines = fh.getvalue().split('\n') + self.assertEqual(lines[:2], ["data_system1", "_entry.id system1"]) + if lines[9] == 'data_system23': + self.assertEqual(lines[9:11], + ["data_system23", "_entry.id 'system 2+3'"]) + else: + self.assertEqual(lines[11:13], + ["data_system23", "_entry.id 'system 2+3'"]) + + def test_audit_conform_dumper(self): + """Test AuditConformDumper""" + system = modelcif.System() + dumper = modelcif.dumper._AuditConformDumper() + out = _get_dumper_output(dumper, system) + lines = sorted(out.split('\n')) + self.assertEqual(lines[1].split()[0], "_audit_conform.dict_location") + self.assertEqual(lines[2].rstrip('\r\n'), + "_audit_conform.dict_name mmcif_ma.dic") + self.assertEqual(lines[3].split()[0], "_audit_conform.dict_version") + + def test_database_dumper(self): + """Test DatabaseDumper""" + system = modelcif.System() + dumper = modelcif.dumper._DatabaseDumper() + out = _get_dumper_output(dumper, system) + self.assertEqual(out, '') + + system = modelcif.System( + database=modelcif.Database(id='foo', code='bar')) + dumper = modelcif.dumper._DatabaseDumper() + out = _get_dumper_output(dumper, system) + self.assertEqual(out, "_database_2.database_code bar\n" + "_database_2.database_id foo\n") + + def test_software_group_dumper(self): + """Test SoftwareGroupDumper""" + class MockObject: + pass + p1 = modelcif.SoftwareParameter(name='foo', value=42) + p2 = modelcif.SoftwareParameter(name='bar', value=True) + p3 = modelcif.SoftwareParameter(name='baz', value='ok') + intlist = modelcif.SoftwareParameter(name='intlist', value=[1, 2, 3]) + floatlist = modelcif.SoftwareParameter( + name='floatlist', value=(1., 2., 3.)) + mixlist = modelcif.SoftwareParameter(name='mixlist', value=[1, 2., 3]) + s1 = modelcif.Software( + name='s1', classification='test code', + description='Some test program', + version=1, location='http://test.org') + s1._id = 1 + s2 = modelcif.Software( + name='s2', classification='test code', + description='Some test program', + version=1, location='http://test.org') + s2._id = 2 + s3 = modelcif.Software( + name='s3', classification='test code', + description='Some test program', + version=1, location='http://test.org') + s3._id = 3 + system = modelcif.System() + aln1 = MockObject() + aln1.pairs = [] + aln1.software = modelcif.SoftwareGroup((s1, s2)) + # SoftwareGroup.parameters should be ignored + aln1.software.parameters.append('garbage') + aln2 = MockObject() + aln2.pairs = [] + aln2.software = s3 + aln3 = MockObject() + aln3.pairs = [] + s3param = modelcif.SoftwareWithParameters( + software=s3, parameters=[p1, p2, p3, intlist, floatlist, mixlist]) + aln3.software = modelcif.SoftwareGroup((s2, s3param)) + + # Duplicate parameters, should get the same ID as for aln3 + aln4 = MockObject() + aln4.pairs = [] + aln4.software = modelcif.SoftwareGroup((s3param,)) + + system.alignments.extend((aln1, aln2, aln3, aln4)) + system._before_write() # populate system.software_groups + dumper = modelcif.dumper._SoftwareGroupDumper() + dumper.finalize(system) + out = _get_dumper_output(dumper, system) + # Should have two groups (s1, s2) and (s2, s3) and another + # singleton group (s3) + self.assertEqual(out, """# +loop_ +_ma_software_parameter.parameter_id +_ma_software_parameter.group_id +_ma_software_parameter.data_type +_ma_software_parameter.name +_ma_software_parameter.value +_ma_software_parameter.description +1 1 integer foo 42 . +2 1 boolean bar YES . +3 1 string baz ok . +4 1 integer-csv intlist 1,2,3 . +5 1 float-csv floatlist 1.0,2.0,3.0 . +6 1 float-csv mixlist 1,2.0,3 . +# +# +loop_ +_ma_software_group.ordinal_id +_ma_software_group.group_id +_ma_software_group.software_id +_ma_software_group.parameter_group_id +1 1 1 . +2 1 2 . +3 2 3 . +4 3 2 . +5 3 3 1 +6 4 3 1 +# +""") + + def test_bad_software_parameter(self): + """Test invalid SoftwareParameter""" + p1 = modelcif.SoftwareParameter(name='foo', value=['string', 'list']) + s1 = modelcif.Software( + name='s1', classification='test code', + description='Some test program', + version=1, location='http://test.org') + system = modelcif.System() + system.software.append(s1) + s1param = modelcif.SoftwareWithParameters(s1, parameters=[p1]) + sg1 = modelcif.SoftwareGroup([s1param]) + system.software_groups.append(sg1) + dumper = modelcif.dumper._SoftwareGroupDumper() + dumper.finalize(system) + # Only lists of ints or floats are supported, not strings + self.assertRaises(TypeError, _get_dumper_output, dumper, system) + + def test_data_dumper(self): + """Test DataDumper""" + system = modelcif.System() + entity = modelcif.Entity("DMA", description='test entity') + system.entities.append(entity) + # Template and target use same entity here (but different data IDs) + template = modelcif.Template( + entity, asym_id="A", model_num=1, name="test template", + transformation=modelcif.Transformation.identity()) + system.templates.append(template) + system.data.append(modelcif.data.Data(name="test other", + details="test details")) + system._before_write() # populate system.data + dumper = modelcif.dumper._DataDumper() + dumper.finalize(system) + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_data.id +_ma_data.name +_ma_data.content_type +_ma_data.content_type_other_details +1 'test other' other 'test details' +2 'test template' 'template structure' . +3 'test entity' target . +# +""") + + def test_data_group_dumper(self): + """Test DataGroupDumper""" + system = modelcif.System() + tgt_e1 = modelcif.Entity("D") + tgt_e2 = modelcif.Entity("M") + tgt_e3 = modelcif.Entity("A") + tgt_e1._data_id = 1 + tgt_e2._data_id = 2 + tgt_e3._data_id = 3 + system.entities.extend((tgt_e1, tgt_e2, tgt_e3)) + dg12 = modelcif.data.DataGroup((tgt_e1, tgt_e2)) + p = modelcif.protocol.Protocol() + p.steps.append(modelcif.protocol.ModelingStep( + input_data=dg12, output_data=tgt_e3)) + system.protocols.append(p) + system._before_write() # populate system.data_groups + dumper = modelcif.dumper._DataGroupDumper() + dumper.finalize(system) + out = _get_dumper_output(dumper, system) + # First group (tgt_e1,tgt_e2); second group contains just tgt_e3 + self.assertEqual(out, """# +loop_ +_ma_data_group.ordinal_id +_ma_data_group.group_id +_ma_data_group.data_id +1 1 1 +2 1 2 +3 2 3 +# +""") + + def test_data_ref_db_dumper(self): + """Test DataRefDBDumper""" + system = modelcif.System() + system.data.append(modelcif.ReferenceDatabase( + name='testdb', url='testurl', version='1.0', + release_date=date(1979, 11, 22))) + system.data.append(modelcif.data.Data(name="test other", + details="test details")) + dumper = modelcif.dumper._DataDumper() + dumper.finalize(system) # Assign Data IDs + dumper = modelcif.dumper._DataRefDBDumper() + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_data_ref_db.data_id +_ma_data_ref_db.name +_ma_data_ref_db.location_url +_ma_data_ref_db.version +_ma_data_ref_db.release_date +1 testdb testurl 1.0 1979-11-22 +# +""") + + def test_qa_metric_dumper(self): + """Test QAMetricDumper""" + system = modelcif.System() + s1 = modelcif.Software( + name='s1', classification='test code', + description='Some test program', + version=1, location='http://test.org') + s1._group_id = 1 + + class MockObject: + pass + + class CustomMetricType(modelcif.qa_metric.MetricType): + """my custom type""" + + class DistanceScore(modelcif.qa_metric.Global, + modelcif.qa_metric.Distance): + """test description""" + name = "test score" + software = s1 + + class CustomScore(modelcif.qa_metric.Global, CustomMetricType): + """Description does not match docstring""" + description = "custom description" + software = None + + class LocalScore(modelcif.qa_metric.Local, modelcif.qa_metric.ZScore): + """custom local description + Second line of docstring (ignored)""" + name = "custom local score" + software = None + + class PairScore(modelcif.qa_metric.LocalPairwise, + modelcif.qa_metric.Energy): + """custom pair description""" + name = "custom pair score" + software = None + + class FeatureScore(modelcif.qa_metric.Feature, + modelcif.qa_metric.ZScore): + """feature score""" + name = "feature score" + software = None + + class FeaturePairwiseScore(modelcif.qa_metric.FeaturePairwise, + modelcif.qa_metric.ZScore): + """feature pairwise score""" + name = "feature pairwise score" + software = None + + m1 = DistanceScore(42.) + m2 = CustomScore(99.) + m3 = DistanceScore(60.) + e1 = modelcif.Entity('ACGT') + asym = modelcif.AsymUnit(e1, 'foo') + asym._id = 'Z' + m4 = LocalScore(asym.residue(2), 20.) + m5 = PairScore(asym.residue(1), asym.residue(3), 30.) + resf = modelcif.PolyResidueFeature((asym.residue(1), asym.residue(2))) + instf = modelcif.EntityInstanceFeature((asym,)) + m6 = FeatureScore(resf, 40.) + m7 = FeaturePairwiseScore(resf, instf, 50.) + model = MockObject() + model._id = 18 + model.qa_metrics = [m1, m2, m3, m4, m5, m6, m7] + mg = modelcif.model.ModelGroup((model,)) + system.model_groups.append(mg) + # Assign feature IDs + dumper = modelcif.dumper._FeatureDumper() + dumper.finalize(system) + dumper = modelcif.dumper._QAMetricDumper() + dumper.finalize(system) + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_qa_metric.id +_ma_qa_metric.name +_ma_qa_metric.description +_ma_qa_metric.type +_ma_qa_metric.mode +_ma_qa_metric.type_other_details +_ma_qa_metric.software_group_id +1 'test score' 'test description' distance global . 1 +2 CustomScore 'custom description' other global 'my custom type' . +3 'custom local score' 'custom local description' zscore local . . +4 'custom pair score' 'custom pair description' energy local-pairwise . . +5 'feature score' 'feature score' zscore per-feature . . +6 'feature pairwise score' 'feature pairwise score' zscore per-feature-pair . . +# +# +loop_ +_ma_qa_metric_global.ordinal_id +_ma_qa_metric_global.model_id +_ma_qa_metric_global.metric_id +_ma_qa_metric_global.metric_value +1 18 1 42.000 +2 18 2 99.000 +3 18 1 60.000 +# +# +loop_ +_ma_qa_metric_local.ordinal_id +_ma_qa_metric_local.model_id +_ma_qa_metric_local.label_asym_id +_ma_qa_metric_local.label_seq_id +_ma_qa_metric_local.label_comp_id +_ma_qa_metric_local.metric_id +_ma_qa_metric_local.metric_value +1 18 Z 2 CYS 3 20.000 +# +# +loop_ +_ma_qa_metric_local_pairwise.ordinal_id +_ma_qa_metric_local_pairwise.model_id +_ma_qa_metric_local_pairwise.label_asym_id_1 +_ma_qa_metric_local_pairwise.label_seq_id_1 +_ma_qa_metric_local_pairwise.label_comp_id_1 +_ma_qa_metric_local_pairwise.label_asym_id_2 +_ma_qa_metric_local_pairwise.label_seq_id_2 +_ma_qa_metric_local_pairwise.label_comp_id_2 +_ma_qa_metric_local_pairwise.metric_id +_ma_qa_metric_local_pairwise.metric_value +1 18 Z 1 ALA Z 3 GLY 4 30.000 +# +# +loop_ +_ma_qa_metric_feature.ordinal_id +_ma_qa_metric_feature.model_id +_ma_qa_metric_feature.feature_id +_ma_qa_metric_feature.metric_id +_ma_qa_metric_feature.metric_value +1 18 1 5 40.000 +# +# +loop_ +_ma_qa_metric_feature_pairwise.ordinal_id +_ma_qa_metric_feature_pairwise.model_id +_ma_qa_metric_feature_pairwise.feature_id_1 +_ma_qa_metric_feature_pairwise.feature_id_2 +_ma_qa_metric_feature_pairwise.metric_id +_ma_qa_metric_feature_pairwise.metric_value +1 18 1 2 6 50.000 +# +""") + + def test_feature_dumper(self): + """Test FeatureDumper""" + system = modelcif.System() + + class MockObject: + pass + + class TestScore(modelcif.qa_metric.Feature, modelcif.qa_metric.ZScore): + """test score""" + name = "test score" + software = None + + e1 = modelcif.Entity('ACGT') + asym = modelcif.AsymUnit(e1, 'foo') + e2 = ihm.Entity([ihm.NonPolymerChemComp('HEM')]) + heme = modelcif.AsymUnit(e2, 'heme') + + asym._id = 'Y' + heme._id = 'Z' + atomf = modelcif.AtomFeature((1, 2, 3), details='atom f') + resf = modelcif.PolyResidueFeature((asym.residue(1), asym.residue(2)), + details='prf') + instf = modelcif.EntityInstanceFeature((asym,)) + inst2f = modelcif.EntityInstanceFeature((heme,)) + atoms = TestScore(atomf, 20.) + ress = TestScore(resf, 30.) + insts = TestScore(instf, 40.) + inst2s = TestScore(inst2f, 40.) + + model = MockObject() + model._id = 18 + model.qa_metrics = [atoms, ress, insts, inst2s] + mg = modelcif.model.ModelGroup((model,)) + system.model_groups.append(mg) + + dumper = modelcif.dumper._FeatureDumper() + dumper.finalize(system) + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_feature_list.feature_id +_ma_feature_list.feature_type +_ma_feature_list.entity_type +_ma_feature_list.details +1 atom other 'atom f' +2 residue polymer prf +3 'entity instance' polymer . +4 'entity instance' non-polymer . +# +# +loop_ +_ma_atom_feature.ordinal_id +_ma_atom_feature.feature_id +_ma_atom_feature.atom_id +1 1 1 +2 1 2 +3 1 3 +# +# +loop_ +_ma_poly_residue_feature.ordinal_id +_ma_poly_residue_feature.feature_id +_ma_poly_residue_feature.label_asym_id +_ma_poly_residue_feature.label_seq_id +_ma_poly_residue_feature.label_comp_id +1 2 Y 1 ALA +2 2 Y 2 CYS +# +# +loop_ +_ma_entity_instance_feature.ordinal_id +_ma_entity_instance_feature.feature_id +_ma_entity_instance_feature.label_asym_id +1 3 Y +2 4 Z +# +""") + # Test empty feature + emptyf = modelcif.EntityInstanceFeature(()) + emptys = TestScore(emptyf, 20.) + model.qa_metrics = [emptys] + dumper = modelcif.dumper._FeatureDumper() + dumper.finalize(system) + self.assertRaises(ValueError, _get_dumper_output, dumper, system) + # Should work with checks disabled + _ = _get_dumper_output(dumper, system, check=False) + + # Test feature that selects multiple entity types + multf = modelcif.EntityInstanceFeature((asym, heme)) + mults = TestScore(multf, 20.) + model.qa_metrics = [mults] + dumper = modelcif.dumper._FeatureDumper() + dumper.finalize(system) + self.assertRaises(ValueError, _get_dumper_output, dumper, system) + # Should work with checks disabled + _ = _get_dumper_output(dumper, system, check=False) + + def test_protocol_dumper(self): + """Test ProtocolDumper""" + class MockObject: + pass + indat = MockObject() + indat._data_group_id = 1 + outdat = MockObject() + outdat._data_group_id = 2 + system = modelcif.System() + s1 = modelcif.Software( + name='s1', classification='test code', + description='Some test program', + version=1, location='http://test.org') + s1._group_id = 42 + p = modelcif.protocol.Protocol() + p.steps.append(modelcif.protocol.TemplateSearchStep( + name='tsstep', details="some details", software=s1, + input_data=indat, output_data=outdat)) + p.steps.append(modelcif.protocol.ModelingStep( + name='modstep', input_data=indat, output_data=outdat)) + p.steps.append(modelcif.protocol.ModelingStep( + name='nullstep', input_data=None, output_data=None)) + system.protocols.append(p) + dumper = modelcif.dumper._ProtocolDumper() + dumper.finalize(system) + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_protocol_step.ordinal_id +_ma_protocol_step.protocol_id +_ma_protocol_step.step_id +_ma_protocol_step.method_type +_ma_protocol_step.step_name +_ma_protocol_step.details +_ma_protocol_step.software_group_id +_ma_protocol_step.input_data_group_id +_ma_protocol_step.output_data_group_id +1 1 1 'template search' tsstep 'some details' 42 1 2 +2 1 2 modeling modstep . . 1 2 +3 1 3 modeling nullstep . . . . +# +""") + + def test_model_dumper(self): + """Test ModelDumper""" + class CustomModel(modelcif.model.Model): + """custom model""" + + system = modelcif.System() + e1 = modelcif.Entity('ACGT') + e1._id = 9 + system.entities.append(e1) + asym = modelcif.AsymUnit(e1, 'foo') + asym._id = 'A' + system.asym_units.append(asym) + asmb = modelcif.Assembly((asym,)) + asmb._id = 2 + model1 = modelcif.model.HomologyModel(assembly=asmb, name='test model') + model1._data_id = 42 + model1._atoms = [modelcif.model.Atom(asym_unit=asym, seq_id=1, + atom_id='C', type_symbol='C', + x=1.0, y=2.0, z=3.0)] + model2 = modelcif.model.AbInitioModel(assembly=asmb, name='model2') + model2._data_id = 43 + model3 = CustomModel(assembly=asmb, name='model3') + model3._data_id = 44 + mg = modelcif.model.ModelGroup((model1, model2, model3), + name='test group') + system.model_groups.append(mg) + # model1 is in both groups + mg = modelcif.model.ModelGroup((model1,), + name='second group') + # ModelGroup constructor only supports details with python-ihm > 1.8 + mg.details = 'second group details' + system.model_groups.append(mg) + dumper = modelcif.dumper._ModelDumper() + dumper.finalize(system) + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_model_list.ordinal_id +_ma_model_list.model_name +_ma_model_list.data_id +_ma_model_list.model_type +_ma_model_list.model_type_other_details +1 'test model' 42 'Homology model' . +2 model2 43 'Ab initio model' . +3 model3 44 Other 'custom model' +# +# +loop_ +_ma_model_group.id +_ma_model_group.name +_ma_model_group.details +1 'test group' . +2 'second group' 'second group details' +# +# +loop_ +_ma_model_group_link.group_id +_ma_model_group_link.model_id +1 1 +1 2 +1 3 +2 1 +# +# +loop_ +_atom_site.group_PDB +_atom_site.id +_atom_site.type_symbol +_atom_site.label_atom_id +_atom_site.label_alt_id +_atom_site.label_comp_id +_atom_site.label_seq_id +_atom_site.auth_seq_id +_atom_site.pdbx_PDB_ins_code +_atom_site.label_asym_id +_atom_site.Cartn_x +_atom_site.Cartn_y +_atom_site.Cartn_z +_atom_site.occupancy +_atom_site.label_entity_id +_atom_site.auth_asym_id +_atom_site.auth_comp_id +_atom_site.B_iso_or_equiv +_atom_site.pdbx_PDB_model_num +ATOM 1 C C . ALA 1 1 ? A 1.000 2.000 3.000 . 9 A ALA . 1 +# +# +loop_ +_atom_type.symbol +C +# +""") + + def test_poly_seq_scheme_dumper(self): + """Test PolySeqSchemeDumper with ModelCIF models""" + system = modelcif.System() + e1 = modelcif.Entity('ACGT') + e1._id = 9 + system.entities.append(e1) + asym = modelcif.AsymUnit(e1, 'foo') + asym._id = 'A' + system.asym_units.append(asym) + asmb = modelcif.Assembly((asym,)) + asmb._id = 2 + model1 = modelcif.model.HomologyModel(assembly=asmb, name='test model') + model1._data_id = 42 + model1._atoms = [modelcif.model.Atom(asym_unit=asym, seq_id=1, + atom_id='C', type_symbol='C', + x=1.0, y=2.0, z=3.0)] + mg = modelcif.model.ModelGroup((model1,), + name='test group') + # Add at least one model, since the PolySeqSchemeDumper checks all + # models' not_modeled_residue_ranges member when writing the table + system.model_groups.append(mg) + dumper = ihm.dumper._PolySeqSchemeDumper() + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_pdbx_poly_seq_scheme.asym_id +_pdbx_poly_seq_scheme.entity_id +_pdbx_poly_seq_scheme.seq_id +_pdbx_poly_seq_scheme.mon_id +_pdbx_poly_seq_scheme.pdb_seq_num +_pdbx_poly_seq_scheme.auth_seq_num +_pdbx_poly_seq_scheme.pdb_mon_id +_pdbx_poly_seq_scheme.auth_mon_id +_pdbx_poly_seq_scheme.pdb_strand_id +_pdbx_poly_seq_scheme.pdb_ins_code +A 9 1 ALA 1 1 ALA ALA A . +A 9 2 CYS 2 2 CYS CYS A . +A 9 3 GLY 3 3 GLY GLY A . +A 9 4 THR 4 4 THR THR A . +# +""") + + def test_target_ref_db_dumper(self): + """Test TargetRefDBDumper""" + + class CustomRef(modelcif.reference.TargetReference): + """my custom ref""" + + system = modelcif.System() + # Default alignment but with explicit align begin, end + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + ref1 = modelcif.reference.UniProt( + code='testcode', accession='testacc', align_begin=4, + align_end=8, isoform='testiso', ncbi_taxonomy_id='1234', + organism_scientific='testorg', + sequence_version_date=date(1979, 11, 22), + sequence_crc64="A123B456C789D1E2", + sequence='ACGT', + is_primary=True) + # Default alignment (entire sequence) + ref2 = modelcif.reference.UniProt(code='c2', accession='a2', + sequence='ACGT') + ref3 = CustomRef(code='c3', accession='a3', isoform=ihm.unknown, + sequence='ACGT') + + # Explicit alignment that extends to the end of the db sequence + ref4 = modelcif.reference.UniProt(code='c4', accession='a4', + sequence='CCACGT') + ref4.alignments.append(modelcif.reference.Alignment(db_begin=3)) + + # Explicit alignment with explicit db_end + ref5 = modelcif.reference.UniProt(code='c5', accession='a5', + sequence='XXXACXXGTXXX', + is_primary=False) + ref5.alignments.append(modelcif.reference.Alignment( + db_begin=4, db_end=5)) + ref5.alignments.append(modelcif.reference.Alignment( + db_begin=8, db_end=9)) + + e1 = modelcif.Entity('ACGT', references=[ref1, ref2, ref3, ref4, ref5]) + e1._id = 1 + system.entities.append(e1) + + dumper = modelcif.dumper._TargetRefDBDumper() + dumper.finalize(system) + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_target_ref_db_details.target_entity_id +_ma_target_ref_db_details.db_name +_ma_target_ref_db_details.db_name_other_details +_ma_target_ref_db_details.db_code +_ma_target_ref_db_details.db_accession +_ma_target_ref_db_details.seq_db_isoform +_ma_target_ref_db_details.seq_db_align_begin +_ma_target_ref_db_details.seq_db_align_end +_ma_target_ref_db_details.ncbi_taxonomy_id +_ma_target_ref_db_details.organism_scientific +_ma_target_ref_db_details.seq_db_sequence_version_date +_ma_target_ref_db_details.seq_db_sequence_checksum +_ma_target_ref_db_details.is_primary +1 UNP . testcode testacc testiso 4 8 1234 testorg 1979-11-22 A123B456C789D1E2 +YES +1 UNP . c2 a2 . 1 4 . . . . . +1 Other 'my custom ref' c3 a3 ? 1 4 . . . . . +1 UNP . c4 a4 . 3 6 . . . . . +1 UNP . c5 a5 . 4 9 . . . . NO +# +""") + + def test_alignment_dumper(self): + """Test AlignmentDumper""" + + class CustomRef(modelcif.reference.TemplateReference): + """my custom ref""" + + class Alignment(modelcif.alignment.Global, + modelcif.alignment.Pairwise): + pass + + class LocalAlignment(modelcif.alignment.Local, + modelcif.alignment.Multiple): + pass + + system = modelcif.System() + tmp_e = modelcif.Entity('ACG') + tgt_e = modelcif.Entity('ACE') + tgt_e._id = 1 + system.entities.extend((tmp_e, tgt_e)) + asym = modelcif.AsymUnit(tgt_e, id='A') + asym._id = 'A' + system.asym_units.append(asym) + ref1 = modelcif.reference.PDB('1abc', + db_version_date=date(1979, 11, 22)) + ref2 = CustomRef('2xyz') + ref3 = modelcif.reference.PubChem("1234") + ref4 = modelcif.reference.AlphaFoldDB("P12345", + db_version_date=date(2022, 6, 1)) + tr = modelcif.Transformation.identity() + tr._id = 42 + t = modelcif.Template(tmp_e, asym_id='H', model_num=1, name='testtmp', + transformation=tr, + references=[ref1, ref2, ref3, ref4], + strand_id='Z') + t._data_id = 99 + p = modelcif.alignment.Pair( + template=t.segment('AC-G', 1, 3), + target=asym.segment('ACE-', 1, 3), + score=modelcif.alignment.BLASTEValue("1e-15"), + identity=modelcif.alignment.ShorterSequenceIdentity(42.)) + aln = Alignment(name='testaln', pairs=[p]) + aln._data_id = 100 + system.alignments.append(aln) + # The same alignment using HHblits e-value + p1 = modelcif.alignment.Pair( + template=p.template, + target=p.target, + score=modelcif.alignment.HHblitsEValue("1e-14"), + identity=p.identity) + # The same alignment with missing score and identity + p2 = modelcif.alignment.Pair( + template=p.template, + target=p.target) + aln = Alignment(name='testaln', pairs=[p1, p2]) + aln._data_id = 101 + system.alignments.append(aln) + # Local alignment with no pairs + aln2 = LocalAlignment(name='testaln2', pairs=[]) + aln2._data_id = 102 + system.alignments.append(aln2) + system._before_write() # populate system.templates + + dumper = modelcif.dumper._AlignmentDumper() + dumper.finalize(system) + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_template_details.ordinal_id +_ma_template_details.template_id +_ma_template_details.template_origin +_ma_template_details.template_entity_type +_ma_template_details.template_trans_matrix_id +_ma_template_details.template_data_id +_ma_template_details.target_asym_id +_ma_template_details.template_label_asym_id +_ma_template_details.template_label_entity_id +_ma_template_details.template_model_num +_ma_template_details.template_auth_asym_id +1 1 'reference database' polymer 42 99 A H . 1 Z +2 1 'reference database' polymer 42 99 A H . 1 Z +3 1 'reference database' polymer 42 99 A H . 1 Z +# +# +loop_ +_ma_template_poly.template_id +_ma_template_poly.seq_one_letter_code +_ma_template_poly.seq_one_letter_code_can +1 ACG ACG +# +# +loop_ +_ma_template_poly_segment.id +_ma_template_poly_segment.template_id +_ma_template_poly_segment.residue_number_begin +_ma_template_poly_segment.residue_number_end +1 1 1 3 +# +# +loop_ +_ma_template_ref_db_details.template_id +_ma_template_ref_db_details.db_name +_ma_template_ref_db_details.db_name_other_details +_ma_template_ref_db_details.db_accession_code +_ma_template_ref_db_details.db_version_date +1 PDB . 1abc 1979-11-22 +1 Other 'my custom ref' 2xyz . +1 PubChem . 1234 . +1 AlphaFoldDB . P12345 2022-06-01 +# +# +loop_ +_ma_target_template_poly_mapping.id +_ma_target_template_poly_mapping.template_segment_id +_ma_target_template_poly_mapping.target_asym_id +_ma_target_template_poly_mapping.target_seq_id_begin +_ma_target_template_poly_mapping.target_seq_id_end +1 1 A 1 3 +2 1 A 1 3 +3 1 A 1 3 +# +# +loop_ +_ma_alignment_info.alignment_id +_ma_alignment_info.data_id +_ma_alignment_info.software_group_id +_ma_alignment_info.alignment_length +_ma_alignment_info.alignment_type +_ma_alignment_info.alignment_mode +1 100 . 4 'target-template pairwise alignment' global +2 101 . 4 'target-template pairwise alignment' global +3 102 . . 'target-template MSA' local +# +# +loop_ +_ma_alignment_details.ordinal_id +_ma_alignment_details.alignment_id +_ma_alignment_details.template_segment_id +_ma_alignment_details.target_asym_id +_ma_alignment_details.score_type +_ma_alignment_details.score_type_other_details +_ma_alignment_details.score_value +_ma_alignment_details.percent_sequence_identity +_ma_alignment_details.sequence_identity_denominator +_ma_alignment_details.sequence_identity_denominator_other_details +1 1 1 A 'BLAST e-value' . 1e-15 42.000 'Length of the shorter sequence' . +2 2 1 A 'HHblits e-value' . 1e-14 42.000 'Length of the shorter sequence' . +3 2 1 A . . . . . . +# +# +loop_ +_ma_alignment.ordinal_id +_ma_alignment.alignment_id +_ma_alignment.target_template_flag +_ma_alignment.sequence +1 1 1 ACE- +2 1 2 AC-G +3 2 1 ACE- +4 2 2 AC-G +5 2 1 ACE- +6 2 2 AC-G +# +""") + + def test_non_poly_template_unused(self): + """Test AlignmentDumper with unused nonpolymeric template""" + system = modelcif.System() + # Polymeric entity + e1 = ihm.Entity('ACGT') + t1 = modelcif.Template( + e1, asym_id="A", model_num=1, name="test template", + transformation=modelcif.Transformation.identity(), + entity_id=9) + t1._id = 1 + t1._data_id = 99 + # Non-polymeric entity + e2 = ihm.Entity([ihm.NonPolymerChemComp('HEM')], description='heme') + t2 = modelcif.Template( + e2, asym_id="B", model_num=1, name="test template", + transformation=modelcif.Transformation.identity(), + entity_id=10) + t2._id = 2 + t2._data_id = 100 + system.templates.extend((t1, t2)) + dumper = modelcif.dumper._AlignmentDumper() + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_template_details.ordinal_id +_ma_template_details.template_id +_ma_template_details.template_origin +_ma_template_details.template_entity_type +_ma_template_details.template_trans_matrix_id +_ma_template_details.template_data_id +_ma_template_details.target_asym_id +_ma_template_details.template_label_asym_id +_ma_template_details.template_label_entity_id +_ma_template_details.template_model_num +_ma_template_details.template_auth_asym_id +1 1 'reference database' polymer 42 99 . A 9 1 A +2 2 'reference database' non-polymer 42 100 . B 10 1 B +# +# +loop_ +_ma_template_poly.template_id +_ma_template_poly.seq_one_letter_code +_ma_template_poly.seq_one_letter_code_can +1 ACGT ACGT +# +# +loop_ +_ma_template_non_poly.template_id +_ma_template_non_poly.comp_id +_ma_template_non_poly.details +2 HEM heme +# +""") + + def test_non_poly_template_used(self): + """Test AlignmentDumper with used nonpolymeric template""" + system = modelcif.System() + # Polymeric entity + e1 = ihm.Entity('ACGT') + t1 = modelcif.Template( + e1, asym_id="A", model_num=1, name="test template", + transformation=modelcif.Transformation.identity()) + t1._id = 1 + t1._data_id = 98 + # Non-polymeric entity + e2 = ihm.Entity([ihm.NonPolymerChemComp('HEM')], description='heme') + # Template should use entity_id, not e2._id + e2._id = "THIS SHOULD BE IGNORED" + t2 = modelcif.Template( + e2, asym_id="B", model_num=1, name="test template", + transformation=modelcif.Transformation.identity(), + entity_id=9) + t2._id = 2 + t2._data_id = 99 + system.templates.extend((t1, t2)) + + a2 = modelcif.NonPolymerFromTemplate(template=t2, explicit=True) + a2._id = 'X' + system.asym_units.append(a2) + + dumper = modelcif.dumper._AlignmentDumper() + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_template_details.ordinal_id +_ma_template_details.template_id +_ma_template_details.template_origin +_ma_template_details.template_entity_type +_ma_template_details.template_trans_matrix_id +_ma_template_details.template_data_id +_ma_template_details.target_asym_id +_ma_template_details.template_label_asym_id +_ma_template_details.template_label_entity_id +_ma_template_details.template_model_num +_ma_template_details.template_auth_asym_id +1 2 'reference database' non-polymer 42 99 X B 9 1 B +2 1 'reference database' polymer 42 98 . A . 1 A +# +# +loop_ +_ma_template_poly.template_id +_ma_template_poly.seq_one_letter_code +_ma_template_poly.seq_one_letter_code_can +1 ACGT ACGT +# +# +loop_ +_ma_template_non_poly.template_id +_ma_template_non_poly.comp_id +_ma_template_non_poly.details +2 HEM heme +# +""") + + def test_custom_template_unused(self): + """Test AlignmentDumper with custom template""" + system = modelcif.System() + e1 = ihm.Entity('ACGT') + t1 = modelcif.CustomTemplate( + e1, asym_id="A", model_num=1, name="test template", + transformation=modelcif.Transformation.identity(), + entity_id=9, details='my custom template') + t1.atoms.append(modelcif.TemplateAtom( + seq_id=1, atom_id='CA', + type_symbol='C', x=0.0, y=1.0, z=2.0, occupancy=0.5, + biso=2.0, charge=1.0, auth_seq_id=42, auth_comp_id='XXX', + auth_atom_id='X')) + t1.atoms.append(modelcif.TemplateAtom( + seq_id=2, atom_id='OXT', + type_symbol='O', x=1.0, y=2.0, z=3.0)) + t1._id = 1 + t1._data_id = 99 + system.templates.append(t1) + dumper = modelcif.dumper._AlignmentDumper() + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_template_details.ordinal_id +_ma_template_details.template_id +_ma_template_details.template_origin +_ma_template_details.template_entity_type +_ma_template_details.template_trans_matrix_id +_ma_template_details.template_data_id +_ma_template_details.target_asym_id +_ma_template_details.template_label_asym_id +_ma_template_details.template_label_entity_id +_ma_template_details.template_model_num +_ma_template_details.template_auth_asym_id +1 1 customized polymer 42 99 . A 9 1 A +# +# +loop_ +_ma_template_poly.template_id +_ma_template_poly.seq_one_letter_code +_ma_template_poly.seq_one_letter_code_can +1 ACGT ACGT +# +# +loop_ +_ma_template_customized.template_id +_ma_template_customized.details +1 'my custom template' +# +# +loop_ +_ma_template_coord.template_id +_ma_template_coord.group_PDB +_ma_template_coord.ordinal_id +_ma_template_coord.type_symbol +_ma_template_coord.label_atom_id +_ma_template_coord.label_comp_id +_ma_template_coord.label_seq_id +_ma_template_coord.label_asym_id +_ma_template_coord.auth_seq_id +_ma_template_coord.auth_asym_id +_ma_template_coord.auth_atom_id +_ma_template_coord.auth_comp_id +_ma_template_coord.Cartn_x +_ma_template_coord.Cartn_y +_ma_template_coord.Cartn_z +_ma_template_coord.occupancy +_ma_template_coord.label_entity_id +_ma_template_coord.B_iso_or_equiv +_ma_template_coord.formal_charge +1 ATOM 1 C CA ALA 1 A 42 A X XXX 0 1.000 2.000 0.500 9 2.000 1.000 +1 ATOM 2 O OXT CYS 2 A . A . . 1.000 2.000 3.000 . 9 . . +# +""") + + def test_template_transform_dumper(self): + """Test TemplateTransformDumper""" + system = modelcif.System() + tr1 = modelcif.Transformation( + rot_matrix=[[-0.64, 0.09, 0.77], [0.76, -0.12, 0.64], + [0.15, 0.99, 0.01]], + tr_vector=[1., 2., 3.]) + system.template_transformations.append(tr1) + dumper = modelcif.dumper._TemplateTransformDumper() + dumper.finalize(system) + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_template_trans_matrix.id +_ma_template_trans_matrix.rot_matrix[1][1] +_ma_template_trans_matrix.rot_matrix[2][1] +_ma_template_trans_matrix.rot_matrix[3][1] +_ma_template_trans_matrix.rot_matrix[1][2] +_ma_template_trans_matrix.rot_matrix[2][2] +_ma_template_trans_matrix.rot_matrix[3][2] +_ma_template_trans_matrix.rot_matrix[1][3] +_ma_template_trans_matrix.rot_matrix[2][3] +_ma_template_trans_matrix.rot_matrix[3][3] +_ma_template_trans_matrix.tr_vector[1] +_ma_template_trans_matrix.tr_vector[2] +_ma_template_trans_matrix.tr_vector[3] +1 -0.640000 0.760000 0.150000 0.090000 -0.120000 0.990000 0.770000 0.640000 +0.010000 1.000 2.000 3.000 +# +""") + + def test_target_entity_dumper(self): + """Test TargetEntityDumper""" + system = modelcif.System() + e1 = modelcif.Entity("D") + e1._id = 42 + e1._data_id = 99 + system.entities.append(e1) + + a1 = modelcif.AsymUnit(e1, 'foo') + a1._id = 'X' + system.asym_units.append(a1) + + dumper = modelcif.dumper._TargetEntityDumper() + dumper.finalize(system) + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_target_entity.entity_id +_ma_target_entity.data_id +_ma_target_entity.origin +42 99 designed +# +# +loop_ +_ma_target_entity_instance.asym_id +_ma_target_entity_instance.entity_id +_ma_target_entity_instance.details +X 42 foo +# +""") + + def test_associated_dumper(self): + """Test AssociatedDumper""" + system = modelcif.System() + e = modelcif.Entity('M') + # File in a repository + f1 = modelcif.associated.File(path='foo.txt', details='test file') + # File in an archive + f2 = modelcif.associated.File(path='bar.txt', details='test file2') + zf = modelcif.associated.ZipFile(path='t.zip', files=[f2]) + # Local file with data + f3 = modelcif.associated.File(path='baz.txt', details='test file3', + data=e) + f4 = modelcif.associated.QAMetricsFile(path='baz.txt', + details='test file4') + r = modelcif.associated.Repository(url_root='https://example.com', + files=[f1, zf]) + r2 = modelcif.associated.Repository(url_root=None, files=[f3, f4]) + system.repositories.extend((r, r2)) + + system._before_write() # populate data + dumper = modelcif.dumper._DataDumper() + dumper.finalize(system) # Assign Data IDs + + dumper = modelcif.dumper._AssociatedDumper() + dumper.finalize(system) + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_entry_associated_files.id +_ma_entry_associated_files.entry_id +_ma_entry_associated_files.file_url +_ma_entry_associated_files.file_type +_ma_entry_associated_files.file_format +_ma_entry_associated_files.file_content +_ma_entry_associated_files.details +_ma_entry_associated_files.data_id +1 model https://example.com/foo.txt file other other 'test file' . +2 model https://example.com/t.zip archive zip 'archive with multiple files' . . +3 model baz.txt file other other 'test file3' 1 +4 model baz.txt file cif 'QA metrics' 'test file4' . +# +# +loop_ +_ma_associated_archive_file_details.id +_ma_associated_archive_file_details.archive_file_id +_ma_associated_archive_file_details.file_path +_ma_associated_archive_file_details.file_format +_ma_associated_archive_file_details.file_content +_ma_associated_archive_file_details.description +_ma_associated_archive_file_details.data_id +1 2 bar.txt other other 'test file2' . +# +""") + + # Should be an error to put a zip file inside another zip + zf2 = modelcif.associated.ZipFile(path='test2.zip', files=[]) + zf.files.append(zf2) + self.assertRaises(ValueError, dumper.finalize, system) + + def test_write_associated(self): + """Test write() function with associated files""" + s = modelcif.System(id='system1') + + f = modelcif.associated.CIFFile( + path='test_write_associated.cif', + categories=['struct', '_AUDIT_CONFORM'], + entry_details='test details', entry_id='testcif') + f2 = modelcif.associated.File(path='foo.txt', details='test file') + r = modelcif.associated.Repository(url_root='https://example.com', + files=[f, f2]) + s.repositories.append(r) + + fh = StringIO() + modelcif.dumper.write(fh, [s]) + main_file = fh.getvalue() + with open('test_write_associated.cif') as fh: + assoc_file = fh.read() + os.unlink('test_write_associated.cif') + # struct and audit_conform categories should be in associated file, + # not the main file + self.assertIn('_struct.title', assoc_file) + self.assertNotIn('_struct.title', main_file) + self.assertIn('_audit_conform.dict_name', assoc_file) + self.assertNotIn('_audit_conform.dict_name', main_file) + + def test_write_associated_in_zip(self): + """Test write() function with associated files in a ZipFile""" + s = modelcif.System(id='system1') + + f = modelcif.associated.CIFFile( + path='test_write_associated_in_zip.cif', + categories=['struct', '_AUDIT_CONFORM'], + entry_details='test details', entry_id='testcif') + zf = modelcif.associated.ZipFile(path='t.zip', files=[f]) + r = modelcif.associated.Repository(url_root='https://example.com', + files=[zf]) + s.repositories.append(r) + + fh = StringIO() + modelcif.dumper.write(fh, [s]) + main_file = fh.getvalue() + with open('test_write_associated_in_zip.cif') as fh: + assoc_file = fh.read() + os.unlink('test_write_associated_in_zip.cif') + # struct and audit_conform categories should be in associated file, + # not the main file + self.assertIn('_struct.title', assoc_file) + self.assertNotIn('_struct.title', main_file) + self.assertIn('_audit_conform.dict_name', assoc_file) + self.assertNotIn('_audit_conform.dict_name', main_file) + + def test_write_associated_copy(self): + """Test write() function with associated files, copy_categories""" + s = modelcif.System(id='system1') + + e1 = modelcif.Entity('ACGT') + e1._id = 42 + s.entities.append(e1) + + f = modelcif.associated.CIFFile( + path='/not/exist/foo.cif', + local_path='test_write_associated_copy.cif', + categories=['struct'], copy_categories=['entity', 'audit_conform'], + entry_details='test details', entry_id='testcif') + r = modelcif.associated.Repository(url_root='https://example.com', + files=[f]) + s.repositories.append(r) + + fh = StringIO() + modelcif.dumper.write(fh, [s]) + main_file = fh.getvalue() + with open('test_write_associated_copy.cif') as fh: + assoc_file = fh.read() + os.unlink('test_write_associated_copy.cif') + # struct category should be in associated file, not the main file + self.assertIn('_struct.title', assoc_file) + self.assertNotIn('_struct.title', main_file) + # entity and audit conform categories should be in *both* files + self.assertIn('_entity.type', assoc_file) + self.assertIn('_entity.type', main_file) + self.assertIn('_audit_conform.dict_name', assoc_file) + self.assertIn('_audit_conform.dict_name', main_file) + + def test_write_associated_none(self): + """Test write() function with associated files, no categories""" + s = modelcif.System(id='system1') + + f = modelcif.associated.CIFFile( + path='test_write_associated_none.cif') + r = modelcif.associated.Repository(url_root='https://example.com', + files=[f]) + s.repositories.append(r) + + fh = StringIO() + modelcif.dumper.write(fh, [s]) + main_file = fh.getvalue() + self.assertIn('_struct.title', main_file) + self.assertIn('_audit_conform.dict_name', main_file) + + @unittest.skipIf(msgpack is None, "needs Python 3 and msgpack") + def test_write_associated_binary(self): + """Test write() function with associated binary files""" + s = modelcif.System(id='system1') + + f = modelcif.associated.CIFFile( + path='test_write_associated_binary.bcif', + categories=['struct', '_AUDIT_CONFORM'], + entry_details='test details', entry_id='testcif', binary=True) + r = modelcif.associated.Repository(url_root='https://example.com', + files=[f]) + s.repositories.append(r) + + fh = StringIO() + modelcif.dumper.write(fh, [s]) + main_file = fh.getvalue() + with open('test_write_associated_binary.bcif', 'rb') as fh: + assoc_file = msgpack.unpack(fh, raw=False) + os.unlink('test_write_associated_binary.bcif') + assoc_cats = frozenset( + x['name'] for x in assoc_file['dataBlocks'][0]['categories']) + + self.assertIn('_struct', assoc_cats) + self.assertNotIn('_struct.title', main_file) + self.assertIn('_audit_conform', assoc_cats) + self.assertNotIn('_audit_conform.dict_name', main_file) + + def test_system_writer(self): + """Test _SystemWriter utility class""" + class BaseWriter: + def flush(self): + return 'flush called' + + def write_comment(self, comment): + return 'write comment ' + comment + + s = modelcif.dumper._SystemWriter(BaseWriter(), {}, {}) + # These methods are not usually called in ordinary operation, but + # we should provide them for Writer compatibility + self.assertEqual(s.flush(), 'flush called') + self.assertEqual(s.write_comment('foo'), 'write comment foo') + + def test_entity_non_poly_dumper(self): + """Test EntityNonPolyDumper""" + system = modelcif.System() + # Polymeric entity (ignored) + e1 = modelcif.Entity('ACGT') + e1._id = 1 + e2 = ihm.Entity([ihm.NonPolymerChemComp('HEM')], description='heme') + e2._id = 2 + e3 = ihm.Entity([ihm.NonPolymerChemComp('ZN')], description='zinc') + e3._id = 3 + system.entities.extend((e1, e2, e3)) + + t2 = modelcif.Template(e2, 'A', model_num=1, transformation=None) + a1 = modelcif.AsymUnit(e1, 'foo') + a2 = modelcif.NonPolymerFromTemplate(template=t2, explicit=True) + system.asym_units.extend((a1, a2)) + + dumper = modelcif.dumper._EntityNonPolyDumper() + dumper.finalize(system) + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_pdbx_entity_nonpoly.entity_id +_pdbx_entity_nonpoly.name +_pdbx_entity_nonpoly.comp_id +_pdbx_entity_nonpoly.ma_model_mode +2 heme HEM explicit +3 zinc ZN . +# +""") + + def test_chem_comp_dumper(self): + """Test ChemCompDumper""" + system = modelcif.System() + + # ChemComp without ccd + c1 = ihm.NonPolymerChemComp('C1', name='C1') + + # ChemComp using core CCD + c2 = ihm.NonPolymerChemComp('C2', name='C2') + c2.ccd = 'core' + + # ChemComp using MA CCD + c3 = ihm.NonPolymerChemComp('C3', name='C3') + c3.ccd = 'ma' + + # ChemComp with descriptors (local) + c4 = ihm.NonPolymerChemComp('C4', name='C4') + c4.ccd = None + c4.descriptors = [modelcif.descriptor.IUPACName("foo")] + + e1 = modelcif.Entity(['A', 'C', c1, c2, c3, c4]) + system.entities.append(e1) + + e2 = modelcif.Entity('GT') + t2 = modelcif.Template(e2, 'A', model_num=1, transformation=None) + system.templates.append(t2) + + dumper = modelcif.dumper._ChemCompDumper() + out = _get_dumper_output(dumper, system) + # chem_comp should include both system.entities and system.templates + self.assertEqual(out, """# +loop_ +_chem_comp.id +_chem_comp.type +_chem_comp.name +_chem_comp.formula +_chem_comp.formula_weight +_chem_comp.ma_provenance +ALA 'L-peptide linking' ALANINE 'C3 H7 N O2' 89.094 'CCD Core' +C1 non-polymer C1 . . 'CCD Core' +C2 non-polymer C2 . . 'CCD Core' +C3 non-polymer C3 . . 'CCD MA' +C4 non-polymer C4 . . 'CCD local' +CYS 'L-peptide linking' CYSTEINE 'C3 H7 N O2 S' 121.154 'CCD Core' +GLY 'peptide linking' GLYCINE 'C2 H5 N O2' 75.067 'CCD Core' +THR 'L-peptide linking' THREONINE 'C4 H9 N O3' 119.120 'CCD Core' +# +""") + + def test_chem_comp_dumper_bad_ccd(self): + """Test ChemCompDumper with invalid value for ccd""" + system = modelcif.System() + + c1 = ihm.NonPolymerChemComp('C1', name='C1') + c1.ccd = 'garbage' + + e1 = modelcif.Entity([c1]) + system.entities.append(e1) + + dumper = modelcif.dumper._ChemCompDumper() + self.assertRaises(KeyError, _get_dumper_output, dumper, system) + # Should work with checks disabled + _ = _get_dumper_output(dumper, system, check=False) + + def test_chem_comp_descriptor_dumper(self): + """Test ChemCompDescriptorDumper""" + class MockObject: + pass + + system = modelcif.System() + + # Old-style ChemComp without descriptors + c1 = ihm.NonPolymerChemComp('C1', name='C1name') + if hasattr(c1, 'descriptor'): + del c1.descriptors + + c2 = ihm.NonPolymerChemComp('C2', name='C2name') + c2.ccd = None + soft = MockObject() + soft._id = 42 + c2.descriptors = [modelcif.descriptor.IUPACName("foo"), + modelcif.descriptor.PubChemCID( + "bar", details="test details", software=soft)] + + e1 = modelcif.Entity(['A', 'C', c1, c2]) + system.entities.append(e1) + + dumper = modelcif.dumper._ChemCompDescriptorDumper() + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_ma_chem_comp_descriptor.ordinal_id +_ma_chem_comp_descriptor.chem_comp_id +_ma_chem_comp_descriptor.chem_comp_name +_ma_chem_comp_descriptor.type +_ma_chem_comp_descriptor.value +_ma_chem_comp_descriptor.details +_ma_chem_comp_descriptor.software_id +1 C2 C2name 'IUPAC Name' foo . . +2 C2 C2name 'PubChem CID' bar 'test details' 42 +# +""") + + def test_struct_ref(self): + """Test StructRefDumper""" + system = ihm.System() + lpep = ihm.LPeptideAlphabet() + sd = modelcif.reference.SeqDif( + seq_id=2, db_monomer=lpep['W'], + monomer=lpep['S'], details='Test mutation') + # Test non-mandatory db_monomer + sd2 = modelcif.reference.SeqDif( + seq_id=3, db_monomer=None, + monomer=lpep['P'], details='Test mutation') + r1 = modelcif.reference.UniProt( + code='NUP84_YEAST', accession='P52891', sequence='MELWPTYQT', + details='test sequence') + r1.alignments.append(modelcif.reference.Alignment( + db_begin=3, seq_dif=[sd, sd2])) + r2 = modelcif.reference.UniProt( + code='testcode', accession='testacc', sequence='MELSPTYQT', + details='test2') + r2.alignments.append(modelcif.reference.Alignment( + db_begin=4, db_end=5, entity_begin=2, entity_end=3)) + r2.alignments.append(modelcif.reference.Alignment( + db_begin=9, db_end=9, entity_begin=4, entity_end=4)) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + r3 = modelcif.reference.UniProt( + code='testcode2', accession='testacc2', sequence=None) + r3.alignments.append(modelcif.reference.Alignment( + db_begin=4, db_end=5, entity_begin=2, entity_end=3)) + r4 = modelcif.reference.UniProt( + code='testcode3', accession='testacc3', sequence=ihm.unknown) + r4.alignments.append(modelcif.reference.Alignment( + db_begin=4, db_end=5, entity_begin=2, entity_end=3)) + system.entities.append(modelcif.Entity( + 'LSPT', references=[r1, r2, r3, r4])) + dumper = ihm.dumper._EntityDumper() + dumper.finalize(system) # Assign entity IDs + + dumper = ihm.dumper._StructRefDumper() + dumper.finalize(system) # Assign IDs + out = _get_dumper_output(dumper, system) + self.assertEqual(out, """# +loop_ +_struct_ref.id +_struct_ref.entity_id +_struct_ref.db_name +_struct_ref.db_code +_struct_ref.pdbx_db_accession +_struct_ref.pdbx_align_begin +_struct_ref.pdbx_seq_one_letter_code +_struct_ref.details +1 1 UNP NUP84_YEAST P52891 3 LWPTYQT 'test sequence' +2 1 UNP testcode testacc 4 SPTYQT test2 +3 1 UNP testcode2 testacc2 4 . . +4 1 UNP testcode3 testacc3 4 ? . +# +# +loop_ +_struct_ref_seq.align_id +_struct_ref_seq.ref_id +_struct_ref_seq.seq_align_beg +_struct_ref_seq.seq_align_end +_struct_ref_seq.db_align_beg +_struct_ref_seq.db_align_end +1 1 1 4 3 6 +2 2 2 3 4 5 +3 2 4 4 9 9 +4 3 2 3 4 5 +5 4 2 3 4 5 +# +# +loop_ +_struct_ref_seq_dif.pdbx_ordinal +_struct_ref_seq_dif.align_id +_struct_ref_seq_dif.seq_num +_struct_ref_seq_dif.db_mon_id +_struct_ref_seq_dif.mon_id +_struct_ref_seq_dif.details +1 1 2 TRP SER 'Test mutation' +2 1 3 ? PRO 'Test mutation' +# +""") + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_edit.py b/test/test_edit.py new file mode 100644 index 0000000..148eefc --- /dev/null +++ b/test/test_edit.py @@ -0,0 +1,150 @@ +import utils +import os +import unittest +import io + +TOPDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +utils.set_search_paths(TOPDIR) +import modelcif.reader +import modelcif.dumper + + +class Tests(unittest.TestCase): + def test_model_with_assembly(self): + """Test read of Model with Assembly followed by write""" + sin = io.StringIO(""" +loop_ +_entity.id +_entity.type +_entity.pdbx_description +_entity.pdbx_number_of_molecules +_entity.formula_weight +_entity.details +1 polymer Nup84 2 100.0 . +# +loop_ +_entity_poly_seq.entity_id +_entity_poly_seq.num +_entity_poly_seq.mon_id +_entity_poly_seq.hetero +1 1 ALA . +1 2 CYS . +# +loop_ +_struct_asym.id +_struct_asym.entity_id +_struct_asym.details +A 1 'Model subunit A' +# +loop_ +_ma_struct_assembly.ordinal_id +_ma_struct_assembly.assembly_id +_ma_struct_assembly.entity_id +_ma_struct_assembly.asym_id +_ma_struct_assembly.seq_id_begin +_ma_struct_assembly.seq_id_end +1 1 1 A 1 2 +# +loop_ +_ma_model_list.ordinal_id +_ma_model_list.model_id +_ma_model_list.model_group_id +_ma_model_list.model_name +_ma_model_list.model_group_name +_ma_model_list.assembly_id +_ma_model_list.data_id +_ma_model_list.model_type +_ma_model_list.model_type_other_details +1 1 1 'Best scoring model' 'All models' 1 4 'Homology model' . +# +loop_ +_atom_site.group_PDB +_atom_site.id +_atom_site.type_symbol +_atom_site.label_atom_id +_atom_site.label_alt_id +_atom_site.label_comp_id +_atom_site.label_seq_id +_atom_site.auth_seq_id +_atom_site.pdbx_PDB_ins_code +_atom_site.label_asym_id +_atom_site.Cartn_x +_atom_site.Cartn_y +_atom_site.Cartn_z +_atom_site.occupancy +_atom_site.label_entity_id +_atom_site.auth_asym_id +_atom_site.B_iso_or_equiv +_atom_site.pdbx_PDB_model_num +ATOM 1 C CA . ALA 1 1 ? A 1.000 2.000 3.000 . 1 A . 1 +""") + s, = modelcif.reader.read(sin) + sout = io.StringIO() + modelcif.dumper.write(sout, [s]) + + def test_model_without_assembly(self): + """Test read of Model without Assembly followed by write""" + sin = io.StringIO(""" +loop_ +_entity.id +_entity.type +_entity.pdbx_description +_entity.pdbx_number_of_molecules +_entity.formula_weight +_entity.details +1 polymer Nup84 2 100.0 . +# +loop_ +_entity_poly_seq.entity_id +_entity_poly_seq.num +_entity_poly_seq.mon_id +_entity_poly_seq.hetero +1 1 ALA . +1 2 CYS . +# +loop_ +_struct_asym.id +_struct_asym.entity_id +_struct_asym.details +A 1 'Model subunit A' +# +loop_ +_ma_model_list.ordinal_id +_ma_model_list.model_id +_ma_model_list.model_group_id +_ma_model_list.model_name +_ma_model_list.model_group_name +_ma_model_list.assembly_id +_ma_model_list.data_id +_ma_model_list.model_type +_ma_model_list.model_type_other_details +1 1 1 'Best scoring model' 'All models' . 4 'Homology model' . +# +loop_ +_atom_site.group_PDB +_atom_site.id +_atom_site.type_symbol +_atom_site.label_atom_id +_atom_site.label_alt_id +_atom_site.label_comp_id +_atom_site.label_seq_id +_atom_site.auth_seq_id +_atom_site.pdbx_PDB_ins_code +_atom_site.label_asym_id +_atom_site.Cartn_x +_atom_site.Cartn_y +_atom_site.Cartn_z +_atom_site.occupancy +_atom_site.label_entity_id +_atom_site.auth_asym_id +_atom_site.B_iso_or_equiv +_atom_site.pdbx_PDB_model_num +ATOM 1 C CA . ALA 1 1 ? A 1.000 2.000 3.000 . 1 A . 1 +""") + s, = modelcif.reader.read(sin) + sout = io.StringIO() + modelcif.dumper.write(sout, [s]) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_examples.py b/test/test_examples.py new file mode 100644 index 0000000..53e75a3 --- /dev/null +++ b/test/test_examples.py @@ -0,0 +1,103 @@ +import utils +import os +import unittest +import sys +import shutil +import subprocess +try: + import msgpack +except ImportError: + msgpack = None + +TOPDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +utils.set_search_paths(TOPDIR) + +import modelcif.reader + + +def get_example_dir(): + return os.path.join(TOPDIR, "examples") + + +def get_example_path(fname): + return os.path.join(get_example_dir(), fname) + + +class Tests(unittest.TestCase): + + @unittest.skipIf('APPVEYOR' in os.environ, + "AppVeyor environments have old SSL certs") + @unittest.skipIf('GITHUB_ACTIONS' in os.environ, + "Example is slow and fails when ModBase is down") + def test_validate_modbase_example(self): + """Test validate_modbase example""" + subprocess.check_call([sys.executable, + get_example_path("validate_modbase.py")]) + + @unittest.skipIf('APPVEYOR' in os.environ, + "AppVeyor environments have old SSL certs") + @unittest.skipIf('GITHUB_ACTIONS' in os.environ, + "Example is slow and fails when ModBase is down") + def test_validate_mmcif_example(self): + """Test validate_mmcif example""" + with utils.temporary_directory() as tmpdir: + subprocess.check_call([sys.executable, + get_example_path("validate_mmcif.py"), + get_example_path("input/ligands.cif")], + cwd=tmpdir) + + def test_associated_example(self): + """Test associated example""" + subprocess.check_call([sys.executable, + get_example_path("associated.py")]) + + def test_mkmodbase_example(self): + """Test mkmodbase example""" + with utils.temporary_directory() as tmpdir: + subprocess.check_call([sys.executable, + get_example_path("mkmodbase.py")], + cwd=tmpdir) + + # Make sure that a complete output file was produced and that we + # can read it + with open(os.path.join(tmpdir, 'output.cif')) as fh: + contents = fh.readlines() + self.assertEqual(len(contents), 451) + with open(os.path.join(tmpdir, 'output.cif')) as fh: + s, = modelcif.reader.read(fh) + + def test_ligands_example(self): + """Test ligands example""" + with utils.temporary_directory() as tmpdir: + subprocess.check_call([sys.executable, + get_example_path("ligands.py")], + cwd=tmpdir) + + # Make sure that a complete output file was produced and that we + # can read it + with open(os.path.join(tmpdir, 'output.cif')) as fh: + contents = fh.readlines() + self.assertEqual(len(contents), 334) + with open(os.path.join(tmpdir, 'output.cif')) as fh: + s, = modelcif.reader.read(fh) + + @unittest.skipIf(msgpack is None, "BinaryCIF needs msgpack") + def test_convert_bcif_example(self): + """Test convert_bcif example""" + with utils.temporary_directory() as tmpdir: + from_input = get_example_path("input") + to_input = os.path.join(tmpdir, 'input') + os.mkdir(to_input) + shutil.copy(os.path.join(from_input, "ligands.cif"), to_input) + subprocess.check_call([sys.executable, + get_example_path("convert_bcif.py")], + cwd=tmpdir) + + # Make sure that a complete output file was produced and that we + # can read it + with open(os.path.join(tmpdir, 'ligands.bcif'), 'rb') as fh: + s, = modelcif.reader.read(fh, format='BCIF') + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_main.py b/test/test_main.py new file mode 100644 index 0000000..747d46a --- /dev/null +++ b/test/test_main.py @@ -0,0 +1,230 @@ +import os +import unittest +import utils + +TOPDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +utils.set_search_paths(TOPDIR) +import modelcif +import modelcif.protocol +import modelcif.descriptor +import modelcif.associated +import ihm + + +class Tests(unittest.TestCase): + def test_all_data(self): + """Test _all_data() method""" + s = modelcif.System() + e1 = modelcif.Entity("D") + e2 = modelcif.Entity("M") + s.entities.extend((e1, e2)) + + e3 = modelcif.Entity("A") + s.data.extend((e1, e3)) + + s.data_groups.append('something not a group') + e4 = modelcif.Entity("M") + s.data_groups.append(modelcif.data.DataGroup([e1, e4])) + + e5 = modelcif.Entity("M") + f = modelcif.associated.File(path='foo', details='bar', data=e5) + s.repositories.append(modelcif.associated.Repository('/', [f])) + + d = s._all_data() + # List may contain duplicates + self.assertEqual(list(d), [e1, e3, e1, e2, e1, e4, e5]) + + def test_all_asym_units(self): + """Test _all_asym_units() method""" + s = modelcif.System() + e1 = modelcif.Entity("DDDD") + e2 = modelcif.Entity("MMMM") + a1 = modelcif.AsymUnit(e1) + a2 = modelcif.AsymUnit(e2) + s.asym_units.append(a1) + + asmb = modelcif.Assembly((a1, a2(1, 2))) + s.assemblies.append(asmb) + + asyms = s._all_asym_units() + # List may contain duplicates and should be all AsymUnit, + # not AsymUnitRange + self.assertEqual(list(asyms), [a1, a1, a2]) + + def test_all_entities(self): + """Test _all_entities() method""" + s = modelcif.System() + e1 = modelcif.Entity("DDDD") + e2 = modelcif.Entity("MMMM") + s.entities.append(e1) + + a1 = modelcif.AsymUnit(e1) + s.asym_units.append(a1) + + t2 = modelcif.Template(e2, asym_id='A', model_num=1, + transformation=None) + s.templates.append(t2) + + es = s._all_entities() + # List may contain duplicates, but does not contain template entity e2 + self.assertEqual(list(es), [e1, e1]) + + def test_all_data_groups(self): + """Test _all_data_groups() method""" + s = modelcif.System() + e1 = modelcif.Entity("A") + s.data_groups.append(e1) + e2 = modelcif.Entity("C") + + p = modelcif.protocol.Protocol() + p.steps.append(modelcif.protocol.ModelingStep( + input_data=e1, output_data=e2)) + p.steps.append(modelcif.protocol.ModelingStep( + input_data=None, output_data=None)) + s.protocols.append(p) + + d = s._all_data_groups() + self.assertEqual(list(d), [e1, e1, e2]) + + def test_all_template_transformations(self): + """Test _all_template_transformations() method""" + s = modelcif.System() + tr1 = 'tr1' + tr2 = 'tr2' + s.template_transformations.extend((tr1, tr2)) + + template = modelcif.Template('mockentity', asym_id="A", model_num=1, + name="test template", + transformation=tr1) + s.templates.append(template) + + tt = s._all_template_transformations() + # List may contain duplicates + self.assertEqual(list(tt), [tr1, tr2, tr1]) + + def test_transformation(self): + """Test Transformation class""" + _ = modelcif.Transformation([[1, 0, 0], [0, 1, 0], [0, 0, 1]], + [1, 2, 3]) + + def test_identity_transformation(self): + """Test identity transformation""" + t = modelcif.Transformation.identity() + for i in range(3): + self.assertAlmostEqual(t.tr_vector[i], 0., delta=0.1) + for j in range(3): + self.assertAlmostEqual(t.rot_matrix[i][j], + 1. if i == j else 0., delta=0.1) + + # Should always get the same object + t2 = modelcif.Transformation.identity() + self.assertIs(t, t2) + + def test_all_software_groups(self): + """Test _all_software_groups() method""" + s = modelcif.System() + sg1 = 'sg1' + sg2 = 'sg2' + s.software_groups.extend((sg1, sg2)) + + p = modelcif.protocol.Protocol() + p.steps.append(modelcif.protocol.ModelingStep( + input_data=None, output_data=None, software=sg1)) + s.protocols.append(p) + + allsg = s._all_software_groups() + # List may contain duplicates + self.assertEqual(list(allsg), [sg1, sg2, sg1]) + + def test_all_ref_software(self): + """Test _all_ref_software() method""" + s1 = modelcif.Software( + name='foo', version='1.0', + classification='1', description='2', location='3') + s2 = modelcif.Software( + name='foo', version='2.0', + classification='4', description='5', location='6') + p = modelcif.SoftwareParameter(name='foo', value='bar') + s2param = modelcif.SoftwareWithParameters(s2, [p]) + s = modelcif.System() + s.software_groups.append(modelcif.SoftwareGroup((s1, s2param))) + s.software_groups.append(s1) + + e1 = modelcif.Entity("DDDD") + t1 = modelcif.Template(e1, asym_id='A', model_num=1, + transformation=None) + s.templates.append(t1) + + # Old-style ChemComp without descriptors + c1 = ihm.NonPolymerChemComp('C1', name='C1') + if hasattr(c1, 'descriptors'): + del c1.descriptors + + # ChemComp with descriptor without software + c2 = ihm.NonPolymerChemComp('C2', name='C2') + c2.descriptors = [modelcif.descriptor.IUPACName('foo')] + + # ChemComp with descriptor with software + c3 = ihm.NonPolymerChemComp('C3', name='C3') + s3 = modelcif.Software( + name='foo', version='2.0', + classification='4', description='5', location='6') + c3.descriptors = [modelcif.descriptor.IUPACName('foo', software=s3)] + + e2 = modelcif.Entity([c1, c2, c3]) + s.entities.append(e2) + + alls = s._all_ref_software() + # List may contain duplicates + self.assertEqual(list(alls), [s1, s2, s1, s3]) + + def test_software_parameter(self): + """Test SoftwareParameter class""" + p = modelcif.SoftwareParameter(name='foo', value=42) + self.assertEqual(p.name, 'foo') + self.assertEqual(p.value, 42) + self.assertIsNone(p.description) + _ = repr(p) + + def test_software_with_parameters(self): + """Test SoftwareWithParameters class""" + s = modelcif.Software( + name='foo', version='1.0', + classification='1', description='2', location='3') + p = modelcif.SoftwareParameter(name='foo', value=42) + swp = modelcif.SoftwareWithParameters(software=s, parameters=[p]) + self.assertEqual(swp.software.name, 'foo') + self.assertEqual(swp.parameters, [p]) + self.assertEqual(swp.name, 'foo') + self.assertEqual(swp.classification, '1') + self.assertEqual(swp.description, '2') + self.assertEqual(swp.location, '3') + self.assertEqual(swp.type, 'program') + self.assertEqual(swp.version, '1.0') + self.assertIsNone(swp.citation) + + def test_template(self): + """Test Template class""" + e1 = modelcif.Entity("DDDD") + t1 = modelcif.Template(e1, asym_id='A', model_num=1, + transformation=None) + self.assertEqual(t1.seq_id_range, (1, 4)) + self.assertEqual(t1.template, t1) + + def test_software_group_parameters(self): + """Test old-style SoftwareGroup construction with parameters""" + s = modelcif.Software( + name='foo', version='1.0', + classification='1', description='2', location='3') + p = modelcif.SoftwareParameter(name='foo', value=42) + self.assertWarns(UserWarning, + modelcif.SoftwareGroup, [s], parameters=[p]) + + def test_feature(self): + """Test Feature base class""" + f = modelcif.Feature() + self.assertIs(f._get_entity_type(), ihm.unknown) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_make_mmcif.py b/test/test_make_mmcif.py new file mode 100644 index 0000000..8ce837a --- /dev/null +++ b/test/test_make_mmcif.py @@ -0,0 +1,97 @@ +import utils +import os +import sys +import unittest +import subprocess + +TOPDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +utils.set_search_paths(TOPDIR) +import modelcif.reader +import modelcif.util.make_mmcif # Script should also be importable + + +MAKE_MMCIF = os.path.join(TOPDIR, 'modelcif', 'util', 'make_mmcif.py') + + +class Tests(unittest.TestCase): + def test_simple(self): + """Simple test of make_mmcif utility script""" + incif = utils.get_input_file_name(TOPDIR, 'struct_only.cif') + subprocess.check_call([sys.executable, MAKE_MMCIF, incif]) + with open('output.cif') as fh: + s, = modelcif.reader.read(fh) + self.assertEqual(s.title, + 'Architecture of Pol II(G) and molecular mechanism ' + 'of transcription regulation by Gdown1') + os.unlink('output.cif') + + def test_non_default_output(self): + """Simple test of make_mmcif with non-default output name""" + incif = utils.get_input_file_name(TOPDIR, 'struct_only.cif') + subprocess.check_call([sys.executable, MAKE_MMCIF, incif, + 'non-default-output.cif']) + with open('non-default-output.cif') as fh: + s, = modelcif.reader.read(fh) + self.assertEqual(s.title, + 'Architecture of Pol II(G) and molecular mechanism ' + 'of transcription regulation by Gdown1') + os.unlink('non-default-output.cif') + + def test_no_title(self): + """Check that make_mmcif adds missing title""" + incif = utils.get_input_file_name(TOPDIR, 'no_title.cif') + subprocess.check_call([sys.executable, MAKE_MMCIF, incif]) + with open('output.cif') as fh: + s, = modelcif.reader.read(fh) + self.assertEqual(s.title, 'Auto-generated system') + os.unlink('output.cif') + + def test_bad_usage(self): + """Bad usage of make_mmcif utility script""" + ret = subprocess.call([sys.executable, MAKE_MMCIF]) + self.assertEqual(ret, 2) + + def test_same_file(self): + """Check that make_mmcif fails if input and output are the same""" + incif = utils.get_input_file_name(TOPDIR, 'struct_only.cif') + ret = subprocess.call([sys.executable, MAKE_MMCIF, incif, incif]) + self.assertEqual(ret, 1) + + def test_not_modeled(self): + """Check addition of not-modeled residue information""" + incif = utils.get_input_file_name(TOPDIR, 'not_modeled.cif') + subprocess.check_call([sys.executable, MAKE_MMCIF, incif]) + with open('output.cif') as fh: + contents = fh.readlines() + loop = contents.index("_pdbx_poly_seq_scheme.pdb_ins_code\n") + scheme = "".join(contents[loop - 11:loop + 11]) + # Residues 5 and 6 in chain A, and 2 in chain B, are missing from + # atom_site, so should now be missing from the scheme table. + self.assertEqual(scheme, """# +loop_ +_pdbx_poly_seq_scheme.asym_id +_pdbx_poly_seq_scheme.entity_id +_pdbx_poly_seq_scheme.seq_id +_pdbx_poly_seq_scheme.mon_id +_pdbx_poly_seq_scheme.pdb_seq_num +_pdbx_poly_seq_scheme.auth_seq_num +_pdbx_poly_seq_scheme.pdb_mon_id +_pdbx_poly_seq_scheme.auth_mon_id +_pdbx_poly_seq_scheme.pdb_strand_id +_pdbx_poly_seq_scheme.pdb_ins_code +A 1 1 VAL 2 2 VAL VAL A ? +A 1 2 GLY 3 3 GLY GLY A ? +A 1 3 GLN 4 4 GLN GLN A ? +A 1 4 GLN 5 5 GLN GLN A ? +A 1 5 TYR 5 ? ? ? A . +A 1 6 SER 6 ? ? ? A . +A 1 7 SER 8 8 SER SER A ? +B 2 1 ASP 3 3 ASP ASP B ? +B 2 2 GLU 2 ? ? ? B . +# +""") + os.unlink('output.cif') + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_model.py b/test/test_model.py new file mode 100644 index 0000000..6e9e3fd --- /dev/null +++ b/test/test_model.py @@ -0,0 +1,33 @@ +import utils +import os +import unittest + +TOPDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +utils.set_search_paths(TOPDIR) +import modelcif.model + + +class Tests(unittest.TestCase): + def test_model(self): + """Test Model classes""" + m = modelcif.model.HomologyModel([]) + self.assertEqual(m.model_type, "Homology model") + self.assertIsNone(m.other_details) + + # generic "other" model + m = modelcif.model.Model([]) + self.assertEqual(m.model_type, "Other") + self.assertIsNone(m.other_details) + + # custom "other" model + class CustomRef(modelcif.model.Model): + """foo + bar""" + + m = CustomRef([]) + self.assertEqual(m.model_type, "Other") + self.assertEqual(m.other_details, "foo") + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_qa_metric.py b/test/test_qa_metric.py new file mode 100644 index 0000000..50e70f4 --- /dev/null +++ b/test/test_qa_metric.py @@ -0,0 +1,101 @@ +import os +import unittest +import utils + +TOPDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +utils.set_search_paths(TOPDIR) +import modelcif.qa_metric + + +class Tests(unittest.TestCase): + def test_metric_types(self): + """Test MetricType subclasses""" + self.assertEqual(modelcif.qa_metric.Energy.type, "energy") + self.assertIsNone(modelcif.qa_metric.Energy.other_details) + self.assertEqual(modelcif.qa_metric.PAE.type, "PAE") + self.assertEqual(modelcif.qa_metric.ContactProbability.type, + "contact probability") + + # MetricType itself should have no other_details + class Custom1(modelcif.qa_metric.Global, + modelcif.qa_metric.MetricType): + """Custom 1""" + x = Custom1(42) + self.assertEqual(x.type, "other") + self.assertIsNone(x.other_details) + + class CustomMetricType(modelcif.qa_metric.MetricType): + """foo + bar""" + + class Custom2(modelcif.qa_metric.Global, CustomMetricType): + """Custom 2""" + x = Custom2(42) + self.assertEqual(x.type, "other") + self.assertEqual(x.other_details, "foo") + + # MetricType in the enumeration should have no "other_details" + + class EnumMetricType(modelcif.qa_metric.MetricType): + """foo""" + type = "enum" + + class Custom3(modelcif.qa_metric.Global, EnumMetricType): + """Custom 3""" + x = Custom3(42) + self.assertEqual(x.type, "enum") + self.assertIsNone(x.other_details) + + def test_global_metric(self): + """Test Global MetricMode""" + class MyScore(modelcif.qa_metric.Global, modelcif.qa_metric.Energy): + pass + + q = MyScore(42) + _ = repr(q) + + def test_local_metric(self): + """Test Local MetricMode""" + class MyScore(modelcif.qa_metric.Local, modelcif.qa_metric.Energy): + pass + + e1 = modelcif.Entity('ACGT') + asym = modelcif.AsymUnit(e1, 'foo') + q = MyScore(asym.residue(2), 42) + _ = repr(q) + + def test_local_pairwise_metric(self): + """Test LocalPairwise MetricMode""" + class MyScore(modelcif.qa_metric.LocalPairwise, + modelcif.qa_metric.Energy): + pass + + e1 = modelcif.Entity('ACGT') + asym = modelcif.AsymUnit(e1, 'foo') + q = MyScore(asym.residue(2), asym.residue(3), 42) + _ = repr(q) + + def test_feature_metric(self): + """Test Feature MetricMode""" + class MyScore(modelcif.qa_metric.Feature, modelcif.qa_metric.Energy): + pass + + e1 = modelcif.Entity('ACGT') + asym = modelcif.AsymUnit(e1, 'foo') + q = MyScore(asym.residue(2), 42) + _ = repr(q) + + def test_feature_pairwise_metric(self): + """Test FeaturePairwise MetricMode""" + class MyScore(modelcif.qa_metric.FeaturePairwise, + modelcif.qa_metric.Energy): + pass + + e1 = modelcif.Entity('ACGT') + asym = modelcif.AsymUnit(e1, 'foo') + q = MyScore(asym.residue(2), asym.residue(3), 42) + _ = repr(q) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_reader.py b/test/test_reader.py new file mode 100644 index 0000000..f971cd1 --- /dev/null +++ b/test/test_reader.py @@ -0,0 +1,2003 @@ +from datetime import date +import unittest +import utils +import os +import datetime +from io import StringIO + +TOPDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +utils.set_search_paths(TOPDIR) +import modelcif.reader +import modelcif.reference +import ihm +import ihm.reader + + +ASYM_ENTITY = """ +loop_ +_entity_poly_seq.entity_id +_entity_poly_seq.num +_entity_poly_seq.mon_id +_entity_poly_seq.hetero +1 1 MET . +1 2 CYS . +1 3 MET . +1 4 SER . +# +loop_ +_struct_asym.id +_struct_asym.entity_id +_struct_asym.details +A 1 foo +""" + + +class Tests(unittest.TestCase): + + def test_old_file_read_default(self): + """Test default handling of old files""" + cif = """ +loop_ +_audit_conform.dict_name +_audit_conform.dict_version +mmcif_pdbx.dic 5.311 +mmcif_ma.dic 0.14 +""" + s, = modelcif.reader.read(StringIO(cif)) + + def test_old_file_read_fail(self): + """Test failure reading old files""" + cif = """ +loop_ +_audit_conform.dict_name +_audit_conform.dict_version +mmcif_pdbx.dic 5.311 +mmcif_ma.dic 0.1.3 +""" + self.assertRaises(modelcif.reader.OldFileError, + modelcif.reader.read, StringIO(cif), + reject_old_file=True) + + def test_new_file_read_ok(self): + """Test success reading not-old files""" + # File read is OK if version is new enough, or version cannot be parsed + # because it is non-int or has too many elements + for ver in ('1.3.2', '1.3', '0.0.4.3', '0.0a'): + cif = """ +loop_ +_audit_conform.dict_name +_audit_conform.dict_version +mmcif_pdbx.dic 5.311 +mmcif_ma.dic %s +""" % ver + s, = modelcif.reader.read(StringIO(cif), reject_old_file=True) + + def test_software_group_handler(self): + """Test SoftwareGroupHandler and SoftwareParameterHandler""" + cif = """ +loop_ +_ma_software_parameter.parameter_id +_ma_software_parameter.group_id +_ma_software_parameter.data_type +_ma_software_parameter.name +_ma_software_parameter.value +_ma_software_parameter.description +1 1 integer foo 42 foodesc +2 1 boolean bar YES . +3 1 string baz ok . +4 1 integer-csv intlist 1,2,3,4 . +5 1 float-csv floatlist 1.5,3.8 . +# +loop_ +_ma_software_group.ordinal_id +_ma_software_group.group_id +_ma_software_group.software_id +_ma_software_group.parameter_group_id +1 1 1 . +2 1 2 . +3 2 3 . +4 2 4 1 +""" + s, = modelcif.reader.read(StringIO(cif)) + s1, s2, s3, s4 = s.software + g1, g2 = s.software_groups + self.assertEqual(len(g1), 2) + self.assertEqual(len(g2), 2) + self.assertIsInstance(g1[0], modelcif.Software) + self.assertIsInstance(g1[1], modelcif.Software) + self.assertEqual(g1[0], s1) + self.assertEqual(g1[1], s2) + + self.assertIsInstance(g2[0], modelcif.Software) + self.assertIsInstance(g2[1], modelcif.SoftwareWithParameters) + self.assertEqual(g2[0], s3) + self.assertEqual(g2[1].software, s4) + p1, p2, p3, intlist, floatlist = g2[1].parameters + self.assertEqual(p1.name, 'foo') + self.assertEqual(p1.value, 42) + self.assertEqual(p1.description, 'foodesc') + self.assertEqual(p2.name, 'bar') + self.assertTrue(p2.value) + self.assertIsNone(p2.description) + self.assertEqual(p3.name, 'baz') + self.assertEqual(p3.value, 'ok') + self.assertIsNone(p3.description) + self.assertEqual(intlist.value, [1, 2, 3, 4]) + f1, f2 = floatlist.value + self.assertAlmostEqual(f1, 1.5, delta=1e-1) + self.assertAlmostEqual(f2, 3.8, delta=1e-1) + + def test_enumeration_mapper(self): + """Test EnumerationMapper class""" + m = modelcif.reader._EnumerationMapper( + modelcif.reference, modelcif.reference.TargetReference) + # Check get of a handled enumeration value + unp = m.get('UNP', None) + self.assertIs(unp, modelcif.reference.UniProt) + self.assertEqual(unp.name, 'UNP') + self.assertIsNone(unp.other_details) + # We should get the same class each time (case insensitive) + unp2 = m.get('unp', None) + self.assertIs(unp, unp2) + # Check get of an unhandled value + miss = m.get('MIS', None) + self.assertEqual(miss.name, 'MIS') + self.assertIsNone(unp.other_details) + # We should get the same class each time (case insensitive) + miss2 = m.get('mis', None) + self.assertIs(miss, miss2) + # Check get of a custom "other" value + custom = m.get('other', "custom type 1") + self.assertEqual(custom.name, 'Other') + self.assertEqual(custom.other_details, "custom type 1") + # We should get the same class each time (case insensitive) + custom2 = m.get('Other', "CUSTOM TYPE 1") + self.assertIs(custom, custom2) + # Check get of a different custom "other" value + custom = m.get('other', "custom type 2") + self.assertEqual(custom.name, 'Other') + self.assertEqual(custom.other_details, "custom type 2") + + def test_database_handler(self): + """Test DatabaseHandler""" + cif = """ +_database_2.database_id 'PDB' +_database_2.database_code '5HVP' +""" + s, = modelcif.reader.read(StringIO(cif)) + self.assertEqual(s.database.id, 'PDB') + self.assertEqual(s.database.code, '5HVP') + + def test_target_ref_db_handler(self): + """Test TargetRefDBHander""" + cif = """ +loop_ +_ma_target_ref_db_details.target_entity_id +_ma_target_ref_db_details.db_name +_ma_target_ref_db_details.db_name_other_details +_ma_target_ref_db_details.db_code +_ma_target_ref_db_details.db_accession +_ma_target_ref_db_details.seq_db_isoform +_ma_target_ref_db_details.seq_db_align_begin +_ma_target_ref_db_details.seq_db_align_end +_ma_target_ref_db_details.ncbi_taxonomy_id +_ma_target_ref_db_details.organism_scientific +_ma_target_ref_db_details.seq_db_sequence_version_date +_ma_target_ref_db_details.seq_db_sequence_checksum +_ma_target_ref_db_details.is_primary +1 UNP . MED1_YEAST Q12321 test_iso 1 10 test_tax test_org 1996-11-01 +637FEA3E78D915BC YES +1 Other foo . . ? 1 10 . . . . NO +1 other bar . . ? 1 10 . . . . . +1 MIS baz . . ? 1 10 . . . . . +""" + s, = modelcif.reader.read(StringIO(cif)) + e, = s.entities + r1, r2, r3, r4 = e.references + self.assertIsInstance(r1, modelcif.reference.UniProt) + self.assertEqual(r1.code, 'MED1_YEAST') + self.assertEqual(r1.accession, 'Q12321') + self.assertEqual(r1.isoform, 'test_iso') + self.assertEqual(r1.align_begin, 1) + self.assertEqual(r1.align_end, 10) + self.assertEqual(r1.ncbi_taxonomy_id, 'test_tax') + self.assertEqual(r1.organism_scientific, 'test_org') + self.assertEqual(r1.sequence_version_date, date(1996, 11, 1)) + self.assertIsNone(r1.sequence) + self.assertIsNone(r1.details) + self.assertTrue(r1.is_primary) + self.assertEqual(r1.alignments, []) + self.assertEqual(r2.name, 'Other') + self.assertFalse(r2.is_primary) + self.assertEqual(r2.other_details, 'foo') + self.assertEqual(r3.name, 'Other') + self.assertEqual(r3.other_details, 'bar') + self.assertIsNone(r3.is_primary) + self.assertEqual(r4.name, 'MIS') + self.assertIsNone(r4.other_details) # should be ignored + + def test_target_ref_db_handler_with_struct_ref(self): + """Test TargetRefDBHander combined with struct_ref info""" + cif = """ +loop_ +_struct_ref.id +_struct_ref.entity_id +_struct_ref.db_name +_struct_ref.db_code +_struct_ref.pdbx_db_accession +_struct_ref.pdbx_align_begin +_struct_ref.pdbx_seq_one_letter_code +_struct_ref.details +1 1 UNP MED1_YEAST Q12321 1 DSYVETLDCC "test details" +2 1 UNP sr_only_code sr_only_acc 1 DSYVETLDPP . +# +# +loop_ +_struct_ref_seq.align_id +_struct_ref_seq.ref_id +_struct_ref_seq.seq_align_beg +_struct_ref_seq.seq_align_end +_struct_ref_seq.db_align_beg +_struct_ref_seq.db_align_end +1 1 1 10 1 10 +2 2 1 10 1 10 +# +loop_ +_ma_target_ref_db_details.target_entity_id +_ma_target_ref_db_details.db_name +_ma_target_ref_db_details.db_name_other_details +_ma_target_ref_db_details.db_code +_ma_target_ref_db_details.db_accession +_ma_target_ref_db_details.seq_db_isoform +_ma_target_ref_db_details.seq_db_align_begin +_ma_target_ref_db_details.seq_db_align_end +_ma_target_ref_db_details.ncbi_taxonomy_id +_ma_target_ref_db_details.organism_scientific +_ma_target_ref_db_details.seq_db_sequence_version_date +_ma_target_ref_db_details.seq_db_sequence_checksum +_ma_target_ref_db_details.is_primary +1 UNP . MED1_YEAST Q12321 test_iso 1 10 test_tax test_org 1996-11-01 +637FEA3E78D915BC YES +1 UNP . rd_only_code rd_only_acc rd_only_iso . . . . . . NO +""" + s, = modelcif.reader.read(StringIO(cif)) + e, = s.entities + r1, r2, r3 = e.references + # r1 should contain both target_ref_db and struct_ref info + self.assertIsInstance(r1, modelcif.reference.UniProt) + self.assertEqual(r1.code, 'MED1_YEAST') + self.assertEqual(r1.accession, 'Q12321') + self.assertEqual(r1.isoform, 'test_iso') + self.assertEqual(r1.align_begin, 1) + self.assertEqual(r1.align_end, 10) + self.assertEqual(r1.ncbi_taxonomy_id, 'test_tax') + self.assertEqual(r1.organism_scientific, 'test_org') + self.assertEqual(r1.sequence_version_date, date(1996, 11, 1)) + self.assertEqual(r1.sequence, 'DSYVETLDCC') + self.assertEqual(r1.details, "test details") + self.assertTrue(r1.is_primary) + a, = r1.alignments + self.assertEqual(a.db_begin, 1) + self.assertEqual(a.db_end, 10) + self.assertEqual(a.entity_begin, 1) + self.assertEqual(a.entity_end, 10) + # r2 should contain only target_ref_db info + self.assertIsInstance(r2, modelcif.reference.UniProt) + self.assertEqual(r2.code, 'rd_only_code') + self.assertEqual(r2.accession, 'rd_only_acc') + self.assertEqual(r2.isoform, 'rd_only_iso') + self.assertIsNone(r2.sequence) + self.assertFalse(r2.is_primary) + # r3 should contain only struct_ref info + self.assertIsInstance(r3, modelcif.reference.UniProt) + self.assertEqual(r3.code, 'sr_only_code') + self.assertEqual(r3.accession, 'sr_only_acc') + self.assertIsNone(r3.isoform) + self.assertIsNone(r3.ncbi_taxonomy_id) + self.assertEqual(r3.sequence, 'DSYVETLDPP') + self.assertIsNone(r3.is_primary) + a, = r3.alignments + self.assertEqual(a.db_begin, 1) + self.assertEqual(a.db_end, 10) + self.assertEqual(a.entity_begin, 1) + self.assertEqual(a.entity_end, 10) + + def test_transformation_handler(self): + """Test _TransformationHandler""" + cif = """ +loop_ +_ma_template_trans_matrix.id +_ma_template_trans_matrix.rot_matrix[1][1] +_ma_template_trans_matrix.rot_matrix[2][1] +_ma_template_trans_matrix.rot_matrix[3][1] +_ma_template_trans_matrix.rot_matrix[1][2] +_ma_template_trans_matrix.rot_matrix[2][2] +_ma_template_trans_matrix.rot_matrix[3][2] +_ma_template_trans_matrix.rot_matrix[1][3] +_ma_template_trans_matrix.rot_matrix[2][3] +_ma_template_trans_matrix.rot_matrix[3][3] +_ma_template_trans_matrix.tr_vector[1] +_ma_template_trans_matrix.tr_vector[2] +_ma_template_trans_matrix.tr_vector[3] +1 1.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 +1.000000 0.000 0.000 0.000 +""" + s, = modelcif.reader.read(StringIO(cif)) + t, = s.template_transformations + self.assertAlmostEqual(t.rot_matrix[0][0], 1.0, delta=1e-6) + self.assertAlmostEqual(t.tr_vector[0], 0.0, delta=1e-6) + + def test_template_details_handler(self): + """Test _TemplateDetailsHandler""" + cif = """ +loop_ +_ma_template_details.ordinal_id +_ma_template_details.template_id +_ma_template_details.template_origin +_ma_template_details.template_entity_type +_ma_template_details.template_trans_matrix_id +_ma_template_details.template_data_id +_ma_template_details.target_asym_id +_ma_template_details.template_label_asym_id +_ma_template_details.template_label_entity_id +_ma_template_details.template_model_num +_ma_template_details.template_auth_asym_id +1 1 'reference database' polymer 1 2 A B 3 4 Z +2 2 'reference database' polymer 2 3 . B 3 4 Z +3 3 customized polymer 2 3 . B 3 4 Z +""" + s, = modelcif.reader.read(StringIO(cif)) + t, t2, t3 = s.templates + self.assertIsInstance(t, modelcif.Template) + self.assertIsInstance(t2, modelcif.Template) + self.assertIsInstance(t3, modelcif.CustomTemplate) + self.assertEqual(t.entity_id, '3') + self.assertEqual(t.model_num, 4) + self.assertEqual(t.asym_id, 'B') + self.assertEqual(t.strand_id, 'Z') + self.assertEqual(len(s.alignments), 0) + + def test_template_customized_handler(self): + """Test _TemplateCustomizedHandler""" + cif = """ +loop_ +_ma_template_details.ordinal_id +_ma_template_details.template_id +1 1 +# +loop_ +_ma_template_customized.template_id +_ma_template_customized.details +1 'details x' +2 'details y' +""" + s, = modelcif.reader.read(StringIO(cif)) + t1, t2 = s.templates + # template_details does not specify template_origin, so template #1 + # will be initially instantiated as a Template, and should be corrected + # to CustomTemplate on reading template_customized: + self.assertIsInstance(t1, modelcif.CustomTemplate) + self.assertEqual(t1.details, 'details x') + self.assertEqual(len(t1.atoms), 0) + self.assertIsInstance(t2, modelcif.CustomTemplate) + self.assertEqual(t2.details, 'details y') + self.assertEqual(len(t2.atoms), 0) + + def test_template_details_handler_nonpoly(self): + """Test _TemplateDetailsHandler with nonpolymeric template""" + cif = """ +loop_ +_pdbx_entity_nonpoly.entity_id +_pdbx_entity_nonpoly.name +_pdbx_entity_nonpoly.comp_id +_pdbx_entity_nonpoly.ma_model_mode +3 Heme HEM explicit +# +loop_ +_ma_template_details.ordinal_id +_ma_template_details.template_id +_ma_template_details.template_origin +_ma_template_details.template_entity_type +_ma_template_details.template_trans_matrix_id +_ma_template_details.template_data_id +_ma_template_details.target_asym_id +_ma_template_details.template_label_asym_id +_ma_template_details.template_label_entity_id +_ma_template_details.template_model_num +_ma_template_details.template_auth_asym_id +1 1 'reference database' non-polymer 1 2 A B 3 4 Z +# +loop_ +_ma_template_non_poly.template_id +_ma_template_non_poly.comp_id +_ma_template_non_poly.details +1 HEM "Template Heme" +""" + s, = modelcif.reader.read(StringIO(cif)) + t, = s.templates + self.assertEqual(t.entity_id, '3') + self.assertEqual(t.model_num, 4) + self.assertEqual(t.asym_id, 'B') + self.assertEqual(t.strand_id, 'Z') + self.assertEqual(len(s.alignments), 0) + self.assertEqual(t.entity.description, 'Template Heme') + a, = s.asym_units + self.assertIsInstance(a, modelcif.NonPolymerFromTemplate) + self.assertIs(a.template, t) + self.assertTrue(a.explicit) + + def test_custom_template_coord_handler(self): + """Test reading of coordinates for CustomTemplate""" + cif = """ +loop_ +_ma_template_details.ordinal_id +_ma_template_details.template_id +_ma_template_details.template_origin +_ma_template_details.template_entity_type +_ma_template_details.template_trans_matrix_id +_ma_template_details.template_data_id +_ma_template_details.target_asym_id +_ma_template_details.template_label_asym_id +_ma_template_details.template_label_entity_id +_ma_template_details.template_model_num +_ma_template_details.template_auth_asym_id +1 1 customized polymer 2 3 . B 3 4 Z +# +loop_ +_ma_template_customized.template_id +_ma_template_customized.details +1 'Provided by user' +# +loop_ +_ma_template_coord.template_id +_ma_template_coord.group_PDB +_ma_template_coord.ordinal_id +_ma_template_coord.type_symbol +_ma_template_coord.label_atom_id +_ma_template_coord.label_comp_id +_ma_template_coord.label_seq_id +_ma_template_coord.label_asym_id +_ma_template_coord.auth_seq_id +_ma_template_coord.auth_asym_id +_ma_template_coord.auth_atom_id +_ma_template_coord.auth_comp_id +_ma_template_coord.Cartn_x +_ma_template_coord.Cartn_y +_ma_template_coord.Cartn_z +_ma_template_coord.occupancy +_ma_template_coord.label_entity_id +_ma_template_coord.B_iso_or_equiv +_ma_template_coord.formal_charge +1 ATOM 1 C CA ALA 1 A 42 A X XXX 0 1.000 2.000 0.500 9 2.000 1.000 +1 ATOM 2 O OXT CYS 2 A . A . . 1.000 2.000 3.000 . 9 . . +# +""" + s, = modelcif.reader.read(StringIO(cif)) + t, = s.templates + self.assertIsInstance(t, modelcif.CustomTemplate) + self.assertEqual(t.details, 'Provided by user') + self.assertEqual(len(t.atoms), 2) + a1 = t.atoms[0] + self.assertEqual(a1.seq_id, 1) + self.assertEqual(a1.atom_id, 'CA') + self.assertEqual(a1.type_symbol, 'C') + self.assertAlmostEqual(a1.x, 0.0, delta=1e-2) + self.assertAlmostEqual(a1.y, 1.0, delta=1e-2) + self.assertAlmostEqual(a1.z, 2.0, delta=1e-2) + self.assertAlmostEqual(a1.occupancy, 0.5, delta=1e-2) + self.assertAlmostEqual(a1.biso, 2.0, delta=1e-2) + self.assertAlmostEqual(a1.charge, 1.0, delta=1e-2) + self.assertEqual(a1.auth_seq_id, 42) + self.assertEqual(a1.auth_comp_id, 'XXX') + self.assertEqual(a1.auth_atom_id, 'X') + + a2 = t.atoms[1] + self.assertEqual(a2.seq_id, 2) + self.assertEqual(a2.atom_id, 'OXT') + self.assertEqual(a2.type_symbol, 'O') + + def test_entity_nonpoly_bad_model_mode(self): + """Test pdbx_entity_nonpoly with missing ma_model_mode""" + cif = """ +loop_ +_struct_asym.id +_struct_asym.entity_id +A 1 +B 2 +C 3 +# +loop_ +_pdbx_entity_nonpoly.entity_id +_pdbx_entity_nonpoly.name +_pdbx_entity_nonpoly.comp_id +_pdbx_entity_nonpoly.ma_model_mode +1 test1 TE1 explicit +2 test2 TE2 . +3 test3 TE3 ? +# +loop_ +_ma_template_details.ordinal_id +_ma_template_details.template_id +_ma_template_details.template_origin +_ma_template_details.template_entity_type +_ma_template_details.template_trans_matrix_id +_ma_template_details.template_data_id +_ma_template_details.target_asym_id +_ma_template_details.template_label_asym_id +_ma_template_details.template_label_entity_id +_ma_template_details.template_model_num +_ma_template_details.template_auth_asym_id +1 1 'reference database' non-polymer 1 2 A A 1 4 Z +2 2 'reference database' non-polymer 1 3 B B 2 4 Z +3 3 'reference database' non-polymer 1 4 C C 3 4 Z +# +loop_ +_ma_template_non_poly.template_id +_ma_template_non_poly.comp_id +_ma_template_non_poly.details +1 TE1 test1 +2 TE2 test2 +3 TE3 test3 +""" + s, = modelcif.reader.read(StringIO(cif)) + a1, a2, a3 = s.asym_units + self.assertTrue(a1.explicit) + self.assertIsNone(a2.explicit) + self.assertIs(a3.explicit, ihm.unknown) + + def test_template_ref_db_handler(self): + """Test _TemplateRefDBHandler""" + cif = """ +loop_ +_ma_template_ref_db_details.template_id +_ma_template_ref_db_details.db_name +_ma_template_ref_db_details.db_name_other_details +_ma_template_ref_db_details.db_accession_code +_ma_template_ref_db_details.db_version_date +1 PDB . 3nc1 2021-10-06 +1 MIS . testacc . +1 Other foo acc2 . +1 PubChem . 1046 . +1 AlphaFoldDB . I6XD65 2022-06-01 +""" + s, = modelcif.reader.read(StringIO(cif)) + t, = s.templates + r1, r2, r3, r4, r5 = t.references + self.assertIsInstance(r1, modelcif.reference.PDB) + self.assertEqual(r1.accession, '3nc1') + self.assertEqual(r1.db_version_date, date(2021, 10, 6)) + self.assertEqual(r2.name, 'MIS') + self.assertIsNone(r2.other_details) + self.assertIsNone(r2.db_version_date) + self.assertEqual(r3.name, 'Other') + self.assertEqual(r3.other_details, 'foo') + self.assertIsInstance(r4, modelcif.reference.PubChem) + self.assertEqual(r4.accession, '1046') + self.assertIsInstance(r5, modelcif.reference.AlphaFoldDB) + self.assertEqual(r5.accession, 'I6XD65') + self.assertEqual(r5.db_version_date, date(2022, 6, 1)) + + def _get_models_cif(self, old=False): + if old: + cif = """ +loop_ +_ma_model_list.ordinal_id +_ma_model_list.model_id +_ma_model_list.model_group_id +_ma_model_list.model_name +_ma_model_list.model_group_name +_ma_model_list.assembly_id +_ma_model_list.data_id +_ma_model_list.model_type +_ma_model_list.model_type_other_details +1 1 1 'Best scoring model' 'All models' 99 4 'Homology model' . +2 2 1 '2nd best scoring model' 'All models' 99 5 'Ab initio model' . +3 3 2 'Best scoring model' 'group2' 99 6 'Other' 'Custom other model' +# +""" + else: + cif = """ +loop_ +_ma_model_list.ordinal_id +_ma_model_list.model_name +_ma_model_list.assembly_id +_ma_model_list.data_id +_ma_model_list.model_type +_ma_model_list.model_type_other_details +1 'Best scoring model' 99 4 'Homology model' . +2 '2nd best scoring model' 99 5 'Ab initio model' . +3 'Best scoring model' 99 6 'Other' 'Custom other model' +# +loop_ +_ma_model_group.id +_ma_model_group.name +_ma_model_group.details +1 'All models' . +2 'group2' 'second group details' +# +# +loop_ +_ma_model_group_link.group_id +_ma_model_group_link.model_id +1 1 +1 2 +2 3 +# +""" + cif += """ +loop_ +_atom_site.group_PDB +_atom_site.id +_atom_site.type_symbol +_atom_site.label_atom_id +_atom_site.label_alt_id +_atom_site.label_comp_id +_atom_site.label_seq_id +_atom_site.auth_seq_id +_atom_site.pdbx_PDB_ins_code +_atom_site.label_asym_id +_atom_site.Cartn_x +_atom_site.Cartn_y +_atom_site.Cartn_z +_atom_site.occupancy +_atom_site.label_entity_id +_atom_site.auth_asym_id +_atom_site.B_iso_or_equiv +_atom_site.pdbx_PDB_model_num +ATOM 1 C CA . ASP 1 1 ? A 1.000 2.000 3.000 . 1 A . 6 +ATOM 2 C CA . ASP 1 1 ? A 1.000 2.000 3.000 . 1 A . 8 +""" + return cif + + def test_model_list_handler_default_old(self): + """Test _ModelListHandler with default model class, old dictionary""" + self._test_model_list_handler_default(old=True) + + def test_model_list_handler_default(self): + """Test _ModelListHandler with default model class""" + self._test_model_list_handler_default(old=False) + + def _test_model_list_handler_default(self, old): + cif = self._get_models_cif(old=old) + s, = modelcif.reader.read(StringIO(cif)) + mg1, mg2, mg3 = s.model_groups + self.assertEqual(mg1.name, 'All models') + m1, m2 = list(mg1) + self.assertIsInstance(m1, modelcif.model.HomologyModel) + self.assertEqual(m1.model_type, 'Homology model') + self.assertIsNone(m1.other_details) + self.assertIsInstance(m2, modelcif.model.AbInitioModel) + self.assertEqual(m2.model_type, 'Ab initio model') + self.assertIsNone(m2.other_details) + self.assertEqual(m1.name, 'Best scoring model') + self.assertEqual(m2.name, '2nd best scoring model') + self.assertEqual(mg2.name, 'group2') + m1, = list(mg2) + self.assertEqual(m1.model_type, 'Other') + self.assertEqual(m1.other_details, 'Custom other model') + self.assertEqual(m1.name, 'Best scoring model') + self.assertEqual(m1.assembly._id, '99') + # Last group is auto-created to contain the non-grouped models + # referenced by atom_site + self.assertIsNone(mg3.name) + m1, m2 = list(mg3) + self.assertEqual(m1.model_type, 'Other') + self.assertEqual(m1._id, '6') + self.assertEqual(m2.model_type, 'Other') + self.assertEqual(m2._id, '8') + + def test_model_list_handler_group_new_old(self): + """Test _ModelListHandler handling mix of new and old style groups""" + cif = """ +loop_ +_ma_model_list.ordinal_id +_ma_model_list.model_id +_ma_model_list.model_group_id +_ma_model_list.model_name +_ma_model_list.model_group_name +_ma_model_list.data_id +_ma_model_list.model_type +_ma_model_list.model_type_other_details +1 1 1 . . 4 'Homology model' . +2 2 . . . 4 'Homology model' . +3 3 1 . . 4 'Homology model' . +# +# +loop_ +_ma_model_group.id +_ma_model_group.name +_ma_model_group.details +1 'group1' . +2 'group2' . +# +# +loop_ +_ma_model_group_link.model_id +_ma_model_group_link.group_id +2 1 +3 2 +""" + s, = modelcif.reader.read(StringIO(cif)) + # model1 is in group1, using old-style tables; + # model2 is in group1, using new-style tables; + # model3 is in group2 according to new-style tables but group1 + # according to old style (new-style should take precedence) + mg1, mg2 = s.model_groups + self.assertEqual(mg1._id, '1') + self.assertEqual(mg2._id, '2') + self.assertEqual([m._id for m in mg1], ['2', '1']) + self.assertEqual([m._id for m in mg2], ['3']) + + def test_model_list_handler_custom(self): + """Test _ModelListHandler with custom model class""" + class MyModel(modelcif.model.Model): + """Custom model type""" + pass + cif = self._get_models_cif() + s, = modelcif.reader.read(StringIO(cif), model_class=MyModel) + mg1, mg2, mg3 = s.model_groups + m1, m2 = list(mg1) + m3, = list(mg2) + m4, m5 = list(mg3) + # Custom model type should always be returned, regardless of what + # the mmCIF file says it is, but model_type should be set + self.assertIsInstance(m1, MyModel) + self.assertIsInstance(m2, MyModel) + self.assertIsInstance(m3, MyModel) + self.assertIsInstance(m4, MyModel) + self.assertIsInstance(m5, MyModel) + self.assertEqual(m1.model_type, 'Homology model') + self.assertEqual(m2.model_type, 'Ab initio model') + self.assertEqual(m3.model_type, 'Other') + self.assertEqual(m4.model_type, 'Other') + self.assertEqual(m5.model_type, 'Other') + + def test_assembly_handler(self): + """Test _AssemblyHandler and _AssemblyDetailsHandler""" + cif = """ +loop_ +_entity_poly_seq.entity_id +_entity_poly_seq.num +_entity_poly_seq.mon_id +1 1 ALA +1 2 ALA +# +loop_ +_struct_asym.id +_struct_asym.entity_id +_struct_asym.details +A 1 Nup84 +# +loop_ +_ma_struct_assembly.ordinal_id +_ma_struct_assembly.assembly_id +_ma_struct_assembly.entity_id +_ma_struct_assembly.asym_id +_ma_struct_assembly.seq_id_begin +_ma_struct_assembly.seq_id_end +1 1 1 A 1 2 +2 1 1 A 1 1 +3 1 1 A . . +# +loop_ +_ma_struct_assembly_details.assembly_id +_ma_struct_assembly_details.assembly_name +_ma_struct_assembly_details.assembly_description +1 foo bar +""" + s, = modelcif.reader.read(StringIO(cif)) + a, = s.assemblies + self.assertEqual(a.name, 'foo') + self.assertEqual(a.description, 'bar') + self.assertEqual(len(a), 3) + # Complete asym + self.assertIsInstance(a[0], modelcif.AsymUnit) + # asym range + self.assertIsInstance(a[1], modelcif.AsymUnitRange) + self.assertEqual(a[1].seq_id_range, (1, 1)) + # No specified range -> complete asym + self.assertIsInstance(a[2], modelcif.AsymUnit) + + def test_template_poly_segment_handler(self): + """Test _TemplatePolySegmentHandler""" + cif = """ +loop_ +_ma_template_poly_segment.id +_ma_template_poly_segment.template_id +_ma_template_poly_segment.residue_number_begin +_ma_template_poly_segment.residue_number_end +1 42 2 9 +""" + s, = modelcif.reader.read(StringIO(cif)) + seg, = s.template_segments + self.assertEqual(seg.template._id, '42') + self.assertEqual(seg.seq_id_range, (2, 9)) + + def test_data__handler(self): + """Test _DataHandler""" + cif = """ +loop_ +_ma_data.id +_ma_data.name +_ma_data.content_type +_ma_data.content_type_other_details +1 'Template Structure' 'template structure' . +2 'Model subunit' target . +3 'Default model name' 'model coordinates' . +# +loop_ +_ma_template_details.ordinal_id +_ma_template_details.template_id +_ma_template_details.template_origin +_ma_template_details.template_entity_type +_ma_template_details.template_trans_matrix_id +_ma_template_details.template_data_id +_ma_template_details.target_asym_id +_ma_template_details.template_label_asym_id +_ma_template_details.template_label_entity_id +_ma_template_details.template_model_num +_ma_template_details.template_auth_asym_id +1 1 'reference database' polymer 1 1 A A 1 1 Z +# +loop_ +_ma_model_list.ordinal_id +_ma_model_list.model_id +_ma_model_list.model_group_id +_ma_model_list.model_name +_ma_model_list.model_group_name +_ma_model_list.assembly_id +_ma_model_list.data_id +_ma_model_list.model_type +_ma_model_list.model_type_other_details +1 1 1 'Model name' 'All models' 1 3 'Homology model' . +""" + s, = modelcif.reader.read(StringIO(cif)) + d1, d2, d3 = s.data + self.assertIsInstance(d1, modelcif.Template) + # d2 is not referenced by any other table, so gets Data base class + self.assertIsInstance(d2, modelcif.data.Data) + self.assertIsInstance(d3, modelcif.model.Model) + # Name not given in template_details so taken from ma_data + self.assertEqual(d1.name, 'Template Structure') + self.assertEqual(d2.name, 'Model subunit') + # Name in model_list used rather than that from ma_data + self.assertEqual(d3.name, 'Model name') + + def test_data_group_handler(self): + """Test _DataGroupHandler""" + cif = """ +loop_ +_ma_data.id +_ma_data.name +_ma_data.content_type +_ma_data.content_type_other_details +1 'Template Structure' 'template structure' . +2 'Model subunit' target . +# +loop_ +_ma_template_details.ordinal_id +_ma_template_details.template_id +_ma_template_details.template_origin +_ma_template_details.template_entity_type +_ma_template_details.template_trans_matrix_id +_ma_template_details.template_data_id +_ma_template_details.target_asym_id +_ma_template_details.template_label_asym_id +_ma_template_details.template_label_entity_id +_ma_template_details.template_model_num +_ma_template_details.template_auth_asym_id +1 1 'reference database' polymer 1 1 A A 1 1 Z +# +loop_ +_ma_data_group.ordinal_id +_ma_data_group.group_id +_ma_data_group.data_id +1 1 1 +2 1 2 +3 2 3 +""" + s, = modelcif.reader.read(StringIO(cif)) + g1, g2, = s.data_groups + self.assertEqual(len(g1), 2) + self.assertIsInstance(g1[0], modelcif.Template) + self.assertEqual(g1[0]._data_id, '1') + self.assertIsInstance(g1[1], modelcif.data.Data) + self.assertEqual(g1[1]._data_id, '2') + self.assertEqual(len(g2), 1) + self.assertIsNone(g2[0]) + + def test_data_ref_db_handler(self): + """Test _DataRefDBHandler""" + cif = """ +loop_ +_ma_data.id +_ma_data.name +_ma_data.content_type +_ma_data.content_type_other_details +1 defaultname1 'reference database' . +2 defaultname2 'reference database' . +# +loop_ +_ma_data_ref_db.data_id +_ma_data_ref_db.name +_ma_data_ref_db.location_url +_ma_data_ref_db.version +_ma_data_ref_db.release_date +1 name1 url1 1.0 1979-11-22 +2 . url2 . . +""" + s, = modelcif.reader.read(StringIO(cif)) + d1, d2 = s.data + self.assertIsInstance(d1, modelcif.ReferenceDatabase) + self.assertIsInstance(d2, modelcif.ReferenceDatabase) + # Name in ma_data_ref_db used rather than that from ma_data + self.assertEqual(d1.name, 'name1') + self.assertEqual(d1.url, 'url1') + self.assertEqual(d1.version, '1.0') + self.assertIsInstance(d1.release_date, date) + self.assertEqual(d1.release_date, date(1979, 11, 22)) + # Name not given in ma_data_ref_db so taken from ma_data + self.assertEqual(d2.name, 'defaultname2') + self.assertIsNone(d2.version) + self.assertIsNone(d2.release_date) + + def test_protocol_handler(self): + """Test _ProtocolHandler""" + cif = """ +loop_ +_ma_protocol_step.ordinal_id +_ma_protocol_step.protocol_id +_ma_protocol_step.step_id +_ma_protocol_step.method_type +_ma_protocol_step.step_name +_ma_protocol_step.details +_ma_protocol_step.software_group_id +_ma_protocol_step.input_data_group_id +_ma_protocol_step.output_data_group_id +1 1 1 'template search' 'ModPipe Seq-Prf (0001)' . 1 1 2 +2 1 2 'template selection' . . . . . +3 1 3 'target-template alignment' . . . . . +4 1 4 modeling . . 2 2 1 +5 1 5 'model selection' . . 1 1 1 +6 1 6 'model refinement' . . . . . +7 1 7 other testname testdetails 42 99 66 +""" + s, = modelcif.reader.read(StringIO(cif)) + p, = s.protocols + self.assertEqual(len(p.steps), 7) + s1, s2, s3, s4, s5, s6, s7 = p.steps + self.assertIsInstance(s1, modelcif.protocol.TemplateSearchStep) + self.assertIsInstance(s2, modelcif.protocol.TemplateSelectionStep) + self.assertIsInstance(s3, + modelcif.protocol.TargetTemplateAlignmentStep) + self.assertIsInstance(s4, modelcif.protocol.ModelingStep) + self.assertIsInstance(s5, modelcif.protocol.ModelSelectionStep) + self.assertIsInstance(s6, modelcif.protocol.ModelRefinementStep) + self.assertIsInstance(s7, modelcif.protocol.Step) + self.assertEqual(s7.method_type, "other") + self.assertEqual(s7.name, "testname") + self.assertEqual(s7.details, "testdetails") + self.assertEqual(s7.input_data._id, '99') + self.assertEqual(s7.output_data._id, '66') + self.assertEqual(s7.software._id, '42') + + def test_target_entity_handler(self): + """Test _TargetEntityHandler""" + cif = """ +loop_ +_ma_target_entity.entity_id +_ma_target_entity.data_id +_ma_target_entity.origin +1 2 'reference database' +""" + s, = modelcif.reader.read(StringIO(cif)) + e, = s.entities + self.assertEqual(e._data_id, '2') + + def test_qa_metric_global_handler(self): + """Test _QAMetricGlobalHandler""" + cif = """ +loop_ +_ma_model_list.ordinal_id +_ma_model_list.model_id +_ma_model_list.model_group_id +_ma_model_list.model_name +_ma_model_list.model_group_name +_ma_model_list.assembly_id +_ma_model_list.data_id +_ma_model_list.model_type +_ma_model_list.model_type_other_details +1 1 1 'Best scoring model' 'All models' 1 4 'Homology model' . +# +loop_ +_ma_qa_metric.id +_ma_qa_metric.name +_ma_qa_metric.description +_ma_qa_metric.type +_ma_qa_metric.mode +_ma_qa_metric.type_other_details +_ma_qa_metric.software_group_id +1 MPQS 'ModPipe Quality Score' other global +'composite score, values >1.1 are considered reliable' 1 +2 zDOPE 'Normalized DOPE' zscore global . 2 +3 'TSVMod RMSD' 'TSVMod predicted RMSD (MSALL)' distance global . . +4 'TSVMod NO35' . 'normalized score' global . . +# +loop_ +_ma_qa_metric_global.ordinal_id +_ma_qa_metric_global.model_id +_ma_qa_metric_global.metric_id +_ma_qa_metric_global.metric_value +1 1 1 1.0 +2 1 2 2.0 +3 1 3 3.0 +4 1 4 4.0 +""" + s, = modelcif.reader.read(StringIO(cif)) + mg, = s.model_groups + m, = mg + q1, q2, q3, q4 = m.qa_metrics + self.assertIsInstance(q1, modelcif.qa_metric.Global) + self.assertEqual(q1.type, "other") + self.assertEqual(q1.name, "MPQS") + self.assertEqual(type(q1).__name__, "MPQS") + self.assertEqual(q1.description, "ModPipe Quality Score") + self.assertEqual(q1.__doc__, "ModPipe Quality Score") + self.assertEqual(q1.software._id, '1') + self.assertAlmostEqual(q1.value, 1.0, delta=1e-6) + + self.assertIsInstance(q2, modelcif.qa_metric.Global) + self.assertIsInstance(q2, modelcif.qa_metric.ZScore) + self.assertAlmostEqual(q2.value, 2.0, delta=1e-6) + + self.assertIsInstance(q3, modelcif.qa_metric.Global) + self.assertIsInstance(q3, modelcif.qa_metric.Distance) + self.assertAlmostEqual(q3.value, 3.0, delta=1e-6) + + self.assertIsInstance(q4, modelcif.qa_metric.Global) + self.assertIsInstance(q4, modelcif.qa_metric.NormalizedScore) + self.assertIsNone(q4.description) + self.assertIsNone(q4.__doc__) + + def test_qa_metric_local_handler(self): + """Test _QAMetricLocalHandler""" + cif = """ +loop_ +_ma_model_list.ordinal_id +_ma_model_list.model_id +_ma_model_list.model_group_id +_ma_model_list.model_name +_ma_model_list.model_group_name +_ma_model_list.assembly_id +_ma_model_list.data_id +_ma_model_list.model_type +_ma_model_list.model_type_other_details +1 1 1 'Best scoring model' 'All models' 1 4 'Homology model' . +# +loop_ +_ma_qa_metric.id +_ma_qa_metric.name +_ma_qa_metric.description +_ma_qa_metric.type +_ma_qa_metric.mode +_ma_qa_metric.type_other_details +_ma_qa_metric.software_group_id +1 'test local' 'some local score' 'normalized score' local . . +# +loop_ +_ma_qa_metric_local.ordinal_id +_ma_qa_metric_local.model_id +_ma_qa_metric_local.label_asym_id +_ma_qa_metric_local.label_seq_id +_ma_qa_metric_local.label_comp_id +_ma_qa_metric_local.metric_id +_ma_qa_metric_local.metric_value +1 1 A 2 CYS 1 1.0 +""" + s, = modelcif.reader.read(StringIO(cif)) + mg, = s.model_groups + m, = mg + q1, = m.qa_metrics + self.assertIsInstance(q1, modelcif.qa_metric.Local) + self.assertIsInstance(q1, modelcif.qa_metric.NormalizedScore) + self.assertEqual(q1.type, "normalized score") + self.assertEqual(q1.name, "test local") + self.assertEqual(q1.description, "some local score") + self.assertIsNone(q1.software) + self.assertEqual(q1.residue.asym._id, 'A') + self.assertEqual(q1.residue.seq_id, 2) + self.assertAlmostEqual(q1.value, 1.0, delta=1e-6) + + def test_qa_metric_pairwise_handler(self): + """Test _QAMetricPairwiseHandler""" + cif = """ +loop_ +_ma_model_list.ordinal_id +_ma_model_list.model_id +_ma_model_list.model_group_id +_ma_model_list.model_name +_ma_model_list.model_group_name +_ma_model_list.assembly_id +_ma_model_list.data_id +_ma_model_list.model_type +_ma_model_list.model_type_other_details +1 1 1 'Best scoring model' 'All models' 1 4 'Homology model' . +# +loop_ +_ma_qa_metric.id +_ma_qa_metric.name +_ma_qa_metric.description +_ma_qa_metric.type +_ma_qa_metric.mode +_ma_qa_metric.type_other_details +_ma_qa_metric.software_group_id +1 'test pair' 'some pair score' 'normalized score' local-pairwise . . +# +loop_ +_ma_qa_metric_local_pairwise.ordinal_id +_ma_qa_metric_local_pairwise.model_id +_ma_qa_metric_local_pairwise.label_asym_id_1 +_ma_qa_metric_local_pairwise.label_seq_id_1 +_ma_qa_metric_local_pairwise.label_comp_id_1 +_ma_qa_metric_local_pairwise.label_asym_id_2 +_ma_qa_metric_local_pairwise.label_seq_id_2 +_ma_qa_metric_local_pairwise.label_comp_id_2 +_ma_qa_metric_local_pairwise.metric_id +_ma_qa_metric_local_pairwise.metric_value +1 1 A 2 CYS B 4 GLY 1 1.0 +""" + s, = modelcif.reader.read(StringIO(cif)) + mg, = s.model_groups + m, = mg + q1, = m.qa_metrics + self.assertIsInstance(q1, modelcif.qa_metric.LocalPairwise) + self.assertIsInstance(q1, modelcif.qa_metric.NormalizedScore) + self.assertEqual(q1.type, "normalized score") + self.assertEqual(q1.name, "test pair") + self.assertEqual(q1.description, "some pair score") + self.assertIsNone(q1.software) + self.assertEqual(q1.residue1.asym._id, 'A') + self.assertEqual(q1.residue1.seq_id, 2) + self.assertEqual(q1.residue2.asym._id, 'B') + self.assertEqual(q1.residue2.seq_id, 4) + self.assertAlmostEqual(q1.value, 1.0, delta=1e-6) + + def test_qa_metric_feature_handler(self): + """Test _QAMetricFeatureHandler""" + feat = """ +loop_ +_ma_atom_feature.ordinal_id +_ma_atom_feature.feature_id +_ma_atom_feature.atom_id +1 1 1 +# +loop_ +_ma_poly_residue_feature.ordinal_id +_ma_poly_residue_feature.feature_id +_ma_poly_residue_feature.label_asym_id +_ma_poly_residue_feature.label_seq_id +_ma_poly_residue_feature.label_comp_id +1 2 Y 1 ALA +# +loop_ +_ma_entity_instance_feature.ordinal_id +_ma_entity_instance_feature.feature_id +_ma_entity_instance_feature.label_asym_id +1 3 Y +""" + qa = """ +loop_ +_ma_feature_list.feature_id +_ma_feature_list.feature_type +_ma_feature_list.entity_type +_ma_feature_list.details +1 atom other 'atom f' +2 residue polymer prf +3 'entity instance' polymer . +# +loop_ +_ma_model_list.ordinal_id +_ma_model_list.model_id +_ma_model_list.model_group_id +_ma_model_list.model_name +_ma_model_list.model_group_name +_ma_model_list.assembly_id +_ma_model_list.data_id +_ma_model_list.model_type +_ma_model_list.model_type_other_details +1 1 1 'Best scoring model' 'All models' 1 4 'Homology model' . +# +loop_ +_ma_qa_metric.id +_ma_qa_metric.name +_ma_qa_metric.description +_ma_qa_metric.type +_ma_qa_metric.mode +_ma_qa_metric.type_other_details +_ma_qa_metric.software_group_id +1 'test local' 'some local score' 'normalized score' per-feature . . +# +loop_ +_ma_qa_metric_feature.ordinal_id +_ma_qa_metric_feature.model_id +_ma_qa_metric_feature.feature_id +_ma_qa_metric_feature.metric_id +_ma_qa_metric_feature.metric_value +1 1 1 1 1.0 +2 1 2 1 2.0 +3 1 3 1 3.0 +""" + # Test both ways to make sure features still work if they are + # referenced by ID before their type is known + for cif in (feat + qa, qa + feat): + s, = modelcif.reader.read(StringIO(cif)) + mg, = s.model_groups + m, = mg + q1, q2, q3 = m.qa_metrics + self.assertIsInstance(q1, modelcif.qa_metric.Feature) + self.assertIsInstance(q1, modelcif.qa_metric.NormalizedScore) + self.assertIsInstance(q1.feature, modelcif.AtomFeature) + self.assertEqual(q1.feature.details, 'atom f') + self.assertAlmostEqual(q1.value, 1.0, delta=1e-6) + self.assertIsInstance(q2.feature, modelcif.PolyResidueFeature) + self.assertEqual(len(q2.feature.residues), 1) + self.assertEqual(q2.feature.residues[0].seq_id, 1) + self.assertAlmostEqual(q2.value, 2.0, delta=1e-6) + self.assertIsInstance(q3.feature, modelcif.EntityInstanceFeature) + self.assertEqual(len(q3.feature.asym_units), 1) + self.assertAlmostEqual(q3.value, 3.0, delta=1e-6) + + def test_qa_metric_feature_pairwise_handler(self): + """Test _QAMetricFeaturePairwiseHandler""" + feat = """ +loop_ +_ma_poly_residue_feature.ordinal_id +_ma_poly_residue_feature.feature_id +_ma_poly_residue_feature.label_asym_id +_ma_poly_residue_feature.label_seq_id +_ma_poly_residue_feature.label_comp_id +1 1 Y 1 ALA +2 2 Y 2 CYS +""" + qa = """ +loop_ +_ma_feature_list.feature_id +_ma_feature_list.feature_type +_ma_feature_list.entity_type +_ma_feature_list.details +1 residue polymer . +2 residue polymer . +# +loop_ +_ma_model_list.ordinal_id +_ma_model_list.model_id +_ma_model_list.model_group_id +_ma_model_list.model_name +_ma_model_list.model_group_name +_ma_model_list.assembly_id +_ma_model_list.data_id +_ma_model_list.model_type +_ma_model_list.model_type_other_details +1 1 1 'Best scoring model' 'All models' 1 4 'Homology model' . +# +loop_ +_ma_qa_metric.id +_ma_qa_metric.name +_ma_qa_metric.description +_ma_qa_metric.type +_ma_qa_metric.mode +_ma_qa_metric.type_other_details +_ma_qa_metric.software_group_id +1 'test local' 'some local score' 'normalized score' per-feature-pair . . +# +loop_ +_ma_qa_metric_feature_pairwise.ordinal_id +_ma_qa_metric_feature_pairwise.model_id +_ma_qa_metric_feature_pairwise.feature_id_1 +_ma_qa_metric_feature_pairwise.feature_id_2 +_ma_qa_metric_feature_pairwise.metric_id +_ma_qa_metric_feature_pairwise.metric_value +1 1 1 2 1 50.000 +""" + # Test both ways to make sure features still work if they are + # referenced by ID before their type is known + for cif in (feat + qa, qa + feat): + s, = modelcif.reader.read(StringIO(cif)) + mg, = s.model_groups + m, = mg + q1, = m.qa_metrics + self.assertIsInstance(q1, modelcif.qa_metric.FeaturePairwise) + self.assertIsInstance(q1, modelcif.qa_metric.NormalizedScore) + self.assertIsInstance(q1.feature1, modelcif.PolyResidueFeature) + self.assertIsInstance(q1.feature2, modelcif.PolyResidueFeature) + self.assertAlmostEqual(q1.value, 50.0, delta=1e-6) + + def test_alignment_info_details_handler(self): + """Test _AlignmentInfoHandler and _AlignmentDetailsHandler""" + cif = """ +loop_ +_ma_alignment_info.alignment_id +_ma_alignment_info.data_id +_ma_alignment_info.software_group_id +_ma_alignment_info.alignment_length +_ma_alignment_info.alignment_type +_ma_alignment_info.alignment_mode +1 3 1 . 'target-template pairwise alignment' global +2 4 1 . 'target-template pairwise alignment' global +3 5 1 . 'target-template MSA' local +# +# +loop_ +_ma_alignment_details.ordinal_id +_ma_alignment_details.alignment_id +_ma_alignment_details.template_segment_id +_ma_alignment_details.target_asym_id +_ma_alignment_details.score_type +_ma_alignment_details.score_type_other_details +_ma_alignment_details.score_value +_ma_alignment_details.percent_sequence_identity +_ma_alignment_details.sequence_identity_denominator +_ma_alignment_details.sequence_identity_denominator_other_details +1 1 1 A 'BLAST e-value' . 1.0 45.000 'Length of the shorter sequence' . +2 2 1 A . . . . . . +3 3 1 A 'HHblits e-value' . 2.0 45.000 'Arithmetic mean sequence length' . +# +loop_ +_ma_alignment.ordinal_id +_ma_alignment.alignment_id +_ma_alignment.target_template_flag +_ma_alignment.sequence +1 1 1 DSYV-ETLD +2 1 2 DMACDTFIK +3 1 1 DSYV-ETLD +4 1 2 DMACDTFIK +# +loop_ +_ma_target_template_poly_mapping.id +_ma_target_template_poly_mapping.template_segment_id +_ma_target_template_poly_mapping.target_asym_id +_ma_target_template_poly_mapping.target_seq_id_begin +_ma_target_template_poly_mapping.target_seq_id_end +1 1 A 1 8 +2 1 A 1 8 +""" + s, = modelcif.reader.read(StringIO(cif)) + a1, a2, a3, = s.alignments + self.assertIs(a1.__class__, a2.__class__) + self.assertIsInstance(a1, modelcif.alignment.Global) + self.assertIsInstance(a1, modelcif.alignment.Pairwise) + p, = a1.pairs + self.assertIsInstance(p.score, modelcif.alignment.BLASTEValue) + self.assertAlmostEqual(p.score.value, 1.0, delta=1e-6) + self.assertIsInstance(p.identity, + modelcif.alignment.ShorterSequenceIdentity) + self.assertAlmostEqual(p.identity.value, 45.0, delta=1e-6) + self.assertIsInstance(p.template, modelcif.TemplateSegment) + self.assertEqual(p.template._id, '1') + self.assertEqual(p.template.gapped_sequence, 'DMACDTFIK') + self.assertIsInstance(p.target, ihm.AsymUnitSegment) + self.assertEqual(p.target.asym._id, 'A') + self.assertEqual(p.target.gapped_sequence, 'DSYV-ETLD') + self.assertEqual(p.target.seq_id_range, (1, 8)) + self.assertIsInstance(a3, modelcif.alignment.Local) + self.assertIsInstance(a3, modelcif.alignment.Multiple) + p, = a2.pairs + self.assertIsNone(p.score) + self.assertIsNone(p.identity) + p, = a3.pairs + self.assertIsInstance(p.score, modelcif.alignment.HHblitsEValue) + self.assertAlmostEqual(p.score.value, 2.0, delta=1e-6) + self.assertIsInstance(p.identity, + modelcif.alignment.MeanSequenceIdentity) + + def test_associated_files(self): + """Test _AssociatedHandler and _AssociatedArchiveHandler""" + cif = """ +loop_ +_ma_data.id +_ma_data.name +_ma_data.content_type +_ma_data.content_type_other_details +42 'Model subunit' target . +loop_ +_ma_target_entity.entity_id +_ma_target_entity.data_id +_ma_target_entity.origin +1 99 'reference database' +loop_ +_ma_entry_associated_files.id +_ma_entry_associated_files.entry_id +_ma_entry_associated_files.file_url +_ma_entry_associated_files.file_type +_ma_entry_associated_files.file_format +_ma_entry_associated_files.file_content +_ma_entry_associated_files.details +_ma_entry_associated_files.data_id +1 model https://example.com/foo.txt file other other 'test file' . +2 model https://example.com/t.zip archive zip 'archive with multiple files' . . +3 model baz.txt file other other 'test file3' . +4 model baz.cif file cif other 'test mmCIF' . +5 model baz.bcif file bcif other 'test BinaryCIF' 42 +# +# +loop_ +_ma_associated_archive_file_details.id +_ma_associated_archive_file_details.archive_file_id +_ma_associated_archive_file_details.file_path +_ma_associated_archive_file_details.file_format +_ma_associated_archive_file_details.file_content +_ma_associated_archive_file_details.description +_ma_associated_archive_file_details.data_id +1 2 bar.txt other other 'test file2' . +2 99 99.txt other other 'test file99' . +3 2 bar.cif cif other 'test mmCIF in zip' . +4 2 bar.bcif bcif 'local pairwise QA scores' 'test BinaryCIF in zip' 99 +5 2 bar2.bcif bcif 'QA metrics' 'test BinaryCIF in zip' 99 +""" + s, = modelcif.reader.read(StringIO(cif)) + r1, r2 = s.repositories + self.assertEqual(r1.url_root, 'https://example.com') + f1, zf = r1.files + self.assertIsInstance(f1, modelcif.associated.File) + self.assertEqual(f1.path, 'foo.txt') + self.assertEqual(f1.details, 'test file') + + self.assertIsInstance(zf, modelcif.associated.ZipFile) + self.assertEqual(zf.path, 't.zip') + self.assertIsNone(zf.details) + + f2, f3, f4, f5 = zf.files + self.assertEqual(f2.path, 'bar.txt') + self.assertEqual(f2.details, 'test file2') + self.assertIsNone(f2.data) + self.assertIsInstance(f3, modelcif.associated.CIFFile) + self.assertFalse(f3.binary) + # QA metrics file using old "local pairwise QA scores" name + self.assertIsInstance( + f4, modelcif.associated.QAMetricsFile) + self.assertEqual(f4.file_content, 'QA metrics') + self.assertTrue(f4.binary) + self.assertIsInstance(f4.data, modelcif.Entity) + self.assertIsInstance( + f5, modelcif.associated.QAMetricsFile) + self.assertEqual(f5.file_content, 'QA metrics') + self.assertTrue(f5.binary) + self.assertIsInstance(f5.data, modelcif.Entity) + + self.assertIsNone(r2.url_root) + f3, f4, f5 = r2.files + self.assertEqual(f3.path, 'baz.txt') + self.assertEqual(f3.details, 'test file3') + self.assertIsInstance(f4, modelcif.associated.CIFFile) + self.assertFalse(f4.binary) + self.assertIsNone(f4.data) + self.assertIsInstance(f5, modelcif.associated.CIFFile) + self.assertTrue(f5.binary) + self.assertEqual(f5.data.__class__, modelcif.data.Data) + + def test_template_poly_handler(self): + """Test _TemplatePolyHandler""" + cif = """ +loop_ +_chem_comp.id +_chem_comp.type +_chem_comp.name +_chem_comp.formula +MYTYPE 'D-PEPTIDE LINKING' 'MY CUSTOM COMPONENT' 'C6 H12' +MYTYP2 'D-PEPTIDE LINKING' 'MY CUSTOM COMPONENT2' 'C6 H12' +# +loop_ +_ma_template_details.ordinal_id +_ma_template_details.template_id +_ma_template_details.template_origin +_ma_template_details.template_entity_type +_ma_template_details.template_trans_matrix_id +_ma_template_details.template_data_id +_ma_template_details.target_asym_id +_ma_template_details.template_label_asym_id +_ma_template_details.template_label_entity_id +_ma_template_details.template_model_num +_ma_template_details.template_auth_asym_id +1 1 'reference database' polymer 1 1 A A . 1 A +2 2 'reference database' polymer 1 1 A A . 1 A +3 3 'reference database' polymer 1 1 A A . 1 A +# +loop_ +_ma_template_poly.template_id +_ma_template_poly.seq_one_letter_code +_ma_template_poly.seq_one_letter_code_can +1 A(MYTYPE)V AVV +2 A(MYTYP2)V . +3 . . +4 CCC CCC +""" + s, = modelcif.reader.read(StringIO(cif)) + # template_id=4 in template_poly should be ignored + t1, t2, t3 = s.templates + s1, s2, s3 = t1.entity.sequence + self.assertEqual(s1.id, 'ALA') + self.assertEqual(s1.code, 'A') + # Both one-letter and one-letter-canonical were provided + self.assertEqual(s2.id, 'MYTYPE') + self.assertEqual(s2.code, 'MYTYPE') + self.assertEqual(s2.code_canonical, 'V') + + # Only one-letter was provided + s1, s2, s3 = t2.entity.sequence + self.assertEqual(s2.id, 'MYTYP2') + self.assertEqual(s2.code, 'MYTYP2') + self.assertIsNone(s2.code_canonical) + + # No sequence provided + self.assertEqual(len(t3.entity.sequence), 0) + + def test_template_non_poly_handler(self): + """Test _TemplateNonPolyHandler""" + cif = """ +loop_ +_chem_comp.id +_chem_comp.type +HEM non-polymer +# +loop_ +_ma_template_details.ordinal_id +_ma_template_details.template_id +_ma_template_details.template_origin +_ma_template_details.template_entity_type +_ma_template_details.template_trans_matrix_id +_ma_template_details.template_data_id +_ma_template_details.target_asym_id +_ma_template_details.template_label_asym_id +_ma_template_details.template_label_entity_id +_ma_template_details.template_model_num +_ma_template_details.template_auth_asym_id +1 1 'reference database' polymer 1 1 A A . 1 A +# +loop_ +_ma_template_non_poly.template_id +_ma_template_non_poly.comp_id +_ma_template_non_poly.details +1 HEM Heme +""" + s, = modelcif.reader.read(StringIO(cif)) + t, = s.templates + s1, = t.entity.sequence + self.assertEqual(s1.id, 'HEM') + self.assertEqual(s1.type, 'non-polymer') + self.assertIsInstance(s1, ihm.NonPolymerChemComp) + + def test_chem_comp_handler(self): + """Test ChemCompHandler and ChemCompDescriptorHandler""" + cif = """ +loop_ +_chem_comp.id +_chem_comp.type +_chem_comp.name +_chem_comp.formula +_chem_comp.ma_provenance +MET 'L-peptide linking' . . . +CYS 'D-peptide linking' CYSTEINE . ? +ALA 'L-peptide linking' ALANINE . 'CCD Core' +MATYPE 'L-PEPTIDE LINKING' 'MODELARCHIVE COMPONENT' . 'CCD MA' +MYTYPE 'L-PEPTIDE LINKING' 'MY CUSTOM COMPONENT' . 'CCD local' +# +loop_ +_ma_chem_comp_descriptor.ordinal_id +_ma_chem_comp_descriptor.chem_comp_id +_ma_chem_comp_descriptor.chem_comp_name +_ma_chem_comp_descriptor.type +_ma_chem_comp_descriptor.value +_ma_chem_comp_descriptor.details +_ma_chem_comp_descriptor.software_id +1 MYTYPE 'ignored' 'InChI Key' XDAOLTSRNUSPPH-XMMPIXPASA-N foo 1 +2 MYTYPE ? 'IUPAC Name' foobar . . +# +loop_ +_entity_poly_seq.entity_id +_entity_poly_seq.num +_entity_poly_seq.mon_id +_entity_poly_seq.hetero +1 1 MET . +1 2 CYS . +1 3 ALA . +1 4 MATYPE . +1 5 MYTYPE . +""" + s, = modelcif.reader.read(StringIO(cif)) + e1, = s.entities + s = e1.sequence + self.assertEqual(len(s), 5) + self.assertEqual(s[2].ccd, 'core') + self.assertEqual(s[3].ccd, 'ma') + self.assertEqual(s[4].ccd, 'local') + d1, d2 = s[4].descriptors + self.assertIsInstance(d1, modelcif.descriptor.InChIKey) + self.assertEqual(d1.value, 'XDAOLTSRNUSPPH-XMMPIXPASA-N') + self.assertEqual(d1.details, 'foo') + self.assertEqual(d1.software._id, '1') + self.assertIsInstance(d2, modelcif.descriptor.IUPACName) + self.assertEqual(d2.value, 'foobar') + self.assertIsNone(d2.details) + self.assertIsNone(d2.software) + + def test_add_to_system(self): + """Test adding new mmCIF input to existing System""" + s = modelcif.System() + e = modelcif.Entity('AHC') + e._id = '42' + s.entities.append(e) + fh = StringIO(""" +loop_ +_struct_asym.id +_struct_asym.entity_id +_struct_asym.details +A 42 foo +B 99 bar +""") + s2, = modelcif.reader.read(fh, add_to_system=s) + self.assertIs(s2, s) + self.assertEqual(len(s.asym_units), 2) + # asym A should point to existing entity + self.assertEqual(s.asym_units[0].id, 'A') + self.assertIs(s.asym_units[0].entity, e) + + def test_audit_revision_handler(self): + """Test AuditRevisionHistoryHandler""" + # We leverage the support in python-ihm, so only a basic test here + cif = """ +loop_ +_pdbx_audit_revision_history.ordinal +_pdbx_audit_revision_history.data_content_type +_pdbx_audit_revision_history.major_revision +_pdbx_audit_revision_history.minor_revision +_pdbx_audit_revision_history.revision_date +40 'Structure model' 1 0 ? +41 'Structure model' 1 0 . +42 'Structure model' 2 0 1979-05-03 +""" + s, = modelcif.reader.read(StringIO(cif)) + r1, r2, r3 = s.revisions + self.assertEqual(r3.major, 2) + self.assertEqual(r3.minor, 0) + self.assertEqual(r3.date, datetime.date(1979, 5, 3)) + + def test_data_usage_handler(self): + """Test DataUsageHandler""" + # We leverage the support in python-ihm, so only a basic test here + cif = """ +loop_ +_pdbx_data_usage.id +_pdbx_data_usage.type +_pdbx_data_usage.details +_pdbx_data_usage.url +_pdbx_data_usage.name +1 license 'some license' someurl somename +""" + s, = modelcif.reader.read(StringIO(cif)) + d1, = s.data_usage + self.assertEqual(d1.details, "some license") + + def test_atom_site_handler_water(self): + """Test AtomSiteHandler reading water molecules""" + fh = StringIO(""" +loop_ +_entity.id +_entity.type +1 water +loop_ +_struct_asym.id +_struct_asym.entity_id +_struct_asym.details +A 1 Water +B 1 Water +# +loop_ +_pdbx_nonpoly_scheme.asym_id +_pdbx_nonpoly_scheme.entity_id +_pdbx_nonpoly_scheme.mon_id +_pdbx_nonpoly_scheme.ndb_seq_num +_pdbx_nonpoly_scheme.pdb_seq_num +_pdbx_nonpoly_scheme.auth_seq_num +_pdbx_nonpoly_scheme.auth_mon_id +_pdbx_nonpoly_scheme.pdb_strand_id +_pdbx_nonpoly_scheme.pdb_ins_code +A 1 HOH 1 50 500 HOH A . +# +loop_ +_atom_site.group_PDB +_atom_site.id +_atom_site.type_symbol +_atom_site.label_atom_id +_atom_site.label_alt_id +_atom_site.label_comp_id +_atom_site.label_seq_id +_atom_site.auth_seq_id +_atom_site.pdbx_PDB_ins_code +_atom_site.label_asym_id +_atom_site.Cartn_x +_atom_site.Cartn_y +_atom_site.Cartn_z +_atom_site.occupancy +_atom_site.label_entity_id +_atom_site.auth_asym_id +_atom_site.B_iso_or_equiv +_atom_site.pdbx_PDB_model_num +_atom_site.ihm_model_id +HETATM 1 O O . HOH . 40 ? A 10.000 10.000 10.000 . 1 A . 1 1 +HETATM 2 O O . HOH . 50 ? A 10.000 10.000 10.000 . 1 A . 1 1 +HETATM 3 O O . HOH . 60 . A 20.000 20.000 20.000 . 1 A . 1 1 +HETATM 4 O O . HOH . 70 . B 20.000 20.000 20.000 . 1 B . 1 1 +""") + s, = modelcif.reader.read(fh) + m = s.model_groups[0][0] + a1, a2, a3, b1 = m._atoms + # Should include info from both atom_site and scheme table + self.assertEqual(a1.asym_unit.auth_seq_id_map, + {1: (40, None), 2: (50, None), 3: (60, None)}) + self.assertEqual(a1.asym_unit.orig_auth_seq_id_map, + {2: 500}) + self.assertEqual(b1.asym_unit.auth_seq_id_map, {1: (70, None)}) + self.assertIsNone(b1.asym_unit.orig_auth_seq_id_map) + # Should get a WaterAsymUnit, not regular AsymUnit + self.assertIsInstance(a1.asym_unit, modelcif.WaterAsymUnit) + self.assertIsInstance(b1.asym_unit, modelcif.WaterAsymUnit) + # seq_id should be assigned based on atom_site + self.assertEqual(a1.seq_id, 1) + self.assertEqual(a2.seq_id, 2) + self.assertEqual(a3.seq_id, 3) + self.assertEqual(b1.seq_id, 1) + + def test_nonpoly_scheme_handler(self): + """Test NonPolySchemeHandler""" + fh = StringIO(""" +loop_ +_chem_comp.id +_chem_comp.type +_chem_comp.name +CA non-polymer 'CALCIUM ION' +# +loop_ +_entity.id +_entity.type +_entity.pdbx_description +1 non-polymer 'CALCIUM ION entity' +2 non-polymer 'no-chem-comp entity' +3 water 'no-chem-comp water' +# +loop_ +_pdbx_entity_nonpoly.entity_id +_pdbx_entity_nonpoly.name +_pdbx_entity_nonpoly.comp_id +1 'CALCIUM ION' CA +# +loop_ +_struct_asym.id +_struct_asym.entity_id +_struct_asym.details +A 1 foo +B 2 bar +C 3 baz +# +loop_ +_pdbx_nonpoly_scheme.asym_id +_pdbx_nonpoly_scheme.entity_id +_pdbx_nonpoly_scheme.mon_id +_pdbx_nonpoly_scheme.ndb_seq_num +_pdbx_nonpoly_scheme.pdb_seq_num +_pdbx_nonpoly_scheme.auth_seq_num +_pdbx_nonpoly_scheme.pdb_strand_id +_pdbx_nonpoly_scheme.pdb_ins_code +A 1 BAR 1 101 202 . . +B 2 BAR 1 1 1 Q X +C 3 HOH . 1 1 . . +C 3 HOH 2 2 2 . . +C 3 HOH 3 5 10 . . +C 3 HOH 4 1 20 . . +C 3 HOH 5 7 7 . . +""") + s, = modelcif.reader.read(fh) + e1, e2, e3 = s.entities + # e1 should have sequence filled in by pdbx_entity_nonpoly + self.assertEqual([cc.name for cc in e1.sequence], ['CALCIUM ION']) + # e2,e3 should have sequence filled in by pdbx_nonpoly_scheme + self.assertEqual([(cc.id, cc.name) for cc in e2.sequence], + [('BAR', 'no-chem-comp entity')]) + self.assertEqual([(cc.id, cc.name) for cc in e3.sequence], + [('HOH', 'WATER')]) + asym, a2, a3 = s.asym_units + # non-polymers have no seq_id_range + self.assertEqual(asym.seq_id_range, (None, None)) + self.assertEqual(asym.auth_seq_id_map, {1: (101, None)}) + self.assertEqual(asym.residue(1).auth_seq_id, 101) + self.assertIsNone(asym.residue(1).ins_code) + self.assertEqual(asym.strand_id, asym._id) + self.assertIsNone(asym._strand_id) + self.assertEqual(asym.orig_auth_seq_id_map, {1: 202}) + + self.assertEqual(a2.auth_seq_id_map, {1: (1, 'X')}) + self.assertEqual(a2.residue(1).auth_seq_id, 1) + self.assertEqual(a2.residue(1).ins_code, 'X') + self.assertEqual(a2.strand_id, 'Q') + self.assertEqual(a2._strand_id, 'Q') + self.assertIsNone(a2.orig_auth_seq_id_map) + + self.assertEqual(a3.auth_seq_id_map, {1: (1, None), 2: (2, None), + 3: (5, None), 4: (1, None), + 5: (7, None)}) + self.assertEqual(a3.orig_auth_seq_id_map, {3: 10, 4: 20}) + + def test_poly_seq_scheme_handler_offset(self): + """Test PolySeqSchemeHandler with constant offset""" + fh = StringIO(ASYM_ENTITY + """ +loop_ +_pdbx_poly_seq_scheme.asym_id +_pdbx_poly_seq_scheme.entity_id +_pdbx_poly_seq_scheme.seq_id +_pdbx_poly_seq_scheme.pdb_seq_num +_pdbx_poly_seq_scheme.pdb_strand_id +A 1 1 6 A +A 1 2 7 A +A 1 3 8 A +A 1 4 9 A +""") + s, = modelcif.reader.read(fh) + asym, = s.asym_units + self.assertEqual(asym.auth_seq_id_map, 5) + self.assertIsNone(asym._strand_id) + self.assertEqual([asym.residue(i).auth_seq_id for i in range(1, 5)], + [6, 7, 8, 9]) + self.assertIsNone(asym.orig_auth_seq_id_map) + + def test_poly_seq_scheme_handler_offset_ins_code(self): + """Test PolySeqSchemeHandler with constant offset but inscodes""" + fh = StringIO(ASYM_ENTITY + """ +loop_ +_pdbx_poly_seq_scheme.asym_id +_pdbx_poly_seq_scheme.entity_id +_pdbx_poly_seq_scheme.seq_id +_pdbx_poly_seq_scheme.pdb_seq_num +_pdbx_poly_seq_scheme.pdb_strand_id +_pdbx_poly_seq_scheme.pdb_ins_code +A 1 1 6 A . +A 1 2 7 A . +A 1 3 8 A . +A 1 4 9 A A +""") + s, = modelcif.reader.read(fh) + asym, = s.asym_units + self.assertEqual(asym.auth_seq_id_map, + {1: (6, None), 2: (7, None), 3: (8, None), + 4: (9, 'A')}) + self.assertIsNone(asym._strand_id) + self.assertEqual([asym.residue(i).auth_seq_id for i in range(1, 5)], + [6, 7, 8, 9]) + self.assertIsNone(asym.residue(1).ins_code) + self.assertEqual(asym.residue(4).ins_code, 'A') + self.assertIsNone(asym.orig_auth_seq_id_map) + + def test_poly_seq_scheme_handler_empty(self): + """Test PolySeqSchemeHandler with no poly_seq_scheme""" + fh = StringIO(ASYM_ENTITY) + s, = modelcif.reader.read(fh) + asym, = s.asym_units + self.assertEqual(asym.auth_seq_id_map, 0) + self.assertEqual([asym.residue(i).auth_seq_id for i in range(1, 5)], + [1, 2, 3, 4]) + self.assertIsNone(asym.orig_auth_seq_id_map) + + def test_poly_seq_scheme_handler_nop(self): + """Test PolySeqSchemeHandler with a do-nothing poly_seq_scheme""" + fh = StringIO(ASYM_ENTITY + """ +loop_ +_pdbx_poly_seq_scheme.asym_id +_pdbx_poly_seq_scheme.entity_id +_pdbx_poly_seq_scheme.seq_id +_pdbx_poly_seq_scheme.pdb_seq_num +A 1 1 1 +A 1 2 2 +A 1 3 3 +""") + s, = modelcif.reader.read(fh) + asym, = s.asym_units + self.assertEqual(asym.auth_seq_id_map, 0) + self.assertEqual([asym.residue(i).auth_seq_id for i in range(1, 5)], + [1, 2, 3, 4]) + self.assertIsNone(asym.orig_auth_seq_id_map) + + def test_poly_seq_scheme_handler_partial(self): + """Test PolySeqSchemeHandler with partial information""" + fh = StringIO(ASYM_ENTITY + """ +loop_ +_pdbx_poly_seq_scheme.asym_id +_pdbx_poly_seq_scheme.entity_id +_pdbx_poly_seq_scheme.seq_id +_pdbx_poly_seq_scheme.pdb_seq_num +_pdbx_poly_seq_scheme.auth_seq_num +A 1 1 6 . +A 1 2 7 9 +A 1 3 8 . +""") + s, = modelcif.reader.read(fh) + asym, = s.asym_units + # No mapping for residue 4 (and no insertion codes at all) + self.assertEqual(asym.auth_seq_id_map, {1: (6, None), 2: (7, None), + 3: (8, None)}) + self.assertEqual([asym.residue(i).auth_seq_id for i in range(1, 5)], + [6, 7, 8, 4]) + self.assertIsNone(asym.residue(1).ins_code) + self.assertEqual(asym.orig_auth_seq_id_map, {2: 9}) + + def test_poly_seq_scheme_handler_incon_off(self): + """Test PolySeqSchemeHandler with inconsistent offset""" + fh = StringIO(ASYM_ENTITY + """ +loop_ +_pdbx_poly_seq_scheme.asym_id +_pdbx_poly_seq_scheme.entity_id +_pdbx_poly_seq_scheme.seq_id +_pdbx_poly_seq_scheme.pdb_seq_num +_pdbx_poly_seq_scheme.pdb_strand_id +A 1 1 6 X +A 1 2 7 X +A 1 3 8 X +A 1 4 10 X +""") + s, = modelcif.reader.read(fh) + asym, = s.asym_units + self.assertEqual(asym._strand_id, 'X') + self.assertEqual(asym.auth_seq_id_map, {1: (6, None), 2: (7, None), + 3: (8, None), 4: (10, None)}) + self.assertEqual([asym.residue(i).auth_seq_id for i in range(1, 5)], + [6, 7, 8, 10]) + self.assertIsNone(asym.residue(1).ins_code) + self.assertIsNone(asym.orig_auth_seq_id_map) + + def test_poly_seq_scheme_handler_unknown_auth_seq(self): + """Test PolySeqSchemeHandler with explicit unknown auth_seq_num""" + fh = StringIO(ASYM_ENTITY + """ +loop_ +_pdbx_poly_seq_scheme.asym_id +_pdbx_poly_seq_scheme.entity_id +_pdbx_poly_seq_scheme.seq_id +_pdbx_poly_seq_scheme.pdb_seq_num +_pdbx_poly_seq_scheme.auth_seq_num +_pdbx_poly_seq_scheme.pdb_strand_id +A 1 1 1 1 A +A 1 2 2 2 A +A 1 3 3 ? A +A 1 4 4 4 A +""") + s, = modelcif.reader.read(fh) + asym, = s.asym_units + self.assertEqual(asym.auth_seq_id_map, 0) + self.assertEqual(asym.orig_auth_seq_id_map, {3: ihm.unknown}) + + def test_poly_seq_scheme_handler_str_seq_id(self): + """Test PolySeqSchemeHandler with a non-integer pdb_seq_num""" + fh = StringIO(ASYM_ENTITY + """ +loop_ +_pdbx_poly_seq_scheme.asym_id +_pdbx_poly_seq_scheme.entity_id +_pdbx_poly_seq_scheme.seq_id +_pdbx_poly_seq_scheme.pdb_seq_num +_pdbx_poly_seq_scheme.auth_seq_num +_pdbx_poly_seq_scheme.pdb_strand_id +_pdbx_poly_seq_scheme.pdb_ins_code +A 1 1 6 6 ? . +A 1 2 7 12 ? . +A 1 3 8 24 ? . +A 1 4 9A 48A ? . +""") + s, = modelcif.reader.read(fh) + asym, = s.asym_units + self.assertIsNone(asym._strand_id) + self.assertEqual(asym.auth_seq_id_map, {1: (6, None), 2: (7, None), + 3: (8, None), 4: ('9A', None)}) + self.assertEqual([asym.residue(i).auth_seq_id for i in range(1, 5)], + [6, 7, 8, '9A']) + self.assertIsNone(asym.residue(1).ins_code) + self.assertIsNone(asym.residue(3).ins_code) + self.assertEqual(asym.orig_auth_seq_id_map, {2: 12, 3: 24, 4: '48A'}) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_reference.py b/test/test_reference.py new file mode 100644 index 0000000..460b2f3 --- /dev/null +++ b/test/test_reference.py @@ -0,0 +1,62 @@ +import utils +import os +import unittest + +TOPDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +utils.set_search_paths(TOPDIR) +import modelcif.reference + + +class Tests(unittest.TestCase): + def test_template_reference(self): + """Test TemplateReference classes""" + ref = modelcif.reference.PDB("1abc") + self.assertEqual(ref.name, "PDB") + self.assertIsNone(ref.other_details) + + # generic "other" reference + ref = modelcif.reference.TemplateReference("1abc") + self.assertEqual(ref.name, "Other") + self.assertIsNone(ref.other_details) + + # custom "other" reference + class CustomRef(modelcif.reference.TemplateReference): + """foo + bar""" + + ref = CustomRef("1abc") + self.assertEqual(ref.name, "Other") + self.assertEqual(ref.other_details, "foo") + + def test_target_reference(self): + """Test TargetReference classes""" + ref = modelcif.reference.UniProt("code", "acc", sequence='CC') + self.assertEqual(ref.name, "UNP") + self.assertIsNone(ref.other_details) + + # Reference with (deprecated) align begin, end + self.assertWarns(UserWarning, modelcif.reference.UniProt, + "code", "acc", align_begin=1, align_end=10, + sequence='CC') + + # Reference without explicit sequence + self.assertWarns(UserWarning, modelcif.reference.UniProt, + "code", "acc") + + # generic "other" reference + ref = modelcif.reference.TargetReference("code", "acc", sequence='CC') + self.assertEqual(ref.name, "Other") + self.assertIsNone(ref.other_details) + + # custom "other" reference + class CustomRef(modelcif.reference.TargetReference): + """foo + bar""" + + ref = CustomRef("code", "acc", sequence='CC') + self.assertEqual(ref.name, "Other") + self.assertEqual(ref.other_details, "foo") + + +if __name__ == '__main__': + unittest.main() diff --git a/test/utils.py b/test/utils.py new file mode 100644 index 0000000..050eaab --- /dev/null +++ b/test/utils.py @@ -0,0 +1,51 @@ +import os +import sys +import tempfile +import contextlib +import shutil + + +def set_search_paths(topdir): + """Set search paths so that we can import Python modules""" + os.environ['PYTHONPATH'] = topdir + os.pathsep \ + + os.environ.get('PYTHONPATH', '') + sys.path.insert(0, topdir) + + +def get_input_file_name(topdir, fname): + """Return full path to a test input file""" + return os.path.join(topdir, 'test', 'input', fname) + + +@contextlib.contextmanager +def temporary_directory(dir=None): + _tmpdir = tempfile.mkdtemp(dir=dir) + yield _tmpdir + shutil.rmtree(_tmpdir, ignore_errors=True) + + +if 'coverage' in sys.modules: + import atexit + # Collect coverage information from subprocesses + __site_tmpdir = tempfile.mkdtemp() + with open(os.path.join(__site_tmpdir, 'sitecustomize.py'), 'w') as fh: + fh.write(""" +import coverage +import atexit +import os + +_cov = coverage.coverage(branch=True, data_suffix=True, auto_data=True, + data_file=os.path.join('%s', '.coverage')) +_cov.start() + +def _coverage_cleanup(c): + c.stop() +atexit.register(_coverage_cleanup, _cov) +""" % os.getcwd()) + + os.environ['PYTHONPATH'] = __site_tmpdir + os.pathsep \ + + os.environ.get('PYTHONPATH', '') + + def __cleanup(d): + shutil.rmtree(d, ignore_errors=True) + atexit.register(__cleanup, __site_tmpdir) |
