Skip to content

Commit

Permalink
Squashed 'modules/core/dependency/python-ihm/' changes from d3bfbf128…
Browse files Browse the repository at this point in the history
…3..f49b8e9917

f49b8e9917 Fully qualify class name
176f85e771 Prepare for 0.40 release
0800e3e48d Add recent changes
80f6512447 Add default v2 readthedocs config
ec985b7aff Add basic support for links in branched entities
9c0281b62a Add support for branch descriptors
7533fc50b7 Read/write branched entity composition info
ffd29303af Add basic support for oligosaccharides

git-subtree-dir: modules/core/dependency/python-ihm
git-subtree-split: f49b8e99177e2648691cbabc36ce57806fa6fee1
  • Loading branch information
benmwebb committed Sep 26, 2023
1 parent 46e7cd7 commit 5dc6320
Show file tree
Hide file tree
Showing 12 changed files with 706 additions and 16 deletions.
35 changes: 35 additions & 0 deletions modules/core/dependency/python-ihm/.readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.11"
# You can also specify other tool versions:
# nodejs: "20"
# rust: "1.70"
# golang: "1.20"

# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
# builder: "dirhtml"
# Fail on all warnings to avoid broken references
# fail_on_warning: true

# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub

# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
# python:
# install:
# - requirements: docs/requirements.txt
18 changes: 18 additions & 0 deletions modules/core/dependency/python-ihm/ChangeLog.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
0.40 - 2023-09-25
=================
- Basic support for oligosaccharides is now provided. New classes are
provided to describe saccharide chemical components
(:class:`ihm.SaccharideChemComp` and subclasses). Unlike polymers and
non-polymers, oligosaccharides can be branched, and a new
:class:`ihm.BranchLink` class allows the linkage between individual
components to be described.
- A summary report of the system can now be produced by calling
:meth:`ihm.System.report`. This can help to reveal errors or
inconsistencies, and will warn about missing data that may not be
technically required for a compliant mmCIF file, but is usually
expected to be present.
- :class:`ihm.metadata.MRCParser` now uses the new EMDB API to extract
version information and details for electron density map datasets.
- RPM packages are now available for recent versions of Fedora and
RedHat Enterprise Linux.

0.39 - 2023-08-04
=================
- :class:`ihm.location.DatabaseLocation` no longer accepts a ``db_name``
Expand Down
2 changes: 1 addition & 1 deletion modules/core/dependency/python-ihm/MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ include examples/*
include util/make-mmcif.py
include src/ihm_format.h
include src/ihm_format.i
include src/ihm_format_wrap_0.39.c
include src/ihm_format_wrap_0.40.c
27 changes: 27 additions & 0 deletions modules/core/dependency/python-ihm/docs/main.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,27 @@ The :mod:`ihm` Python module
.. autoclass:: DNAChemComp
:members:

.. autoclass:: SaccharideChemComp
:members:

.. autoclass:: LSaccharideChemComp
:members:

.. autoclass:: LSaccharideAlphaChemComp
:members:

.. autoclass:: LSaccharideBetaChemComp
:members:

.. autoclass:: DSaccharideChemComp
:members:

.. autoclass:: DSaccharideAlphaChemComp
:members:

.. autoclass:: DSaccharideBetaChemComp
:members:

.. autoclass:: NonPolymerChemComp
:members:

Expand Down Expand Up @@ -89,3 +110,9 @@ The :mod:`ihm` Python module

.. autoclass:: Collection
:members:

.. autoclass:: BranchDescriptor
:members:

.. autoclass:: BranchLink
:members:
126 changes: 124 additions & 2 deletions modules/core/dependency/python-ihm/ihm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import json
from . import util

__version__ = '0.39'
__version__ = '0.40'


class __UnknownValue(object):
Expand Down Expand Up @@ -741,7 +741,10 @@ def auth_sub(m):
class ChemComp(object):
"""A chemical component from which :class:`Entity` objects are constructed.
Usually these are amino acids (see :class:`LPeptideChemComp`) or
nucleic acids (see :class:`DNAChemComp` and :class:`RNAChemComp`).
nucleic acids (see :class:`DNAChemComp` and :class:`RNAChemComp`),
but non-polymers such as ligands or water (see
:class:`NonPolymerChemComp` and :class:`WaterChemComp`) and saccharides
(see :class:`SaccharideChemComp`) are also supported.
For standard amino and nucleic acids, it is generally easier to use
a :class:`Alphabet` and refer to the components with their one-letter
Expand Down Expand Up @@ -887,6 +890,66 @@ class RNAChemComp(ChemComp):
type = 'RNA linking'


class SaccharideChemComp(ChemComp):
"""A saccharide chemical component. Usually a subclass that specifies
the chirality and linkage (e.g. :class:`LSaccharideBetaChemComp`)
is used.
:param str id: A globally unique identifier for this component.
:param str name: A longer human-readable name for the component.
:param str formula: The chemical formula. See :class:`ChemComp` for
more details.
:param str ccd: The chemical component dictionary (CCD) where
this component is defined. See :class:`ChemComp` for
more details.
:param list descriptors: Information on the component's chemistry.
See :class:`ChemComp` for more details.
"""
type = "saccharide"

def __init__(self, id, name=None, formula=None, ccd=None,
descriptors=None):
super(SaccharideChemComp, self).__init__(
id, id, id, name=name, formula=formula,
ccd=ccd, descriptors=descriptors)


class LSaccharideChemComp(SaccharideChemComp):
"""A single saccharide component with L-chirality and unspecified linkage.
See :class:`SaccharideChemComp` for a description of the parameters."""
type = "L-saccharide"


class LSaccharideAlphaChemComp(LSaccharideChemComp):
"""A single saccharide component with L-chirality and alpha linkage.
See :class:`SaccharideChemComp` for a description of the parameters."""
type = "L-saccharide, alpha linking"


class LSaccharideBetaChemComp(LSaccharideChemComp):
"""A single saccharide component with L-chirality and beta linkage.
See :class:`SaccharideChemComp` for a description of the parameters."""
type = "L-saccharide, beta linking"


class DSaccharideChemComp(SaccharideChemComp):
"""A single saccharide component with D-chirality and unspecified linkage.
See :class:`SaccharideChemComp` for a description of the parameters."""
type = "D-saccharide"


class DSaccharideAlphaChemComp(DSaccharideChemComp):
"""A single saccharide component with D-chirality and alpha linkage.
See :class:`SaccharideChemComp` for a description of the parameters."""
type = "D-saccharide, alpha linking"


class DSaccharideBetaChemComp(DSaccharideChemComp):
"""A single saccharide component with D-chirality and beta linkage.
See :class:`SaccharideChemComp` for a description of the parameters."""
type = "D-saccharide, beta linking"


class NonPolymerChemComp(ChemComp):
"""A non-polymer chemical component, such as a ligand or a non-standard
residue (for crystal waters, use :class:`WaterChemComp`).
Expand Down Expand Up @@ -1189,6 +1252,8 @@ class Entity(object):
def __get_type(self):
if self.is_polymeric():
return 'polymer'
elif self.is_branched():
return 'branched'
else:
return 'water' if self.sequence[0].code == 'HOH' else 'non-polymer'
type = property(__get_type)
Expand Down Expand Up @@ -1234,6 +1299,15 @@ def get_chem_comp(s):
self.references = []
self.references.extend(references)

#: String descriptors of branched chemical structure.
#: These generally only make sense for oligosaccharide entities,
#: and should be a list of :class:`BranchDescriptor` objects.
self.branch_descriptors = []

#: Any links between components in a branched entity.
#: This is a list of :class:`BranchLink` objects.
self.branch_links = []

def __str__(self):
return "<ihm.Entity(%s)>" % self.description

Expand All @@ -1246,6 +1320,12 @@ def is_polymeric(self):
and any(isinstance(x, (PeptideChemComp, DNAChemComp,
RNAChemComp)) for x in self.sequence))

def is_branched(self):
"""Return True iff this entity is branched (generally
an oligosaccharide)"""
return (len(self.sequence) > 0
and isinstance(self.sequence[0], SaccharideChemComp))

def residue(self, seq_id):
"""Get a :class:`Residue` at the given sequence position"""
return Residue(entity=self, seq_id=seq_id)
Expand Down Expand Up @@ -1513,3 +1593,45 @@ class Collection(object):
"""
def __init__(self, id, name=None, details=None):
self.id, self.name, self.details = id, name, details


class BranchDescriptor(object):
"""String descriptor of branched chemical structure.
These generally only make sense for oligosaccharide entities.
See :attr:`Entity.branch_descriptors`.
:param str text: The value of this descriptor.
:param str type: The type of the descriptor; one of
"Glycam Condensed Core Sequence", "Glycam Condensed Sequence",
"LINUCS", or "WURCS".
:param str program: The name of the program or library used to compute
the descriptor.
:param str program_version: The version of the program or library
used to compute the descriptor.
"""
def __init__(self, text, type, program=None, program_version=None):
self.text, self.type = text, type
self.program, self.program_version = program, program_version


class BranchLink(object):
"""A link between components in a branched entity.
These generally only make sense for oligosaccharide entities.
See :attr:`Entity.branch_links`.
:param int num1: 1-based index of the first component.
:param str atom_id1: Name of the first atom in the linkage.
:param str leaving_atom_id1: Name of the first leaving atom.
:param int num2: 1-based index of the second component.
:param str atom_id2: Name of the second atom in the linkage.
:param str leaving_atom_id2: Name of the second leaving atom.
:param str order: Bond order (e.g. sing, doub, trip).
:param str details: More information about this link.
"""
def __init__(self, num1, atom_id1, leaving_atom_id1, num2, atom_id2,
leaving_atom_id2, order=None, details=None):
self.num1, self.atom_id1 = num1, atom_id1
self.num2, self.atom_id2 = num2, atom_id2
self.leaving_atom_id1 = leaving_atom_id1
self.leaving_atom_id2 = leaving_atom_id2
self.order, self.details = order, details
90 changes: 87 additions & 3 deletions modules/core/dependency/python-ihm/ihm/dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,29 @@ def dump(self, system, writer):
comp_id_end=entity.sequence[rng.seq_id_range[1] - 1].id)


class _EntityBranchListDumper(Dumper):
def dump(self, system, writer):
with writer.loop("_pdbx_entity_branch_list",
["entity_id", "num", "comp_id", "hetero"]) as lp:
for entity in system.entities:
if not entity.is_branched():
continue
for num, comp in enumerate(entity.sequence):
lp.write(entity_id=entity._id, num=num + 1,
comp_id=comp.id)


class _EntityBranchDumper(Dumper):
def dump(self, system, writer):
# todo: we currently only support branched oligosaccharides
with writer.loop("_pdbx_entity_branch",
["entity_id", "type"]) as lp:
for entity in system.entities:
if not entity.is_branched():
continue
lp.write(entity_id=entity._id, type="oligosaccharide")


class _PolySeqSchemeDumper(Dumper):
"""Output the _pdbx_poly_seq_scheme table.
This is needed because it is a parent category of atom_site.
Expand Down Expand Up @@ -689,7 +712,7 @@ def dump(self, system, writer):
"pdb_ins_code"]) as lp:
for asym in system.asym_units:
entity = asym.entity
if entity.is_polymeric():
if entity.is_polymeric() or entity.is_branched():
continue
for num, comp in enumerate(asym.sequence):
auth_seq_num, ins = asym._get_auth_seq_id_ins_code(num + 1)
Expand All @@ -706,6 +729,65 @@ def dump(self, system, writer):
auth_mon_id=comp.id, pdb_ins_code=ins)


class _BranchSchemeDumper(Dumper):
def dump(self, system, writer):
with writer.loop("_pdbx_branch_scheme",
["asym_id", "entity_id", "mon_id", "num",
"pdb_seq_num", "auth_seq_num",
"auth_mon_id", "pdb_asym_id"]) as lp:
for asym in system.asym_units:
entity = asym.entity
if not entity.is_branched():
continue
for num, comp in enumerate(asym.sequence):
auth_seq_num, ins = asym._get_auth_seq_id_ins_code(num + 1)
# Assume num counts sequentially from 1 (like seq_id)
lp.write(asym_id=asym._id, pdb_asym_id=asym.strand_id,
entity_id=entity._id,
num=num + 1,
pdb_seq_num=auth_seq_num,
auth_seq_num=auth_seq_num,
mon_id=comp.id,
auth_mon_id=comp.id)


class _BranchDescriptorDumper(Dumper):
def dump(self, system, writer):
ordinal = itertools.count(1)
with writer.loop("_pdbx_entity_branch_descriptor",
["ordinal", "entity_id", "descriptor", "type",
"program", "program_version"]) as lp:
for entity in system.entities:
for d in entity.branch_descriptors:
lp.write(ordinal=next(ordinal), entity_id=entity._id,
descriptor=d.text, type=d.type, program=d.program,
program_version=d.program_version)


class _BranchLinkDumper(Dumper):
def dump(self, system, writer):
ordinal = itertools.count(1)
with writer.loop("_pdbx_entity_branch_link",
["link_id", "entity_id", "entity_branch_list_num_1",
"comp_id_1", "atom_id_1", "leaving_atom_id_1",
"entity_branch_list_num_2", "comp_id_2", "atom_id_2",
"leaving_atom_id_2", "value_order",
"details"]) as lp:
for entity in system.entities:
for lnk in entity.branch_links:
lp.write(
link_id=next(ordinal), entity_id=entity._id,
entity_branch_list_num_1=lnk.num1,
comp_id_1=entity.sequence[lnk.num1 - 1].id,
atom_id_1=lnk.atom_id1,
leaving_atom_id_1=lnk.leaving_atom_id1,
entity_branch_list_num_2=lnk.num2,
comp_id_2=entity.sequence[lnk.num2 - 1].id,
atom_id_2=lnk.atom_id2,
leaving_atom_id_2=lnk.leaving_atom_id2,
value_order=lnk.order, details=lnk.details)


class _AsymIDProvider(object):
"""Provide unique asym IDs"""
def __init__(self, seen_ids):
Expand Down Expand Up @@ -3246,8 +3328,10 @@ class IHMVariant(Variant):
_ChemDescriptorDumper, _EntityDumper, _EntitySrcGenDumper,
_EntitySrcNatDumper, _EntitySrcSynDumper, _StructRefDumper,
_EntityPolyDumper, _EntityNonPolyDumper, _EntityPolySeqDumper,
_EntityPolySegmentDumper, _StructAsymDumper, _PolySeqSchemeDumper,
_NonPolySchemeDumper, _AssemblyDumper, _ExternalReferenceDumper,
_EntityPolySegmentDumper, _EntityBranchListDumper, _EntityBranchDumper,
_StructAsymDumper, _PolySeqSchemeDumper,
_NonPolySchemeDumper, _BranchSchemeDumper, _BranchDescriptorDumper,
_BranchLinkDumper, _AssemblyDumper, _ExternalReferenceDumper,
_DatasetDumper, _ModelRepresentationDumper, _StartingModelDumper,
_ProtocolDumper, _PostProcessDumper, _PseudoSiteDumper,
_GeometricObjectDumper, _FeatureDumper, _CrossLinkDumper,
Expand Down
Loading

0 comments on commit 5dc6320

Please sign in to comment.