"""Defines a `Struc` class as a generic represention of molecular structure
"""
import copy
import hashlib
import os
[docs]class Struc(object):
"""
"""
[docs] def __init__(self):
# Public attributes:
self.atom_prop = []
# Private attributes:
self._id = None
for i in range(len(self.atom) + 1):
self.atom_prop.append({})
def __str__(self):
return self.title()
def _atom(self, index):
"""
Returns the index-th atom.
"""
raise NotImplementedError(
"`_atom()' method not implemented by subclass")
[docs] def id(self):
"""
Returns the ID of this structure.
"""
if (self._id is None):
self._id = hashlib.sha1(self.title()).hexdigest()
return self._id
[docs] def set_id(self, id):
"""
Sets the ID for this structure.
"""
self._id = id
[docs] def copy(self):
raise NotImplementedError("`copy' method not implemented by subclass")
[docs] def title(self):
raise NotImplementedError("`title' method not implemented by subclass")
[docs] def set_title(self, new_title):
raise NotImplementedError(
"`set_title' method not implemented by subclass")
[docs] def heavy_atoms(self):
"""
Returns a list of indices of heavy atoms (viz non-hydrogen atoms).
"""
raise NotImplementedError(
"`heavy_atoms' method not implemented by subclass")
[docs] def is_chiral_atom(self, atom_index):
"""
Returns true if the atom indicated by `atom_index` is chiral;
otherwise, false.
:type atom_index: `int`
:param atom_index: Atom index
"""
raise NotImplementedError(
"`is_chiral_atom' method not implemented by subclass")
[docs] def chiral_atoms(self):
"""
Returns the indices of the chiral atoms.
:rtype: `list` of `int`
:return: A list of atom indices
"""
raise NotImplementedError(
"`chiral_atoms' method not implemented by subclass")
[docs] def ring_atoms(self):
"""
Returns a set of ring atoms.
:rtype: `set` of `int`
:return: A set of atom indices
"""
raise NotImplementedError(
"`ring_atom' method not implemented by subclass")
[docs] def bonded_atoms(self, atom_index):
"""
Returns a list of atom indices of atoms bonded to the indicated atom.
:type atom_index: `int`
:param atom_index: A single index or a list of indices of the atoms to
be deleted
:rtype: `list` of `int`
:return: A list of atom indices of atoms bonded to the indicated atom
"""
raise NotImplementedError(
"`bonded_atoms' method not implemented by subclass")
[docs] def total_charge(self):
"""
Returns the total charge of the structure.
"""
raise NotImplementedError(
"`total_charge' method not implemented by subclass")
[docs] def delete_atom(self, atom_index):
"""
Deletes a atom.
:type atom_index: `int` or `list` of `int`
:param atom_index: A single index or a list of indices of the atoms to
be deleted
"""
raise NotImplementedError(
"`delete_atoms' method not implemented by subclass")
[docs] def smarts(self):
"""
Returns a SMARTS string of this structure.
"""
raise NotImplementedError("`smarts' method not implemented by subclass")
[docs] def smiles(self):
"""
Returns a SMILES string of this structure.
"""
raise NotImplementedError("`smiles' method not implemented by subclass")
[docs] def write(filename, format, mode="a"):
"""
Writes this structure into a file in the designated format.
:type mode: `char`, 'a' | 'w'
:param mode: When a file of the same name exists, this determines
whether to overwrite ('w') or append ('a') to the file.
"""
raise NotImplementedError("`write' method not implemented by subclass")
[docs]class SchrodStruc(Struc):
"""
A `Struc` subclass based on Schrodinger's infrastructure
"""
[docs] def __init__(self, struc):
"""
`struc` should be a `schrodinger.structure.Structure` object.
"""
self._struc = struc
# Public attributes:
self.atom = self._struc.atom
self.bond = self._struc.bond
# Cached values:
self._heavy_atom_cache = None
Struc.__init__(self)
def __setstate__(self, dict):
self.__dict__.update(dict)
self.atom = self._struc.atom
self.bond = self._struc.bond
[docs] def copy(self):
"""
Returns a copy of this structure.
"""
ret = SchrodStruc(self._struc.copy())
ret.atom_prop = copy.deepcopy(self.atom_prop)
return ret
[docs] def title(self):
"""
Returns the title of this structure. (Normally title's a user-friendly
description)
"""
return self._struc.title
[docs] def set_title(self, new_title):
"""
Sets a new title to this structure.
"""
self._struc.title = new_title
[docs] def heavy_atoms(self):
"""
Returns a list of indices of heavy atoms (viz non-hydrogen atoms).
"""
if (self._heavy_atom_cache is None):
ret = []
for e in self.atom:
if (e.atomic_number > 1):
ret.append(int(e))
else:
ret = self._heavy_atom_cache
return ret
[docs] def is_chiral_atom(self, atom_index):
"""
Returns true if the atom indicated by `atom_index` is chiral;
otherwise, false.
:type atom_index: `int`
:param atom_index: Atom index
"""
return self._struc.atom[atom_index].chirality in [
"R", "S", "ANR", "ANS"
]
[docs] def chiral_atoms(self):
"""
Returns the indices of the chiral atoms.
:rtype: `list` of `int`
:return: A list of atom indices
"""
ret = []
for atom in self._struc.atom:
if (atom.chirality in ["R", "S", "ANR", "ANS"]):
ret.append(int(atom))
return ret
[docs] def ring_atoms(self, aromaticity=0, group=False):
"""
Returns ring atoms.
:type aromaticity: `int` -1, 0, 1
:param aromaticity: -1 = non-aromatic, 0 = all, 1 = aromatic. Make the
function return the specified type of ring atoms.
:type group: `bool`
:param group: If true, returns a list of `set` objects, each of which
is a set of indices of atoms in the same ring; otherwise, returns a
single set containing indices of all selected ring atoms.
:rtype: `set` of `int` or a `list` of `set` of `int`
:return: A set or a list of sets of atom indices
"""
ret = []
for e in self._struc.ring:
if (aromaticity == 0 or (e.isAromatic() and aromaticity == 1) or
(not e.isAromatic() and aromaticity == -1)):
if (group):
ret.append(set(e.getAtomIndices()))
else:
ret.extend(e.getAtomIndices())
return ret if (group) else set(ret)
[docs] def bonded_atoms(self, atom_index):
"""
Returns a list of atom indices of atoms bonded to the indicated atom.
:type atom_index: `int`
:param atom_index: A single index or a list of indices of the atoms to
be deleted
:rtype: `list` of `int`
:return: A list of atom indices of atoms bonded to the indicated atom
"""
ret = []
for e in self._struc.atom[atom_index].bonded_atoms:
ret.append(int(e))
return ret
[docs] def molecules(self):
"""
Returns a list of atom lists. Each element list is a list of atoms of a
molecule in the structure. The first element in the returned list
belongs to the biggest molecule.
"""
ret = []
for mol in self._struc.molecule:
ret.append(mol.getAtomIndices())
def key_func(x):
num_x = len(x)
heavy_atoms = set(self.heavy_atoms())
num_heavy_in_x = len(set(x) - heavy_atoms)
return num_x, num_heavy_in_x
ret.sort(key=key_func, reverse=True)
return ret
[docs] def total_charge(self):
"""
Returns the formal charge of the structure
"""
return self._struc.formal_charge
[docs] def add_hydrogens(self):
"""
Adds hydrogen atoms to this molecule.
"""
import schrodinger.structutils.build as build
build.add_hydrogens(self._struc)
[docs] def delete_atom(self, atom_index):
"""
Deletes a atom.
:type atom_index: `int` or `list` of `int`
:param atom_index: A single index or a list of indices of the atoms to
be deleted
"""
if (not isinstance(atom_index, list)):
atom_index = [
atom_index,
]
atom_index.sort()
atom_index.reverse()
self._struc.deleteAtoms(atom_index)
for i in atom_index:
del self.atom_prop[i]
self._heavy_atom_cache = None
[docs] def smarts(self, atoms=None):
"""
Returns a SMARTS string for this structure.
:type atoms: `list` of `int`
:param atoms: A list of atom indices
"""
import schrodinger.structutils.analyze as analyze
return analyze.generate_smarts(self._struc, atoms)
[docs] def smiles(self):
"""
Returns a SMILES string for this structure.
"""
import schrodinger.structutils.analyze as analyze
return analyze.generate_smiles(self._struc)
[docs] def write(self, filename, format=None, mode="a", cil=False):
"""
Writes this structure into a file in the designated format.
:type format: `str` or `None`
:param format: If its value is `None`, the file format is determined
from the filename suffix. If specified, it must be one
of the following case-sensitive strings: "pdb", "mol2",
"sd", "maestro", "smiles", and "smilescsv".
"""
if (cil):
import time
self._struc.property["r_fep_number"] = time.time()
if ('a' == mode):
if (os.path.isfile(filename)):
self._struc.append(filename, format)
else:
self._struc.write(filename, format)
elif ('w' == mode):
self._struc.write(filename, format)
else:
raise ValueError(
"Invalid value for `mode' argument: '%s', should be one of 'a' and 'w'."
)