"""
"""
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (http://www.ccpn.ac.uk) 2014 - 2021"
__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 http://www.ccpn.ac.uk/v3-software/downloads/license")
__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: Ed Brooksbank $"
__dateModified__ = "$dateModified: 2021-11-04 20:12:04 +0000 (Thu, November 04, 2021) $"
__version__ = "$Revision: 3.0.4 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: CCPN $"
__date__ = "$Date: 2017-04-07 10:28:41 +0000 (Fri, April 07, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================
from typing import Sequence, Tuple, Union
import collections
from ccpn.core._implementation.AbstractWrapperObject import AbstractWrapperObject
from ccpn.core.Project import Project
from ccpn.core.RestraintTable import RestraintTable
from ccpn.core.Peak import Peak
from ccpnmodel.ccpncore.api.ccp.nmr import NmrConstraint
from ccpn.util.decorators import logCommand
from ccpn.core.lib.ContextManagers import newObject
[docs]class Restraint(AbstractWrapperObject):
"""Restraint. The type is defined in the containing RestraintTable.
Most of the values are the consensus of the values in the contained
RestraintContributions. In the normal case, where you have only one
RestraintContribution per Restraint, you can get and set the values
directly from the Restraint without reference to the RestraintContributions. """
#: Short class name, for PID.
shortClassName = 'RE'
# Attribute it necessary as subclasses must use superclass className
className = 'Restraint'
_parentClass = RestraintTable
#: Name of plural link to instances of class
_pluralLinkName = 'restraints'
#: List of child classes.
_childClasses = []
# Qualified name of matching API class
_apiClassQualifiedName = NmrConstraint.AbstractConstraint._metaclass.qualifiedName()
# CCPN properties
@property
def _apiConstraint(self) -> NmrConstraint.AbstractConstraint:
""" CCPN API Constraint matching Restraint"""
return self._wrappedData
@property
def _parent(self) -> RestraintTable:
"""RestraintTable object containing restraint."""
return self._project._data2Obj[self._wrappedData.parentList]
restraintTable = _parent
@property
def _key(self) -> str:
"""id string - serial number converted to string"""
return str(self._wrappedData.serial)
@property
def serial(self) -> int:
"""serial number of Restraint, used in Pid and to identify the Restraint. """
return self._wrappedData.serial
@property
def peaks(self) -> Tuple[Peak, ...]:
"""peaks used to derive restraint"""
ff = self._project._data2Obj.get
return tuple(sorted(ff(x) for x in self._wrappedData.peaks))
@peaks.setter
def peaks(self, value: Sequence):
value = tuple(self.getByPid(x) if isinstance(x, str) else x for x in value)
self._wrappedData.peaks = [x._wrappedData for x in value]
@property
def targetValue(self) -> float:
"""target value of constraint - consensus of all contributions or None"""
aSet = set(x.targetValue for x in self._wrappedData.contributions)
if len(aSet) == 1:
return aSet.pop()
else:
return None
@targetValue.setter
def targetValue(self, value: float):
for contribution in self._wrappedData.contributions:
contribution.targetValue = value
@property
def error(self) -> float:
"""standard error of restraint - consensus of all contributions or None"""
aSet = set(x.error for x in self._wrappedData.contributions)
if len(aSet) == 1:
return aSet.pop()
else:
return None
@error.setter
def error(self, value: float):
for contribution in self._wrappedData.contributions:
contribution.error = value
@property
def weight(self) -> float:
"""weight of restraint - consensus of all contributions or None"""
aSet = set(x.weight for x in self._wrappedData.contributions)
if len(aSet) == 1:
return aSet.pop()
else:
return None
@weight.setter
def weight(self, value: float):
for contribution in self._wrappedData.contributions:
contribution.weight = value
@property
def upperLimit(self) -> float:
"""upperLimit of restraint - consensus of all contributions or None"""
aSet = set(x.upperLimit for x in self._wrappedData.contributions)
if len(aSet) == 1:
return aSet.pop()
else:
return None
@upperLimit.setter
def upperLimit(self, value: float):
for contribution in self._wrappedData.contributions:
contribution.upperLimit = value
@property
def lowerLimit(self) -> float:
"""lowerLimit of restraint - consensus of all contributions or None"""
aSet = set(x.lowerLimit for x in self._wrappedData.contributions)
if len(aSet) == 1:
return aSet.pop()
else:
return None
@lowerLimit.setter
def lowerLimit(self, value: float):
for contribution in self._wrappedData.contributions:
contribution.lowerLimit = value
@property
def additionalUpperLimit(self) -> float:
"""additionalUpperLimit of restraint - consensus of all contributions or None.
Used for potential functions that require more than one parameter, typically for
parabolic-linear potentials where the additionalUpperLimit marks the transition from
parabolic to linear potential"""
aSet = set(x.additionalUpperLimit for x in self._wrappedData.contributions)
if len(aSet) == 1:
return aSet.pop()
else:
return None
@additionalUpperLimit.setter
def additionalUpperLimit(self, value: float):
for contribution in self._wrappedData.contributions:
contribution.additionalUpperLimit = value
@property
def additionalLowerLimit(self) -> float:
"""additionalLowerLimit of restraint - consensus of all contributions or None
Used for potential functions that require more than one parameter, typically for
parabolic-linear potentials where the additionalLowerLimit marks the transition from
parabolic to linear potential"""
aSet = set(x.additionalLowerLimit for x in self._wrappedData.contributions)
if len(aSet) == 1:
return aSet.pop()
else:
return None
@additionalLowerLimit.setter
def additionalLowerLimit(self, value: float):
for contribution in self._wrappedData.contributions:
contribution.additionalLowerLimit = value
@property
def vectorLength(self) -> str:
"""Reference vector length, where applicable. (Mainly?) for Rdc"""
return self._wrappedData.vectorLength
@vectorLength.setter
def vectorLength(self, value: float):
self._wrappedData.vectorLength = value
@property
def figureOfMerit(self) -> str:
"""Restraint figure of merit, between 0.0 and 1.0 inclusive."""
return self._wrappedData.figureOfMerit
@figureOfMerit.setter
def figureOfMerit(self, value: float):
self._wrappedData.figureOfMerit = value
#=========================================================================================
# Implementation functions
#=========================================================================================
@classmethod
def _getAllWrappedData(cls, parent: RestraintTable) -> list:
"""get wrappedData - all Constraint children of parent ConstraintList"""
return parent._wrappedData.sortedConstraints()
#=========================================================================================
# CCPN functions
#=========================================================================================
#===========================================================================================
# new'Object' and other methods
# Call appropriate routines in their respective locations
#===========================================================================================
[docs] @logCommand(get='self')
def newRestraintContribution(self, targetValue: float = None, error: float = None,
weight: float = 1.0, upperLimit: float = None, lowerLimit: float = None,
additionalUpperLimit: float = None, additionalLowerLimit: float = None,
scale: float = 1.0, isDistanceDependent: bool = False, combinationId: int = None,
restraintItems: Sequence = (), **kwds):
"""Create new RestraintContribution within Restraint.
See the RestraintContribution class for details.
Optional keyword arguments can be passed in; see RestraintContribution._newRestraintContribution for details.
:param targetValue:
:param error:
:param weight:
:param upperLimit:
:param lowerLimit:
:param additionalUpperLimit:
:param additionalLowerLimit:
:param scale:
:param isDistanceDependent:
:param combinationId:
:param restraintItems:
:param serial: optional serial number.
:return: a new RestraintContribution instance.
"""
from ccpn.core.RestraintContribution import _newRestraintContribution
return _newRestraintContribution(self, targetValue=targetValue, error=error,
weight=weight, upperLimit=upperLimit, lowerLimit=lowerLimit,
additionalUpperLimit=additionalUpperLimit, additionalLowerLimit=additionalLowerLimit,
scale=scale, isDistanceDependent=isDistanceDependent, combinationId=combinationId,
restraintItems=restraintItems, **kwds)
#=========================================================================================
# Connections to parents:
#=========================================================================================
def getter(self: Peak) -> Tuple[Restraint, ...]:
getDataObj = self._project._data2Obj.get
result = []
apiPeak = self._wrappedData
# for restraintTable in self._project.restraintTables:
# for apiRestraint in restraintTable._wrappedData.constraints:
# if apiPeak in apiRestraint.peaks:
# result.append(getDataObj(apiRestraint))
result = [getDataObj(apiRestraint) for restraintTable in self._project.restraintTables
for apiRestraint in restraintTable._wrappedData.constraints if apiPeak in apiRestraint.peaks]
return tuple(sorted(result))
def setter(self: Peak, value: Sequence):
apiPeak = self._wrappedData
for restraint in self.restraints:
restraint._wrappedData.removePeak(apiPeak)
for restraint in value:
restraint._wrappedData.addPeak(apiPeak)
Peak.restraints = property(getter, setter, None,
"Restraints corresponding to Peak")
del getter
del setter
#=========================================================================================
@newObject(Restraint)
def _newRestraint(self: RestraintTable, figureOfMerit: float = None, comment: str = None,
peaks: Sequence[Union['Peak', str]] = (), vectorLength: float = None) -> Restraint:
"""Create new Restraint within RestraintTable.
ADVANCED: Note that you just create at least one RestraintContribution afterwards in order to
have valid data. Use the simpler createSimpleRestraint instead, unless you have specific
reasons for needing newRestraint
See the Restraint class for details.
:param figureOfMerit:
:param comment: optional comment string
:param peaks: optional list of peaks as objects or pids
:param vectorLength:
:return: a new Restraint instance.
"""
dd = {'figureOfMerit': figureOfMerit, 'vectorLength': vectorLength, 'details': comment,}
if peaks:
getByPid = self._project.getByPid
peaks = [(getByPid(x) if isinstance(x, str) else x) for x in peaks]
apiPeaks = [pk._wrappedData for pk in peaks if isinstance(pk, Peak)]
if apiPeaks:
dd['peaks'] = apiPeaks
apiRestraint = self._wrappedData.newGenericConstraint(**dd)
result = self._project._data2Obj.get(apiRestraint)
if result is None:
raise RuntimeError('Unable to generate new Restraint item')
return result
def _createSimpleRestraint(self: RestraintTable, comment: str = None, figureOfMerit: float = None,
peaks: Sequence[Peak] = (), targetValue: float = None, error: float = None,
weight: float = 1.0, upperLimit: float = None, lowerLimit: float = None,
additionalUpperLimit: float = None, additionalLowerLimit: float = None,
scale=1.0, vectorLength=None, restraintItems: Sequence = ()) -> Restraint:
"""Create a Restraint with a single RestraintContribution within the RestraintTable.
The function takes all the information needed and creates the RestraintContribution as
well as the Restraint proper.
This function should be used routinely, unless there is a need to create more complex
Restraints.
See the Restraint class for details.
:param comment:
:param figureOfMerit:
:param peaks:
:param targetValue:
:param error:
:param weight:
:param upperLimit:
:param lowerLimit:
:param additionalUpperLimit:
:param additionalLowerLimit:
:param scale:
:param vectorLength:
:param restraintItems:
:return: a new Restraint instance.
"""
# defaults = collections.OrderedDict(
# (
# ('comment', None), ('figureOfMerit', None), ('peaks', ()), ('targetValue', None), ('error', None), ('weight', 1.0),
# ('upperLimit', None), ('lowerLimit', None), ('additionalUpperLimit', None),
# ('additionalLowerLimit', None), ('scale', 1.0), ('vectorLength', None), ('restraintItems', ()),
# )
# )
# values = locals().copy()
# if peaks:
# getByPid = self._project.getByPid
# peaks = [(getByPid(x) if isinstance(x, str) else x) for x in peaks]
# this doesn't appear to be used
# values['peaks'] = tuple(pk.pid for pk in peaks if isinstance(pk, Peak))
restraint = _newRestraint(self, comment=comment, peaks=peaks, figureOfMerit=figureOfMerit,
vectorLength=vectorLength, )
restraint.newRestraintContribution(targetValue=targetValue, error=error, weight=weight,
upperLimit=upperLimit, lowerLimit=lowerLimit,
additionalUpperLimit=additionalUpperLimit,
additionalLowerLimit=additionalLowerLimit, scale=scale,
restraintItems=restraintItems)
return restraint
#EJB 20181205: moved to RestraintTable
# RestraintTable.newRestraint = _newRestraint
# del _newRestraint
# RestraintTable.createSimpleRestraint = _createSimpleRestraint
# Notifiers:
for clazz in NmrConstraint.ConstraintPeakContrib._metaclass.getNonAbstractSubtypes():
className = clazz.qualifiedName()
Project._apiNotifiers.extend(
(('_modifiedLink', {'classNames': ('Peak', 'Restraint')}, className, 'delete'),
('_modifiedLink', {'classNames': ('Peak', 'Restraint')}, className, 'create'),
)
)