summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/input/mini.cif116
-rw-r--r--test/input/no_title.cif3
-rw-r--r--test/input/not_modeled.cif89
-rw-r--r--test/input/struct_only.cif4
-rw-r--r--test/test_alignment.py37
-rw-r--r--test/test_associated.py19
-rw-r--r--test/test_descriptor.py19
-rw-r--r--test/test_dumper.py1579
-rw-r--r--test/test_edit.py150
-rw-r--r--test/test_examples.py103
-rw-r--r--test/test_main.py230
-rw-r--r--test/test_make_mmcif.py97
-rw-r--r--test/test_model.py33
-rw-r--r--test/test_qa_metric.py101
-rw-r--r--test/test_reader.py2003
-rw-r--r--test/test_reference.py62
-rw-r--r--test/utils.py51
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)