Source code for ccpn.framework.lib.NTdb.NTdbDefs

"""
NTdb-related info; adapted from the cing code and NTdb2Json.py script

CingDefs(dict)
    - name, ResidueDef(dict)
            - contains list of AtomDef(dict)'s
              .atoms -> (name, AtomDef) dict

            - contains list of DihedralsDef(dict)'s
              .dihedrals -> (name, DihedralDef) dict

"""
import json

from ccpn.util.Path import Path, aPath
from ccpn.util.Logging import getLogger
from ccpn.util.decorators import singleton


[docs]class DihedralDef(dict): """ Simple class to store Dihedral definitions """ # These definitions come from cing NTdb saveKeys = 'convention name aliases atoms karplus'.split() ATOMS = 'atoms' # List of atoms: (i, name) tuple # i: -1=previous residue # 0=current residue # 1=next residue def __init__(self): for k in self.saveKeys: self[k] = None self.parent = None @property def name(self) -> str: """:return the name of self """ return self['name'] @property def id(self) -> str: """:return a 'id' string, that can be used to for lookup """ return f'{self.parent.name}.{self.name}' def __str__(self): return '<DihedralDef: (%s)>' % self.id __repr__ = __str__
#end class
[docs]class AtomDef(dict): """ Simple class to store Atom definitions """ # These definitions come from cing NTdb saveKeys = 'convention name nameDict aliases canBeModified topology NterminalTopology CterminalTopology ' \ 'spinType shift hetatm properties'.split() def __init__(self): for k in self.saveKeys + 'pseudoAtom hasPseudoAtom isPseudoAtom realAtoms'.split(): self[k] = None self.parent = None @property def name(self) -> str: """:return the name of self """ return self['name'] @property def id(self) -> str: """:return a 'id' string, that can be used to for lookup """ return f'{self.parent.name}.{self.name}' @property def nameTuple(self): "Return (residueDef.name, atomDef.name) tuple" return (self.parent.name, self.name) @property def properties(self) -> list: "Return the properties of the ArtomDef" return self['properties'] @property def isPseudoAtom(self) -> bool: "Return True if it is pseudoAtom" return self['isPseudoAtom'] @property def hasPseudoAtom(self) -> bool: "Return True if atom has a one or more pseudoAtoms" return self['hasPseudoAtom']
[docs] def getPseudoAtom(self) : """:return the pseudoAtom AtomDef instance or None is self does not have a pseudoAtom """ if not self.hasPseudoAtom: return None _pseudo = self.get('pseudoAtom') if _pseudo is None: raise RuntimeError(f'getPseudoAtom: Something has gone wrong; undefined pseudoAtom') return self.parent.atomDefsDict.get(_pseudo)
[docs] def getRealAtoms(self) -> list: """:return the real atoms AtomDef instances or [] is self is not a pseudoAtom """ if not self.isPseudoAtom: return [] _realAtoms = self.get('realAtoms') if _realAtoms is None: raise RuntimeError(f'getRealAtoms: Something has gone wrong; undefined realAtoms') return [self.parent.atomDefsDict.get(aName) for aName in _realAtoms]
@property def isProton(self) -> bool: """:return True if it is a proton or pseudoAtom """ return self['name'][0] == 'H' or (self['isPseudoAtom'] and self['realAtoms'][0][0] == 'H') @property def attachedHeavyAtom(self): """:return the attached heavyAtom (AtomDef instance) if self is a proton, else None """ if not self.isProton: return None _cName = self['topology'][0][1] return self.parent.atomDefsDict.get(_cName) @property def otherAttachedProtons(self) -> list: """:return a list of all other protons (AtomDef instances) also attached to the attachedHeavyAtom of self if self is a proton, else None """ if not self.isProton: return None return [p for p in self.attachedHeavyAtom.attachedProtons if p is not self] @property def attachedProtons(self) -> list: """:return a list of the attached protons (AtomDef instances) of self if self is a non-proton, i.e. carbon, nitrogen, else None """ if self.isProton: return None _aDefs = [self.parent.atomDefsDict.get(aName) for _offset, aName in self['topology'] if _offset == 0] return [aDef for aDef in _aDefs if aDef.isProton] @property def isCarbon(self): "Return True if it is a cabon" return self['name'][0] == 'C' @property def isNitrogen(self): "Return True if it is a Nitrogen" return self['name'][0] == 'N' @property def isMethyl(self) -> bool: """:return True if self is a methyl""" return 'isMethyl' in self.properties @property def isMethylene(self) -> bool: """:return True if self is a methylene""" return 'isMethylene' in self.properties @property def isAromatic(self) -> bool: """:return True if self is a aromatic""" return 'isAromatic' in self.properties def __str__(self): return '<AtomDef: (%s)>' % self.id __repr__ = __str__
#end class
[docs]class ResidueDef(dict): """ Simple class to store Residue definitions """ # These definitions come from cing NTdb saveKeys = 'convention name commonName shortName nameDict canBeModified shouldBeSaved cingDefinition ' \ 'comment properties'.split() # These keys define the recursive structure ATOM_DEFS = 'atomDefs' DIHEDRAL_DEFS = 'dihedralDefs' def __init__(self): self[self.ATOM_DEFS] = [] self[self.DIHEDRAL_DEFS] = [] for k in self.saveKeys: self[k] = None self.path = None # path saved or restored from @property def name(self) -> str: """:return the name of self """ return self['name'] @property def id(self) -> str: """:return a 'id' string, that can be used to for lookup """ return f'{self.name}' @property def properties(self) -> list: "Return the properties of the ResidueDef" return self['properties']
[docs] def addAtomDef(self, atomDef): "Add an atomDef to the list" self[self.ATOM_DEFS].append(atomDef) atomDef.parent = self
[docs] def addDihedralDef(self, dihedralDef): "Add an dihedralDef to the list" self[self.DIHEDRAL_DEFS].append(dihedralDef) dihedralDef.parent = self
#------------------------------------------------------------------------------------------------------ # atom-related properties #------------------------------------------------------------------------------------------------------ @property def atomDefs(self) -> list: """:return list of AtomDefs """ return self[self.ATOM_DEFS] @property def atomDefsDict(self) -> dict: """:return (atomName, atomsDefs) as a dict """ return dict([(a['name'], a) for a in self[self.ATOM_DEFS]]) @property def atomNames(self) -> list: """:return a list of atoms names """ return list[self.atomDefsDict.keys()] @property def realAtoms(self) -> list: """:return all real atoms atomsDefs as a list """ return [a for a in self.atomDefs if not a.isPseudoAtom] @property def realAtomsDict(self) -> dict: """:return all real atoms as a (atomName, atomsDefs) dict """ return dict([(a['name'], a) for a in self.realAtoms]) @property def pseudoAtoms(self) -> list: """:return all pseudo atoms atomsDefs as a list """ return [a for a in self.atomDefs if a.isPseudoAtom] @property def pseudoAtomsDict(self) -> dict: """:return all pseudo atoms as a (atomName, atomsDefs) dict """ return dict([(a['name'], a) for a in self.pseudoAtoms]) @property def protons(self) -> list: """:return all proton atomsDefs as a list """ return [a for a in self.atomDefs if a.isProton] @property def carbons(self) -> list: """:return all carbon atomsDefs as a list """ return [a for a in self.atomDefs if a.isCarbon] @property def nitrogens(self) ->list: """:return all nitrogen atomsDefs as a list """ return [a for a in self.atomDefs if a.isNitrogen] @property def isAminoAcid(self) -> bool: """:return True is residue defines a amino acid """ return 'protein' in self.properties @property def isNucleicAcid(self) -> bool: """:return True is residue defines a Nucleic acid """ return 'nucleic' in self.properties #------------------------------------------------------------------------------------------------------ # dihedral-related properties #------------------------------------------------------------------------------------------------------ @property def dihedralDefs(self) -> list: """Return list of dihedralDefs """ return self[self.DIHEDRAL_DEFS] @property def dihedralDefsDict(self) -> dict: """Return (dihedralName, dihedralDefs) as a dict """ return dict([(a['name'], a) for a in self.dihedralDefs]) #------------------------------------------------------------------------------------------------------ # Json save and restore #------------------------------------------------------------------------------------------------------
[docs] def toJson(self, path=None): "Convert self to json string; optionally save to path" if path is None: return json.dumps(self, indent=4, sort_keys=True) else: with open(path, 'w') as fp: json.dump(self, fp, indent=4, sort_keys=True) self.path = path
[docs] def fromJson(self, path): "Restore self from json file path" logger = getLogger() logger.debug2('Restoring ResidueDef from %s' % path) with open(path, 'r') as fp: tmp = json.load(fp) self.update(tmp) # restore child objects self[self.ATOM_DEFS] = [] for theDict in tmp[self.ATOM_DEFS]: #print('>> name:', theDict['name'], theDict) aDef = AtomDef() aDef.update(theDict) self.addAtomDef(aDef) # restore child objects self[self.DIHEDRAL_DEFS] = [] for theDict in tmp[self.DIHEDRAL_DEFS]: #print('>> name:', theDict['name'], theDict) aDef = DihedralDef() aDef.update(theDict) self.addDihedralDef(aDef) self.path = path
#------------------------------------------------------------------------------------------------------ # others #------------------------------------------------------------------------------------------------------ def __str__(self): return '<ResidueDef: %s>' % self.id __repr__ = __str__
#end class
[docs]@singleton class NTdbDefs(dict): """ Class to contain the NTdbDef (Cing) residue definitions """ @property def residueDefs(self) -> list: "Return list of residueDefs" return list(self.values()) @property def allRealAtoms(self) -> list: "Return all real atoms as a AtomsDefs list" return [atm for res in self.residueDefs for atm in res.realAtoms]
[docs] def addDef(self, rDef): "Add ResidueDef instance to self" self[rDef.name] = rDef
#------------------------------------------------------------------------------------------------------ # Json save and restore #------------------------------------------------------------------------------------------------------
[docs] def toJson(self, path=None): "Convert content of self to json files; path should be a directory" logger = getLogger() if path is None: logger.error('Saving NTdb definitions to json: path is None') return path = aPath(path) if not path.exists(): path.mkdir(parents=True) if not path.is_dir(): logger.error('Saving NTdb definitions to json; invalid path %s' % path) for name, rDef in self.items(): p = path / rDef.name + '.json' rDef.toJson(str(p))
# rDef.toJson(path + rDef.name + '.json')
[docs] def fromJson(self, path): "Restore self from json files in path" logger = getLogger() if path is None: logger.error('restoring NTdb definitions from json: path is None') return path = aPath(path) if not path.exists() or not path.is_dir(): logger.error(f'Restoring NTdb definitions; invalid path "{path}"') logger.debug(f'Restoring NTdb definitions from "{path}" directory') for p in path.glob('*.json'): rDef = ResidueDef() rDef.fromJson(str(p)) self.addDef(rDef)
[docs] def getDef(self, item, default=None): """Routine to get a definitions as 'resName.atomName' or ('resName', 'atomName') or 'resName.dihedralName' or ('resName', 'dihedralName') """ if isinstance(item, str): _items = item.split('.') elif isinstance(item, (list, tuple)): _items = item else: raise ValueError(f'Invalid item "{item}"') if len(_items) == 0 or len(_items) > 2: raise ValueError(f'Invalid item "{item}"; expected length of 1 or 2') key = _items[0] _result = super().get(key, None) if _result is not None and len(_items) > 1: key = _items[1] _itemsDict = _result.atomDefsDict _itemsDict.update(_result.dihedralDefsDict) _result = _itemsDict.get(key, None) if _result is None: _result = default return _result
#end class
[docs]def getNTdbDefs() -> dict: """:return the NTdbDefs (i.e. NTdbDefs) instance (singleton) """ from ccpn.framework.PathsAndUrls import ccpnConfigPath defs = NTdbDefs() _path = ccpnConfigPath / 'NTdb_json' defs.fromJson(_path) return defs