Source code for ccpn.core.StructureData

"""
"""
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (https://www.ccpn.ac.uk) 2014 - 2022"
__credits__ = ("Ed Brooksbank, Joanna Fox, Victoria A Higman, Luca Mureddu, Eliza Płoskoń",
               "Timothy J Ragan, Brian O Smith, Gary S Thompson & Geerten W Vuister")
__licence__ = ("CCPN licence. See https://ccpn.ac.uk/software/licensing/")
__reference__ = ("Skinner, S.P., Fogh, R.H., Boucher, W., Ragan, T.J., Mureddu, L.G., & Vuister, G.W.",
                 "CcpNmr AnalysisAssign: a flexible platform for integrated NMR analysis",
                 "J.Biomol.Nmr (2016), 66, 111-124, http://doi.org/10.1007/s10858-016-0060-y")
#=========================================================================================
# Last code modification
#=========================================================================================
__modifiedBy__ = "$modifiedBy: Luca Mureddu $"
__dateModified__ = "$dateModified: 2022-02-28 11:43:53 +0000 (Mon, February 28, 2022) $"
__version__ = "$Revision: 3.1.0 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: CCPN $"
__date__ = "$Date: 2017-04-07 10:28:41 +0000 (Fri, April 07, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================

import datetime
import pandas as pd
from typing import Optional

from ccpn.core._implementation.AbstractWrapperObject import AbstractWrapperObject
from ccpn.core.Project import Project
from ccpnmodel.ccpncore.api.ccp.nmr.NmrConstraint import NmrConstraintStore as ApiNmrConstraintStore
from ccpnmodel.ccpncore.api.ccp.nmr.NmrConstraint import FixedResonance as ApiFixedResonance
from ccpn.core.lib import Pid
from ccpn.core.lib.ContextManagers import newObject, renameObject
from ccpn.util.decorators import logCommand
from ccpn.util.isotopes import name2IsotopeCode
from ccpn.util.Logging import getLogger


[docs]class StructureData(AbstractWrapperObject): """Data set. Used to store the input to (or output from) a calculation, including data selection and parameters, to group Restraints that are used together, to track data history and file loads. """ #: Short class name, for PID. shortClassName = 'SD' # Attribute it necessary as subclasses must use superclass className className = 'StructureData' _parentClass = Project #: Name of plural link to instances of class _pluralLinkName = 'structureData' #: List of child classes. _childClasses = [] # Qualified name of matching API class _apiClassQualifiedName = ApiNmrConstraintStore._metaclass.qualifiedName() # Internal NameSpace _MoleculeFilePath = '_MoleculeFilePath' _MOLECULEFILEPATH = 'moleculeFilePath' # CCPN properties @property def _apiStructureData(self) -> ApiNmrConstraintStore: """ CCPN NmrConstraintStore matching StructureData""" return self._wrappedData @property def _key(self) -> str: """id string - serial number converted to string""" #return str(self._wrappedData.serial) #return str(self.title)+'_'+str(self.serial) return self.name.translate(Pid.remapSeparators) # Title should not be unique @property def serial(self) -> int: """serial number of StructureData, used in Pid and to identify the StructureData. """ return self._wrappedData.serial @property def _parent(self) -> Project: """Parent (containing) object.""" return self._project @property def title(self) -> str: """Title of StructureData. """ getLogger().warning('Deprecated, please use StructureData.name') return self.name @title.setter def title(self, value: str): """Set title of the StructureData. """ getLogger().warning('Deprecated, please use StructureData.name') self.name = value @property def name(self) -> str: """Name of StructureData. """ # Reading V2 project resulted in name being None; create one on the fly if self._wrappedData.name is None: # needed to stop recursion of generating unique names name = StructureData._uniqueApiName(self.project) self._wrappedData.__dict__['name'] = name # The only way to access this return self._wrappedData.name @name.setter def name(self, value: str): self.rename(value) @property def programName(self) -> str: """Name of program performing the calculation """ return self._none2str(self._wrappedData.programName) @programName.setter def programName(self, value: str): self._wrappedData.programName = self._str2none(value) @property def programVersion(self) -> Optional[str]: """Version of program performing the calculation """ return self._none2str(self._wrappedData.programVersion) @programVersion.setter def programVersion(self, value: str): self._wrappedData.programVersion = self._str2none(value) @property def dataPath(self) -> Optional[str]: """File path where structureData is stored""" return self._none2str(self._wrappedData.dataPath) @dataPath.setter def dataPath(self, value: str): self._wrappedData.dataPath = self._str2none(value) @property def creationDate(self) -> Optional[datetime.datetime]: """Creation timestamp for StructureData""" return self._wrappedData.creationDate @creationDate.setter def creationDate(self, value: datetime.datetime): self._wrappedData.creationDate = self._str2none(value) @property def uuid(self) -> Optional[str]: """Universal identifier for structureData""" return self._none2str(self._wrappedData.uuid) @uuid.setter def uuid(self, value: str): self._wrappedData.uuid = self._str2none(value) @property def moleculeFilePath(self): """ :return: a filePath for corresponding molecule. E.g., PDB file path for displaying molecules in a molecular viewer """ path = self._getInternalParameter(self._MOLECULEFILEPATH) return path @moleculeFilePath.setter def moleculeFilePath(self, filePath: str = None): """ :param filePath: a filePath for corresponding molecule :return: None """ self._setInternalParameter(self._MOLECULEFILEPATH, filePath) def _fetchFixedResonance(self, assignment: str, checkUniqueness: bool = True) -> ApiFixedResonance: """Fetch FixedResonance matching assignment string, creating anew if needed. If checkUniqueness is False the uniqueness of FixedResonance assignments will not be checked. NB, the odd duplicate should not be a major problem.""" from ccpn.core.NmrAtom import UnknownIsotopeCode apiNmrConstraintStore = self._wrappedData tt = [x or None for x in Pid.splitId(assignment)] if len(tt) != 4: raise ValueError("assignment %s must have four dot-separated fields" % tt) dd = { 'chainCode' : tt[0], 'sequenceCode': tt[1], 'residueType' : tt[2], 'name' : tt[3] } if checkUniqueness: result = apiNmrConstraintStore.findFirstFixedResonance(**dd) else: result = None if result is None: dd['isotopeCode'] = name2IsotopeCode(tt[3]) or UnknownIsotopeCode result = apiNmrConstraintStore.newFixedResonance(**dd) # return result def _getTempItemMap(self) -> dict: """Get itemString:FixedResonance map, used as optional input for RestraintContribution.addRestraintItem(), as a faster alternative to calling _fetchFixedResonance (above). No other uses expected. Since FixedResonances are in principle mutable, this map should be used for a single series of creation operations (making or loading a set of restraint lists) and then discarded.""" result = {} for fx in self._wrappedData.fixedResonances: ss = '.'.join(x or '' for x in (fx.chainCode, fx.sequenceCode, fx.residueType, fx.name)) result[ss] = fx # return result @property def inputCalculationSteps(self): """STUB: hot-fixed later""" return () @property def outputCalculationSteps(self): """STUB: hot-fixed later""" return () #========================================================================================= # Implementation functions #========================================================================================= @classmethod def _getAllWrappedData(cls, parent: Project) -> list: """get wrappedData for all NmrConstraintStores linked to NmrProject""" return parent._wrappedData.sortedNmrConstraintStores()
[docs] @renameObject() @logCommand(get='self') def rename(self, value: str): """Rename StructureData, changing its name and Pid. NB, the serial remains immutable. """ return self._rename(value)
@classmethod def _restoreObject(cls, project, apiObj): """Subclassed to allow for initialisations on restore """ resList = super()._restoreObject(project, apiObj) # update the list of substances if resList._MoleculeFilePath in resList._ccpnInternalData: value = resList._ccpnInternalData.get(resList._MoleculeFilePath) if value: resList._setInternalParameter(resList._MOLECULEFILEPATH, value) del resList._ccpnInternalData[resList._MoleculeFilePath] # @classmethod # def _restoreObject(cls, project, apiObj): # """Subclassed to allow for initialisations on restore # """ # from ccpn.ui.gui.modules.RestraintAnalysisTable import Headers # # _headers = [(['restraintpid', 'atoms', 'min', 'max', 'mean', 'std', 'count_0_3', 'count_0_5'], # Headers), # ] # # result = super()._restoreObject(project, apiObj) # # for data in result.data: # parameters = data.parameters # modified = False # for k, df in parameters.items(): # if isinstance(df, pd.DataFrame): # headers = list(df.columns) # for oldHeaders, newHeaders in _headers: # if headers == oldHeaders: # df.rename(columns={old: new for old, new in zip(oldHeaders, newHeaders)}, inplace=True) # modified = True # # if modified: # data.updateParameters(parameters) # # return result #========================================================================================= # CCPN functions #========================================================================================= #=========================================================================================== # new<Object> and other methods # Call appropriate routines in their respective locations #===========================================================================================
[docs] @logCommand(get='self') def newRestraintTable(self, restraintType, name: str = None, origin: str = None, comment: str = None, unit: str = None, potentialType: str = 'unknown', tensorMagnitude: float = 0.0, tensorRhombicity: float = 0.0, tensorIsotropicValue: float = 0.0, tensorChainCode: str = None, tensorSequenceCode: str = None, tensorResidueType: str = None, restraintItemLength=None, **kwds): """Create new RestraintTable of type restraintType within StructureData. See the RestraintTable class for details. Optional keyword arguments can be passed in; see RestraintTable._newRestraintTable for details. :param restraintType: :param name: :param origin: :param comment: :param unit: :param potentialType: :param tensorMagnitude: :param tensorRhombicity: :param tensorIsotropicValue: :param tensorChainCode: :param tensorSequenceCode: :param tensorResidueType: :param restraintItemLength: :return: a new RestraintTable instance. """ from ccpn.core.RestraintTable import _newRestraintTable return _newRestraintTable(self, restraintType, name=name, origin=origin, comment=comment, unit=unit, potentialType=potentialType, tensorMagnitude=tensorMagnitude, tensorRhombicity=tensorRhombicity, tensorIsotropicValue=tensorIsotropicValue, tensorChainCode=tensorChainCode, tensorSequenceCode=tensorSequenceCode, tensorResidueType=tensorResidueType, restraintItemLength=restraintItemLength, **kwds)
[docs] @logCommand(get='self') def newCalculationStep(self, programName: str = None, programVersion: str = None, scriptName: str = None, script: str = None, inputDataUuid: str = None, outputDataUuid: str = None, inputStructureData=None, outputStructureData=None, **kwds): """Create new CalculationStep within StructureData. See the CalculationStep class for details. Optional keyword arguments can be passed in; see CalculationStep._newCalculationStep for details. :param programName: :param programVersion: :param scriptName: :param script: :param inputDataUuid: :param outputDataUuid: :param inputStructureData: :param outputStructureData: :return: a new CalculationStep instance. """ from ccpn.core.CalculationStep import _newCalculationStep return _newCalculationStep(self, programName=programName, programVersion=programVersion, scriptName=scriptName, script=script, inputDataUuid=inputDataUuid, outputDataUuid=outputDataUuid, inputStructureData=inputStructureData, outputStructureData=outputStructureData, **kwds)
[docs] @logCommand(get='self') def newData(self, name: str, attachedObjectPid: str = None, attachedObject: AbstractWrapperObject = None, **kwds): """Create new Data within StructureData. See the Data class for details. Optional keyword arguments can be passed in; see Data._newData for details. :param name: :param attachedObjectPid: :param attachedObject: :return: a new Data instance. """ from ccpn.core.Data import _newData return _newData(self, name=name, attachedObjectPid=attachedObjectPid, attachedObject=attachedObject, **kwds)
[docs] @logCommand(get='self') def newViolationTable(self, name: str = None, data=None, comment: str = None, **kwds): """Create new ViolationTable. See the ViolationTable class for details. Optional keyword arguments can be passed in; see ViolationTable._newViolationTable for details. :param name: new name for the ViolationTable. :param data: Pandas dataframe. :param comment: optional comment string :return: a new ViolationTable instance. """ from ccpn.core.ViolationTable import _newViolationTable return _newViolationTable(self, name=name, data=data, comment=comment, **kwds)
#========================================================================================= # Connections to parents: #========================================================================================= @newObject(StructureData) def _newStructureData(self: Project, name: str = None, title: str = None, programName: str = None, programVersion: str = None, dataPath: str = None, creationDate: datetime.datetime = None, uuid: str = None, moleculeFilePath: str = None, comment: str = None) -> StructureData: """Create new StructureData See the StructureData class for details. :param name: :param programName: :param programVersion: :param dataPath: :param creationDate: :param uuid: :param comment: :return: a new StructureData instance. """ if title and name: raise TypeError('Cannot create new StructureData with title and name; StructureData.title is deprecated, please use StructureData.name') if title: getLogger().warning('StructureData.title is deprecated, please use StructureData.name') name = title name = StructureData._uniqueName(project=self, name=name) nmrProject = self._wrappedData if programName is not None and not isinstance(programName, str): raise TypeError("programName must be a string") if programVersion is not None and not isinstance(programVersion, str): raise TypeError("programName must be a string") apiNmrConstraintStore = nmrProject.root.newNmrConstraintStore(nmrProject=nmrProject, name=name, programName=programName, programVersion=programVersion, dataPath=dataPath, creationDate=creationDate, uuid=uuid, details=comment) result = self._data2Obj.get(apiNmrConstraintStore) if result is None: raise RuntimeError('Unable to generate new StructureData item') if moleculeFilePath: result.moleculeFilePath = moleculeFilePath return result