import collections
from schrodinger import structure
[docs]class LDData:
"""
Class for storing information about Maestro-level data that may be exported
to LiveDesign. (Objects from this class should only store Maestro-level
data, not LiveDesign data.)
Specifically, this class is not meant to store the values of the data to be
exported, but rather to serve as a "view" to that data. It should:
1. provide identifying information to the user about the data that is
available for export (e.g. by providing user-friendly property names to
tables and other view widgets), and
2. provide an internal "pointer" to data to be exported (by uniquely
identifying exactly what data should be exported, as selected by the
user)
This class is meant to be general enough to store information about both
structure property data and attachment data (e.g. 3D, images, files).
"""
[docs] def __init__(self,
data_name=None,
family_name=None,
user_name=None,
requires_3d=False,
requires_ffc=False):
"""
Instances representing structure properties should be initialized with
the property data name (e.g. `data_name="s_m_title"`).
If there is no data name, e.g. for image and attachment data, instances
should be initialized with both the family name and the user name (e.g.
"Atom Properties", "Hot Atoms").
If the family or user name is supplied along with the data name, these
will be used for display purposes within the panel rather than the
values derived from the data name.
:raise ValueError: if insufficient information is supplied on
initialization to fully specify a unique instance
"""
self._requires_3d = requires_3d
self._requires_ffc = requires_ffc
# If not provided on initialization, these values will be determined
# from the data name
self._family_name = family_name
self._user_name = user_name
if not data_name and not (family_name and user_name):
msg = ('LDData requires either a structure property data name or'
' both family name and user name.')
raise ValueError(msg)
elif data_name:
self._prop_name = structure.PropertyName(data_name)
else:
self._prop_name = None
@property
def family_name(self):
"""
Return the family name if provided on initialization. Otherwise,
determine family name from the data name.
:return: family name for display
:rtype: str
"""
if self._family_name:
return self._family_name
if self._prop_name:
return get_long_family_name(self._prop_name.family)
@property
def user_name(self):
"""
Return the user name if provided on initialization. Otherwise, determine
user name from the data name.
:return: user name for display
:rtype: str
"""
if self._user_name:
return self._user_name
if self._prop_name:
return self._prop_name.userName()
@property
def data_name(self):
"""
Return data name if provided on initialization.
:return: data name or `None`
:rtype: `str` or `None`
"""
if self._prop_name:
return self._prop_name.dataName()
return None
@property
def requires_3d(self):
"""
:return: whether the export of this data requires the export of 3D data
:rtype: bool
"""
return self._requires_3d
@property
def requires_ffc(self):
"""
:return: whether this data needs to be export as a freeform column
:rtype: bool
"""
return self._requires_ffc
def __repr__(self):
return str(self)
def __str__(self):
msg = 'LDData(data_name="{0}", family_name="{1}", user_name="{2}")'
return msg.format(self.data_name, self.family_name, self.user_name)
def __eq__(self, other):
if not isinstance(other, LDData):
return False
return (self.family_name == other.family_name and
self.user_name == other.user_name and
self.data_name == other.data_name)
def __hash__(self):
return hash((self.data_name, self.family_name, self.user_name))
[docs]class ReceptorLigandPair:
"""
Data class for storing a receptor structure and a ligand structure.
"""
[docs] def __init__(self, receptor=None, ligand=None):
self.receptor = receptor
self.ligand = ligand
def __copy__(self):
rec = self.receptor.copy() if self.receptor else None
lig = self.ligand.copy() if self.ligand else None
return ReceptorLigandPair(rec, lig)
def __hash__(self):
return hash((self.receptor, self.ligand))
def __eq__(self, other):
if not isinstance(other, type(self)):
return False
return (self.receptor, self.ligand) == (other.receptor, other.ligand)
[docs]class ReceptorLigandGroup:
"""
Data class for unambiguously storing a group of receptor and ligand
structures. In addition to the primary ligand, this class also supports
storing an "alternate" ligand meant for 3D upload in place of the primary
ligand and "additional" ligands meant for 3D upload in addition to the
primary ligand.
The alternate ligand may have a distinct ligand pose, or it may have
a different structure entirely. For example, the primary ligand may be a
ligand after covalently binding to a receptor, and the alternate ligand may
be the independent ligand molecule prior to complexing.
Each of these structures is optional; a `ReceptorLigandGroup` instance
will not always need to contain a receptor and a ligand, or any other
structures.
"""
[docs] def __init__(self,
receptor=None,
ligand=None,
alt_ligand=None,
add_rl_pairs=None):
"""
:param receptor: a receptor structure or None
:type receptor: structure.Structure or None
:param ligand: a ligand structure or None
:type ligand: structure.Structure or None
:param alt_ligand: extra structure data associated with the ligand, e.g.
to be used as 3D data for certain LiveDesign uploads
:type alt_ligand: structure.Structure or None
:param add_rl_pairs: additional ligand/receptor structures to be
uploaded as 3D data in addition to the conventional (ligand or
alternate ligand) 3D uploads
:type add_rl_pairs: list(ReceptorLigandPair) or None
"""
self.receptor = receptor
self.ligand = ligand
self.alt_ligand = alt_ligand
self.add_rl_pairs = add_rl_pairs or []
def __copy__(self):
rec_copy = self.receptor.copy() if self.receptor else None
lig_copy = self.ligand.copy() if self.ligand else None
alt_lig_copy = self.alt_ligand.copy() if self.alt_ligand else None
add_rl_pair_copies = [
add_rl_pair.copy() for add_rl_pair in self.add_rl_pairs
]
return ReceptorLigandGroup(receptor=rec_copy,
ligand=lig_copy,
alt_ligand=alt_lig_copy,
add_rl_pairs=add_rl_pair_copies)
[docs]class ReceptorLigandMap(collections.defaultdict):
"""
A specialized dictionary for organizing receptor and ligand structures. Each
key points to a list of receptor-ligand groups associated with that key. For
convenience, this class also features several generators.
"""
[docs] def __init__(self):
"""
Initialize as a `list`-based `collections.defaultDict`.
"""
super(ReceptorLigandMap, self).__init__(list)
@property
def num_rl_groups(self):
"""
:return: the number of receptor-ligand groups stored in this object.
:rtype: int
"""
count = 0
for item in self.rl_group_items:
count += 1
return count
@property
def rl_group_items(self):
"""
:return: an iterator for (key, receptor-ligand group) pairs stored in
this map. Note that each key can correspond to multiple receptor-
ligand groups.
:rtype: iterator((str, ReceptorLigandGroup))
"""
for key, group_list in self.items():
for rl_group in group_list:
yield key, rl_group
@property
def rl_groups(self):
"""
:return: an iterator for receptor-ligand group objects stored in this
map.
:rtype: iterator(ReceptorLigandGroup)
"""
for key, rl_group in self.rl_group_items:
yield rl_group
@property
def ligand_items(self):
"""
:return: an iterator for (key, ligand) pairs stored in this map
:rtype: iterator((str, structure.Structure))
"""
for key, rl_group in self.rl_group_items:
if rl_group.ligand:
yield key, rl_group.ligand
@property
def ligands(self):
"""
:return: an iterator for ligand structures stored in this map
:rtype: iterator(structure.Structure)
"""
for key, ligand_st in self.ligand_items:
yield ligand_st
@property
def alt_ligand_items(self):
"""
:return: an iterator for (key, alternative ligand) pairs stored in this
map
:rtype: iterator((str, structure.Structure))
"""
for key, rl_group in self.rl_group_items:
if rl_group.alt_ligand:
yield key, rl_group.alt_ligand
@property
def alt_ligands(self):
"""
:return: an iterator for alternative ligand structures stored in this
map
:rtype: iterator(structure.Structure)
"""
for key, alt_lig_st in self.alt_ligand_items:
yield alt_lig_st
@property
def add_rl_pair_items(self):
"""
:return: an iterator for (key, additional receptor-ligand pair) 2-tuples
stored in this map
:rtype: iterator((str, ReceptorLigandPair))
"""
for key, rl_group in self.rl_group_items:
for add_rl_pair in rl_group.add_rl_pairs:
yield key, add_rl_pair
@property
def add_rl_pairs(self):
"""
:return: an iterator for additional receptor-ligand pairs stored on this
map
:rtype: iterator(ReceptorLigandPair)
"""
for key, rl_pair in self.add_rl_pair_items:
yield rl_pair
@property
def receptor_items(self):
"""
:return: an iterator for (key, receptor) pairs stored in this map
:rtype: iterator((str, structure.Structure))
"""
for key, rl_group in self.rl_group_items:
if rl_group.receptor:
yield key, rl_group.receptor
@property
def receptors(self):
"""
:return: an iterator for receptor structures stored in this map
:rtype: iterator(structure.Structure)
"""
for key, receptor_st in self.receptor_items:
yield receptor_st
@property
def structures(self):
"""
:return: an iterator for all structures stored in this map
:rtype: iterator(structure.Structure)
"""
for rl_group in self.rl_groups:
if rl_group.receptor is not None:
yield rl_group.receptor
if rl_group.ligand is not None:
yield rl_group.ligand
for add_rl_pair in rl_group.add_rl_pairs:
if add_rl_pair.receptor is not None:
yield add_rl_pair.receptor
if add_rl_pair.ligand is not None:
yield add_rl_pair.ligand
def __copy__(self):
rl_map_copy = ReceptorLigandMap()
st_copy_cache = {None: None}
def get_cached_copy(st):
"""
Retrieve a structure from the cache, or create one and cache it.
:param st: a structure to copy, or `None`
:type st: structure.Structure or NoneType
:return: a copy of `st`, or `None` (if `st` is `None`)
:rtype: structure.Structure or NoneType
"""
if st not in st_copy_cache:
st_copy_cache[st] = st.copy()
return st_copy_cache[st]
for key, rl_group in self.rl_group_items:
rl_group_copy = ReceptorLigandGroup()
rl_group_copy.receptor = get_cached_copy(rl_group.receptor)
rl_group_copy.ligand = get_cached_copy(rl_group.ligand)
rl_group_copy.alt_ligand = get_cached_copy(rl_group.alt_ligand)
for rl_pair in rl_group.add_rl_pairs:
rec_copy = get_cached_copy(rl_pair.receptor)
lig_copy = get_cached_copy(rl_pair.ligand)
rl_pair_copy = ReceptorLigandPair(receptor=rec_copy,
ligand=lig_copy)
rl_group_copy.add_rl_pairs += [rl_pair_copy]
rl_map_copy[key].append(rl_group_copy)
return rl_map_copy
[docs]def get_long_family_name(short_family_name):
"""
Given the short family name of a structure property (e.g. "m"), return the
corresponding long family name (e.g. "Maestro"). If no long family name is
defined, return the short family name argument.
:param short_family_name: the short family name of a structure property
:param short_family_name: str
:return: the corresponding long family name if one exists, otherwise the
short family name
:rtype: str
"""
return structure.PROP_LONG_NAME.get(short_family_name, short_family_name)