Source code for pmlbeta.secstructdb

"""Implement a database of secondary structures using the plugin preference store of PyMOL"""

import typing
import warnings

try:
    from pymol import cmd
    import pymol.plugins
except ImportError:
    warnings.warn(
        'Cannot import PyMOL: functionality will suffer (you can ignore this if you are just building the documentation).')
    cmd = None


[docs]class SecondaryStructureDB: """A database of the known secondary structures and the corresponding dihedral angles""" _instance = None DEFAULT_HELIXTYPES = { 'Z6M': (126.7, 62.6, 152.7), 'Z6P': (-126.7, -62.6, -152.7), 'Z8M': (47.5, 53.5, -104.3), 'Z8P': (-47.5, -53.5, 104.3), 'H8M': (76.8, -120.6, 52.7), 'H8P': (-76.8, 120.6, -52.7), 'H10M': (-77.5, -51.8, -75.1), 'H10P': (77.5, 51.8, 75.1), 'H12M': (92.3, -90.0, 104.6), 'H12P': (-92.3, 90.0, -104.6), 'H14M': (-140.3, 66.5, -136.8), 'H14P': (140.3, -66.5, 136.8), 'SM': (70.5, 176.2, 168.9), 'SP': (-70.5, -176.2, -168.9), 'Straight': (180, 180, 180), 'Straight alpha': (180, None, 180), 'Alpha-helix': (-57, None, -47), '3_10-helix': (-49, None, -26), 'P-beta-sheet': (-119, None, 113), 'AP-beta-sheet': (-139, None, 135), } def __new__(cls): if cls._instance is None: obj = super().__new__() cls._instance = obj return cls._instance
[docs] @classmethod def getAll(cls, alpha: bool = True, beta: bool = True) -> typing.Dict[str, typing.Tuple[float, float, float]]: """Get the list of secondary structures from the PyMOL plugin preferences :param alpha: if secondary structures for alpha-peptides are requested :type alpha: bool :param beta: if secondary structures for beta-peptides are requested :type beta: bool :return: the known secondary structures and their backbone torsion angles :rtype: a dict mapping names to triplets of floats """ ss = pymol.plugins.pref_get('BETAFAB_HELIXTYPES', cls.DEFAULT_HELIXTYPES) ssbeta = {k: v for k, v in ss.items() if v[1] is not None} ssalpha = {k: v for k, v in ss.items() if v[1] is None} dic = {} dic.update(ssalpha if alpha else {}) dic.update(ssbeta if beta else {}) return dic
[docs] @classmethod def add(cls, name: str, phi: float, theta: float, psi: float): """Add or edit an entry in the secondary structure torsion angle database :param name: the name of the secondary structure :type name: str :param phi: the first torsion angle :type phi: float :param theta: the second torsion angle (None for alpha-amino acid secondary structures) :type theta: float or None :param psi: the third torsion angle :type psi: float """ dic = cls.getAll() dic[name] = (phi, theta, psi) pymol.plugins.pref_set('BETAFAB_HELIXTYPES', dic) pymol.plugins.pref_save(quiet=True)
[docs] @classmethod def remove(cls, name: str): """Remove an entry in the secondary structure torsion angle database :param name: the name of the secondary structure :type name: str """ dic = cls.getAll() try: del dic[name] except KeyError: pass pymol.plugins.pref_set('BETAFAB_HELIXTYPES', dic) pymol.plugins.pref_save(quiet=True)
[docs] @classmethod def find(cls, phi: float, theta: typing.Optional[float], psi: float, tolerance: float = 0.5) -> typing.Optional[ str]: """Try to find a secondary structure in the database which matches the given torsion angles :param phi: the first torsion angle :type phi: float :param theta: the second torsion angle (None for alpha-amino acids) :type theta: float or None :param psi: the third torsion angle :type psi: float :param tolerance: absolute tolerance in each angle :type tolerance: float :return: the name of the closest matching secondary structure or None if no match :rtype: str or None """ if theta is None: # find only alpha-amino acid sec.structures diff = {k: abs(angles[0] - phi) + abs(angles[2] - psi) for k, angles in cls.getAll().items() if angles[1] is None and abs(angles[0] - phi) < tolerance and abs(angles[2] - psi) < tolerance} else: # find only beta-amino acid sec.structures diff = {k: abs(angles[0] - phi) + abs(angles[1] - theta) + abs(angles[2] - psi) for k, angles in cls.getAll().items() if angles[1] is not None and abs(angles[0] - phi) < tolerance and abs(angles[1] - theta) < tolerance and abs(angles[2] - psi) < tolerance} if not diff: return None mindiff = min(diff.values()) return [k for k in diff if diff[k] == mindiff][0]
[docs] @classmethod def dihedrals(cls, name: str) -> typing.Tuple[float, typing.Optional[float], float]: """Get the dihedral angles corresponding to a secondary structure. :param name: the name of the secondary structure :type name: str :return: the dihedral angles corresponding to that secondary structure, in degrees :rtype: a tuple of three floats, the central one can be None :raises KeyError: if the named entry does not exist """ return cls.getAll(alpha=True, beta=True)[name]
[docs] @classmethod def addDefaults(cls): """Add the default secondary structure types to the list""" dic = cls.getAll() dic.update(cls.DEFAULT_HELIXTYPES) pymol.plugins.pref_set('BETAFAB_HELIXTYPES', dic) pymol.plugins.pref_save(quiet=True)
[docs]def ssdb_add(entryname: str, phi: typing.Union[str, float], theta: typing.Union[str, float], psi: typing.Union[str, float, None] = None, _self=None): """ DESCRIPTION Add/modify an entry in the secondary structure database USAGE ssdb_add entryname, phi, theta, psi # for beta-amino acids or ssdb_add entryname, phi, psi # for alpha-amino acids ARGUMENTS entryname = string: the name of the entry phi = float: the first (N-terminal) backbone dihedral angle: (C-, N, CA, C) for alpha-amino acids and (C-, N, CB, CA) for beta-amino acids theta = float: the middle backbone dihedral angle for beta-amino acids: (N, CB, CA, C) psi = float: the last (C-terminal) backbone dihedral angle: (N, CA, C, N+) for alpha-amino acids and (CB, CA, C, N+) for beta-amino acids NOTES changes are automatically saved for further PyMOL sessions. SEE ALSO ssdb_del, ssdb_list, ssdb_resetdefaults, ssdb_dihedrals """ if psi is None: phi = float(phi) psi = float(theta) theta = None else: phi = float(phi) theta = float(theta) psi = float(psi) SecondaryStructureDB.add(entryname, phi, theta, psi)
[docs]def ssdb_del(entryname: str, _self=None): """ DESCRIPTION Remove an entry from the secondary structure database USAGE ssdb_del entryname ARGUMENTS entryname = string: the name of the entry NOTES changes are automatically saved for further PyMOL sessions. SEE ALSO ssdb_add, ssdb_list, ssdb_resetdefaults, ssdb_dihedrals """ SecondaryStructureDB.remove(entryname)
[docs]def ssdb_list(_self=None): """ DESCRIPTION Lists the entries in the secondary structure database USAGE ssdb_list SEE ALSO ssdb_add, ssdb_del, ssdb_resetdefaults, ssdb_dihedrals """ dic = SecondaryStructureDB.getAll() if not dic: print('No entries in the secondary structure database.') return namelen = max(max([len(e) for e in dic]), len('Secondary structure') + 2) numberformat = ' {:7.1f}° ' numberlen = len(numberformat.format(-360.0)) sepline = '+' + '-' * (namelen) + '+' + (numberlen * '-' + '+') * 3 headerline = '|{{:^{}}}|'.format(namelen).format('Secondary structure') + '{{:^{}}}|'.format(numberlen).format( 'φ') + '{{:^{}}}|'.format(numberlen).format('ϑ') + '{{:^{}}}|'.format(numberlen).format('ψ') print(sepline) print(headerline) print(sepline.replace('-', '=')) for entry in sorted(dic): if dic[entry][1] is not None: print('|{{:^{}}}|'.format(namelen).format(entry) + numberformat.format( dic[entry][0]) + '|' + numberformat.format(dic[entry][1]) + '|' + numberformat.format( dic[entry][2]) + '|') else: print('|{{:^{}}}|'.format(namelen).format(entry) + numberformat.format( dic[entry][0]) + '|' + '{{:^{}}}'.format(numberlen).format('--') + '|' + numberformat.format( dic[entry][2]) + '|') print(sepline)
[docs]def ssdb_resetdefaults(_self=None): """ DESCRIPTION Add back the default entries to the secondary structure database USAGE ssdb_resetdefaults NOTES changes are automatically saved for further PyMOL sessions. SEE ALSO ssdb_add, ssdb_del, ssdb_list, ssdb_dihedrals """ SecondaryStructureDB.addDefaults()
[docs]def ssdb_dihedrals(entryname: str, _self=None): """ DESCRIPTION Print the dihedral angles corresponding to a given entry secondary structure database USAGE ssdb_dihedrals entryname ARGUMENTS entryname = string: the name of the entry SEE ALSO ssdb_add, ssdb_del, ssdb_list, ssdb_resetdefaults """ try: dihedrals = SecondaryStructureDB.dihedrals(entryname) except KeyError: print('Entry {} does not exist in the secondary structure database!'.format(entryname)) return print('Dihedral angles for {}:'.format(entryname)) for anglename, value in zip('φϑψ', dihedrals): if value is None: continue print(' {}: {:8.2f}°'.format(anglename, value))
if cmd is not None: cmd.extend('ssdb_add', ssdb_add) cmd.extend('ssdb_del', ssdb_del) cmd.extend('ssdb_list', ssdb_list) cmd.extend('ssdb_dihedrals', ssdb_dihedrals) cmd.extend('ssdb_resetdefaults', ssdb_resetdefaults)