summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorSteffen Moeller <moeller@debian.org>2025-10-03 16:07:11 +0100
committergit-ubuntu importer <ubuntu-devel-discuss@lists.ubuntu.com>2025-10-28 22:36:18 +0000
commit8ed8247f0973f900898167c0eebf94825504b2ce (patch)
tree9ab3f04e1ae81543bbd7fdce37786a2cc34f35c7 /examples
Imported using git-ubuntu import.
Notes
Notes:
Diffstat (limited to 'examples')
-rw-r--r--examples/README.md24
-rw-r--r--examples/associated.py50
-rw-r--r--examples/convert_bcif.py15
-rw-r--r--examples/input/ligands.cif323
-rw-r--r--examples/ligands.py98
-rw-r--r--examples/mkmodbase.py216
-rwxr-xr-xexamples/validate_mmcif.py27
-rw-r--r--examples/validate_modbase.py36
8 files changed, 789 insertions, 0 deletions
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..6e8b2b9
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,24 @@
+These examples demonstrate some of the functionality of the python-modelcif
+software. See [the documentation](https://python-modelcif.readthedocs.io/)
+for more information.
+
+ - [convert\_bcif.py](convert_bcif.py) converts an mmCIF file containing
+ ModelCIF data to [BinaryCIF](https://github.com/molstar/BinaryCIF) format.
+ - [ligands.py](ligands.py) demonstrates writing an mmCIF file of a typical
+ single-template homology or comparative model, including a ligand.
+ - [mkmodbase.py](mkmodbase.py) demonstrates using the library to make an mmCIF
+ file of a typical single-template single-chain homology or comparative
+ model, similar to those generated by
+ [MODELLER](https://salilab.org/modeller/) and deposited in the
+ [ModBase database](https://modbase.compbio.ucsf.edu/).
+ - [associated.py](associated.py) demonstrates reading a deposition from
+ [ModelArchive](https://www.modelarchive.org/) that has been split into
+ multiple mmCIF files.
+ - [validate\_mmcif.py](validate_mmcif.py) demonstrates the use of the
+ library's validator to validate a user-provided input mmCIF file against the
+ [ModelCIF dictionary](https://mmcif.wwpdb.org/dictionaries/mmcif_ma.dic/Index/).
+ - [validate\_modbase.py](validate_modbase.py) demonstrates downloading an
+ mmCIF structure from the [ModBase database](https://modbase.compbio.ucsf.edu/)
+ and validating it against the
+ [ModelCIF dictionary](https://mmcif.wwpdb.org/dictionaries/mmcif_ma.dic/Index/)
+ for compliance.
diff --git a/examples/associated.py b/examples/associated.py
new file mode 100644
index 0000000..00ae002
--- /dev/null
+++ b/examples/associated.py
@@ -0,0 +1,50 @@
+# This example demonstrates reading ModelCIF "associated" files.
+# Some repositories, such as ModelArchive, split the deposited data into
+# multiple mmCIF files, putting some of the quality score information not
+# into the main mmCIF file but into a separate "associated" file.
+# These associated files are referenced in the main file
+# (see System.repositories) so we can programmatically download and
+# extract them.
+# This example requires Python 3.
+
+import urllib.request
+import zipfile
+import tempfile
+import shutil
+import modelcif.reader
+
+
+# Get any associated files containing pairwise QA scores
+def _get_zip_scores_files(s):
+ for repo in s.repositories:
+ for f in repo.files:
+ if isinstance(f, modelcif.associated.ZipFile):
+ for zf in f.files:
+ if isinstance(
+ zf, modelcif.associated.QAMetricsFile):
+ yield zf, f, repo
+
+
+# Download entry ma-bak-cepc-0944 directly from ModelArchive
+url = "https://www.modelarchive.org/doi/10.5452/ma-bak-cepc-0944.cif"
+with urllib.request.urlopen(url) as fh:
+ s, = modelcif.reader.read(fh)
+
+
+# Get any referenced associated files containing QA scores. For ModelArchive,
+# these are stored in an mmCIF file that is then compressed into a zip file
+for scores, archive, repo in _get_zip_scores_files(s):
+ url = repo.get_url(archive)
+ # Download the referenced zip file directly from ModelArchive
+ with urllib.request.urlopen(repo.get_url(archive)) as f_url:
+ with tempfile.NamedTemporaryFile() as f_zip:
+ shutil.copyfileobj(f_url, f_zip)
+ # Extract the scores file from the zip file
+ with zipfile.ZipFile(f_zip) as zf:
+ with zf.open(scores.path) as f_scores:
+ # Add scores in the file to our existing System
+ modelcif.reader.read(f_scores, add_to_system=s)
+
+for mg in s.model_groups:
+ for m in mg:
+ print("Model %s contains %d QA metrics" % (m, len(m.qa_metrics)))
diff --git a/examples/convert_bcif.py b/examples/convert_bcif.py
new file mode 100644
index 0000000..2e109f8
--- /dev/null
+++ b/examples/convert_bcif.py
@@ -0,0 +1,15 @@
+# This example demonstrates using the library to convert an mmCIF file
+# containing ModelCIF data to BinaryCIF format.
+
+# Import used classes.
+import modelcif
+import modelcif.dumper
+import modelcif.reader
+
+# Read in an existing mmCIF file:
+with open('input/ligands.cif') as fh:
+ systems = modelcif.reader.read(fh, format='mmCIF')
+
+# Write a new BinaryCIF file containing the same data:
+with open('ligands.bcif', 'wb') as fh:
+ modelcif.dumper.write(fh, systems, format='BCIF')
diff --git a/examples/input/ligands.cif b/examples/input/ligands.cif
new file mode 100644
index 0000000..782c305
--- /dev/null
+++ b/examples/input/ligands.cif
@@ -0,0 +1,323 @@
+data_model
+_entry.id model
+_struct.entry_id model
+_struct.pdbx_model_details .
+_struct.pdbx_structure_determination_methodology computational
+_struct.title 'Ligand example'
+_audit_conform.dict_location https://raw.githubusercontent.com/ihmwg/ModelCIF/557bda7/base/mmcif_ma-core.dic
+_audit_conform.dict_name mmcif_ma.dic
+_audit_conform.dict_version 1.4.1
+#
+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'
+ASN 'L-peptide linking' ASPARAGINE 'C4 H8 N2 O3' 132.119 'CCD Core'
+ASP 'L-peptide linking' 'ASPARTIC ACID' 'C4 H7 N O4' 133.103 'CCD Core'
+CYS 'L-peptide linking' CYSTEINE 'C3 H7 N O2 S' 121.154 'CCD Core'
+ILE 'L-peptide linking' ISOLEUCINE 'C6 H13 N O2' 131.175 'CCD Core'
+LYS 'L-peptide linking' LYSINE 'C6 H15 N2 O2 1' 147.198 'CCD Core'
+PHE 'L-peptide linking' PHENYLALANINE 'C9 H11 N O2' 165.192 'CCD Core'
+SER 'L-peptide linking' SERINE 'C3 H7 N O3' 105.093 'CCD Core'
+SF4 non-polymer 'IRON/SULFUR CLUSTER' 'Fe4 S4' 351.620 'CCD Core'
+THR 'L-peptide linking' THREONINE 'C4 H9 N O3' 119.120 'CCD Core'
+TYR 'L-peptide linking' TYROSINE 'C9 H11 N O3' 181.191 'CCD Core'
+VAL 'L-peptide linking' VALINE 'C5 H11 N O2' 117.148 'CCD Core'
+#
+#
+loop_
+_entity.id
+_entity.type
+_entity.src_method
+_entity.pdbx_description
+_entity.formula_weight
+_entity.pdbx_number_of_molecules
+_entity.details
+1 polymer man 'Model subunit' 1230.346 1 .
+2 non-polymer man 'IRON/SULFUR CLUSTER' 351.620 1 .
+#
+#
+loop_
+_entity_poly.entity_id
+_entity_poly.type
+_entity_poly.nstd_linkage
+_entity_poly.nstd_monomer
+_entity_poly.pdbx_strand_id
+_entity_poly.pdbx_seq_one_letter_code
+_entity_poly.pdbx_seq_one_letter_code_can
+1 polypeptide(L) no no A AYVINDSCIA AYVINDSCIA
+#
+#
+loop_
+_pdbx_entity_nonpoly.entity_id
+_pdbx_entity_nonpoly.name
+_pdbx_entity_nonpoly.comp_id
+_pdbx_entity_nonpoly.ma_model_mode
+2 'IRON/SULFUR CLUSTER' SF4 implicit
+#
+#
+loop_
+_entity_poly_seq.entity_id
+_entity_poly_seq.num
+_entity_poly_seq.mon_id
+_entity_poly_seq.hetero
+1 1 ALA .
+1 2 TYR .
+1 3 VAL .
+1 4 ILE .
+1 5 ASN .
+1 6 ASP .
+1 7 SER .
+1 8 CYS .
+1 9 ILE .
+1 10 ALA .
+#
+#
+loop_
+_struct_asym.id
+_struct_asym.entity_id
+_struct_asym.details
+A 1 'Model subunit A'
+B 2 'Model subunit B'
+#
+#
+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 ALA 1 1 ALA ALA A .
+A 1 2 TYR 2 2 TYR TYR A .
+A 1 3 VAL 3 3 VAL VAL A .
+A 1 4 ILE 4 4 ILE ILE A .
+A 1 5 ASN 5 5 ASN ASN A .
+A 1 6 ASP 6 6 ASP ASP A .
+A 1 7 SER 7 7 SER SER A .
+A 1 8 CYS 8 8 CYS CYS A .
+A 1 9 ILE 9 9 ILE ILE A .
+A 1 10 ALA 10 10 ALA ALA A .
+#
+#
+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
+B 2 SF4 1 1 1 SF4 B .
+#
+#
+loop_
+_ma_data.id
+_ma_data.name
+_ma_data.content_type
+_ma_data.content_type_other_details
+1 'Template polymer' 'template structure' .
+2 'Template ligand' 'template structure' .
+3 'Model subunit' target .
+4 'IRON/SULFUR CLUSTER' target .
+5 'Modeling alignment' 'target-template alignment' .
+6 'Best scoring model' 'model coordinates' .
+#
+#
+loop_
+_ma_data_group.ordinal_id
+_ma_data_group.group_id
+_ma_data_group.data_id
+1 1 5
+2 2 6
+#
+#
+loop_
+_ma_target_entity.entity_id
+_ma_target_entity.data_id
+_ma_target_entity.origin
+1 3 designed
+2 4 designed
+#
+#
+loop_
+_ma_target_entity_instance.asym_id
+_ma_target_entity_instance.entity_id
+_ma_target_entity_instance.details
+A 1 'Model subunit A'
+B 2 'Model subunit B'
+#
+#
+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 0 0
+#
+#
+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' non-polymer 1 2 B B . 1 B
+#
+#
+loop_
+_ma_template_poly.template_id
+_ma_template_poly.seq_one_letter_code
+_ma_template_poly.seq_one_letter_code_can
+1 AFVVTDNCIKCK AFVVTDNCIKCK
+#
+#
+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 12
+#
+#
+loop_
+_ma_template_non_poly.template_id
+_ma_template_non_poly.comp_id
+_ma_template_non_poly.details
+2 SF4 'IRON/SULFUR CLUSTER'
+#
+#
+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 . 5fd1 .
+2 PDB . 5fd1 .
+#
+#
+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 10
+#
+#
+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 5 . 12 'target-template pairwise alignment' global
+#
+#
+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 45.000 'Length of the shorter sequence' .
+#
+#
+loop_
+_ma_alignment.ordinal_id
+_ma_alignment.alignment_id
+_ma_alignment.target_template_flag
+_ma_alignment.sequence
+1 1 1 AYVINDSC--IA
+2 1 2 AFVVTDNCIKCK
+#
+#
+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 modeling . . . 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.data_id
+_ma_model_list.model_type
+_ma_model_list.model_type_other_details
+1 1 1 'Best scoring model' 'All models' 6 '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
+ATOM 2 C CA . TYR 2 2 ? A 4.000 5.000 6.000 . 1 A . 1
+ATOM 3 C CA . VAL 3 3 ? A 7.000 8.000 9.000 . 1 A . 1
+HETATM 4 FE FE . SF4 . 1 ? B 10.000 10.000 10.000 . 2 B . 1
+#
+#
+loop_
+_atom_type.symbol
+C
+FE
+#
diff --git a/examples/ligands.py b/examples/ligands.py
new file mode 100644
index 0000000..244961e
--- /dev/null
+++ b/examples/ligands.py
@@ -0,0 +1,98 @@
+# This example demonstrates writing an mmCIF file of a typical
+# single-template homology or comparative model, including a ligand.
+#
+# This is very similar to the mkmodbase.py example; see that example for
+# more details.
+
+# Import used classes
+import modelcif
+import modelcif.model
+import modelcif.dumper
+import modelcif.reference
+import modelcif.protocol
+import modelcif.alignment
+from modelcif.alignment import ShorterSequenceIdentity as SequenceIdentity
+import ihm
+
+system = modelcif.System(title='Ligand example')
+
+# Describe the amino acid (polymer) sequences as Entity objects, for both
+# template and model:
+template_e = modelcif.Entity('AFVVTDNCIKCK', description='Template subunit')
+model_e = modelcif.Entity('AYVINDSCIA', description='Model subunit')
+
+# For non-polymers (e.g. ligands) we need to describe the chemistry of the
+# ligand as a chemical component object, then create an Entity using that
+# component. We only need to do this once because the ligand is the same
+# in both template and model:
+sf4 = ihm.NonPolymerChemComp("SF4", name='IRON/SULFUR CLUSTER',
+ formula='Fe4 S4')
+ligand_e = modelcif.Entity([sf4], description='IRON/SULFUR CLUSTER')
+
+# Create a Template for each chain (amino acids in chain A, ligand in chain B)
+# and point to the original PDB, 5fd1:
+s = modelcif.reference.PDB('5fd1')
+templateA = modelcif.Template(
+ entity=template_e, asym_id='A', model_num=1, name="Template polymer",
+ transformation=modelcif.Transformation.identity(), references=[s])
+templateB = modelcif.Template(
+ entity=ligand_e, asym_id='B', model_num=1, name='Template ligand',
+ transformation=modelcif.Transformation.identity(), references=[s])
+
+# Define the model assembly, as two AsymUnits. NonPolymerFromTemplate is a
+# subclass of AsymUnit that additionally notes the Template from which it
+# was derived. In this case we state that the ligand was simply copied from
+# the template into the target (explicit=False):
+asymA = modelcif.AsymUnit(model_e, details='Model subunit A', id='A')
+asymB = modelcif.NonPolymerFromTemplate(template=templateB, explicit=False,
+ details='Model subunit B', id='B')
+modeled_assembly = modelcif.Assembly((asymA, asymB), name='Modeled assembly')
+
+
+# For the amino acid chain, add the modeling alignment, just as in the
+# mkmodbase.py example:
+class Alignment(modelcif.alignment.Global, modelcif.alignment.Pairwise):
+ pass
+
+
+p = modelcif.alignment.Pair(
+ template=templateA.segment("AFVVTDNCIKCK", 1, 12),
+ target=asymA.segment("AYVINDSC--IA", 1, 10),
+ score=modelcif.alignment.BLASTEValue(1e-15),
+ identity=SequenceIdentity(45.0))
+aln = Alignment(name="Modeling alignment",
+ pairs=[p])
+system.alignments.append(aln)
+
+# Add model coordinates, similarly to the mkmodbase.py example.
+# Note that nonpolymers are not "sequences" and so seq_id=None.
+atoms = [('A', 1, 'C', 'CA', 1., 2., 3.),
+ ('A', 2, 'C', 'CA', 4., 5., 6.),
+ ('A', 3, 'C', 'CA', 7., 8., 9.),
+ ('B', None, 'FE', 'FE', 10., 10., 10.)]
+
+
+class MyModel(modelcif.model.HomologyModel):
+ asym_unit_map = {'A': asymA, 'B': asymB}
+
+ def get_atoms(self):
+ for asym, seq_id, type_symbol, atom_id, x, y, z in atoms:
+ yield modelcif.model.Atom(
+ asym_unit=self.asym_unit_map[asym], type_symbol=type_symbol,
+ seq_id=seq_id, atom_id=atom_id, x=x, y=y, z=z,
+ het=seq_id is None)
+
+
+# Add the model and modeling protocol to the file and write them out:
+model = MyModel(assembly=modeled_assembly, name='Best scoring model')
+
+model_group = modelcif.model.ModelGroup([model], name='All models')
+system.model_groups.append(model_group)
+
+protocol = modelcif.protocol.Protocol()
+protocol.steps.append(modelcif.protocol.ModelingStep(
+ input_data=aln, output_data=model))
+system.protocols.append(protocol)
+
+with open('output.cif', 'w') as fh:
+ modelcif.dumper.write(fh, [system])
diff --git a/examples/mkmodbase.py b/examples/mkmodbase.py
new file mode 100644
index 0000000..780b535
--- /dev/null
+++ b/examples/mkmodbase.py
@@ -0,0 +1,216 @@
+# This example demonstrates using the library to make an mmCIF file of a
+# typical single-template single-chain homology or comparative model, similar
+# to those generated by MODELLER (https://salilab.org/modeller/) and deposited
+# in the ModBase database (https://modbase.compbio.ucsf.edu/)
+
+# For a more complete (but less documented) script to convert a complete
+# ModBase PDB file into a corresponding mmCIF or BinaryCIF file, see
+# https://github.com/salilab/modbase_utils/blob/main/modbase_pdb_to_cif.py
+
+# Import used classes.
+import modelcif
+import modelcif.protocol
+import modelcif.model
+import modelcif.dumper
+import modelcif.reference
+import modelcif.qa_metric
+import modelcif.alignment
+# Different methods measure "sequence identity" in different ways, so import
+# the class that matches the way Modeller understands it (number of identical
+# aligned residues, divided by the length of the shorter sequence)
+from modelcif.alignment import ShorterSequenceIdentity as SequenceIdentity
+import ihm.citations
+import modelcif.reader
+
+# First, we create a system, which contains everything we know about the
+# modeling. A single mmCIF file can contain multiple Systems, but in most
+# cases we use just one:
+system = modelcif.System(title='S54091 hypothetical protein YPR070w')
+
+# List the authors of this file (here these are the ModBase authors)
+system.authors.extend(('Pieper U', 'Webb B', 'Narayanan E', 'Sali A'))
+
+# Describe the software that was used in the modeling
+modpipe_software = modelcif.Software(
+ name='ModPipe', classification='comparative modeling',
+ location='https://salilab.org/modpipe/', type='program',
+ version='SVN.r1703', description='Comparative modeling pipeline')
+# Every object we create must ultimately be linked to the System, which
+# maintains simple lists for each type of object. For example, there is a
+# list system.software (like system.authors above) which can be used for
+# any Software object not referenced by any other object. But in this case
+# we're going to use these Software objects further on in the script, so
+# don't need to explicitly add them here.
+
+modeller_software = modelcif.Software(
+ name='MODELLER', classification='comparative modeling',
+ location='https://salilab.org/modeller/', type='program',
+ version='SVN', citation=ihm.citations.modeller,
+ description='Comparative modeling by satisfaction of spatial restraints')
+
+# Next, we define "entities", unique sequences in the system, as Entity
+# objects. First, the template sequence:
+template_e = modelcif.Entity('DMACDTFIKCC', description='Template subunit')
+
+# Next, the target (model) sequence, together with a link to the reference
+# sequence (in UniProt):
+s = modelcif.reference.UniProt(code='MED1_YEAST', accession='Q12321',
+ sequence='DSYVETLDCC')
+model_e = modelcif.Entity('DSYVETLDCC', description='Model subunit',
+ references=[s])
+
+# Next, we define asymmetric units for everything we modeled.
+# These roughly correspond to chains in a traditional PDB file. Multiple
+# asymmetric units may map to the same entity (for example if there are
+# several copies of a given protein).
+asymA = modelcif.AsymUnit(model_e, details='Model subunit A', id='A')
+
+# Next, we group asymmetric units into assemblies.
+modeled_assembly = modelcif.Assembly((asymA,), name='Modeled assembly')
+
+# In a similar fashion, we declare a Template for each chain that we used
+# as a template structure, with a link to the reference structure database
+# (PDB).
+s = modelcif.reference.PDB('3nc1')
+template = modelcif.Template(
+ entity=template_e, asym_id='A', model_num=1, name="Template Structure",
+ transformation=modelcif.Transformation.identity(),
+ references=[s])
+
+
+# Now, we describe the alignment between target and template.
+# python-ma provides various subclasses to use here. All ModBase structures
+# use a simple pairwise global alignment between target and template, so
+# declare a suitable class:
+class Alignment(modelcif.alignment.Global, modelcif.alignment.Pairwise):
+ pass
+
+
+# An alignment consists of a list of aligned target-template segments.
+# Here we provide the residue ranges and the actual alignment, including gaps,
+# between the two, together with the sequence identity and any score available
+# for the alignment (here we have the BLAST e-value):
+p = modelcif.alignment.Pair(
+ template=template.segment("DMACDTFIK", 1, 9),
+ target=asymA.segment("DSYV-ETLD", 1, 8),
+ score=modelcif.alignment.BLASTEValue(1e-15),
+ identity=SequenceIdentity(45.0))
+aln = Alignment(name="Modeling alignment", software=modpipe_software,
+ pairs=[p])
+# Alignments aren't used by any objects; they should be added directly
+# to the System:
+system.alignments.append(aln)
+
+# For the actual model coordinates, we must subclass a suitable class and
+# override the get_atoms() method to return a list of Atom objects. This design
+# avoids having a separate copy of every atom in memory.
+# Modeller models are comparative or homology models, so we subclass
+# HomologyModel. For the purposes of this example, we just return a simple
+# static list of atoms:
+
+atoms = [('A', 1, 'C', 'CA', 1., 2., 3.),
+ ('A', 2, 'C', 'CA', 4., 5., 6.),
+ ('A', 3, 'C', 'CA', 7., 8., 9.),
+ ('A', 4, 'C', 'CA', 10., 11., 12.)]
+
+
+class MyModel(modelcif.model.HomologyModel):
+ # Map our asym unit names to ModelCIF asym_unit objects:
+ asym_unit_map = {'A': asymA}
+
+ def get_atoms(self):
+ for asym, seq_id, type_symbol, atom_id, x, y, z in atoms:
+ yield modelcif.model.Atom(
+ asym_unit=self.asym_unit_map[asym], type_symbol=type_symbol,
+ seq_id=seq_id, atom_id=atom_id, x=x, y=y, z=z)
+
+
+# Link the model to the Assembly that describes all subunits
+model = MyModel(assembly=modeled_assembly, name='Best scoring model')
+
+# Next, we describe the modeling protocol:
+protocol = modelcif.protocol.Protocol()
+protocol.steps.append(modelcif.protocol.TemplateSearchStep(
+ name='ModPipe Seq-Prf (0001)', software=modpipe_software,
+ input_data=model_e, output_data=aln))
+protocol.steps.append(modelcif.protocol.ModelingStep(
+ software=modeller_software, input_data=aln, output_data=model))
+protocol.steps.append(modelcif.protocol.ModelSelectionStep(
+ software=modpipe_software, input_data=model, output_data=model))
+# Protocols aren't used by any other objects; they should be added directly
+# to the System:
+system.protocols.append(protocol)
+
+
+# We can also attach quality scores to our model(s). To do this we must
+# first define the scores by creating subclasses using a MetricMode
+# (e.g. global, per-residue) and a MetricType (e.g. distance, z-score).
+# Here we define the quality scores used by the ModPipe pipeline that is used
+# by ModBase. Note that one score (MPQS) uses a custom metric type, while
+# another (zDOPE) is a simple global z-score:
+class MPQSMetricType(modelcif.qa_metric.MetricType):
+ """composite score, values >1.1 are considered reliable"""
+
+
+class MPQS(modelcif.qa_metric.Global, MPQSMetricType):
+ """ModPipe Quality Score"""
+ software = modpipe_software
+
+
+class zDOPE(modelcif.qa_metric.Global, modelcif.qa_metric.ZScore):
+ """Normalized DOPE"""
+ software = modeller_software
+
+
+class TSVModRMSD(modelcif.qa_metric.Global, modelcif.qa_metric.Distance):
+ """TSVMod predicted RMSD (MSALL)"""
+ software = None
+
+
+class TSVModNO35(modelcif.qa_metric.Global,
+ modelcif.qa_metric.NormalizedScore):
+ """TSVMod predicted native overlap (MSALL)"""
+ software = None
+
+
+# Add qa metrics to the model
+model.qa_metrics.extend((MPQS(0.853452), zDOPE(0.31), TSVModRMSD(12.996),
+ TSVModNO35(0.143)))
+
+# All ModBase QA metrics are global, but the library also supports per-residue
+# or pairwise (between two residues) scores. Here's a fictional example for a
+# z-score on the 4th residue of the first chain in the model, and a distance
+# score between the 1st and 3rd residues:
+
+
+class SomeLocalScore(modelcif.qa_metric.Local, modelcif.qa_metric.ZScore):
+ """A per-residue z-score"""
+ software = None
+
+
+class SomePairScore(modelcif.qa_metric.LocalPairwise,
+ modelcif.qa_metric.Distance):
+ """A distance score between two residues"""
+ software = None
+
+
+model.qa_metrics.append(SomeLocalScore(asymA.residue(4), -0.1))
+model.qa_metrics.append(SomePairScore(asymA.residue(1), asymA.residue(3), 1.0))
+
+# Models should be grouped together using ModelGroup and then added to the
+# top-level System. Here we only have a single model in the group:
+model_group = modelcif.model.ModelGroup([model], name='All models')
+system.model_groups.append(model_group)
+
+# Once the system is complete, we can write it out to an mmCIF file:
+with open('output.cif', 'w') as fh:
+ modelcif.dumper.write(fh, [system])
+
+# We can also *read* an mmCIF file and create a set of Python objects from it.
+# Here we read in the file we just created:
+with open('output.cif') as fh:
+ s, = modelcif.reader.read(fh)
+for t in s.templates:
+ print(t.name, "-".join(c.id for c in t.entity.sequence))
+for e in s.entities:
+ print(e.description, "-".join(c.id for c in e.sequence))
diff --git a/examples/validate_mmcif.py b/examples/validate_mmcif.py
new file mode 100755
index 0000000..32a5fba
--- /dev/null
+++ b/examples/validate_mmcif.py
@@ -0,0 +1,27 @@
+# This example demonstrates the use of the Python IHM library's validator
+# to validate a user-provided input mmCIF file.
+# See also validate_modbase.py for a more detailed example.
+
+import sys
+import ihm.dictionary
+import urllib.request
+
+
+if len(sys.argv) != 2:
+ print("Usage: %s input.cif" % sys.argv[0], file=sys.stderr)
+ sys.exit(1)
+fname = sys.argv[1]
+
+# Read in the ModelCIF and PDBx dictionary from https://mmcif.wwpdb.org/
+with urllib.request.urlopen(
+ 'https://mmcif.wwpdb.org/dictionaries/ascii/mmcif_ma.dic') as fh:
+ pdbx_mcif = ihm.dictionary.read(fh)
+
+# Validate the mmCIF file assuming it is UTF8 encoded.
+# See validate_modbase.py for code to fallback to ASCII for non-UTF8 files.
+with open(fname, encoding='UTF-8') as fh:
+ pdbx_mcif.validate(fh)
+
+# Similarly, to validate a BinaryCIF file, use:
+# with open(fname, 'rb') as fh:
+# pdbx_mcif.validate(fh, format='BCIF')
diff --git a/examples/validate_modbase.py b/examples/validate_modbase.py
new file mode 100644
index 0000000..1c80531
--- /dev/null
+++ b/examples/validate_modbase.py
@@ -0,0 +1,36 @@
+# This example demonstrates the use of the Python IHM library's validator.
+# A structure is downloaded from the ModBase database and checked against
+# the ModelCIF dictionary for compliance. This validator can be used
+# to perform basic integrity checking against any mmCIF dictionary.
+# See also validate_mmcif.py for a simpler script to validate a
+# user-provided mmCIF file.
+
+import io
+import ihm.dictionary
+import urllib.request
+
+# Read in the ModelCIF dictionary from wwPDB as a Dictionary object.
+# Note that the ModelCIF dictionary also includes the PDBx dictionary,
+# so we don't need to read that in separately
+fh = urllib.request.urlopen(
+ 'https://mmcif.wwpdb.org/dictionaries/ascii/mmcif_ma.dic')
+pdbx_mc = ihm.dictionary.read(fh)
+fh.close()
+
+# Validate a structure against PDBx+ModelCIF.
+# A correct structure here should result in no output; an invalid structure
+# will result in a ValidatorError Python exception.
+# Here, a structure from ModBase (which should be valid) is used.
+acc = 'P21812'
+cif = urllib.request.urlopen('https://salilab.org/modbase/retrieve'
+ '?databaseID=%s&format=mmcif' % acc).read()
+
+# The encoding for mmCIF files isn't strictly defined, so first try UTF-8
+# and if that fails, strip out any non-ASCII characters. This ensures that
+# we handle accented characters in string fields correctly.
+try:
+ fh = io.StringIO(cif.decode('utf-8'))
+except UnicodeDecodeError:
+ fh = io.StringIO(cif.decode('ascii', errors='ignore'))
+
+pdbx_mc.validate(fh)