summaryrefslogtreecommitdiff
path: root/test/test_reader.py
diff options
Diffstat (limited to 'test/test_reader.py')
-rw-r--r--test/test_reader.py2003
1 files changed, 2003 insertions, 0 deletions
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()